blob: e227717678ffb6f1fa4e9a5209c6450a1be52b3b [file] [log] [blame]
// Copyright (c) 2015 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 "ui/gl/gpu_timing.h"
#include "base/time/time.h"
#include "ui/gl/gl_bindings.h"
#include "ui/gl/gl_context.h"
#include "ui/gl/gl_version_info.h"
namespace gfx {
GPUTiming::GPUTiming(GLContextReal* context) {
DCHECK(context);
const GLVersionInfo* version_info = context->GetVersionInfo();
DCHECK(version_info);
if (version_info->is_es3 && // glGetInteger64v is supported under ES3.
context->HasExtension("GL_EXT_disjoint_timer_query")) {
timer_type_ = kTimerTypeDisjoint;
} else if (context->HasExtension("GL_ARB_timer_query")) {
timer_type_ = kTimerTypeARB;
} else if (context->HasExtension("GL_EXT_timer_query")) {
timer_type_ = kTimerTypeEXT;
}
}
GPUTiming::~GPUTiming() {
}
scoped_refptr<GPUTimingClient> GPUTiming::CreateGPUTimingClient() {
return new GPUTimingClient(this);
}
uint32_t GPUTiming::GetDisjointCount() {
if (timer_type_ == kTimerTypeDisjoint) {
GLint disjoint_value = 0;
glGetIntegerv(GL_GPU_DISJOINT_EXT, &disjoint_value);
if (disjoint_value) {
disjoint_counter_++;
}
}
return disjoint_counter_;
}
GPUTimer::~GPUTimer() {
// Destroy() must be called before the destructor.
DCHECK(queries_[0] == 0);
DCHECK(queries_[1] == 0);
}
void GPUTimer::Destroy(bool have_context) {
if (have_context) {
glDeleteQueries(2, queries_);
}
memset(queries_, 0, sizeof(queries_));
}
void GPUTimer::Start() {
switch (gpu_timing_client_->gpu_timing_->timer_type_) {
case GPUTiming::kTimerTypeARB:
case GPUTiming::kTimerTypeDisjoint:
// GL_TIMESTAMP and GL_TIMESTAMP_EXT both have the same value.
glQueryCounter(queries_[0], GL_TIMESTAMP);
break;
case GPUTiming::kTimerTypeEXT:
glBeginQuery(GL_TIME_ELAPSED_EXT, queries_[0]);
break;
default:
NOTREACHED();
}
}
void GPUTimer::End() {
end_requested_ = true;
DCHECK(gpu_timing_client_->gpu_timing_);
switch (gpu_timing_client_->gpu_timing_->timer_type_) {
case GPUTiming::kTimerTypeARB:
case GPUTiming::kTimerTypeDisjoint:
offset_ = gpu_timing_client_->CalculateTimerOffset();
glQueryCounter(queries_[1], GL_TIMESTAMP);
break;
case GPUTiming::kTimerTypeEXT:
glEndQuery(GL_TIME_ELAPSED_EXT);
break;
default:
NOTREACHED();
}
}
bool GPUTimer::IsAvailable() {
if (!gpu_timing_client_->IsAvailable() || !end_requested_) {
return false;
}
GLint done = 0;
glGetQueryObjectiv(queries_[1] ? queries_[1] : queries_[0],
GL_QUERY_RESULT_AVAILABLE, &done);
return done != 0;
}
void GPUTimer::GetStartEndTimestamps(int64* start, int64* end) {
DCHECK(start && end);
DCHECK(IsAvailable());
DCHECK(gpu_timing_client_->gpu_timing_);
DCHECK(gpu_timing_client_->gpu_timing_->timer_type_ !=
GPUTiming::kTimerTypeEXT);
GLuint64 begin_stamp = 0;
GLuint64 end_stamp = 0;
// 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, &begin_stamp);
glGetQueryObjectui64v(queries_[1], GL_QUERY_RESULT, &end_stamp);
*start = (begin_stamp / base::Time::kNanosecondsPerMicrosecond) + offset_;
*end = (end_stamp / base::Time::kNanosecondsPerMicrosecond) + offset_;
}
int64 GPUTimer::GetDeltaElapsed() {
DCHECK(gpu_timing_client_->gpu_timing_);
switch (gpu_timing_client_->gpu_timing_->timer_type_) {
case GPUTiming::kTimerTypeARB:
case GPUTiming::kTimerTypeDisjoint: {
int64 start = 0;
int64 end = 0;
GetStartEndTimestamps(&start, &end);
return end - start;
} break;
case GPUTiming::kTimerTypeEXT: {
GLuint64 delta = 0;
glGetQueryObjectui64v(queries_[0], GL_QUERY_RESULT, &delta);
return static_cast<int64>(delta / base::Time::kNanosecondsPerMicrosecond);
} break;
default:
NOTREACHED();
}
return 0;
}
GPUTimer::GPUTimer(scoped_refptr<GPUTimingClient> gpu_timing_client)
: gpu_timing_client_(gpu_timing_client) {
DCHECK(gpu_timing_client_);
memset(queries_, 0, sizeof(queries_));
int queries = 0;
DCHECK(gpu_timing_client_->gpu_timing_);
switch (gpu_timing_client_->gpu_timing_->timer_type_) {
case GPUTiming::kTimerTypeARB:
case GPUTiming::kTimerTypeDisjoint:
queries = 2;
break;
case GPUTiming::kTimerTypeEXT:
queries = 1;
break;
default:
NOTREACHED();
}
glGenQueries(queries, queries_);
}
GPUTimingClient::GPUTimingClient(GPUTiming* gpu_timing)
: gpu_timing_(gpu_timing) {
if (gpu_timing) {
timer_type_ = gpu_timing->GetTimerType();
disjoint_counter_ = gpu_timing_->GetDisjointCount();
}
}
scoped_ptr<GPUTimer> GPUTimingClient::CreateGPUTimer() {
return make_scoped_ptr(new GPUTimer(this));
}
bool GPUTimingClient::IsAvailable() {
return timer_type_ != GPUTiming::kTimerTypeInvalid;
}
bool GPUTimingClient::IsTimerOffsetAvailable() {
return timer_type_ == GPUTiming::kTimerTypeARB ||
timer_type_ == GPUTiming::kTimerTypeDisjoint;
}
const char* GPUTimingClient::GetTimerTypeName() const {
switch (timer_type_) {
case GPUTiming::kTimerTypeDisjoint:
return "GL_EXT_disjoint_timer_query";
case GPUTiming::kTimerTypeARB:
return "GL_ARB_timer_query";
case GPUTiming::kTimerTypeEXT:
return "GL_EXT_timer_query";
default:
return "Unknown";
}
}
bool GPUTimingClient::CheckAndResetTimerErrors() {
if (timer_type_ == GPUTiming::kTimerTypeDisjoint) {
DCHECK(gpu_timing_ != nullptr);
const uint32_t total_disjoint_count = gpu_timing_->GetDisjointCount();
const bool disjoint_triggered = total_disjoint_count != disjoint_counter_;
disjoint_counter_ = total_disjoint_count;
return disjoint_triggered;
}
return false;
}
int64 GPUTimingClient::CalculateTimerOffset() {
DCHECK(IsTimerOffsetAvailable());
if (!offset_valid_) {
GLint64 gl_now = 0;
glGetInteger64v(GL_TIMESTAMP, &gl_now);
int64 now =
cpu_time_for_testing_.is_null()
? (base::TraceTicks::Now() - base::TraceTicks()).InMicroseconds()
: cpu_time_for_testing_.Run();
offset_ = now - gl_now / base::Time::kNanosecondsPerMicrosecond;
offset_valid_ = timer_type_ == GPUTiming::kTimerTypeARB;
}
return offset_;
}
void GPUTimingClient::InvalidateTimerOffset() {
offset_valid_ = false;
}
void GPUTimingClient::SetCpuTimeForTesting(
const base::Callback<int64(void)>& cpu_time) {
cpu_time_for_testing_ = cpu_time;
}
GPUTimingClient::~GPUTimingClient() {
}
} // namespace gfx