Merge pull request #21392 from chealy/gl-timer-query-profiler

OpenGL: Add GPU timestamp profiling with GL_EXT_disjoint_timer_query
This commit is contained in:
Henrik Rydgård authored and GitHub committed 2026-03-21 15:51:08 +01:00
commit 2d8832ec71
13 files changed
+277

No files matched your search

+2
View File
@@ -771,6 +771,8 @@ add_library(Common STATIC
Common/GPU/OpenGL/GLRenderManager.h
Common/GPU/OpenGL/GLQueueRunner.cpp
Common/GPU/OpenGL/GLQueueRunner.h
Common/GPU/OpenGL/GLProfiler.cpp
Common/GPU/OpenGL/GLProfiler.h
Common/GPU/OpenGL/DataFormatGL.cpp
Common/GPU/OpenGL/DataFormatGL.h
Common/GPU/Vulkan/VulkanBarrier.cpp
+2
View File
@@ -418,6 +418,7 @@
<ClInclude Include="GPU\OpenGL\GLFrameData.h" />
<ClInclude Include="GPU\OpenGL\GLMemory.h" />
<ClInclude Include="GPU\OpenGL\GLQueueRunner.h" />
<ClInclude Include="GPU\OpenGL\GLProfiler.h" />
<ClInclude Include="GPU\OpenGL\GLRenderManager.h" />
<ClInclude Include="GPU\OpenGL\GLSLProgram.h" />
<ClInclude Include="GPU\OpenGL\GLCommon.h" />
@@ -885,6 +886,7 @@
<ClCompile Include="GPU\OpenGL\GLFrameData.cpp" />
<ClCompile Include="GPU\OpenGL\GLMemory.cpp" />
<ClCompile Include="GPU\OpenGL\GLQueueRunner.cpp" />
<ClCompile Include="GPU\OpenGL\GLProfiler.cpp" />
<ClCompile Include="GPU\OpenGL\GLRenderManager.cpp" />
<ClCompile Include="GPU\OpenGL\GLSLProgram.cpp" />
<ClCompile Include="GPU\OpenGL\GLDebugLog.cpp" />
+6
View File
@@ -261,6 +261,9 @@
<ClInclude Include="GPU\OpenGL\GLQueueRunner.h">
<Filter>GPU\OpenGL</Filter>
</ClInclude>
<ClInclude Include="GPU\OpenGL\GLProfiler.h">
<Filter>GPU\OpenGL</Filter>
</ClInclude>
<ClInclude Include="GPU\OpenGL\GLRenderManager.h">
<Filter>GPU\OpenGL</Filter>
</ClInclude>
@@ -966,6 +969,9 @@
<ClCompile Include="GPU\OpenGL\GLQueueRunner.cpp">
<Filter>GPU\OpenGL</Filter>
</ClCompile>
<ClCompile Include="GPU\OpenGL\GLProfiler.cpp">
<Filter>GPU\OpenGL</Filter>
</ClCompile>
<ClCompile Include="GPU\OpenGL\GLRenderManager.cpp">
<Filter>GPU\OpenGL</Filter>
</ClCompile>
+2
View File
@@ -384,6 +384,7 @@ bool CheckGLExtensions() {
gl_extensions.ARB_explicit_attrib_location = g_set_gl_extensions.count("GL_ARB_explicit_attrib_location") != 0;
gl_extensions.ARB_texture_non_power_of_two = g_set_gl_extensions.count("GL_ARB_texture_non_power_of_two") != 0;
gl_extensions.ARB_shader_stencil_export = g_set_gl_extensions.count("GL_ARB_shader_stencil_export") != 0;
gl_extensions.ARB_timer_query = g_set_gl_extensions.count("GL_ARB_timer_query") != 0;
gl_extensions.ARB_texture_compression_bptc = g_set_gl_extensions.count("GL_ARB_texture_compression_bptc") != 0;
gl_extensions.ARB_texture_compression_rgtc = g_set_gl_extensions.count("GL_ARB_texture_compression_rgtc") != 0;
gl_extensions.KHR_texture_compression_astc_ldr = g_set_gl_extensions.count("GL_KHR_texture_compression_astc_ldr") != 0;
@@ -406,6 +407,7 @@ bool CheckGLExtensions() {
gl_extensions.EXT_buffer_storage = g_set_gl_extensions.count("GL_EXT_buffer_storage") != 0;
gl_extensions.EXT_clip_cull_distance = g_set_gl_extensions.count("GL_EXT_clip_cull_distance") != 0;
gl_extensions.EXT_depth_clamp = g_set_gl_extensions.count("GL_EXT_depth_clamp") != 0;
gl_extensions.EXT_disjoint_timer_query = g_set_gl_extensions.count("GL_EXT_disjoint_timer_query") != 0;
gl_extensions.APPLE_clip_distance = g_set_gl_extensions.count("GL_APPLE_clip_distance") != 0;
#if defined(__ANDROID__)
+2
View File
@@ -86,6 +86,8 @@ struct GLExtensions {
bool KHR_texture_compression_astc_ldr;
// EXT
bool EXT_disjoint_timer_query;
bool ARB_timer_query;
bool EXT_texture_compression_s3tc;
bool EXT_swap_control_tear;
bool EXT_discard_framebuffer;
+174
View File
@@ -0,0 +1,174 @@
#include <cstdarg>
#include "Common/GPU/OpenGL/GLProfiler.h"
#include "Common/GPU/OpenGL/GLCommon.h"
#include "Common/GPU/OpenGL/GLFeatures.h"
#include "Common/GPU/OpenGL/GLDebugLog.h"
#include "Common/Log.h"
// For iOS, define function pointer types and variables locally since gl3stub.h
// content is excluded on iOS. These will remain NULL since iOS doesn't support
// GL_EXT_disjoint_timer_query.
#if PPSSPP_PLATFORM(IOS)
typedef void (*PFNGLQUERYCOUNTERPROC)(GLuint id, GLenum target);
typedef void (*PFNGLGETQUERYOBJECTUI64VPROC)(GLuint id, GLenum pname, GLuint64 *params);
static PFNGLQUERYCOUNTERPROC glQueryCounter = nullptr;
static PFNGLGETQUERYOBJECTUI64VPROC glGetQueryObjectui64v = nullptr;
#endif
// Constants - same values for both EXT and ARB versions
#ifndef GL_TIMESTAMP
#define GL_TIMESTAMP 0x8E28
#endif
#ifndef GL_QUERY_RESULT
#define GL_QUERY_RESULT 0x8866
#endif
#ifndef GL_GPU_DISJOINT_EXT
#define GL_GPU_DISJOINT_EXT 0x8FBB
#endif
// GLCommon.h provides access to GL functions:
// - On GLES: function pointers from gl3stub.h, loaded via eglGetProcAddress
// - On desktop GL: GLEW provides the functions
void GLProfiler::Init() {
supported_ = false;
firstFrame_ = true;
numQueries_ = 0;
scopes_.clear();
scopeStack_.clear();
// Check for extension support
// Function pointers are declared in gl3stub.h and loaded appropriately per platform
if (gl_extensions.EXT_disjoint_timer_query) {
// GLES path - use function pointers loaded with EXT suffix
if (glQueryCounter && glGetQueryObjectui64v) {
supported_ = true;
INFO_LOG(Log::G3D, "GLProfiler: Using GL_EXT_disjoint_timer_query");
}
} else if (gl_extensions.ARB_timer_query) {
// Desktop GL path - use function pointers (no suffix)
if (glQueryCounter && glGetQueryObjectui64v) {
supported_ = true;
INFO_LOG(Log::G3D, "GLProfiler: Using GL_ARB_timer_query");
}
}
if (supported_) {
// Pre-allocate query objects
queries_.resize(MAX_QUERY_COUNT);
glGenQueries(MAX_QUERY_COUNT, queries_.data());
CHECK_GL_ERROR_IF_DEBUG();
}
}
void GLProfiler::Shutdown() {
if (supported_ && !queries_.empty()) {
glDeleteQueries((GLsizei)queries_.size(), queries_.data());
queries_.clear();
}
supported_ = false;
scopes_.clear();
scopeStack_.clear();
}
void GLProfiler::BeginFrame() {
if (!supported_) {
return;
}
// Check if profiling is enabled
if (enabledPtr_ && !*enabledPtr_) {
scopes_.clear();
scopeStack_.clear();
numQueries_ = 0;
return;
}
// Check for disjoint operation (GPU frequency changed) - GLES only
if (gl_extensions.EXT_disjoint_timer_query) {
GLint disjoint = 0;
glGetIntegerv(GL_GPU_DISJOINT_EXT, &disjoint);
if (disjoint) {
// Results are invalid, just clear and start fresh
WARN_LOG(Log::G3D, "GLProfiler: GPU disjoint detected, timing results discarded");
scopes_.clear();
scopeStack_.clear();
numQueries_ = 0;
firstFrame_ = true;
return;
}
}
// Read results from previous frame (guaranteed complete now)
if (numQueries_ > 0 && !firstFrame_) {
static const char * const indent[4] = { "", " ", " ", " " };
if (!scopes_.empty()) {
INFO_LOG(Log::G3D, "OpenGL profiling events this frame:");
}
// Log results
for (auto &scope : scopes_) {
if (scope.endQueryId == -1) {
WARN_LOG(Log::G3D, "Unclosed scope: %s", scope.name);
continue;
}
GLuint64 startTime = 0, endTime = 0;
glGetQueryObjectui64v(queries_[scope.startQueryId], GL_QUERY_RESULT, &startTime);
glGetQueryObjectui64v(queries_[scope.endQueryId], GL_QUERY_RESULT, &endTime);
// Times are in nanoseconds, convert to milliseconds
double milliseconds = (double)(endTime - startTime) / 1000000.0;
INFO_LOG(Log::G3D, "%s%s (%0.3f ms)", indent[scope.level & 3], scope.name, milliseconds);
}
}
firstFrame_ = false;
scopes_.clear();
scopeStack_.clear();
numQueries_ = 0;
}
void GLProfiler::Begin(const char *fmt, ...) {
if (!supported_ || (enabledPtr_ && !*enabledPtr_) || numQueries_ >= MAX_QUERY_COUNT - 1) {
return;
}
GLProfilerScope scope;
va_list args;
va_start(args, fmt);
vsnprintf(scope.name, sizeof(scope.name), fmt, args);
va_end(args);
scope.startQueryId = numQueries_;
scope.endQueryId = -1;
scope.level = (int)scopeStack_.size();
scopeStack_.push_back(scopes_.size());
scopes_.push_back(scope);
glQueryCounter(queries_[numQueries_], GL_TIMESTAMP);
numQueries_++;
}
void GLProfiler::End() {
if (!supported_ || (enabledPtr_ && !*enabledPtr_) || numQueries_ >= MAX_QUERY_COUNT - 1) {
return;
}
if (scopeStack_.empty()) {
WARN_LOG(Log::G3D, "GLProfiler::End called without matching Begin");
return;
}
size_t scopeId = scopeStack_.back();
scopeStack_.pop_back();
GLProfilerScope &scope = scopes_[scopeId];
scope.endQueryId = numQueries_;
glQueryCounter(queries_[numQueries_], GL_TIMESTAMP);
numQueries_++;
}
+53
View File
@@ -0,0 +1,53 @@
#pragma once
#include <vector>
#include <string>
#include <cstdint>
#include "Common/GPU/OpenGL/GLCommon.h"
#include "Common/GPU/OpenGL/gl3stub.h"
// Simple scoped based profiler for OpenGL, similar to VulkanProfiler.
// Uses GL_EXT_disjoint_timer_query (GLES) or GL_ARB_timer_query (desktop GL).
// Put the whole thing in a FrameData to allow for overlap.
struct GLProfilerScope {
char name[52]; // to make a struct size of 64, just because
int startQueryId;
int endQueryId;
int level;
};
class GLProfiler {
public:
void Init();
void Shutdown();
void BeginFrame();
void Begin(const char *fmt, ...)
#ifdef __GNUC__
__attribute__((format(printf, 2, 3)))
#endif
;
void End();
void SetEnabledPtr(bool *enabledPtr) {
enabledPtr_ = enabledPtr;
}
bool IsSupported() const { return supported_; }
private:
bool supported_ = false;
bool firstFrame_ = true;
bool *enabledPtr_ = nullptr;
std::vector<GLuint> queries_;
std::vector<GLProfilerScope> scopes_;
int numQueries_ = 0;
std::vector<size_t> scopeStack_;
static const int MAX_QUERY_COUNT = 1024;
};
+19
View File
@@ -75,9 +75,12 @@ void GLQueueRunner::CreateDeviceObjects() {
#if !PPSSPP_ARCH(X86) // Doesn't work on AMD for some reason. See issue #17787
useDebugGroups_ = !gl_extensions.IsGLES && gl_extensions.VersionGEThan(4, 3);
#endif
profiler_.Init();
}
void GLQueueRunner::DestroyDeviceObjects() {
profiler_.Shutdown();
CHECK_GL_ERROR_IF_DEBUG();
if (gl_extensions.ARB_vertex_array_object) {
glDeleteVertexArrays(1, &globalVAO_);
@@ -667,6 +670,12 @@ void GLQueueRunner::RunSteps(const std::vector<GLRStep *> &steps, GLFrameData &f
}
}
// GPU timestamp profiling - read results from previous frame and prepare for new queries
if (frameData.profile.enabled) {
profiler_.SetEnabledPtr(&frameData.profile.enabled);
}
profiler_.BeginFrame();
CHECK_GL_ERROR_IF_DEBUG();
size_t renderCount = 0;
for (size_t i = 0; i < steps.size(); i++) {
@@ -680,24 +689,34 @@ void GLQueueRunner::RunSteps(const std::vector<GLRStep *> &steps, GLFrameData &f
switch (step.stepType) {
case GLRStepType::RENDER:
renderCount++;
profiler_.Begin("RenderPass %s", step.tag);
if (IsVREnabled()) {
PreprocessStepVR(&step);
PerformRenderPass(step, renderCount == 1, renderCount == totalRenderCount, frameData.profile);
} else {
PerformRenderPass(step, renderCount == 1, renderCount == totalRenderCount, frameData.profile);
}
profiler_.End();
break;
case GLRStepType::COPY:
profiler_.Begin("Copy");
PerformCopy(step);
profiler_.End();
break;
case GLRStepType::BLIT:
profiler_.Begin("Blit");
PerformBlit(step);
profiler_.End();
break;
case GLRStepType::READBACK:
profiler_.Begin("Readback");
PerformReadback(step);
profiler_.End();
break;
case GLRStepType::READBACK_IMAGE:
profiler_.Begin("ReadbackImage");
PerformReadbackImage(step);
profiler_.End();
break;
case GLRStepType::RENDER_SKIP:
break;
+3
View File
@@ -7,6 +7,7 @@
#include "Common/GPU/OpenGL/GLCommon.h"
#include "Common/GPU/OpenGL/GLFrameData.h"
#include "Common/GPU/OpenGL/GLProfiler.h"
#include "Common/GPU/DataFormat.h"
#include "Common/GPU/Shader.h"
#include "Common/GPU/thin3d.h"
@@ -417,6 +418,8 @@ private:
bool sawOutOfMemory_ = false;
bool useDebugGroups_ = false;
GLProfiler profiler_;
ErrorCallbackFn errorCallback_ = nullptr;
void *errorCallbackUserData_ = nullptr;
};
+8
View File
@@ -38,6 +38,10 @@ GLboolean gl3stubInit() {
FIND_PROC(glEndQuery);
FIND_PROC(glGetQueryiv);
FIND_PROC(glGetQueryObjectuiv);
// GL_EXT_disjoint_timer_query - load EXT suffix versions into non-suffix pointers
glQueryCounter = (void*)eglGetProcAddress("glQueryCounterEXT");
glGetQueryObjecti64v = (void*)eglGetProcAddress("glGetQueryObjecti64vEXT");
glGetQueryObjectui64v = (void*)eglGetProcAddress("glGetQueryObjectui64vEXT");
FIND_PROC(glUnmapBuffer);
FIND_PROC(glGetBufferPointerv);
FIND_PROC(glDrawBuffers);
@@ -271,6 +275,10 @@ GL_APICALL void (* GL_APIENTRY glBeginQuery) (GLenum target, GLuint id
GL_APICALL void (* GL_APIENTRY glEndQuery) (GLenum target);
GL_APICALL void (* GL_APIENTRY glGetQueryiv) (GLenum target, GLenum pname, GLint* params);
GL_APICALL void (* GL_APIENTRY glGetQueryObjectuiv) (GLuint id, GLenum pname, GLuint* params);
// GL_EXT_disjoint_timer_query / GL_ARB_timer_query
GL_APICALL void (* GL_APIENTRY glQueryCounter) (GLuint id, GLenum target);
GL_APICALL void (* GL_APIENTRY glGetQueryObjecti64v) (GLuint id, GLenum pname, GLint64* params);
GL_APICALL void (* GL_APIENTRY glGetQueryObjectui64v) (GLuint id, GLenum pname, GLuint64* params);
GL_APICALL GLboolean (* GL_APIENTRY glUnmapBuffer) (GLenum target);
GL_APICALL void (* GL_APIENTRY glGetBufferPointerv) (GLenum target, GLenum pname, GLvoid** params);
GL_APICALL void (* GL_APIENTRY glDrawBuffers) (GLsizei n, const GLenum* bufs);
+4
View File
@@ -409,6 +409,10 @@ extern GL_APICALL void (* GL_APIENTRY glBeginQuery) (GLenum target, GL
extern GL_APICALL void (* GL_APIENTRY glEndQuery) (GLenum target);
extern GL_APICALL void (* GL_APIENTRY glGetQueryiv) (GLenum target, GLenum pname, GLint* params);
extern GL_APICALL void (* GL_APIENTRY glGetQueryObjectuiv) (GLuint id, GLenum pname, GLuint* params);
// GL_EXT_disjoint_timer_query / GL_ARB_timer_query
extern GL_APICALL void (* GL_APIENTRY glQueryCounter) (GLuint id, GLenum target);
extern GL_APICALL void (* GL_APIENTRY glGetQueryObjecti64v) (GLuint id, GLenum pname, GLint64* params);
extern GL_APICALL void (* GL_APIENTRY glGetQueryObjectui64v) (GLuint id, GLenum pname, GLuint64* params);
extern GL_APICALL GLboolean (* GL_APIENTRY glUnmapBuffer) (GLenum target);
extern GL_APICALL void (* GL_APIENTRY glGetBufferPointerv) (GLenum target, GLenum pname, GLvoid** params);
extern GL_APICALL void (* GL_APIENTRY glDrawBuffers) (GLsizei n, const GLenum* bufs);
+1
View File
@@ -115,6 +115,7 @@ NATIVE_FILES :=\
$(SRC)/Common/GPU/OpenGL/GLMemory.cpp \
$(SRC)/Common/GPU/OpenGL/GLRenderManager.cpp \
$(SRC)/Common/GPU/OpenGL/GLQueueRunner.cpp \
$(SRC)/Common/GPU/OpenGL/GLProfiler.cpp \
$(SRC)/Common/GPU/OpenGL/DataFormatGL.cpp
VULKAN_FILES := \
+1
View File
@@ -476,6 +476,7 @@ SOURCES_CXX += \
$(COMMONDIR)/GPU/OpenGL/GLRenderManager.cpp \
$(COMMONDIR)/GPU/OpenGL/GLMemory.cpp \
$(COMMONDIR)/GPU/OpenGL/GLQueueRunner.cpp \
$(COMMONDIR)/GPU/OpenGL/GLProfiler.cpp \
$(COMMONDIR)/GPU/OpenGL/DataFormatGL.cpp \
$(COMMONDIR)/GPU/Vulkan/thin3d_vulkan.cpp \
$(COMMONDIR)/GPU/Vulkan/VulkanQueueRunner.cpp \