// 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/debug/trace_event.h" #include "base/strings/string_util.h" #include "base/time/time.h" #include "gpu/command_buffer/common/gles2_cmd_utils.h" namespace gpu { namespace gles2 { static const unsigned int kProcessInterval = 16; static TraceOutputter* g_outputter_thread = NULL; 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::Trace(const std::string& name, int64 start_time, int64 end_time) { TRACE_EVENT_COPY_BEGIN_WITH_ID_TID_AND_TIMESTAMP0( TRACE_DISABLED_BY_DEFAULT("gpu.device"), name.c_str(), local_trace_id_, named_thread_.thread_id(), start_time); TRACE_EVENT_COPY_END_WITH_ID_TID_AND_TIMESTAMP0( TRACE_DISABLED_BY_DEFAULT("gpu.device"), name.c_str(), local_trace_id_, named_thread_.thread_id(), end_time); ++local_trace_id_; } class NoopTrace : public Trace { public: explicit NoopTrace(const std::string& name) : Trace(name) {} // Implementation of Tracer virtual void Start() OVERRIDE { TRACE_EVENT_COPY_ASYNC_BEGIN0( TRACE_DISABLED_BY_DEFAULT("gpu.service"), name().c_str(), this); } virtual void End() OVERRIDE { TRACE_EVENT_COPY_ASYNC_END0( TRACE_DISABLED_BY_DEFAULT("gpu.service"), name().c_str(), this); } virtual bool IsAvailable() OVERRIDE { return true; } virtual bool IsProcessable() OVERRIDE { return false; } virtual void Process() OVERRIDE {} private: virtual ~NoopTrace() {} DISALLOW_COPY_AND_ASSIGN(NoopTrace); }; struct TraceMarker { TraceMarker(const std::string& name, GpuTracerSource source) : name_(name), source_(source) {} std::string name_; GpuTracerSource source_; scoped_refptr<Trace> trace_; }; class GPUTracerImpl : public GPUTracer, public base::SupportsWeakPtr<GPUTracerImpl> { public: GPUTracerImpl() : 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"))), gpu_executing_(false), process_posted_(false) {} virtual ~GPUTracerImpl() {} // Implementation of gpu::gles2::GPUTracer virtual bool BeginDecoding() OVERRIDE; virtual bool EndDecoding() OVERRIDE; virtual bool Begin(const std::string& name, GpuTracerSource source) OVERRIDE; virtual bool End(GpuTracerSource source) OVERRIDE; virtual const std::string& CurrentName() const OVERRIDE; virtual bool IsTracing() OVERRIDE { return (*gpu_trace_srv_category != 0) || (*gpu_trace_dev_category != 0); } virtual void CalculateTimerOffset() {} // Process any completed traces. virtual void Process(); virtual void ProcessTraces(); protected: // Create a new trace. virtual scoped_refptr<Trace> CreateTrace(const std::string& name); const unsigned char* gpu_trace_srv_category; const unsigned char* gpu_trace_dev_category; protected: void IssueProcessTask(); std::vector<TraceMarker> markers_; std::deque<scoped_refptr<Trace> > traces_; bool gpu_executing_; bool process_posted_; DISALLOW_COPY_AND_ASSIGN(GPUTracerImpl); }; class GPUTracerARBTimerQuery : public GPUTracerImpl { public: explicit GPUTracerARBTimerQuery(gles2::GLES2Decoder* decoder); virtual ~GPUTracerARBTimerQuery(); // Implementation of GPUTracerImpl virtual void ProcessTraces() OVERRIDE; protected: // Implementation of GPUTracerImpl. virtual bool BeginDecoding() OVERRIDE; virtual bool EndDecoding() OVERRIDE; virtual scoped_refptr<Trace> CreateTrace(const std::string& name) OVERRIDE; virtual void CalculateTimerOffset() OVERRIDE; scoped_refptr<Outputter> outputter_; bool gpu_timing_synced_; int64 timer_offset_; gles2::GLES2Decoder* decoder_; DISALLOW_COPY_AND_ASSIGN(GPUTracerARBTimerQuery); }; bool Trace::IsProcessable() { return true; } const std::string& Trace::name() { return name_; } GLARBTimerTrace::GLARBTimerTrace(scoped_refptr<Outputter> outputter, const std::string& name, int64 offset) : Trace(name), outputter_(outputter), offset_(offset), start_time_(0), end_time_(0), end_requested_(false) { glGenQueries(2, queries_); } GLARBTimerTrace::~GLARBTimerTrace() { glDeleteQueries(2, queries_); } void GLARBTimerTrace::Start() { TRACE_EVENT_COPY_ASYNC_BEGIN0( TRACE_DISABLED_BY_DEFAULT("gpu.service"), name().c_str(), this); glQueryCounter(queries_[0], GL_TIMESTAMP); } void GLARBTimerTrace::End() { glQueryCounter(queries_[1], GL_TIMESTAMP); end_requested_ = true; TRACE_EVENT_COPY_ASYNC_END0( TRACE_DISABLED_BY_DEFAULT("gpu.service"), name().c_str(), this); } bool GLARBTimerTrace::IsAvailable() { if (!end_requested_) return false; GLint done = 0; glGetQueryObjectiv(queries_[1], GL_QUERY_RESULT_AVAILABLE, &done); return !!done; } void GLARBTimerTrace::Process() { DCHECK(IsAvailable()); GLuint64 timestamp; // TODO(dsinclair): It's possible for the timer to wrap during the start/end. // We need to detect if the end is less then the start and correct for the // wrapping. glGetQueryObjectui64v(queries_[0], GL_QUERY_RESULT, ×tamp); start_time_ = (timestamp / base::Time::kNanosecondsPerMicrosecond) + offset_; glGetQueryObjectui64v(queries_[1], GL_QUERY_RESULT, ×tamp); end_time_ = (timestamp / base::Time::kNanosecondsPerMicrosecond) + offset_; glDeleteQueries(2, queries_); outputter_->Trace(name(), start_time_, end_time_); } bool GPUTracerImpl::BeginDecoding() { if (gpu_executing_) return false; gpu_executing_ = true; if (IsTracing()) { // Begin a Trace for all active markers for (size_t i = 0; i < markers_.size(); i++) { markers_[i].trace_ = CreateTrace(markers_[i].name_); markers_[i].trace_->Start(); } } return true; } bool GPUTracerImpl::EndDecoding() { if (!gpu_executing_) return false; // End Trace for all active markers if (IsTracing()) { for (size_t i = 0; i < markers_.size(); i++) { if (markers_[i].trace_) { markers_[i].trace_->End(); if (markers_[i].trace_->IsProcessable()) traces_.push_back(markers_[i].trace_); markers_[i].trace_ = 0; } } IssueProcessTask(); } gpu_executing_ = false; return true; } bool GPUTracerImpl::Begin(const std::string& name, GpuTracerSource source) { if (!gpu_executing_) return false; // Push new marker from given 'source' markers_.push_back(TraceMarker(name, source)); // Create trace if (IsTracing()) { scoped_refptr<Trace> trace = CreateTrace(name); trace->Start(); markers_.back().trace_ = trace; } return true; } bool GPUTracerImpl::End(GpuTracerSource source) { if (!gpu_executing_) return false; // Pop last marker with matching 'source' for (int i = markers_.size() - 1; i >= 0; i--) { if (markers_[i].source_ == source) { // End trace if (IsTracing()) { scoped_refptr<Trace> trace = markers_[i].trace_; if (trace) { trace->End(); if (trace->IsProcessable()) traces_.push_back(trace); IssueProcessTask(); } } markers_.erase(markers_.begin() + i); return true; } } return false; } void GPUTracerImpl::Process() { process_posted_ = false; ProcessTraces(); IssueProcessTask(); } void GPUTracerImpl::ProcessTraces() { while (!traces_.empty() && traces_.front()->IsAvailable()) { traces_.front()->Process(); traces_.pop_front(); } } const std::string& GPUTracerImpl::CurrentName() const { if (markers_.empty()) return base::EmptyString(); return markers_.back().name_; } scoped_refptr<Trace> GPUTracerImpl::CreateTrace(const std::string& name) { return new NoopTrace(name); } void GPUTracerImpl::IssueProcessTask() { if (traces_.empty() || process_posted_) return; process_posted_ = true; base::MessageLoop::current()->PostDelayedTask( FROM_HERE, base::Bind(&GPUTracerImpl::Process, base::AsWeakPtr(this)), base::TimeDelta::FromMilliseconds(kProcessInterval)); } GPUTracerARBTimerQuery::GPUTracerARBTimerQuery(gles2::GLES2Decoder* decoder) : timer_offset_(0), decoder_(decoder) { outputter_ = TraceOutputter::Create("GL_ARB_timer_query"); } GPUTracerARBTimerQuery::~GPUTracerARBTimerQuery() { } scoped_refptr<Trace> GPUTracerARBTimerQuery::CreateTrace( const std::string& name) { if (*gpu_trace_dev_category) return new GLARBTimerTrace(outputter_, name, timer_offset_); return GPUTracerImpl::CreateTrace(name); } bool GPUTracerARBTimerQuery::BeginDecoding() { if (*gpu_trace_dev_category) { // Make sure timing is synced before tracing if (!gpu_timing_synced_) { CalculateTimerOffset(); gpu_timing_synced_ = true; } } else { // If GPU device category is off, invalidate timing sync gpu_timing_synced_ = false; } return GPUTracerImpl::BeginDecoding(); } bool GPUTracerARBTimerQuery::EndDecoding() { bool ret = GPUTracerImpl::EndDecoding(); // NOTE(vmiura_: glFlush() here can help give better trace results, // but it distorts the normal device behavior. return ret; } void GPUTracerARBTimerQuery::ProcessTraces() { TRACE_EVENT0("gpu", "GPUTracerARBTimerQuery::ProcessTraces"); // Make owning decoder's GL context current if (!decoder_->MakeCurrent()) { // Skip subsequent GL calls if MakeCurrent fails traces_.clear(); return; } while (!traces_.empty() && traces_.front()->IsAvailable()) { traces_.front()->Process(); traces_.pop_front(); } // Clear pending traces if there were are any errors GLenum err = glGetError(); if (err != GL_NO_ERROR) traces_.clear(); } void GPUTracerARBTimerQuery::CalculateTimerOffset() { TRACE_EVENT0("gpu", "GPUTracerARBTimerQuery::CalculateTimerOffset"); // NOTE(vmiura): It would be better to use glGetInteger64v, however // it's not available everywhere. GLuint64 gl_now = 0; GLuint query; glFinish(); glGenQueries(1, &query); glQueryCounter(query, GL_TIMESTAMP); glFinish(); glGetQueryObjectui64v(query, GL_QUERY_RESULT, &gl_now); base::TimeTicks system_now = base::TimeTicks::NowFromSystemTraceTime(); gl_now /= base::Time::kNanosecondsPerMicrosecond; timer_offset_ = system_now.ToInternalValue() - gl_now; glDeleteQueries(1, &query); } GPUTracer::GPUTracer() {} GPUTracer::~GPUTracer() {} scoped_ptr<GPUTracer> GPUTracer::Create(gles2::GLES2Decoder* decoder) { if (gfx::g_driver_gl.ext.b_GL_ARB_timer_query) { return scoped_ptr<GPUTracer>(new GPUTracerARBTimerQuery(decoder)); } return scoped_ptr<GPUTracer>(new GPUTracerImpl()); } } // namespace gles2 } // namespace gpu