diff --git a/CMakeLists.txt b/CMakeLists.txt index dcdb2f55ce..6cf9c097e0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/Common/Common.vcxproj b/Common/Common.vcxproj index e83a32ab33..6332411939 100644 --- a/Common/Common.vcxproj +++ b/Common/Common.vcxproj @@ -418,6 +418,7 @@ + @@ -885,6 +886,7 @@ + diff --git a/Common/Common.vcxproj.filters b/Common/Common.vcxproj.filters index 75df93f473..25bf4d40a9 100644 --- a/Common/Common.vcxproj.filters +++ b/Common/Common.vcxproj.filters @@ -261,6 +261,9 @@ GPU\OpenGL + + GPU\OpenGL + GPU\OpenGL @@ -966,6 +969,9 @@ GPU\OpenGL + + GPU\OpenGL + GPU\OpenGL diff --git a/Common/GPU/OpenGL/GLProfiler.cpp b/Common/GPU/OpenGL/GLProfiler.cpp new file mode 100644 index 0000000000..73c690e013 --- /dev/null +++ b/Common/GPU/OpenGL/GLProfiler.cpp @@ -0,0 +1,174 @@ +#include + +#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_++; +} diff --git a/Common/GPU/OpenGL/GLProfiler.h b/Common/GPU/OpenGL/GLProfiler.h new file mode 100644 index 0000000000..3290134547 --- /dev/null +++ b/Common/GPU/OpenGL/GLProfiler.h @@ -0,0 +1,53 @@ +#pragma once + +#include +#include +#include + +#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 queries_; + std::vector scopes_; + int numQueries_ = 0; + + std::vector scopeStack_; + + static const int MAX_QUERY_COUNT = 1024; +}; diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 9ec54ad1f8..4384151c07 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -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 := \ diff --git a/libretro/Makefile.common b/libretro/Makefile.common index 5e305855b2..6bdb4effac 100644 --- a/libretro/Makefile.common +++ b/libretro/Makefile.common @@ -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 \