mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-01 14:58:14 +00:00
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:
13 files changed
+277
No files matched your search
@@ -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
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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__)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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_++;
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 := \
|
||||
|
||||
@@ -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 \
|
||||
|
||||
Reference in new issue
Block a user