// 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 #include "base/bind.h" #include "base/debug/trace_event.h" #include "base/memory/weak_ptr.h" #include "base/strings/string_util.h" #include "base/threading/thread.h" #include "base/time/time.h" #include "ui/gl/gl_bindings.h" namespace gpu { namespace gles2 { namespace { class Outputter; static const unsigned int kProcessInterval = 16; static Outputter* g_outputter_thread = NULL; class Outputter : private base::Thread, public base::RefCounted { public: static scoped_refptr Create(const std::string& name) { if (!g_outputter_thread) { g_outputter_thread = new Outputter(name); g_outputter_thread->Start(); g_outputter_thread->Stop(); } return g_outputter_thread; } uint64 Id() { return thread_id(); } private: friend class base::RefCounted; explicit Outputter(const std::string& name) : base::Thread(name.c_str()) {} virtual ~Outputter() { g_outputter_thread = NULL; } DISALLOW_COPY_AND_ASSIGN(Outputter); }; class Trace : public base::RefCounted { public: explicit Trace(const std::string& name) : name_(name) {} virtual void Start() = 0; virtual void End() = 0; // True if the the results of this query are available. virtual bool IsAvailable() = 0; virtual bool IsProcessable() { return true; } virtual void Process() = 0; virtual const std::string& name() { return name_; } protected: virtual ~Trace() {} private: friend class base::RefCounted; std::string name_; DISALLOW_COPY_AND_ASSIGN(Trace); }; class GLARBTimerTrace : public Trace { public: GLARBTimerTrace(scoped_refptr outputter, const std::string& name, int64 offset); // Implementation of Tracer virtual void Start() OVERRIDE; virtual void End() OVERRIDE; virtual bool IsAvailable() OVERRIDE; virtual void Process() OVERRIDE; private: virtual ~GLARBTimerTrace(); void Output(); scoped_refptr outputter_; int64 offset_; int64 start_time_; int64 end_time_; bool end_requested_; GLuint queries_[2]; DISALLOW_COPY_AND_ASSIGN(GLARBTimerTrace); }; class NoopTrace : public Trace { public: explicit NoopTrace(const std::string& name) : Trace(name) {} // Implementation of Tracer virtual void Start() OVERRIDE {} virtual void End() OVERRIDE {} virtual bool IsAvailable() OVERRIDE { return true; } virtual bool IsProcessable() OVERRIDE { return false; } virtual void Process() OVERRIDE {} private: virtual ~NoopTrace() {} DISALLOW_COPY_AND_ASSIGN(NoopTrace); }; class GPUTracerImpl : public GPUTracer, public base::SupportsWeakPtr { public: GPUTracerImpl() : gpu_category_enabled_( TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED("gpu")), process_posted_(false) { } virtual ~GPUTracerImpl() {} // Implementation of gpu::gles2::GPUTracer virtual bool Begin(const std::string& name) OVERRIDE; virtual bool End() OVERRIDE; virtual const std::string& CurrentName() const OVERRIDE; // Process any completed traces. virtual void Process(); protected: // Create a new trace. virtual scoped_refptr CreateTrace(const std::string& name); const unsigned char* gpu_category_enabled_; private: void IssueProcessTask(); scoped_refptr current_trace_; std::deque > traces_; bool process_posted_; DISALLOW_COPY_AND_ASSIGN(GPUTracerImpl); }; class GPUTracerARBTimerQuery : public GPUTracerImpl { public: GPUTracerARBTimerQuery(); virtual ~GPUTracerARBTimerQuery(); // Implementation of GPUTracerImpl virtual void Process() OVERRIDE; private: // Implementation of GPUTracerImpl. virtual scoped_refptr CreateTrace(const std::string& name) OVERRIDE; void CalculateTimerOffset(); scoped_refptr outputter_; int64 timer_offset_; int64 last_offset_check_; DISALLOW_COPY_AND_ASSIGN(GPUTracerARBTimerQuery); }; GLARBTimerTrace::GLARBTimerTrace(scoped_refptr 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() { } void GLARBTimerTrace::Start() { glQueryCounter(queries_[0], GL_TIMESTAMP); } void GLARBTimerTrace::End() { glQueryCounter(queries_[1], GL_TIMESTAMP); end_requested_ = true; } 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()); GLint64 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. glGetQueryObjecti64v(queries_[0], GL_QUERY_RESULT, ×tamp); start_time_ = (timestamp / base::Time::kNanosecondsPerMicrosecond) + offset_; glGetQueryObjecti64v(queries_[1], GL_QUERY_RESULT, ×tamp); end_time_ = (timestamp / base::Time::kNanosecondsPerMicrosecond) + offset_; glDeleteQueries(2, queries_); TRACE_EVENT_COPY_BEGIN_WITH_ID_TID_AND_TIMESTAMP0("gpu", name().c_str(), this, outputter_->Id(), start_time_); TRACE_EVENT_COPY_END_WITH_ID_TID_AND_TIMESTAMP0("gpu", name().c_str(), this, outputter_->Id(), end_time_); } bool GPUTracerImpl::Begin(const std::string& name) { // Make sure we are not nesting trace commands. if (current_trace_.get()) return false; current_trace_ = CreateTrace(name); current_trace_->Start(); return true; } bool GPUTracerImpl::End() { if (!current_trace_.get()) return false; current_trace_->End(); if (current_trace_->IsProcessable()) traces_.push_back(current_trace_); current_trace_ = NULL; IssueProcessTask(); return true; } void GPUTracerImpl::Process() { process_posted_ = false; while (!traces_.empty() && traces_.front()->IsAvailable()) { traces_.front()->Process(); traces_.pop_front(); } IssueProcessTask(); } const std::string& GPUTracerImpl::CurrentName() const { if (!current_trace_.get()) return EmptyString(); return current_trace_->name(); } scoped_refptr 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() : GPUTracerImpl(), timer_offset_(0), last_offset_check_(0) { CalculateTimerOffset(); outputter_ = Outputter::Create("GL_ARB_timer_query"); } GPUTracerARBTimerQuery::~GPUTracerARBTimerQuery() { } scoped_refptr GPUTracerARBTimerQuery::CreateTrace( const std::string& name) { if (*gpu_category_enabled_) return new GLARBTimerTrace(outputter_, name, timer_offset_); return GPUTracerImpl::CreateTrace(name); } void GPUTracerARBTimerQuery::Process() { GPUTracerImpl::Process(); if (*gpu_category_enabled_ && (last_offset_check_ + base::Time::kMicrosecondsPerSecond) < base::TimeTicks::NowFromSystemTraceTime().ToInternalValue()) CalculateTimerOffset(); } void GPUTracerARBTimerQuery::CalculateTimerOffset() { TRACE_EVENT0("gpu", "CalculateTimerOffset"); // TODO(dsinclair): Change to glGetInteger64v. GLuint64 gl_now = 0; GLuint query; glGenQueries(1, &query); glQueryCounter(query, GL_TIMESTAMP); 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); last_offset_check_ = system_now.ToInternalValue(); } } // namespace scoped_ptr GPUTracer::Create() { if (gfx::g_driver_gl.ext.b_GL_ARB_timer_query) return scoped_ptr(new GPUTracerARBTimerQuery()); return scoped_ptr(new GPUTracerImpl()); } } // namespace gles2 } // namespace gpu