Commit 882e1b72 authored by dyen's avatar dyen Committed by Commit bot

Added support for GPU Tracing on mobile devices which support it.

Newer mobile devices may support the Open GL extension
EXT_disjoint_timer_query, these devices can now be traced in Chrome.

R=vmiura@chromium.org
BUG= https://code.google.com/p/chromium/issues/detail?id=397294
TEST= trybots

Review URL: https://codereview.chromium.org/509723002

Cr-Commit-Position: refs/heads/master@{#293150}
parent bbde62b5
...@@ -59,45 +59,68 @@ void TraceOutputter::Trace(const std::string& name, ...@@ -59,45 +59,68 @@ void TraceOutputter::Trace(const std::string& name,
++local_trace_id_; ++local_trace_id_;
} }
GPUTrace::GPUTrace(const std::string& name)
: name_(name),
outputter_(NULL),
offset_(0),
end_time_(0),
end_requested_(false),
enabled_(false) {
}
GPUTrace::GPUTrace(scoped_refptr<Outputter> outputter, GPUTrace::GPUTrace(scoped_refptr<Outputter> outputter,
const std::string& name, const std::string& name,
int64 offset) int64 offset,
GpuTracerType tracer_type)
: name_(name), : name_(name),
outputter_(outputter), outputter_(outputter),
offset_(offset), offset_(offset),
start_time_(0), start_time_(0),
end_time_(0), end_time_(0),
end_requested_(false), tracer_type_(tracer_type),
enabled_(true) { end_requested_(false) {
glGenQueries(2, queries_); memset(queries_, 0, sizeof(queries_));
switch (tracer_type_) {
case kTracerTypeARBTimer:
case kTracerTypeDisjointTimer:
glGenQueriesARB(2, queries_);
break;
default:
tracer_type_ = kTracerTypeInvalid;
}
} }
GPUTrace::~GPUTrace() { GPUTrace::~GPUTrace() {
if (enabled_) switch (tracer_type_) {
glDeleteQueries(2, queries_); case kTracerTypeInvalid:
break;
case kTracerTypeARBTimer:
case kTracerTypeDisjointTimer:
glDeleteQueriesARB(2, queries_);
break;
}
} }
void GPUTrace::Start() { void GPUTrace::Start() {
TRACE_EVENT_COPY_ASYNC_BEGIN0( TRACE_EVENT_COPY_ASYNC_BEGIN0(
TRACE_DISABLED_BY_DEFAULT("gpu.service"), name().c_str(), this); TRACE_DISABLED_BY_DEFAULT("gpu.service"), name().c_str(), this);
if (enabled_) {
switch (tracer_type_) {
case kTracerTypeInvalid:
break;
case kTracerTypeARBTimer:
case kTracerTypeDisjointTimer:
// GL_TIMESTAMP and GL_TIMESTAMP_EXT both have the same value.
glQueryCounter(queries_[0], GL_TIMESTAMP); glQueryCounter(queries_[0], GL_TIMESTAMP);
break;
} }
} }
void GPUTrace::End() { void GPUTrace::End() {
if (enabled_) {
glQueryCounter(queries_[1], GL_TIMESTAMP);
end_requested_ = true; end_requested_ = true;
switch (tracer_type_) {
case kTracerTypeInvalid:
break;
case kTracerTypeARBTimer:
case kTracerTypeDisjointTimer:
// GL_TIMESTAMP and GL_TIMESTAMP_EXT both have the same value.
glQueryCounter(queries_[1], GL_TIMESTAMP);
break;
} }
TRACE_EVENT_COPY_ASYNC_END0( TRACE_EVENT_COPY_ASYNC_END0(
...@@ -105,34 +128,36 @@ void GPUTrace::End() { ...@@ -105,34 +128,36 @@ void GPUTrace::End() {
} }
bool GPUTrace::IsAvailable() { bool GPUTrace::IsAvailable() {
if (!enabled_) if (tracer_type_ != kTracerTypeInvalid) {
return true; if (!end_requested_)
else if (!end_requested_)
return false; return false;
GLint done = 0; GLint done = 0;
glGetQueryObjectiv(queries_[1], GL_QUERY_RESULT_AVAILABLE, &done); glGetQueryObjectiv(queries_[1], GL_QUERY_RESULT_AVAILABLE, &done);
return !!done; return !!done;
}
return true;
} }
void GPUTrace::Process() { void GPUTrace::Process() {
if (!enabled_) if (tracer_type_ == kTracerTypeInvalid)
return; return;
DCHECK(IsAvailable()); DCHECK(IsAvailable());
GLuint64 timestamp; GLuint64 begin_stamp = 0;
GLuint64 end_stamp = 0;
// TODO(dsinclair): It's possible for the timer to wrap during the start/end. // 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 // We need to detect if the end is less then the start and correct for the
// wrapping. // wrapping.
glGetQueryObjectui64v(queries_[0], GL_QUERY_RESULT, &timestamp); glGetQueryObjectui64v(queries_[0], GL_QUERY_RESULT, &begin_stamp);
start_time_ = (timestamp / base::Time::kNanosecondsPerMicrosecond) + offset_; glGetQueryObjectui64v(queries_[1], GL_QUERY_RESULT, &end_stamp);
glGetQueryObjectui64v(queries_[1], GL_QUERY_RESULT, &timestamp);
end_time_ = (timestamp / base::Time::kNanosecondsPerMicrosecond) + offset_;
glDeleteQueries(2, queries_); start_time_ = (begin_stamp / base::Time::kNanosecondsPerMicrosecond) +
offset_;
end_time_ = (end_stamp / base::Time::kNanosecondsPerMicrosecond) + offset_;
outputter_->Trace(name(), start_time_, end_time_); outputter_->Trace(name(), start_time_, end_time_);
} }
...@@ -144,13 +169,16 @@ GPUTracer::GPUTracer(gles2::GLES2Decoder* decoder) ...@@ -144,13 +169,16 @@ GPUTracer::GPUTracer(gles2::GLES2Decoder* decoder)
decoder_(decoder), decoder_(decoder),
timer_offset_(0), timer_offset_(0),
last_tracer_source_(kTraceGroupInvalid), last_tracer_source_(kTraceGroupInvalid),
enabled_(false), tracer_type_(kTracerTypeInvalid),
gpu_timing_synced_(false), gpu_timing_synced_(false),
gpu_executing_(false), gpu_executing_(false),
process_posted_(false) { process_posted_(false) {
if (gfx::g_driver_gl.ext.b_GL_ARB_timer_query) { if (gfx::g_driver_gl.ext.b_GL_EXT_disjoint_timer_query) {
tracer_type_ = kTracerTypeDisjointTimer;
outputter_ = TraceOutputter::Create("GL_EXT_disjoint_timer_query");
} else if (gfx::g_driver_gl.ext.b_GL_ARB_timer_query) {
tracer_type_ = kTracerTypeARBTimer;
outputter_ = TraceOutputter::Create("GL_ARB_timer_query"); outputter_ = TraceOutputter::Create("GL_ARB_timer_query");
enabled_ = true;
} }
} }
...@@ -158,25 +186,19 @@ GPUTracer::~GPUTracer() { ...@@ -158,25 +186,19 @@ GPUTracer::~GPUTracer() {
} }
bool GPUTracer::BeginDecoding() { bool GPUTracer::BeginDecoding() {
if (enabled_) {
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;
}
}
if (gpu_executing_) if (gpu_executing_)
return false; return false;
CalculateTimerOffset();
gpu_executing_ = true; gpu_executing_ = true;
if (IsTracing()) { if (IsTracing()) {
// Reset disjoint bit for the disjoint timer.
if (tracer_type_ == kTracerTypeDisjointTimer) {
GLint disjoint_value = 0;
glGetIntegerv(GL_GPU_DISJOINT_EXT, &disjoint_value);
}
// Begin a Trace for all active markers // Begin a Trace for all active markers
for (int n = 0; n < NUM_TRACER_SOURCES; n++) { for (int n = 0; n < NUM_TRACER_SOURCES; n++) {
for (size_t i = 0; i < markers_[n].size(); i++) { for (size_t i = 0; i < markers_[n].size(); i++) {
...@@ -272,10 +294,10 @@ const std::string& GPUTracer::CurrentName() const { ...@@ -272,10 +294,10 @@ const std::string& GPUTracer::CurrentName() const {
} }
scoped_refptr<GPUTrace> GPUTracer::CreateTrace(const std::string& name) { scoped_refptr<GPUTrace> GPUTracer::CreateTrace(const std::string& name) {
if (enabled_ && *gpu_trace_dev_category) GpuTracerType tracer_type = *gpu_trace_dev_category ? tracer_type_ :
return new GPUTrace(outputter_, name, timer_offset_); kTracerTypeInvalid;
else
return new GPUTrace(name); return new GPUTrace(outputter_, name, timer_offset_, tracer_type);
} }
void GPUTracer::Process() { void GPUTracer::Process() {
...@@ -285,11 +307,8 @@ void GPUTracer::Process() { ...@@ -285,11 +307,8 @@ void GPUTracer::Process() {
} }
void GPUTracer::ProcessTraces() { void GPUTracer::ProcessTraces() {
if (!enabled_) { if (tracer_type_ == kTracerTypeInvalid) {
while (!traces_.empty() && traces_.front()->IsAvailable()) { traces_.clear();
traces_.front()->Process();
traces_.pop_front();
}
return; return;
} }
...@@ -302,6 +321,14 @@ void GPUTracer::ProcessTraces() { ...@@ -302,6 +321,14 @@ void GPUTracer::ProcessTraces() {
return; return;
} }
// Check if disjoint operation has occurred, discard ongoing traces if so.
if (tracer_type_ == kTracerTypeDisjointTimer) {
GLint disjoint_value = 0;
glGetIntegerv(GL_GPU_DISJOINT_EXT, &disjoint_value);
if (disjoint_value)
traces_.clear();
}
while (!traces_.empty() && traces_.front()->IsAvailable()) { while (!traces_.empty() && traces_.front()->IsAvailable()) {
traces_.front()->Process(); traces_.front()->Process();
traces_.pop_front(); traces_.pop_front();
...@@ -314,23 +341,48 @@ void GPUTracer::ProcessTraces() { ...@@ -314,23 +341,48 @@ void GPUTracer::ProcessTraces() {
} }
void GPUTracer::CalculateTimerOffset() { void GPUTracer::CalculateTimerOffset() {
if (enabled_) { if (tracer_type_ != kTracerTypeInvalid) {
// If GPU device category is off, invalidate timing sync.
if (*gpu_trace_dev_category == '\0') {
gpu_timing_synced_ = false;
return;
}
if (gpu_timing_synced_)
return;
TRACE_EVENT0("gpu", "GPUTracer::CalculateTimerOffset"); TRACE_EVENT0("gpu", "GPUTracer::CalculateTimerOffset");
// NOTE(vmiura): It would be better to use glGetInteger64v, however // NOTE(vmiura): It would be better to use glGetInteger64v, however
// it's not available everywhere. // it's not available everywhere.
GLuint64 gl_now = 0; GLuint64 gl_now = 0;
GLuint query; GLuint query;
GLint disjoint_value = 0;
if (tracer_type_ == kTracerTypeDisjointTimer) {
// Clear the disjoint bit before we do any queries.
glGetIntegerv(GL_GPU_DISJOINT_EXT, &disjoint_value);
}
glFinish(); glFinish();
glGenQueries(1, &query); glGenQueriesARB(1, &query);
glQueryCounter(query, GL_TIMESTAMP); glQueryCounter(query, GL_TIMESTAMP);
glFinish(); glFinish();
glGetQueryObjectui64v(query, GL_QUERY_RESULT, &gl_now); glGetQueryObjectui64v(query, GL_QUERY_RESULT, &gl_now);
glDeleteQueriesARB(1, &query);
if (tracer_type_ == kTracerTypeDisjointTimer) {
glGetIntegerv(GL_GPU_DISJOINT_EXT, &disjoint_value);
if (disjoint_value)
return;
}
base::TimeTicks system_now = base::TimeTicks::NowFromSystemTraceTime(); base::TimeTicks system_now = base::TimeTicks::NowFromSystemTraceTime();
gl_now /= base::Time::kNanosecondsPerMicrosecond; gl_now /= base::Time::kNanosecondsPerMicrosecond;
timer_offset_ = system_now.ToInternalValue() - gl_now; timer_offset_ = system_now.ToInternalValue() - gl_now;
glDeleteQueries(1, &query); gpu_timing_synced_ = true;
} }
} }
......
...@@ -33,6 +33,13 @@ enum GpuTracerSource { ...@@ -33,6 +33,13 @@ enum GpuTracerSource {
NUM_TRACER_SOURCES NUM_TRACER_SOURCES
}; };
enum GpuTracerType {
kTracerTypeInvalid = -1,
kTracerTypeARBTimer,
kTracerTypeDisjointTimer
};
// Marker structure for a Trace. // Marker structure for a Trace.
struct TraceMarker { struct TraceMarker {
TraceMarker(const std::string& name); TraceMarker(const std::string& name);
...@@ -86,7 +93,7 @@ class GPUTracer : public base::SupportsWeakPtr<GPUTracer> { ...@@ -86,7 +93,7 @@ class GPUTracer : public base::SupportsWeakPtr<GPUTracer> {
int64 timer_offset_; int64 timer_offset_;
GpuTracerSource last_tracer_source_; GpuTracerSource last_tracer_source_;
bool enabled_; GpuTracerType tracer_type_;
bool gpu_timing_synced_; bool gpu_timing_synced_;
bool gpu_executing_; bool gpu_executing_;
bool process_posted_; bool process_posted_;
...@@ -126,12 +133,12 @@ class TraceOutputter : public Outputter { ...@@ -126,12 +133,12 @@ class TraceOutputter : public Outputter {
class GPU_EXPORT GPUTrace class GPU_EXPORT GPUTrace
: public base::RefCounted<GPUTrace> { : public base::RefCounted<GPUTrace> {
public: public:
explicit GPUTrace(const std::string& name);
GPUTrace(scoped_refptr<Outputter> outputter, GPUTrace(scoped_refptr<Outputter> outputter,
const std::string& name, const std::string& name,
int64 offset); int64 offset,
GpuTracerType tracer_type);
bool IsEnabled() { return enabled_; } bool IsEnabled() { return tracer_type_ != kTracerTypeInvalid; }
const std::string& name() { return name_; } const std::string& name() { return name_; }
void Start(); void Start();
...@@ -152,8 +159,8 @@ class GPU_EXPORT GPUTrace ...@@ -152,8 +159,8 @@ class GPU_EXPORT GPUTrace
int64 offset_; int64 offset_;
int64 start_time_; int64 start_time_;
int64 end_time_; int64 end_time_;
GpuTracerType tracer_type_;
bool end_requested_; bool end_requested_;
bool enabled_;
GLuint queries_[2]; GLuint queries_[2];
......
...@@ -51,14 +51,14 @@ class GlFakeQueries { ...@@ -51,14 +51,14 @@ class GlFakeQueries {
void SetCurrentGLTime(GLint64 current_time) { current_time_ = current_time; } void SetCurrentGLTime(GLint64 current_time) { current_time_ = current_time; }
void GenQueries(GLsizei n, GLuint* ids) { void GenQueriesARB(GLsizei n, GLuint* ids) {
for (GLsizei i = 0; i < n; i++) { for (GLsizei i = 0; i < n; i++) {
ids[i] = next_query_id_++; ids[i] = next_query_id_++;
alloced_queries_.insert(ids[i]); alloced_queries_.insert(ids[i]);
} }
} }
void DeleteQueries(GLsizei n, const GLuint* ids) { void DeleteQueriesARB(GLsizei n, const GLuint* ids) {
for (GLsizei i = 0; i < n; i++) { for (GLsizei i = 0; i < n; i++) {
alloced_queries_.erase(ids[i]); alloced_queries_.erase(ids[i]);
query_timestamp_.erase(ids[i]); query_timestamp_.erase(ids[i]);
...@@ -109,56 +109,13 @@ class GlFakeQueries { ...@@ -109,56 +109,13 @@ class GlFakeQueries {
std::map<GLuint, GLint64> query_timestamp_; std::map<GLuint, GLint64> query_timestamp_;
}; };
class GpuTracerTest : public GpuServiceTest { class BaseGpuTracerTest : public GpuServiceTest {
public: public:
GpuTracerTest() {} BaseGpuTracerTest() {}
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
protected: void DoTraceTest() {
virtual void SetUp() {
GpuServiceTest::SetUp();
gl_fake_queries_.Reset();
}
virtual void TearDown() {
gl_.reset();
gl_fake_queries_.Reset();
GpuServiceTest::TearDown();
}
void SetupTimerQueryMocks() {
// Delegate query APIs used by GPUTrace to a GlFakeQueries
EXPECT_CALL(*gl_, GenQueries(_, NotNull())).Times(AtLeast(1)).WillOnce(
Invoke(&gl_fake_queries_, &GlFakeQueries::GenQueries));
EXPECT_CALL(*gl_, GetQueryObjectiv(_, GL_QUERY_RESULT_AVAILABLE, NotNull()))
.Times(AtLeast(2))
.WillRepeatedly(
Invoke(&gl_fake_queries_, &GlFakeQueries::GetQueryObjectiv));
EXPECT_CALL(*gl_, QueryCounter(_, GL_TIMESTAMP))
.Times(AtLeast(2))
.WillRepeatedly(
Invoke(&gl_fake_queries_, &GlFakeQueries::QueryCounter));
EXPECT_CALL(*gl_, GetQueryObjectui64v(_, GL_QUERY_RESULT, NotNull()))
.Times(AtLeast(2))
.WillRepeatedly(
Invoke(&gl_fake_queries_, &GlFakeQueries::GetQueryObjectui64v));
EXPECT_CALL(*gl_, DeleteQueries(2, NotNull()))
.Times(AtLeast(1))
.WillRepeatedly(
Invoke(&gl_fake_queries_, &GlFakeQueries::DeleteQueries));
}
GlFakeQueries gl_fake_queries_;
};
TEST_F(GpuTracerTest, GPUTrace) {
// Test basic timer query functionality
{
MockOutputter* outputter = new MockOutputter(); MockOutputter* outputter = new MockOutputter();
scoped_refptr<Outputter> outputter_ref = outputter; scoped_refptr<Outputter> outputter_ref = outputter;
...@@ -180,7 +137,8 @@ TEST_F(GpuTracerTest, GPUTrace) { ...@@ -180,7 +137,8 @@ TEST_F(GpuTracerTest, GPUTrace) {
Trace(trace_name, expect_start_time, expect_end_time)); Trace(trace_name, expect_start_time, expect_end_time));
scoped_refptr<GPUTrace> trace = scoped_refptr<GPUTrace> trace =
new GPUTrace(outputter_ref, trace_name, offset_time); new GPUTrace(outputter_ref, trace_name, offset_time,
GetTracerType());
gl_fake_queries_.SetCurrentGLTime(start_timestamp); gl_fake_queries_.SetCurrentGLTime(start_timestamp);
trace->Start(); trace->Start();
...@@ -203,6 +161,76 @@ TEST_F(GpuTracerTest, GPUTrace) { ...@@ -203,6 +161,76 @@ TEST_F(GpuTracerTest, GPUTrace) {
// Proces should output expected Trace results to MockOutputter // Proces should output expected Trace results to MockOutputter
trace->Process(); trace->Process();
} }
protected:
virtual void SetUp() {
GpuServiceTest::SetUp();
gl_fake_queries_.Reset();
}
virtual void TearDown() {
gl_.reset();
gl_fake_queries_.Reset();
GpuServiceTest::TearDown();
}
virtual void SetupTimerQueryMocks() {
// Delegate query APIs used by GPUTrace to a GlFakeQueries
EXPECT_CALL(*gl_, GenQueriesARB(_, NotNull())).Times(AtLeast(1)).WillOnce(
Invoke(&gl_fake_queries_, &GlFakeQueries::GenQueriesARB));
EXPECT_CALL(*gl_, GetQueryObjectiv(_, GL_QUERY_RESULT_AVAILABLE, NotNull()))
.Times(AtLeast(2))
.WillRepeatedly(
Invoke(&gl_fake_queries_, &GlFakeQueries::GetQueryObjectiv));
EXPECT_CALL(*gl_, QueryCounter(_, GL_TIMESTAMP))
.Times(AtLeast(2))
.WillRepeatedly(
Invoke(&gl_fake_queries_, &GlFakeQueries::QueryCounter));
EXPECT_CALL(*gl_, GetQueryObjectui64v(_, GL_QUERY_RESULT, NotNull()))
.Times(AtLeast(2))
.WillRepeatedly(
Invoke(&gl_fake_queries_, &GlFakeQueries::GetQueryObjectui64v));
EXPECT_CALL(*gl_, DeleteQueriesARB(2, NotNull()))
.Times(AtLeast(1))
.WillRepeatedly(
Invoke(&gl_fake_queries_, &GlFakeQueries::DeleteQueriesARB));
}
virtual GpuTracerType GetTracerType() = 0;
GlFakeQueries gl_fake_queries_;
};
class GpuARBTimerTracerTest : public BaseGpuTracerTest {
protected:
virtual GpuTracerType GetTracerType() OVERRIDE {
return kTracerTypeARBTimer;
}
};
class GpuDisjointTimerTracerTest : public BaseGpuTracerTest {
protected:
virtual GpuTracerType GetTracerType() OVERRIDE {
return kTracerTypeDisjointTimer;
}
};
TEST_F(GpuARBTimerTracerTest, GPUTrace) {
// Test basic timer query functionality
{
DoTraceTest();
}
}
TEST_F(GpuDisjointTimerTracerTest, GPUTrace) {
// Test basic timer query functionality
{
DoTraceTest();
}
} }
} // namespace gles2 } // namespace gles2
......
...@@ -359,10 +359,7 @@ GL_FUNCTIONS = [ ...@@ -359,10 +359,7 @@ GL_FUNCTIONS = [
'names': ['glGetQueryObjecti64v'], 'names': ['glGetQueryObjecti64v'],
'arguments': 'GLuint id, GLenum pname, GLint64* params', }, 'arguments': 'GLuint id, GLenum pname, GLint64* params', },
{ 'return_type': 'void', { 'return_type': 'void',
'names': ['glGetQueryObjectiv'], 'names': ['glGetQueryObjectui64v', 'glGetQueryObjectui64vEXT'],
'arguments': 'GLuint id, GLenum pname, GLint* params', },
{ 'return_type': 'void',
'names': ['glGetQueryObjectui64v'],
'arguments': 'GLuint id, GLenum pname, GLuint64* params', }, 'arguments': 'GLuint id, GLenum pname, GLuint64* params', },
{ 'return_type': 'void', { 'return_type': 'void',
'names': ['glGetQueryObjectuiv'], 'names': ['glGetQueryObjectuiv'],
...@@ -370,6 +367,10 @@ GL_FUNCTIONS = [ ...@@ -370,6 +367,10 @@ GL_FUNCTIONS = [
{ 'return_type': 'void', { 'return_type': 'void',
'names': ['glGetQueryObjectuivARB', 'glGetQueryObjectuivEXT'], 'names': ['glGetQueryObjectuivARB', 'glGetQueryObjectuivEXT'],
'arguments': 'GLuint id, GLenum pname, GLuint* params', }, 'arguments': 'GLuint id, GLenum pname, GLuint* params', },
{ 'return_type': 'void',
'names': ['glGetQueryObjectiv', 'glGetQueryObjectivARB',
'glGetQueryObjectivEXT'],
'arguments': 'GLuint id, GLenum pname, GLint* params', },
{ 'return_type': 'void', { 'return_type': 'void',
'names': ['glGetRenderbufferParameterivEXT', 'glGetRenderbufferParameteriv'], 'names': ['glGetRenderbufferParameterivEXT', 'glGetRenderbufferParameteriv'],
'arguments': 'GLenum target, GLenum pname, GLint* params', }, 'arguments': 'GLenum target, GLenum pname, GLint* params', },
...@@ -501,7 +502,7 @@ GL_FUNCTIONS = [ ...@@ -501,7 +502,7 @@ GL_FUNCTIONS = [
'names': ['glPushGroupMarkerEXT'], 'names': ['glPushGroupMarkerEXT'],
'arguments': 'GLsizei length, const char* marker', }, 'arguments': 'GLsizei length, const char* marker', },
{ 'return_type': 'void', { 'return_type': 'void',
'names': ['glQueryCounter'], 'names': ['glQueryCounter', 'glQueryCounterEXT'],
'arguments': 'GLuint id, GLenum target', }, 'arguments': 'GLuint id, GLenum target', },
{ 'return_type': 'void', { 'return_type': 'void',
'names': ['glReadBuffer'], 'names': ['glReadBuffer'],
......
...@@ -222,6 +222,14 @@ ...@@ -222,6 +222,14 @@
#define GL_PATH_PROJECTION_CHROMIUM 0x1701 #define GL_PATH_PROJECTION_CHROMIUM 0x1701
#endif #endif
#ifndef GL_EXT_disjoint_timer_query
#define GL_EXT_disjoint_timer_query 1
#define GL_QUERY_COUNTER_BITS_EXT 0x8864
#define GL_TIME_ELAPSED_EXT 0x88BF
#define GL_TIMESTAMP_EXT 0x8E28
#define GL_GPU_DISJOINT_EXT 0x8FBB
#endif
#define GL_GLEXT_PROTOTYPES 1 #define GL_GLEXT_PROTOTYPES 1
#if defined(OS_WIN) #if defined(OS_WIN)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment