From cba4e8aed214253235dc7ac81a7e87279a614b02 Mon Sep 17 00:00:00 2001 From: Victor Moya Date: Thu, 29 Dec 2016 10:34:00 +0100 Subject: [PATCH] Implemented OpenGL counters using pipeline stats and occlusion queries Implemented OpenGL counters based on ARB_pipeline_statistics extension and occlusion queries. Because OpenGL doesn't seem to allow concurrent queries of the same type occlusion queries in the log are ignored when executing FetchCounters. Renamed GPUTimer to GPUQueries because D3D11 GPUTimer structure seems to be visible and creates conflicts on VS. --- renderdoc/api/replay/replay_enums.h | 2 + renderdoc/driver/gl/gl_common.cpp | 56 ++-- renderdoc/driver/gl/gl_counters.cpp | 250 ++++++++++++++---- renderdoc/driver/gl/gl_driver.cpp | 2 + renderdoc/driver/gl/gl_driver.h | 14 +- renderdoc/driver/gl/gl_replay.h | 3 +- .../driver/gl/wrappers/gl_query_funcs.cpp | 18 +- 7 files changed, 268 insertions(+), 77 deletions(-) diff --git a/renderdoc/api/replay/replay_enums.h b/renderdoc/api/replay/replay_enums.h index 6aab99f1c..50bb125d9 100644 --- a/renderdoc/api/replay/replay_enums.h +++ b/renderdoc/api/replay/replay_enums.h @@ -508,11 +508,13 @@ enum GPUCounters eCounter_SamplesWritten, eCounter_VSInvocations, eCounter_HSInvocations, + eCounter_TCSInvocations = eCounter_HSInvocations, eCounter_DSInvocations, eCounter_TESInvocations = eCounter_DSInvocations, eCounter_GSInvocations, eCounter_PSInvocations, eCounter_CSInvocations, + eCounter_GLMaxCounters, // IHV specific counters can be set above this point // with ranges reserved for each IHV diff --git a/renderdoc/driver/gl/gl_common.cpp b/renderdoc/driver/gl/gl_common.cpp index 44adf3f58..6392d15c4 100644 --- a/renderdoc/driver/gl/gl_common.cpp +++ b/renderdoc/driver/gl/gl_common.cpp @@ -429,30 +429,56 @@ GLenum BufferEnum(size_t idx) size_t QueryIdx(GLenum query) { + size_t idx = 0; + switch(query) { - case eGL_SAMPLES_PASSED: return 0; - case eGL_ANY_SAMPLES_PASSED: return 1; - case eGL_ANY_SAMPLES_PASSED_CONSERVATIVE: return 2; - case eGL_PRIMITIVES_GENERATED: return 3; - case eGL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN: return 4; - case eGL_TIME_ELAPSED: return 5; + case eGL_SAMPLES_PASSED: idx = 0; break; + case eGL_ANY_SAMPLES_PASSED: idx = 1; break; + case eGL_ANY_SAMPLES_PASSED_CONSERVATIVE: idx = 2; break; + case eGL_PRIMITIVES_GENERATED: idx = 3; break; + case eGL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN: idx = 4; break; + case eGL_TIME_ELAPSED: idx = 5; break; + case eGL_VERTICES_SUBMITTED_ARB: idx = 6; break; + case eGL_PRIMITIVES_SUBMITTED_ARB: idx = 7; break; + case eGL_GEOMETRY_SHADER_PRIMITIVES_EMITTED_ARB: idx = 8; break; + case eGL_CLIPPING_INPUT_PRIMITIVES_ARB: idx = 9; break; + case eGL_CLIPPING_OUTPUT_PRIMITIVES_ARB: idx = 10; break; + case eGL_VERTEX_SHADER_INVOCATIONS_ARB: idx = 11; break; + case eGL_TESS_CONTROL_SHADER_PATCHES_ARB: idx = 12; break; + case eGL_TESS_EVALUATION_SHADER_INVOCATIONS_ARB: idx = 13; break; + case eGL_GEOMETRY_SHADER_INVOCATIONS: idx = 14; break; + case eGL_FRAGMENT_SHADER_INVOCATIONS_ARB: idx = 15; break; + case eGL_COMPUTE_SHADER_INVOCATIONS_ARB: idx = 16; break; + default: RDCERR("Unexpected enum as query target: %s", ToStr::Get(query).c_str()); } - return 0; + if(idx >= WrappedOpenGL::MAX_QUERIES) + RDCERR("Query index for enum %s out of range %d", ToStr::Get(query).c_str(), idx); + + return idx; } GLenum QueryEnum(size_t idx) { - GLenum enums[] = { - eGL_SAMPLES_PASSED, - eGL_ANY_SAMPLES_PASSED, - eGL_ANY_SAMPLES_PASSED_CONSERVATIVE, - eGL_PRIMITIVES_GENERATED, - eGL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, - eGL_TIME_ELAPSED, - }; + GLenum enums[] = {eGL_SAMPLES_PASSED, + eGL_ANY_SAMPLES_PASSED, + eGL_ANY_SAMPLES_PASSED_CONSERVATIVE, + eGL_PRIMITIVES_GENERATED, + eGL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, + eGL_TIME_ELAPSED, + eGL_VERTICES_SUBMITTED_ARB, + eGL_PRIMITIVES_SUBMITTED_ARB, + eGL_GEOMETRY_SHADER_PRIMITIVES_EMITTED_ARB, + eGL_CLIPPING_INPUT_PRIMITIVES_ARB, + eGL_CLIPPING_OUTPUT_PRIMITIVES_ARB, + eGL_VERTEX_SHADER_INVOCATIONS_ARB, + eGL_TESS_CONTROL_SHADER_PATCHES_ARB, + eGL_TESS_EVALUATION_SHADER_INVOCATIONS_ARB, + eGL_GEOMETRY_SHADER_INVOCATIONS, + eGL_FRAGMENT_SHADER_INVOCATIONS_ARB, + eGL_COMPUTE_SHADER_INVOCATIONS_ARB}; if(idx < ARRAY_COUNT(enums)) return enums[idx]; diff --git a/renderdoc/driver/gl/gl_counters.cpp b/renderdoc/driver/gl/gl_counters.cpp index 053bd7ab9..433fb8b28 100644 --- a/renderdoc/driver/gl/gl_counters.cpp +++ b/renderdoc/driver/gl/gl_counters.cpp @@ -47,6 +47,18 @@ vector GLReplay::EnumerateCounters() vector ret; ret.push_back(eCounter_EventGPUDuration); + ret.push_back(eCounter_InputVerticesRead); + ret.push_back(eCounter_IAPrimitives); + ret.push_back(eCounter_GSPrimitives); + ret.push_back(eCounter_RasterizerInvocations); + ret.push_back(eCounter_RasterizedPrimitives); + ret.push_back(eCounter_SamplesWritten); + ret.push_back(eCounter_VSInvocations); + ret.push_back(eCounter_TCSInvocations); + ret.push_back(eCounter_TESInvocations); + ret.push_back(eCounter_GSInvocations); + ret.push_back(eCounter_PSInvocations); + ret.push_back(eCounter_CSInvocations); return ret; } @@ -55,39 +67,142 @@ void GLReplay::DescribeCounter(uint32_t counterID, CounterDescription &desc) { desc.counterID = counterID; - if(counterID == eCounter_EventGPUDuration) + switch(counterID) { - desc.name = "GPU Duration"; - desc.description = - "Time taken for this event on the GPU, as measured by delta between two GPU timestamps."; - desc.resultByteWidth = 8; - desc.resultCompType = eCompType_Double; - desc.units = eUnits_Seconds; - } - else - { - desc.name = "Unknown"; - desc.description = "Unknown counter ID"; - desc.resultByteWidth = 0; - desc.resultCompType = eCompType_None; - desc.units = eUnits_Absolute; + case eCounter_EventGPUDuration: + desc.name = "GPU Duration"; + desc.description = + "Time taken for this event on the GPU, as measured by delta between two GPU timestamps."; + desc.resultByteWidth = 8; + desc.resultCompType = eCompType_Double; + desc.units = eUnits_Seconds; + break; + case eCounter_InputVerticesRead: + desc.name = "Input Vertices Read"; + desc.description = "Number of vertices read by input assembler."; + desc.resultByteWidth = 8; + desc.resultCompType = eCompType_UInt; + desc.units = eUnits_Absolute; + break; + case eCounter_IAPrimitives: + desc.name = "Input Primitives"; + desc.description = "Number of primitives read by the input assembler."; + desc.resultByteWidth = 8; + desc.resultCompType = eCompType_UInt; + desc.units = eUnits_Absolute; + break; + case eCounter_GSPrimitives: + desc.name = "GS Primitives"; + desc.description = "Number of primitives output by a geometry shader."; + desc.resultByteWidth = 8; + desc.resultCompType = eCompType_UInt; + desc.units = eUnits_Absolute; + break; + case eCounter_RasterizerInvocations: + desc.name = "Rasterizer Invocations"; + desc.description = "Number of primitives that were sent to the rasterizer."; + desc.resultByteWidth = 8; + desc.resultCompType = eCompType_UInt; + desc.units = eUnits_Absolute; + break; + case eCounter_RasterizedPrimitives: + desc.name = "Rasterized Primitives"; + desc.description = "Number of primitives that were rendered."; + desc.resultByteWidth = 8; + desc.resultCompType = eCompType_UInt; + desc.units = eUnits_Absolute; + break; + case eCounter_SamplesWritten: + desc.name = "Samples Written"; + desc.description = "Number of samples that passed depth/stencil test."; + desc.resultByteWidth = 8; + desc.resultCompType = eCompType_UInt; + desc.units = eUnits_Absolute; + break; + case eCounter_VSInvocations: + desc.name = "VS Invocations"; + desc.description = "Number of times a vertex shader was invoked."; + desc.resultByteWidth = 8; + desc.resultCompType = eCompType_UInt; + desc.units = eUnits_Absolute; + break; + case eCounter_GSInvocations: + desc.name = "GS Invocations"; + desc.description = "Number of times a geometry shader was invoked."; + desc.resultByteWidth = 8; + desc.resultCompType = eCompType_UInt; + desc.units = eUnits_Absolute; + break; + case eCounter_TCSInvocations: + desc.name = "TCS Invocations"; + desc.description = "Number of times a tesselation control shader was invoked."; + desc.resultByteWidth = 8; + desc.resultCompType = eCompType_UInt; + desc.units = eUnits_Absolute; + break; + case eCounter_TESInvocations: + desc.name = "TES Invocations"; + desc.description = "Number of times a tesselation evaluation shader was invoked."; + desc.resultByteWidth = 8; + desc.resultCompType = eCompType_UInt; + desc.units = eUnits_Absolute; + break; + case eCounter_PSInvocations: + desc.name = "PS Invocations"; + desc.description = "Number of times a pixel shader was invoked."; + desc.resultByteWidth = 8; + desc.resultCompType = eCompType_UInt; + desc.units = eUnits_Absolute; + break; + case eCounter_CSInvocations: + desc.name = "CS Invocations"; + desc.description = "Number of times a compute shader was invoked."; + desc.resultByteWidth = 8; + desc.resultCompType = eCompType_UInt; + desc.units = eUnits_Absolute; + break; + default: + desc.name = "Unknown"; + desc.description = "Unknown counter ID"; + desc.resultByteWidth = 0; + desc.resultCompType = eCompType_None; + desc.units = eUnits_Absolute; + break; } } -struct GPUTimer +struct GPUQueries { - GLuint obj; + GLuint obj[eCounter_GLMaxCounters]; uint32_t eventID; }; struct CounterContext { uint32_t eventStart; - vector timers; + vector queries; int reuseIdx; }; -void GLReplay::FillTimers(CounterContext &ctx, const DrawcallTreeNode &drawnode) +GLenum glCounters[] = { + eGL_NONE, // Undefined!! + eGL_TIME_ELAPSED, // eCounter_EventGPUDuration + eGL_VERTICES_SUBMITTED_ARB, // eCounter_InputVerticesRead + eGL_PRIMITIVES_SUBMITTED_ARB, // eCounter_IAPrimitives + eGL_GEOMETRY_SHADER_PRIMITIVES_EMITTED_ARB, // eCounter_GSPrimitives + eGL_CLIPPING_INPUT_PRIMITIVES_ARB, // eCounter_RasterizerInvocations + eGL_CLIPPING_OUTPUT_PRIMITIVES_ARB, // eCounter_RasterizedPrimitives + eGL_SAMPLES_PASSED, // eCounter_SamplesWritten + eGL_VERTEX_SHADER_INVOCATIONS_ARB, // eCounter_VSInvocations + eGL_TESS_CONTROL_SHADER_PATCHES_ARB, // eCounter_TCSInvocations + eGL_TESS_EVALUATION_SHADER_INVOCATIONS_ARB, // eCounter_TESInvocations + eGL_GEOMETRY_SHADER_INVOCATIONS, // eCounter_GSInvocations + eGL_FRAGMENT_SHADER_INVOCATIONS_ARB, // eCounter_PSInvocations + eGL_COMPUTE_SHADER_INVOCATIONS_ARB // eCounter_CSInvocations +}; + +void GLReplay::FillTimers(CounterContext &ctx, const DrawcallTreeNode &drawnode, + const vector &counters) { if(drawnode.children.empty()) return; @@ -95,42 +210,55 @@ void GLReplay::FillTimers(CounterContext &ctx, const DrawcallTreeNode &drawnode) for(size_t i = 0; i < drawnode.children.size(); i++) { const FetchDrawcall &d = drawnode.children[i].draw; - FillTimers(ctx, drawnode.children[i]); + FillTimers(ctx, drawnode.children[i], counters); if(d.events.count == 0) continue; - GPUTimer *timer = NULL; + GPUQueries *queries = NULL; { if(ctx.reuseIdx == -1) { - ctx.timers.push_back(GPUTimer()); + ctx.queries.push_back(GPUQueries()); - timer = &ctx.timers.back(); - timer->eventID = d.eventID; - timer->obj = 0; + queries = &ctx.queries.back(); + queries->eventID = d.eventID; + for(uint32_t q = 0; q < eCounter_GLMaxCounters; q++) + queries->obj[q] = 0; - m_pDriver->glGenQueries(1, &timer->obj); + for(uint32_t c = 0; c < counters.size(); c++) + { + m_pDriver->glGenQueries(1, &queries->obj[counters[c]]); + if(m_pDriver->glGetError()) + queries->obj[counters[c]] = 0; + } } else { - timer = &ctx.timers[ctx.reuseIdx++]; + queries = &ctx.queries[ctx.reuseIdx++]; } } m_pDriver->ReplayLog(ctx.eventStart, d.eventID, eReplay_WithoutDraw); - if(timer->obj) - { - m_pDriver->glBeginQuery(eGL_TIME_ELAPSED, timer->obj); - m_pDriver->ReplayLog(ctx.eventStart, d.eventID, eReplay_OnlyDraw); - m_pDriver->glEndQuery(eGL_TIME_ELAPSED); - } - else - { - m_pDriver->ReplayLog(ctx.eventStart, d.eventID, eReplay_OnlyDraw); - } + // Reverse order so that Timer counter is queried the last. + for(int32_t q = (eCounter_GLMaxCounters - 1); q >= 0; q--) + if(queries->obj[q]) + { + m_pDriver->glBeginQuery(glCounters[q], queries->obj[q]); + if(m_pDriver->glGetError()) + { + m_pDriver->glDeleteQueries(1, &queries->obj[q]); + queries->obj[q] = 0; + } + } + + m_pDriver->ReplayLog(ctx.eventStart, d.eventID, eReplay_OnlyDraw); + + for(uint32_t q = 0; q < eCounter_GLMaxCounters; q++) + if(queries->obj[q]) + m_pDriver->glEndQuery(glCounters[q]); ctx.eventStart = d.eventID + 1; } @@ -148,19 +276,15 @@ vector GLReplay::FetchCounters(const vector &counters) MakeCurrentReplayContext(&m_ReplayCtx); - uint32_t counterID = counters[0]; - RDCASSERT(counters.size() == 1); - RDCASSERT(counterID == eCounter_EventGPUDuration); - - SCOPED_TIMER("Fetch Counters for %u", counterID); - CounterContext ctx; for(int loop = 0; loop < 1; loop++) { ctx.eventStart = 0; ctx.reuseIdx = loop == 0 ? -1 : 0; - FillTimers(ctx, m_pDriver->GetRootDraw()); + m_pDriver->SetFetchCounters(true); + FillTimers(ctx, m_pDriver->GetRootDraw(), counters); + m_pDriver->SetFetchCounters(false); double nanosToSecs = 1.0 / 1000000000.0; @@ -168,28 +292,42 @@ vector GLReplay::FetchCounters(const vector &counters) m_pDriver->glGetIntegerv(eGL_QUERY_BUFFER_BINDING, (GLint *)&prevbind); m_pDriver->glBindBuffer(eGL_QUERY_BUFFER, 0); - for(size_t i = 0; i < ctx.timers.size(); i++) + for(size_t i = 0; i < ctx.queries.size(); i++) { - if(ctx.timers[i].obj) + for(uint32_t c = 0; c < counters.size(); c++) { - GLuint elapsed = 0; - m_pDriver->glGetQueryObjectuiv(ctx.timers[i].obj, eGL_QUERY_RESULT, &elapsed); + if(ctx.queries[i].obj[counters[c]]) + { + GLuint64 data = 0; + m_pDriver->glGetQueryObjectui64v(ctx.queries[i].obj[counters[c]], eGL_QUERY_RESULT, &data); - double duration = double(elapsed) * nanosToSecs; + double duration = double(data) * nanosToSecs; - ret.push_back(CounterResult(ctx.timers[i].eventID, counterID, duration)); - } - else - { - ret.push_back(CounterResult(ctx.timers[i].eventID, counterID, 0.0)); + if(m_pDriver->glGetError()) + { + data = (uint64_t)-1; + duration = -1; + } + + if(counters[c] == eCounter_EventGPUDuration) + { + ret.push_back(CounterResult(ctx.queries[i].eventID, eCounter_EventGPUDuration, duration)); + } + else + ret.push_back(CounterResult(ctx.queries[i].eventID, counters[c], data)); + } + else + ret.push_back(CounterResult(ctx.queries[i].eventID, counters[c], (uint64_t)-1)); } } m_pDriver->glBindBuffer(eGL_QUERY_BUFFER, prevbind); } - for(size_t i = 0; i < ctx.timers.size(); i++) - m_pDriver->glDeleteQueries(1, &ctx.timers[i].obj); + for(size_t i = 0; i < ctx.queries.size(); i++) + for(uint32_t c = 0; c < counters.size(); c++) + if(ctx.queries[i].obj[counters[c]]) + m_pDriver->glDeleteQueries(1, &ctx.queries[i].obj[counters[c]]); return ret; } diff --git a/renderdoc/driver/gl/gl_driver.cpp b/renderdoc/driver/gl/gl_driver.cpp index 171136b01..911483c9c 100644 --- a/renderdoc/driver/gl/gl_driver.cpp +++ b/renderdoc/driver/gl/gl_driver.cpp @@ -739,6 +739,8 @@ WrappedOpenGL::WrappedOpenGL(const char *logfile, const GLHookSet &funcs) : m_Re m_FirstEventID = 0; m_LastEventID = ~0U; + m_FetchCounters = false; + RDCEraseEl(m_ActiveQueries); m_ActiveConditional = false; m_ActiveFeedback = false; diff --git a/renderdoc/driver/gl/gl_driver.h b/renderdoc/driver/gl/gl_driver.h index f2250b2cd..58abb47a2 100644 --- a/renderdoc/driver/gl/gl_driver.h +++ b/renderdoc/driver/gl/gl_driver.h @@ -156,7 +156,16 @@ private: vector m_LastContexts; - bool m_ActiveQueries[8][8]; // first index type, second index (for some, always 0) +public: + enum + { + MAX_QUERIES = 17, + MAX_QUERY_INDICES = 8 + }; + +private: + bool m_ActiveQueries[MAX_QUERIES] + [MAX_QUERY_INDICES]; // first index type, second index (for some, always 0) bool m_ActiveConditional; bool m_ActiveFeedback; @@ -221,6 +230,8 @@ private: map > m_ResourceUses; + bool m_FetchCounters; + // buffer used vector m_ScratchBuf; @@ -484,6 +495,7 @@ public: GLReplay *GetReplay() { return &m_Replay; } void *GetCtx(); + void SetFetchCounters(bool in) { m_FetchCounters = in; }; const GLHookSet &GetHookset() { return m_Real; } void SetDebugMsgContext(const char *context) { m_DebugMsgContext = context; } void AddDebugMessage(DebugMessage msg) diff --git a/renderdoc/driver/gl/gl_replay.h b/renderdoc/driver/gl/gl_replay.h index 27236deb1..b74dad661 100644 --- a/renderdoc/driver/gl/gl_replay.h +++ b/renderdoc/driver/gl/gl_replay.h @@ -382,7 +382,8 @@ private: // called before the context is destroyed, to shutdown any counters void PreContextShutdownCounters(); - void FillTimers(CounterContext &ctx, const DrawcallTreeNode &drawnode); + void FillTimers(CounterContext &ctx, const DrawcallTreeNode &drawnode, + const vector &counters); GLuint CreateShaderProgram(const vector &vs, const vector &fs, const vector &gs); diff --git a/renderdoc/driver/gl/wrappers/gl_query_funcs.cpp b/renderdoc/driver/gl/wrappers/gl_query_funcs.cpp index f3ce81994..43fb92ffa 100644 --- a/renderdoc/driver/gl/wrappers/gl_query_funcs.cpp +++ b/renderdoc/driver/gl/wrappers/gl_query_funcs.cpp @@ -262,8 +262,12 @@ bool WrappedOpenGL::Serialise_glBeginQuery(GLenum target, GLuint qid) if(m_State < WRITING) { - m_Real.glBeginQuery(Target, GetResourceManager()->GetLiveResource(id).name); - m_ActiveQueries[QueryIdx(Target)][0] = true; + // Queries in the log interfere with the queries from FetchCounters. + if(!m_FetchCounters) + { + m_Real.glBeginQuery(Target, GetResourceManager()->GetLiveResource(id).name); + m_ActiveQueries[QueryIdx(Target)][0] = true; + } } return true; @@ -272,6 +276,8 @@ bool WrappedOpenGL::Serialise_glBeginQuery(GLenum target, GLuint qid) void WrappedOpenGL::glBeginQuery(GLenum target, GLuint id) { m_Real.glBeginQuery(target, id); + if(m_ActiveQueries[QueryIdx(target)][0]) + RDCLOG("Query already active %s", ToStr::Get(target).c_str()); m_ActiveQueries[QueryIdx(target)][0] = true; if(m_State == WRITING_CAPFRAME) @@ -320,8 +326,12 @@ bool WrappedOpenGL::Serialise_glEndQuery(GLenum target) if(m_State < WRITING) { - m_ActiveQueries[QueryIdx(Target)][0] = false; - m_Real.glEndQuery(Target); + // Queries in the log interfere with the queries from FetchCounters. + if(!m_FetchCounters) + { + m_ActiveQueries[QueryIdx(Target)][0] = false; + m_Real.glEndQuery(Target); + } } return true;