| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "gpu/command_buffer/service/gpu_tracer.h" |
| |
| #include <deque> |
| |
| #include "base/bind.h" |
| #include "base/strings/string_util.h" |
| #include "base/time/time.h" |
| #include "base/trace_event/trace_event.h" |
| #include "gpu/command_buffer/common/gles2_cmd_utils.h" |
| #include "gpu/command_buffer/service/context_group.h" |
| #include "ui/gl/gl_bindings.h" |
| #include "ui/gl/gl_version_info.h" |
| #include "ui/gl/gpu_timing.h" |
| |
| namespace gpu { |
| namespace gles2 { |
| |
| static const unsigned int kProcessInterval = 16; |
| static TraceOutputter* g_outputter_thread = NULL; |
| |
| TraceMarker::TraceMarker(const std::string& category, const std::string& name) |
| : category_(category), |
| name_(name), |
| trace_(NULL) { |
| } |
| |
| TraceMarker::~TraceMarker() { |
| } |
| |
| scoped_refptr<TraceOutputter> TraceOutputter::Create(const std::string& name) { |
| if (!g_outputter_thread) { |
| g_outputter_thread = new TraceOutputter(name); |
| } |
| return g_outputter_thread; |
| } |
| |
| TraceOutputter::TraceOutputter(const std::string& name) |
| : named_thread_(name.c_str()), local_trace_id_(0) { |
| named_thread_.Start(); |
| named_thread_.Stop(); |
| } |
| |
| TraceOutputter::~TraceOutputter() { g_outputter_thread = NULL; } |
| |
| void TraceOutputter::TraceDevice(const std::string& category, |
| const std::string& name, |
| int64 start_time, |
| int64 end_time) { |
| TRACE_EVENT_COPY_BEGIN_WITH_ID_TID_AND_TIMESTAMP1( |
| TRACE_DISABLED_BY_DEFAULT("gpu.device"), name.c_str(), local_trace_id_, |
| named_thread_.GetThreadId(), start_time, "gl_category", category.c_str()); |
| TRACE_EVENT_COPY_END_WITH_ID_TID_AND_TIMESTAMP1( |
| TRACE_DISABLED_BY_DEFAULT("gpu.device"), name.c_str(), local_trace_id_, |
| named_thread_.GetThreadId(), end_time, "gl_category", category.c_str()); |
| ++local_trace_id_; |
| } |
| |
| void TraceOutputter::TraceServiceBegin(const std::string& category, |
| const std::string& name) { |
| TRACE_EVENT_COPY_BEGIN1(TRACE_DISABLED_BY_DEFAULT("gpu.service"), |
| name.c_str(), "gl_category", category.c_str()); |
| } |
| |
| void TraceOutputter::TraceServiceEnd(const std::string& category, |
| const std::string& name) { |
| TRACE_EVENT_COPY_END1(TRACE_DISABLED_BY_DEFAULT("gpu.service"), |
| name.c_str(), "gl_category", category.c_str()); |
| } |
| |
| GPUTrace::GPUTrace(scoped_refptr<Outputter> outputter, |
| gfx::GPUTimingClient* gpu_timing_client, |
| const std::string& category, |
| const std::string& name, |
| const bool tracing_service, |
| const bool tracing_device) |
| : category_(category), |
| name_(name), |
| outputter_(outputter), |
| service_enabled_(tracing_service), |
| device_enabled_(tracing_device) { |
| if (tracing_device && |
| gpu_timing_client->IsAvailable() && |
| gpu_timing_client->IsTimerOffsetAvailable()) { |
| gpu_timer_ = gpu_timing_client->CreateGPUTimer(); |
| } |
| } |
| |
| GPUTrace::~GPUTrace() { |
| } |
| |
| void GPUTrace::Destroy(bool have_context) { |
| if (gpu_timer_.get()) { |
| gpu_timer_->Destroy(have_context); |
| } |
| } |
| |
| void GPUTrace::Start() { |
| if (service_enabled_) { |
| outputter_->TraceServiceBegin(category_, name_); |
| } |
| if (gpu_timer_.get()) { |
| gpu_timer_->Start(); |
| } |
| } |
| |
| void GPUTrace::End() { |
| if (gpu_timer_.get()) { |
| gpu_timer_->End(); |
| } |
| if (service_enabled_) { |
| outputter_->TraceServiceEnd(category_, name_); |
| } |
| } |
| |
| bool GPUTrace::IsAvailable() { |
| return !gpu_timer_.get() || gpu_timer_->IsAvailable(); |
| } |
| |
| void GPUTrace::Process() { |
| if (gpu_timer_.get()) { |
| DCHECK(IsAvailable()); |
| |
| int64 start = 0; |
| int64 end = 0; |
| gpu_timer_->GetStartEndTimestamps(&start, &end); |
| outputter_->TraceDevice(category_, name_, start, end); |
| } |
| } |
| |
| GPUTracer::GPUTracer(gles2::GLES2Decoder* decoder) |
| : gpu_trace_srv_category(TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED( |
| TRACE_DISABLED_BY_DEFAULT("gpu.service"))), |
| gpu_trace_dev_category(TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED( |
| TRACE_DISABLED_BY_DEFAULT("gpu.device"))), |
| decoder_(decoder), |
| gpu_executing_(false), |
| process_posted_(false) { |
| DCHECK(decoder_); |
| gfx::GLContext* context = decoder_->GetGLContext(); |
| if (context) { |
| gpu_timing_client_ = context->CreateGPUTimingClient(); |
| } else { |
| gpu_timing_client_ = new gfx::GPUTimingClient(); |
| } |
| } |
| |
| GPUTracer::~GPUTracer() { |
| } |
| |
| void GPUTracer::Destroy(bool have_context) { |
| for (int n = 0; n < NUM_TRACER_SOURCES; n++) { |
| for (size_t i = 0; i < markers_[n].size(); i++) { |
| TraceMarker& marker = markers_[n][i]; |
| if (marker.trace_.get()) { |
| marker.trace_->Destroy(have_context); |
| marker.trace_ = 0; |
| } |
| } |
| } |
| |
| ClearFinishedTraces(have_context); |
| } |
| |
| bool GPUTracer::BeginDecoding() { |
| if (gpu_executing_) |
| return false; |
| |
| if (!outputter_) { |
| outputter_ = CreateOutputter(gpu_timing_client_->GetTimerTypeName()); |
| } |
| |
| if (*gpu_trace_dev_category == '\0') { |
| // If GPU device category is off, invalidate timing sync. |
| gpu_timing_client_->InvalidateTimerOffset(); |
| } |
| |
| gpu_executing_ = true; |
| if (IsTracing()) { |
| gpu_timing_client_->CheckAndResetTimerErrors(); |
| // Begin a Trace for all active markers |
| for (int n = 0; n < NUM_TRACER_SOURCES; n++) { |
| for (size_t i = 0; i < markers_[n].size(); i++) { |
| TraceMarker& trace_marker = markers_[n][i]; |
| trace_marker.trace_ = |
| new GPUTrace(outputter_, gpu_timing_client_.get(), |
| trace_marker.category_, trace_marker.name_, |
| *gpu_trace_srv_category != 0, |
| *gpu_trace_dev_category != 0); |
| trace_marker.trace_->Start(); |
| } |
| } |
| } |
| return true; |
| } |
| |
| bool GPUTracer::EndDecoding() { |
| if (!gpu_executing_) |
| return false; |
| |
| // End Trace for all active markers |
| if (IsTracing()) { |
| for (int n = 0; n < NUM_TRACER_SOURCES; n++) { |
| for (size_t i = 0; i < markers_[n].size(); i++) { |
| TraceMarker& marker = markers_[n][i]; |
| if (marker.trace_.get()) { |
| marker.trace_->End(); |
| |
| finished_traces_.push_back(marker.trace_); |
| marker.trace_ = 0; |
| } |
| } |
| } |
| IssueProcessTask(); |
| } |
| |
| gpu_executing_ = false; |
| |
| // NOTE(vmiura): glFlush() here can help give better trace results, |
| // but it distorts the normal device behavior. |
| return true; |
| } |
| |
| bool GPUTracer::Begin(const std::string& category, const std::string& name, |
| GpuTracerSource source) { |
| if (!gpu_executing_) |
| return false; |
| |
| DCHECK(source >= 0 && source < NUM_TRACER_SOURCES); |
| |
| // Push new marker from given 'source' |
| markers_[source].push_back(TraceMarker(category, name)); |
| |
| // Create trace |
| if (IsTracing()) { |
| scoped_refptr<GPUTrace> trace = new GPUTrace( |
| outputter_, gpu_timing_client_.get(), category, name, |
| *gpu_trace_srv_category != 0, |
| *gpu_trace_dev_category != 0); |
| trace->Start(); |
| markers_[source].back().trace_ = trace; |
| } |
| |
| return true; |
| } |
| |
| bool GPUTracer::End(GpuTracerSource source) { |
| if (!gpu_executing_) |
| return false; |
| |
| DCHECK(source >= 0 && source < NUM_TRACER_SOURCES); |
| |
| // Pop last marker with matching 'source' |
| if (!markers_[source].empty()) { |
| scoped_refptr<GPUTrace> trace = markers_[source].back().trace_; |
| if (trace.get()) { |
| if (IsTracing()) { |
| trace->End(); |
| } |
| |
| finished_traces_.push_back(trace); |
| IssueProcessTask(); |
| } |
| |
| markers_[source].pop_back(); |
| return true; |
| } |
| return false; |
| } |
| |
| bool GPUTracer::IsTracing() { |
| return (*gpu_trace_srv_category != 0) || (*gpu_trace_dev_category != 0); |
| } |
| |
| const std::string& GPUTracer::CurrentCategory(GpuTracerSource source) const { |
| if (source >= 0 && |
| source < NUM_TRACER_SOURCES && |
| !markers_[source].empty()) { |
| return markers_[source].back().category_; |
| } |
| return base::EmptyString(); |
| } |
| |
| const std::string& GPUTracer::CurrentName(GpuTracerSource source) const { |
| if (source >= 0 && |
| source < NUM_TRACER_SOURCES && |
| !markers_[source].empty()) { |
| return markers_[source].back().name_; |
| } |
| return base::EmptyString(); |
| } |
| |
| scoped_refptr<Outputter> GPUTracer::CreateOutputter(const std::string& name) { |
| return TraceOutputter::Create(name); |
| } |
| |
| void GPUTracer::PostTask() { |
| base::MessageLoop::current()->PostDelayedTask( |
| FROM_HERE, |
| base::Bind(&GPUTracer::Process, base::AsWeakPtr(this)), |
| base::TimeDelta::FromMilliseconds(kProcessInterval)); |
| } |
| |
| void GPUTracer::Process() { |
| process_posted_ = false; |
| ProcessTraces(); |
| IssueProcessTask(); |
| } |
| |
| void GPUTracer::ProcessTraces() { |
| if (!gpu_timing_client_->IsAvailable()) { |
| ClearFinishedTraces(false); |
| return; |
| } |
| |
| TRACE_EVENT0("gpu", "GPUTracer::ProcessTraces"); |
| |
| // Make owning decoder's GL context current |
| if (!decoder_->MakeCurrent()) { |
| // Skip subsequent GL calls if MakeCurrent fails |
| ClearFinishedTraces(false); |
| return; |
| } |
| |
| // Check if timers are still valid (e.g: a disjoint operation |
| // might have occurred.) |
| if (gpu_timing_client_->CheckAndResetTimerErrors()) { |
| ClearFinishedTraces(true); |
| } |
| |
| while (!finished_traces_.empty()) { |
| scoped_refptr<GPUTrace>& trace = finished_traces_.front(); |
| if (trace->IsDeviceTraceEnabled()) { |
| if (!finished_traces_.front()->IsAvailable()) |
| break; |
| finished_traces_.front()->Process(); |
| } |
| finished_traces_.front()->Destroy(true); |
| finished_traces_.pop_front(); |
| } |
| |
| // Clear pending traces if there were are any errors |
| GLenum err = glGetError(); |
| if (err != GL_NO_ERROR) |
| ClearFinishedTraces(true); |
| } |
| |
| void GPUTracer::ClearFinishedTraces(bool have_context) { |
| while (!finished_traces_.empty()) { |
| finished_traces_.front()->Destroy(have_context); |
| finished_traces_.pop_front(); |
| } |
| } |
| |
| void GPUTracer::IssueProcessTask() { |
| if (finished_traces_.empty() || process_posted_) |
| return; |
| |
| process_posted_ = true; |
| PostTask(); |
| } |
| |
| } // namespace gles2 |
| } // namespace gpu |