├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── iagp.cpp ├── iagp.h └── iagpConfig.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | set(PROJECT iagp) 4 | 5 | enable_language(C CXX) 6 | 7 | project( 8 | ${PROJECT} 9 | LANGUAGES CXX 10 | ) 11 | 12 | if(USE_SHARED_LIBS) 13 | set(BUILD_SHARED_LIBS ON CACHE BOOL "" FORCE) 14 | set(LLVM_USE_CRT_DEBUG MDd CACHE STRING "" FORCE) 15 | set(LLVM_USE_CRT_MINSIZEREL MD CACHE STRING "" FORCE) 16 | set(LLVM_USE_CRT_RELEASE MD CACHE STRING "" FORCE) 17 | set(LLVM_USE_CRT_RELWITHDEBINFO MD CACHE STRING "" FORCE) 18 | else() 19 | set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) 20 | set(LLVM_USE_CRT_DEBUG MTd CACHE STRING "" FORCE) 21 | set(LLVM_USE_CRT_MINSIZEREL MT CACHE STRING "" FORCE) 22 | set(LLVM_USE_CRT_RELEASE MT CACHE STRING "" FORCE) 23 | set(LLVM_USE_CRT_RELWITHDEBINFO MT CACHE STRING "" FORCE) 24 | endif() 25 | 26 | if(NOT CMAKE_DEBUG_POSTFIX) 27 | set(CMAKE_DEBUG_POSTFIX _debug) 28 | endif() 29 | if(NOT CMAKE_RELEASE_POSTFIX) 30 | set(CMAKE_RELEASE_POSTFIX) 31 | endif() 32 | if(NOT CMAKE_MINSIZEREL_POSTFIX) 33 | set(CMAKE_MINSIZEREL_POSTFIX _minsizerel) 34 | endif() 35 | if(NOT CMAKE_RELWITHDEBINFO_POSTFIX) 36 | set(CMAKE_RELWITHDEBINFO_POSTFIX _reldeb) 37 | endif() 38 | 39 | if (BUILD_SHARED_LIBS) 40 | set(USE_MSVC_RUNTIME_LIBRARY_DLL ON CACHE BOOL "" FORCE) 41 | add_library(${PROJECT} 42 | ${CMAKE_CURRENT_SOURCE_DIR}/iagp.cpp 43 | ${CMAKE_CURRENT_SOURCE_DIR}/iagp.h 44 | ${CMAKE_CURRENT_SOURCE_DIR}/iagpConfig.h 45 | ) 46 | target_compile_definitions(${PROJECT} INTERFACE BUILD_IN_APP_GPU_PROFILER_SHARED_LIBS) 47 | set_target_properties(${PROJECT} PROPERTIES POSITION_INDEPENDENT_CODE ON) 48 | else() 49 | set(USE_MSVC_RUNTIME_LIBRARY_DLL OFF CACHE BOOL "" FORCE) 50 | add_library(${PROJECT} STATIC 51 | ${CMAKE_CURRENT_SOURCE_DIR}/iagp.cpp 52 | ${CMAKE_CURRENT_SOURCE_DIR}/iagp.h 53 | ${CMAKE_CURRENT_SOURCE_DIR}/iagpConfig.h 54 | ) 55 | endif() 56 | 57 | target_include_directories(${PROJECT} PRIVATE 58 | ${CMAKE_CURRENT_SOURCE_DIR}) 59 | 60 | if(UNIX) 61 | target_compile_options(${PROJECT} PUBLIC "-Wno-unknown-pragmas -Wno-unused-variable -Wno-unused-parameter") 62 | endif() 63 | 64 | if(USE_DEBUG_SANITIZER) 65 | target_compile_options(${PROJECT} PRIVATE $<$:-fsanitize=address -static-libasan -static-libasan>) 66 | target_link_options(${PROJECT} PRIVATE $<$:-fsanitize=address -static-libasan>) 67 | message("Address Sanitizer enabled for projet ${PROJECT}") 68 | endif() 69 | 70 | set_target_properties(${PROJECT} PROPERTIES OUTPUT_NAME "iagp") 71 | 72 | set(IN_APP_GPU_PROFILER_INCLUDE_DIRS ${IN_APP_GPU_PROFILER_INCLUDE_DIRS} PARENT_SCOPE) 73 | set(IN_APP_GPU_PROFILER_LIBRARIES ${PROJECT} PARENT_SCOPE) 74 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Aiekick 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Win](https://github.com/aiekick/InAppGpuProfiler/actions/workflows/Win.yml/badge.svg)](https://github.com/aiekick/InAppGpuProfiler/actions/workflows/Win.yml) 2 | [![Linux](https://github.com/aiekick/InAppGpuProfiler/actions/workflows/Linux.yml/badge.svg)](https://github.com/aiekick/InAppGpuProfiler/actions/workflows/Linux.yml) 3 | [![Osx](https://github.com/aiekick/InAppGpuProfiler/actions/workflows/Osx.yml/badge.svg)](https://github.com/aiekick/InAppGpuProfiler/actions/workflows/Osx.yml) 4 | [![Wrapped Dear ImGui Version](https://img.shields.io/badge/Dear%20ImGui%20Version-1.89.9-blue.svg)](https://github.com/ocornut/imgui) 5 | 6 | # [InAppGpuProfiler](https://github.com/aiekick/InAppGpuProfiler) 7 | 8 | # purpose 9 | 10 | This is the In App Gpu Profiler im using since two years in [NoodlesPlate](https://github.com/aiekick/NoodlesPlate). 11 | 12 | Only Opengl is supported for the moment 13 | 14 | ## ImGui Supported Version 15 | 16 | InAppGpuProfiler follow the master and docking branch of ImGui . currently ImGui 1.89.9 17 | 18 | ## Requirements: 19 | 20 | A opengl Loader (like glad) 21 | And of course, have added [Dear ImGui](https://github.com/ocornut/imgui) to your project for this to work at all. 22 | 23 | # Features 24 | 25 | - Compatible with MacOs, Linux, Windows 26 | - Scopped queries functions 27 | - can open profiling section in sub windows 28 | - can open profiling section in the same windows and get a breadcrumb trail to go back to parents 29 | 30 | ## Warnings : 31 | - the circular vizualization is in work in progress state. dont use it for the moment 32 | 33 | ## to do : 34 | - C api 35 | 36 | # how to use it 37 | 38 | ## Mandatory definition 39 | 40 | InAppGpuProfiler is designed to be not invasive with you build configuration 41 | 42 | So you need to create a config. 43 | 44 | * You have a file "InAppGpuProfilerConfig.h" ready for that in the lib directory 45 | * You can also create your own custom file and link it by defining the symbol CUSTOM_IN_APP_GPU_PROFILER_CONFIG 46 | 47 | this is what is done in the demo app by this cmake line : 48 | ``` 49 | add_definitions(-DCUSTOM_IN_APP_GPU_PROFILER_CONFIG="${CMAKE_SOURCE_DIR}/CustomInAppGpuProfiler.h") 50 | ``` 51 | 52 | In this config you have MANDATORY things to define : 53 | - the include of your opengl loader. 54 | - the include of imgui.h and imgui_internal.h 55 | - define he fucntion to get/set the opengl context 56 | - define the opengl context pointer used by you get/set current context functions 57 | 58 | **If you dont define theses mandatory things, you will have linker issues or the profiler will not be able to catch gpu metrics** 59 | 60 | you can check the demo app for a typical use with the couple glfw/glad 61 | 62 | ## What to do in code 63 | 64 | You must call 'AIGPNewFrame' one time per frame. 65 | This must be the root of your gpu metric scope 66 | 67 | ex : 68 | ```cpp 69 | AIGPNewFrame("GPU Frame", "GPU Frame"); // a main Zone is always needed 70 | ``` 71 | 72 | then you msut call 'AIGPScoped' for each zones you want to measure 73 | 74 | ex : 75 | ```cpp 76 | AIGPScoped("render_imgui", "ImGui"); 77 | { 78 | AIGPScoped("Opengl", "glViewport"); 79 | glViewport(0, 0, display_w, display_h); 80 | } 81 | 82 | { 83 | AIGPScoped("Opengl", "glClearColor"); 84 | glClearColor(0.3f, 0.3f, 0.3f, 0.3f); 85 | } 86 | 87 | { 88 | AIGPScoped("Opengl", "glClear"); 89 | glClear(GL_COLOR_BUFFER_BIT); 90 | } 91 | 92 | { 93 | AIGPScoped("ImGui", "RenderDrawData"); 94 | ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); 95 | } 96 | ``` 97 | 98 | you can also use the variadic form : 99 | 100 | ```cpp 101 | AIGPScoped("Opengl", "glGenerateMipmap %u", m_TexId); 102 | ``` 103 | 104 | these functions must be included in a scope. 105 | 106 | you cant put two function in the same scope since the first metric measure 107 | 108 | will be done on constructor of AIGPScoped and the second metric measure 109 | 110 | will be done on destructor of AIGPScoped 111 | 112 | finally you must collect all gpu metrics out of the scope of 'AIGPNewFrame' 113 | 114 | by calling 'AIGPCollect' one time per frame 115 | 116 | ex : 117 | ```cpp 118 | AIGPCollect; // collect all measure queries out of Main Frame 119 | ``` 120 | 121 | full sample from the DemoApp : 122 | 123 | ```cpp 124 | while (!glfwWindowShouldClose(window)) { 125 | { 126 | AIGPNewFrame("GPU Frame", "GPU Frame"); // a main Zone is always needed 127 | glfwMakeContextCurrent(window); 128 | 129 | render_shaders(); // many AIGPScoped are caleed in this function 130 | 131 | ImGui_ImplOpenGL3_NewFrame(); 132 | ImGui_ImplGlfw_NewFrame(); 133 | ImGui::NewFrame(); 134 | 135 | // Cpu Zone : prepare 136 | calc_imgui(); 137 | ImGui::Render(); 138 | 139 | // GPU Zone : Rendering 140 | glfwMakeContextCurrent(window); 141 | { 142 | AIGPScoped("render_imgui", "ImGui"); 143 | glViewport(0, 0, display_w, display_h); 144 | glClearColor(0.3f, 0.3f, 0.3f, 0.3f); 145 | glClear(GL_COLOR_BUFFER_BIT); 146 | 147 | { 148 | AIGPScoped("ImGui", "RenderDrawData"); 149 | ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); 150 | } 151 | } 152 | 153 | { 154 | AIGPScoped("Opengl", "glfwSwapBuffers"); 155 | glfwSwapBuffers(window); 156 | } 157 | } 158 | 159 | AIGPCollect; // collect all measure queries out of Main Frame 160 | } 161 | ``` 162 | 163 | that's all folks :) 164 | 165 | # Feature : BreadCrumb Trail (fil d'ariane) 166 | 167 | By left clicking on a bar, you can open it int the main profiler window. 168 | 169 | When you click on buttons of the breadcrumb trail you can select a parent bar to show 170 | 171 | ![img](https://github.com/aiekick/InAppGpuProfiler/blob/DemoApp/doc/breadcrumbtrail.gif) 172 | 173 | # Feature : Sub Windows per profiler bars 174 | 175 | By right clicking on a bars, you can open the bar in another window. 176 | 177 | ![img](https://github.com/aiekick/InAppGpuProfiler/blob/DemoApp/doc/sub_windows.gif) 178 | 179 | # The Demo App 180 | 181 | The demo app let you see how to use in detail the Profiler 182 | 183 | Its coming with a little opengl framework with some of my [shaders](https://www.shadertoy.com/user/aiekick) 184 | 185 | ![img](https://github.com/aiekick/InAppGpuProfiler/blob/DemoApp/doc/thumbnail.png) 186 | 187 | # Vulkan Support 188 | 189 | A vulkan version exist [HERE](https://github.com/aiekick/Gaia/blob/f7eb02beac1ee54e085a8e73387a782b329fa7f8/src/Gui/VulkanProfiler.cpp) 190 | 191 | but need some work for make it agnostic and dependent only from VK Api 192 | 193 | not found the time for unify the two versions at this moment 194 | -------------------------------------------------------------------------------- /iagp.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2021-2024 Stephane Cuillerdier (aka aiekick) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | // This is an independent m_oject of an individual developer. Dear PVS-Studio, please check it. 26 | // PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com 27 | 28 | #include "iagp.h" 29 | 30 | #include /* va_list, va_start, va_arg, va_end */ 31 | #include 32 | 33 | #ifdef _MSC_VER 34 | #include 35 | #define DEBUG_BREAK \ 36 | if (IsDebuggerPresent()) \ 37 | __debugbreak() 38 | #else 39 | #define DEBUG_BREAK 40 | #endif 41 | 42 | #ifndef IAGP_SUB_WINDOW_MIN_SIZE 43 | #define IAGP_SUB_WINDOW_MIN_SIZE ImVec2(300, 100) 44 | #endif // SUB_IAGP_WINDOW_MIN_SIZE 45 | 46 | #ifndef IAGP_GPU_CONTEXT 47 | #define IAGP_GPU_CONTEXT void* 48 | #endif // GPU_CONTEXT 49 | 50 | #ifndef IAGP_GET_CURRENT_CONTEXT 51 | static IAGP_GPU_CONTEXT GetCurrentContext() { 52 | DEBUG_BREAK; // you need to create your own function for get the opengl context 53 | return nullptr; 54 | } 55 | #define IAGP_GET_CURRENT_CONTEXT GetCurrentContext 56 | #endif // GET_CURRENT_CONTEXT 57 | 58 | #ifndef IAGP_SET_CURRENT_CONTEXT 59 | static void SetCurrentContext(GPU_CONTEXT vContextPtr) { 60 | DEBUG_BREAK; // you need to create your own function for get the opengl context 61 | } 62 | #define SET_CURRENT_CONTEXT SetCurrentContext 63 | #endif // GET_CURRENT_CONTEXT 64 | 65 | #ifndef IAGP_LOG_ERROR_MESSAGE 66 | static void LogError(const char* fmt, ...) { 67 | DEBUG_BREAK; // you need to define your own function for get error messages 68 | } 69 | #define IAGP_LOG_ERROR_MESSAGE LogError 70 | #endif // LOG_ERROR_MESSAGE 71 | 72 | #ifndef IAGP_LOG_DEBUG_ERROR_MESSAGE 73 | static void LogDebugError(const char* fmt, ...) { 74 | DEBUG_BREAK; // you need to define your own function for get error messages in debug 75 | } 76 | #define IAGP_LOG_DEBUG_ERROR_MESSAGE LogDebugError 77 | #endif // LOG_DEBUG_ERROR_MESSAGE 78 | 79 | #ifndef IAGP_IMGUI_BUTTON 80 | #define IAGP_IMGUI_BUTTON ImGui ::Button 81 | #endif // IMGUI_BUTTON 82 | 83 | #ifndef IAGP_IMGUI_PLAY_LABEL 84 | #define IAGP_IMGUI_PLAY_LABEL "Play" 85 | #endif 86 | 87 | #ifndef IAGP_IMGUI_PAUSE_LABEL 88 | #define IAGP_IMGUI_PAUSE_LABEL "Pause" 89 | #endif 90 | 91 | #ifndef IAGP_IMGUI_PLAY_PAUSE_HELP 92 | #define IAGP_IMGUI_PLAY_PAUSE_HELP "Play/Pause Profiling" 93 | #endif 94 | 95 | #ifndef IAGP_IMGUI_PLAY_PAUSE_BUTTON 96 | static bool PlayPauseButton(bool& vPlayPause) { 97 | bool res = false; 98 | const char* play_pause_label = IAGP_IMGUI_PAUSE_LABEL; 99 | if (vPlayPause) { 100 | play_pause_label = IAGP_IMGUI_PLAY_LABEL; 101 | } 102 | if (IAGP_IMGUI_BUTTON(play_pause_label)) { 103 | vPlayPause = !vPlayPause; 104 | res = true; 105 | } 106 | if (ImGui::IsItemHovered()) { 107 | ImGui::SetTooltip(IAGP_IMGUI_PLAY_PAUSE_HELP); 108 | } 109 | return res; 110 | } 111 | #define IAGP_IMGUI_PLAY_PAUSE_BUTTON PlayPauseButton 112 | #endif // LOG_DEBUG_ERROR_MESSAGE 113 | 114 | #ifndef IAGP_DETAILS_TITLE 115 | #define IAGP_DETAILS_TITLE "Profiler Details" 116 | #endif // IAGP_DETAILS_TITLE 117 | 118 | namespace iagp { 119 | 120 | inline void checkGLErrors(const char* vFile, const char* vFunc, const int& vLine) { 121 | #ifdef _DEBUG 122 | const GLenum err(glGetError()); 123 | if (err != GL_NO_ERROR) { 124 | std::string error; 125 | switch (err) { 126 | case GL_INVALID_OPERATION: error = "INVALID_OPERATION"; break; 127 | case GL_INVALID_ENUM: error = "INVALID_ENUM"; break; 128 | case GL_INVALID_VALUE: error = "INVALID_VALUE"; break; 129 | case GL_OUT_OF_MEMORY: error = "OUT_OF_MEMORY"; break; 130 | case GL_INVALID_FRAMEBUFFER_OPERATION: error = "INVALID_FRAMEBUFFER_OPERATION"; break; 131 | case GL_STACK_UNDERFLOW: error = "GL_STACK_UNDERFLOW"; break; 132 | case GL_STACK_OVERFLOW: error = "GL_STACK_OVERFLOW"; break; 133 | } 134 | printf("[%s][%s][%i] GL Errors : %s\n", vFile, vFunc, vLine, error.c_str()); 135 | //DEBUG_BREAK; 136 | } 137 | #endif 138 | } 139 | 140 | #define CheckGLErrors checkGLErrors(__FILE__, __FUNCTION__, __LINE__) 141 | 142 | // contrast from 1 to 21 143 | // https://www.w3.org/TR/WCAG20/#relativeluminancedef 144 | static float CalcContrastRatio(const ImU32& backgroundColor, const ImU32& foreGroundColor) { 145 | const float sa0 = (float)((backgroundColor >> IM_COL32_A_SHIFT) & 0xFF); 146 | const float sa1 = (float)((foreGroundColor >> IM_COL32_A_SHIFT) & 0xFF); 147 | static float sr = 0.2126f / 255.0f; 148 | static float sg = 0.7152f / 255.0f; 149 | static float sb = 0.0722f / 255.0f; 150 | const float contrastRatio = 151 | (sr * sa0 * ((backgroundColor >> IM_COL32_R_SHIFT) & 0xFF) + sg * sa0 * ((backgroundColor >> IM_COL32_G_SHIFT) & 0xFF) + 152 | sb * sa0 * ((backgroundColor >> IM_COL32_B_SHIFT) & 0xFF) + 0.05f) / 153 | (sr * sa1 * ((foreGroundColor >> IM_COL32_R_SHIFT) & 0xFF) + sg * sa1 * ((foreGroundColor >> IM_COL32_G_SHIFT) & 0xFF) + 154 | sb * sa1 * ((foreGroundColor >> IM_COL32_B_SHIFT) & 0xFF) + 0.05f); 155 | if (contrastRatio < 1.0f) 156 | return 1.0f / contrastRatio; 157 | return contrastRatio; 158 | } 159 | 160 | static bool PushStyleColorWithContrast(const ImU32& backGroundColor, const ImGuiCol& foreGroundColor, const ImVec4& invertedColor, 161 | const float& maxContrastRatio) { 162 | const float contrastRatio = CalcContrastRatio(backGroundColor, ImGui::GetColorU32(foreGroundColor)); 163 | if (contrastRatio < maxContrastRatio) { 164 | ImGui::PushStyleColor(foreGroundColor, invertedColor); 165 | return true; 166 | } 167 | return false; 168 | } 169 | 170 | static std::string toStr(const char* fmt, ...) { 171 | va_list args; 172 | va_start(args, fmt); 173 | char TempBuffer[1024 * 3 + 1]; 174 | const int w = vsnprintf(TempBuffer, 3072, fmt, args); 175 | va_end(args); 176 | if (w) { 177 | return std::string(TempBuffer, (size_t)w); 178 | } 179 | return std::string(); 180 | } 181 | 182 | //////////////////////////////////////////////////////////// 183 | /////////////////////// QUERY ZONE ///////////////////////// 184 | //////////////////////////////////////////////////////////// 185 | 186 | GLuint InAppGpuQueryZone::sMaxDepthToOpen = 100U; // the max by default 187 | bool InAppGpuQueryZone::sShowLeafMode = false; 188 | float InAppGpuQueryZone::sContrastRatio = 4.3f; 189 | bool InAppGpuQueryZone::sActivateLogger = false; 190 | std::vector InAppGpuQueryZone::sTabbedQueryZones = {}; 191 | IAGPQueryZonePtr InAppGpuQueryZone::create(IAGP_GPU_CONTEXT vContext, const std::string& vName, const std::string& vSectionName, 192 | const bool vIsRoot) { 193 | auto res = std::make_shared(vContext, vName, vSectionName, vIsRoot); 194 | res->m_This = res; 195 | return res; 196 | } 197 | InAppGpuQueryZone::circularSettings InAppGpuQueryZone::sCircularSettings; 198 | 199 | InAppGpuQueryZone::InAppGpuQueryZone(IAGP_GPU_CONTEXT vContext, const std::string& vName, const std::string& vSectionName, 200 | const bool vIsRoot) 201 | : m_Context(vContext), m_IsRoot(vIsRoot), m_SectionName(vSectionName), name(vName) { 202 | m_StartFrameId = 0; 203 | m_EndFrameId = 0; 204 | m_StartTimeStamp = 0; 205 | m_EndTimeStamp = 0; 206 | m_ElapsedTime = 0.0; 207 | depth = InAppGpuScopedZone::sCurrentDepth; 208 | imGuiLabel = vName + "##InAppGpuQueryZone_" + std::to_string((intptr_t)this); 209 | 210 | IAGP_SET_CURRENT_CONTEXT(m_Context); 211 | CheckGLErrors; 212 | glGenQueries(2, ids); 213 | CheckGLErrors; 214 | } 215 | 216 | InAppGpuQueryZone::~InAppGpuQueryZone() { 217 | IAGP_SET_CURRENT_CONTEXT(m_Context); 218 | CheckGLErrors; 219 | glDeleteQueries(2, ids); 220 | CheckGLErrors; 221 | 222 | name.clear(); 223 | m_StartFrameId = 0; 224 | m_EndFrameId = 0; 225 | m_StartTimeStamp = 0; 226 | m_EndTimeStamp = 0; 227 | m_ElapsedTime = 0.0; 228 | zonesOrdered.clear(); 229 | zonesDico.clear(); 230 | } 231 | 232 | void InAppGpuQueryZone::Clear() { 233 | m_StartFrameId = 0; 234 | m_EndFrameId = 0; 235 | m_StartTimeStamp = 0; 236 | m_EndTimeStamp = 0; 237 | m_ElapsedTime = 0.0; 238 | } 239 | 240 | void InAppGpuQueryZone::SetStartTimeStamp(const GLuint64& vValue) { 241 | m_StartTimeStamp = vValue; 242 | ++m_StartFrameId; 243 | } 244 | 245 | void InAppGpuQueryZone::SetEndTimeStamp(const GLuint64& vValue) { 246 | m_EndTimeStamp = vValue; 247 | ++m_EndFrameId; 248 | 249 | #ifdef IAGP_DEBUG_MODE_LOGGING 250 | IAGP_DEBUG_MODE_LOGGING("%*s end id retrieved : %u", depth, "", ids[1]); 251 | #endif 252 | // start computation of elapsed time 253 | // no needed after 254 | // will be used for Graph and labels 255 | // so DrawMetricGraph must be the first 256 | ComputeElapsedTime(); 257 | 258 | if (InAppGpuQueryZone::sActivateLogger && zonesOrdered.empty()) // only the leafs 259 | { 260 | /*double v = (double)vValue / 1e9; 261 | LogVarLightInfo("", 262 | m_SectionName.c_str(), v, name.c_str(), m_ElapsedTime);*/ 263 | } 264 | } 265 | 266 | void InAppGpuQueryZone::ComputeElapsedTime() { 267 | // we take the last frame 268 | if (m_StartFrameId == m_EndFrameId) { 269 | m_AverageStartValue.AddValue(m_StartTimeStamp); // ns to ms 270 | m_AverageEndValue.AddValue(m_EndTimeStamp); // ns to ms 271 | m_StartTime = (double)(m_AverageStartValue.GetAverage() * 1e-6); 272 | m_EndTime = (double)(m_AverageEndValue.GetAverage() * 1e-6); 273 | m_ElapsedTime = m_EndTime - m_StartTime; 274 | } 275 | } 276 | 277 | void InAppGpuQueryZone::DrawDetails() { 278 | if (m_StartFrameId) { 279 | bool res = false; 280 | 281 | ImGui::TableNextColumn(); // tree 282 | 283 | ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_CollapsingHeader | ImGuiTreeNodeFlags_DefaultOpen; 284 | 285 | bool any_childs_to_show = false; 286 | for (const auto& zone : zonesOrdered) { 287 | if (zone != nullptr && zone->m_ElapsedTime > 0.0) { 288 | any_childs_to_show = true; 289 | break; 290 | } 291 | } 292 | 293 | if (!any_childs_to_show) { 294 | flags |= ImGuiTreeNodeFlags_Leaf; 295 | } 296 | 297 | if (m_Highlighted) { 298 | flags |= ImGuiTreeNodeFlags_Framed; 299 | } 300 | 301 | const auto colorU32 = ImGui::ColorConvertFloat4ToU32(cv4); 302 | const bool pushed = PushStyleColorWithContrast(colorU32, ImGuiCol_Text, ImVec4(0, 0, 0, 1), InAppGpuQueryZone::sContrastRatio); 303 | 304 | ImGui::PushStyleColor(ImGuiCol_Header, cv4); 305 | const auto hovered_color = ImVec4(cv4.x * 0.9f, cv4.y * 0.9f, cv4.z * 0.9f, 1.0f); 306 | const auto active_color = ImVec4(cv4.x * 0.8f, cv4.y * 0.8f, cv4.z * 0.8f, 1.0f); 307 | ImGui::PushStyleColor(ImGuiCol_HeaderHovered, hovered_color); 308 | ImGui::PushStyleColor(ImGuiCol_HeaderActive, active_color); 309 | 310 | if (m_IsRoot) { 311 | res = ImGui::TreeNodeEx(this, flags, "%s : frame [%u]", name.c_str(), m_StartFrameId - 1U); 312 | } else if (!m_SectionName.empty()) { 313 | res = ImGui::TreeNodeEx(this, flags, "%s : %s", m_SectionName.c_str(), name.c_str()); 314 | } else { 315 | res = ImGui::TreeNodeEx(this, flags, "%s", name.c_str()); 316 | } 317 | 318 | ImGui::PopStyleColor(3); 319 | 320 | if (pushed) { 321 | ImGui::PopStyleColor(); 322 | } 323 | 324 | if (ImGui::IsItemHovered()) { 325 | m_Highlighted = true; 326 | } 327 | 328 | #ifdef IAGP_SHOW_COUNT 329 | ImGui::TableNextColumn(); // Elapsed time 330 | ImGui::Text("%u", last_count); 331 | #endif 332 | ImGui::TableNextColumn(); // Elapsed time 333 | ImGui::Text("%.5f ms", m_ElapsedTime); 334 | ImGui::TableNextColumn(); // Max fps 335 | if (m_ElapsedTime > 0.0f) { 336 | ImGui::Text("%.2f f/s", 1000.0f / m_ElapsedTime); 337 | } else { 338 | ImGui::Text("%s", "Infinite"); 339 | } 340 | ImGui::TableNextColumn(); // start time 341 | ImGui::Text("%.5f ms", m_StartTime); 342 | ImGui::TableNextColumn(); // end time 343 | ImGui::Text("%.5f", m_EndTime); 344 | 345 | if (res) { 346 | m_Expanded = true; 347 | ImGui::Indent(); 348 | for (const auto& zone : zonesOrdered) { 349 | if (zone != nullptr && zone->m_ElapsedTime > 0.0) { 350 | zone->DrawDetails(); 351 | } 352 | } 353 | ImGui::Unindent(); 354 | } else { 355 | m_Expanded = false; 356 | } 357 | } 358 | } 359 | 360 | bool InAppGpuQueryZone::DrawFlamGraph(InAppGpuGraphTypeEnum vGraphType, IAGPQueryZoneWeak& vOutSelectedQuery, IAGPQueryZoneWeak vParent, 361 | uint32_t vDepth) { 362 | ImGuiWindow* window = ImGui::GetCurrentWindow(); 363 | if (window->SkipItems) { 364 | return false; 365 | } 366 | 367 | bool pressed = false; 368 | switch (vGraphType) { 369 | case InAppGpuGraphTypeEnum::IN_APP_GPU_HORIZONTAL: // horizontal flame graph (standard and legacy) 370 | pressed = m_DrawHorizontalFlameGraph(m_This.lock(), vOutSelectedQuery, vParent, vDepth); 371 | break; 372 | case InAppGpuGraphTypeEnum::IN_APP_GPU_CIRCULAR: // circular flame graph 373 | pressed = m_DrawCircularFlameGraph(m_This.lock(), vOutSelectedQuery, vParent, vDepth); 374 | break; 375 | case InAppGpuGraphTypeEnum::IN_APP_GPU_Count: 376 | default: break; 377 | } 378 | return pressed; 379 | } 380 | 381 | void InAppGpuQueryZone::UpdateBreadCrumbTrail() { 382 | if (parentPtr != nullptr) { 383 | int32_t _depth = depth; 384 | IAGPQueryZonePtr _parent_ptr = m_This.lock(); 385 | while (_parent_ptr != rootPtr) { 386 | _parent_ptr = _parent_ptr->parentPtr; 387 | if (_parent_ptr && _parent_ptr->depth == (_depth - 1U)) { 388 | _depth = _parent_ptr->depth; 389 | if (_depth < (int32_t)m_BreadCrumbTrail.size()) { 390 | m_BreadCrumbTrail[_depth] = _parent_ptr; 391 | } else { 392 | DEBUG_BREAK; 393 | // maybe you need to define greater value for RECURSIVE_LEVELS_COUNT 394 | break; 395 | } 396 | } 397 | } 398 | 399 | // update the imgui title 400 | imGuiTitle.clear(); 401 | for (GLuint idx = 0U; idx < depth; ++idx) { 402 | if (idx < (GLuint)m_BreadCrumbTrail.size()) { 403 | auto ptr = m_BreadCrumbTrail[idx].lock(); 404 | if (ptr != nullptr) { 405 | if (idx > 0U) { 406 | imGuiTitle += " > "; 407 | } 408 | imGuiTitle += ptr->name; 409 | } 410 | } else { 411 | DEBUG_BREAK; 412 | // maybe you need to define greater value for RECURSIVE_LEVELS_COUNT 413 | break; 414 | } 415 | } 416 | // add the current 417 | imGuiTitle += " > " + name; 418 | 419 | // add the unicity string 420 | imGuiTitle += "##InAppGpuQueryZone_ " + std::to_string((intptr_t)this); 421 | } 422 | } 423 | 424 | void InAppGpuQueryZone::DrawBreadCrumbTrail(IAGPQueryZoneWeak& vOutSelectedQuery) { 425 | ImGui::PushID("DrawBreadCrumbTrail"); 426 | for (GLuint idx = 0U; idx < depth; ++idx) { 427 | if (idx < m_BreadCrumbTrail.size()) { 428 | auto ptr = m_BreadCrumbTrail[idx].lock(); 429 | if (ptr != nullptr) { 430 | if (idx > 0U) { 431 | ImGui::SameLine(); 432 | ImGui::Text("%s", ">"); 433 | ImGui::SameLine(); 434 | } 435 | ImGui::PushID(ptr.get()); 436 | if (IAGP_IMGUI_BUTTON(ptr->imGuiLabel.c_str())) { 437 | vOutSelectedQuery = m_BreadCrumbTrail[idx]; 438 | } 439 | ImGui::PopID(); 440 | } 441 | } else { 442 | DEBUG_BREAK; 443 | // maybe you need to define greater value for RECURSIVE_LEVELS_COUNT 444 | break; 445 | } 446 | } 447 | if (depth > 0) { 448 | ImGui::SameLine(); 449 | ImGui::Text("> %s", name.c_str()); 450 | } 451 | ImGui::PopID(); 452 | } 453 | 454 | void InAppGpuQueryZone::m_DrawList_DrawBar(const char* vLabel, const ImRect& vRect, const ImVec4& vColor, const bool vHovered) { 455 | const ImGuiContext& g = *GImGui; 456 | ImGuiWindow* window = g.CurrentWindow; 457 | const ImGuiStyle& style = g.Style; 458 | const ImVec2 label_size = ImGui::CalcTextSize(vLabel, nullptr, true); 459 | 460 | const auto colorU32 = ImGui::ColorConvertFloat4ToU32(vColor); 461 | ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); 462 | ImGui::RenderFrame(vRect.Min, vRect.Max, colorU32, true, 2.0f); 463 | if (vHovered) { 464 | const auto selectU32 = ImGui::ColorConvertFloat4ToU32(ImVec4(1.0f - cv4.x, 1.0f - cv4.y, 1.0f - cv4.z, 1.0f)); 465 | window->DrawList->AddRect(vRect.Min, vRect.Max, selectU32, true, 0, 2.0f); 466 | } 467 | ImGui::PopStyleVar(); 468 | 469 | const bool pushed = PushStyleColorWithContrast(colorU32, ImGuiCol_Text, ImVec4(0, 0, 0, 1), InAppGpuQueryZone::sContrastRatio); 470 | ImGui::RenderTextClipped(vRect.Min + style.FramePadding, vRect.Max - style.FramePadding, // 471 | vLabel, nullptr, &label_size, ImVec2(0.5f, 0.5f), &vRect); 472 | if (pushed) { 473 | ImGui::PopStyleColor(); 474 | } 475 | } 476 | 477 | bool InAppGpuQueryZone::m_ComputeRatios(IAGPQueryZonePtr vRoot, IAGPQueryZoneWeak vParent, uint32_t vDepth, float& vOutStartRatio, 478 | float& vOutSizeRatio) { 479 | if (depth > InAppGpuQueryZone::sMaxDepthToOpen) { 480 | return false; 481 | } 482 | if (vRoot == nullptr) { 483 | vRoot = m_This.lock(); 484 | } 485 | if (vParent.expired()) { 486 | vParent = m_This; 487 | } 488 | if (vRoot != nullptr && vRoot->m_ElapsedTime > 0.0) { // avoid div by zero 489 | if (vDepth == 0) { 490 | vOutStartRatio = 0.0f; 491 | vOutSizeRatio = 1.0f; 492 | if (rootPtr == nullptr) { 493 | hsv = ImVec4((float)(0.5 - 0.5 * m_ElapsedTime / vRoot->m_ElapsedTime), 0.5f, 1.0f, 1.0f); 494 | } else { 495 | hsv = ImVec4((float)(0.5 - 0.5 * m_ElapsedTime / rootPtr->m_ElapsedTime), 0.5f, 1.0f, 1.0f); 496 | } 497 | } else { 498 | auto parent_ptr = vParent.lock(); 499 | if (parent_ptr) { 500 | if (parent_ptr->m_ElapsedTime > 0.0) { // avoid div by zero 501 | 502 | //////////////////////////////////////////////////////// 503 | // for correct rounding isssue with average values 504 | if (parent_ptr->m_StartTime > m_StartTime) { 505 | m_StartTime = parent_ptr->m_StartTime; 506 | } 507 | if (parent_ptr->m_EndTime < m_EndTime) { 508 | m_EndTime = parent_ptr->m_EndTime; 509 | } 510 | if (m_EndTime < m_StartTime) { 511 | m_EndTime = m_StartTime; 512 | } 513 | m_ElapsedTime = m_EndTime - m_StartTime; 514 | if (m_ElapsedTime < 0.0) { 515 | DEBUG_BREAK; 516 | } 517 | if (m_ElapsedTime > parent_ptr->m_ElapsedTime) { 518 | m_ElapsedTime = parent_ptr->m_ElapsedTime; 519 | } 520 | //////////////////////////////////////////////////////// 521 | 522 | vOutStartRatio = (float)((m_StartTime - vRoot->m_StartTime) / vRoot->m_ElapsedTime); 523 | vOutSizeRatio = (float)(m_ElapsedTime / vRoot->m_ElapsedTime); 524 | if (rootPtr == nullptr) { 525 | hsv = ImVec4((float)(0.5 - 0.5 * m_ElapsedTime / vRoot->m_ElapsedTime), 0.5f, 1.0f, 1.0f); 526 | } else { 527 | hsv = ImVec4((float)(0.5 - 0.5 * m_ElapsedTime / rootPtr->m_ElapsedTime), 0.5f, 1.0f, 1.0f); 528 | } 529 | } 530 | } 531 | } 532 | return true; 533 | } 534 | return false; 535 | } 536 | 537 | bool InAppGpuQueryZone::m_DrawHorizontalFlameGraph(IAGPQueryZonePtr vRoot, IAGPQueryZoneWeak& vOutSelectedQuery, IAGPQueryZoneWeak vParent, 538 | uint32_t vDepth) { 539 | bool pressed = false; 540 | const ImGuiContext& g = *GImGui; 541 | const ImGuiStyle& style = g.Style; 542 | const float aw = ImGui::GetContentRegionAvail().x - style.FramePadding.x; 543 | ImGuiWindow* window = ImGui::GetCurrentWindow(); 544 | float barStartRatio = 0.0f; 545 | float barSizeRatio = 0.0f; 546 | if (m_ComputeRatios(vRoot, vParent, vDepth, barStartRatio, barSizeRatio)) { 547 | if (barSizeRatio > 0.0f) { 548 | if ((zonesOrdered.empty() && InAppGpuQueryZone::sShowLeafMode) || !InAppGpuQueryZone::sShowLeafMode) { 549 | ImGui::PushID(this); 550 | m_BarLabel = toStr("%s (%.2f ms | %.2f f/s)", name.c_str(), m_ElapsedTime, 1000.0f / m_ElapsedTime); 551 | const char* label = m_BarLabel.c_str(); 552 | const ImGuiID id = window->GetID(label); 553 | ImGui::PopID(); 554 | float bar_start = aw * barStartRatio; 555 | float bar_size = aw * barSizeRatio; 556 | const ImVec2 label_size = ImGui::CalcTextSize(label, nullptr, true); 557 | const float height = label_size.y + style.FramePadding.y * 2.0f; 558 | const ImVec2 bPos = ImVec2(bar_start + style.FramePadding.x, vDepth * height + style.FramePadding.y); 559 | const ImVec2 pos = window->DC.CursorPos + bPos; 560 | const ImVec2 size = ImVec2(bar_size, height); 561 | const ImRect bb(pos, pos + size); 562 | bool hovered, held; 563 | pressed = 564 | ImGui::ButtonBehavior(bb, id, &hovered, &held, // 565 | ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight); 566 | if (pressed) { 567 | if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) { 568 | vOutSelectedQuery = m_This; // open in the main window 569 | } else if (ImGui::IsMouseClicked(ImGuiMouseButton_Right) && rootPtr != nullptr) { 570 | sTabbedQueryZones.push_back(m_This); // open new window 571 | } 572 | } 573 | m_Highlighted = false; 574 | if (hovered) { 575 | ImGui::SetTooltip("Section : [%s : %s]\nElapsed time : %.5f ms\nElapsed FPS : %.5f f/s", // 576 | m_SectionName.c_str(), name.c_str(), m_ElapsedTime, 1000.0f / m_ElapsedTime); 577 | m_Highlighted = true; // to highlight label graph by this button 578 | } else if (m_Highlighted) { 579 | hovered = true; // highlight this button by the label graph 580 | } 581 | ImGui::ColorConvertHSVtoRGB(hsv.x, hsv.y, hsv.z, cv4.x, cv4.y, cv4.z); 582 | cv4.w = 1.0f; 583 | ImGui::RenderNavHighlight(bb, id); 584 | m_DrawList_DrawBar(label, bb, cv4, hovered); 585 | ++vDepth; 586 | } 587 | 588 | // we dont show child if this one have elapsed time to 0.0 589 | for (const auto& zone : zonesOrdered) { 590 | if (zone != nullptr) { 591 | pressed |= zone->m_DrawHorizontalFlameGraph(vRoot, vOutSelectedQuery, m_This, vDepth); 592 | } 593 | } 594 | } else { 595 | #ifdef IAGP_DEBUG_MODE_LOGGING 596 | IAGP_DEBUG_MODE_LOGGING("Bar Ms not displayed", name.c_str()); 597 | #endif 598 | } 599 | } 600 | 601 | if (depth == 0 && ((zonesOrdered.empty() && InAppGpuQueryZone::sShowLeafMode) || !InAppGpuQueryZone::sShowLeafMode)) { 602 | const ImVec2 pos = window->DC.CursorPos; 603 | const ImVec2 size = ImVec2(aw, ImGui::GetFrameHeight() * (InAppGpuScopedZone::sMaxDepth + 1U)); 604 | ImGui::ItemSize(size); 605 | const ImRect bb(pos, pos + size); 606 | const ImGuiID id = window->GetID((name + "##canvas").c_str()); 607 | if (!ImGui::ItemAdd(bb, id)) { 608 | return pressed; 609 | } 610 | } 611 | 612 | return pressed; 613 | } 614 | 615 | bool InAppGpuQueryZone::m_DrawCircularFlameGraph(IAGPQueryZonePtr vRoot, IAGPQueryZoneWeak& vOutSelectedQuery, IAGPQueryZoneWeak vParent, 616 | uint32_t vDepth) { 617 | bool pressed = false; 618 | 619 | if (vDepth == 0U) { 620 | if (ImGui::BeginMenuBar()) { 621 | if (ImGui::BeginMenu("Settings")) { 622 | ImGui::SliderFloat("count points", &sCircularSettings.count_point, 1.0f, 240.0f); 623 | ImGui::SliderFloat("base_radius", &sCircularSettings.base_radius, 0.0f, 240.0f); 624 | ImGui::SliderFloat("space", &sCircularSettings.space, 0.0f, 240.0f); 625 | ImGui::SliderFloat("thick", &sCircularSettings.thick, 0.0f, 240.0f); 626 | ImGui::EndMenu(); 627 | } 628 | ImGui::EndMenuBar(); 629 | } 630 | } 631 | 632 | ImGuiWindow* window = ImGui::GetCurrentWindow(); 633 | float barStartRatio = 0.0f; 634 | float barSizeRatio = 0.0f; 635 | if (m_ComputeRatios(vRoot, vParent, vDepth, barStartRatio, barSizeRatio)) { 636 | if (barSizeRatio > 0.0f) { 637 | if ((zonesOrdered.empty() && InAppGpuQueryZone::sShowLeafMode) || !InAppGpuQueryZone::sShowLeafMode) { 638 | ImVec2 center = window->DC.CursorPos + ImGui::GetContentRegionAvail() * 0.5f; 639 | ImGui::ColorConvertHSVtoRGB(hsv.x, hsv.y, hsv.z, cv4.x, cv4.y, cv4.z); 640 | cv4.w = 1.0f; 641 | 642 | float min_radius = sCircularSettings.base_radius + sCircularSettings.space * vDepth + sCircularSettings.thick * vDepth; 643 | float max_radius = sCircularSettings.base_radius + sCircularSettings.space * vDepth + sCircularSettings.thick * (vDepth + 1U); 644 | 645 | auto draw_list_ptr = window->DrawList; 646 | auto colU32 = ImGui::GetColorU32(cv4); 647 | 648 | float full_length = _1PI_; 649 | float full_offset = _1PI_; 650 | 651 | float base_st = full_length / sCircularSettings.count_point; 652 | 653 | float bar_start = full_length * barStartRatio; 654 | float bar_size = full_length * (barStartRatio + barSizeRatio); 655 | float st = bar_size / ImMax(floor(bar_size / base_st), 3.0f); // 2 points mini par barre 656 | 657 | ImVector path; 658 | float co = 0.0f, si = 0.0f, ac = 0.0f, oc = 0.0f; 659 | for (ac = bar_start; ac < bar_size; ac += st) { 660 | ac = ImMin(ac, bar_size); 661 | oc = ac + full_offset; 662 | co = std::cos(oc) * sCircularSettings.scaleX; 663 | si = std::sin(oc) * sCircularSettings.scaleY; 664 | m_P0.x = co * min_radius + center.x; 665 | m_P0.y = si * min_radius + center.y; 666 | m_P1.x = co * max_radius + center.x; 667 | m_P1.y = si * max_radius + center.y; 668 | if (ac > bar_start) { 669 | // draw_list_ptr->AddQuadFilled(m_P0, m_P1, m_LP1, m_LP0, colU32); 670 | draw_list_ptr->AddQuad(m_P0, m_P1, m_LP1, m_LP0, colU32, 2.0f); // m_BlackU32 671 | 672 | // draw_list_ptr->PathLineTo(m_P0); 673 | // draw_list_ptr->PathLineTo(m_P1); 674 | // draw_list_ptr->PathLineTo(m_LP1); 675 | // draw_list_ptr->PathLineTo(m_LP0); 676 | } 677 | m_LP0 = m_P0; 678 | m_LP1 = m_P1; 679 | } 680 | // draw_list_ptr->PathStroke(colU32, ImDrawFlags_Closed, 2.0f); 681 | 682 | #ifdef _DEBUG 683 | // draw_list_ptr->AddLine(center, center - ImVec2(0, 150.0f), colU32, 2.0f); 684 | #endif 685 | 686 | ++vDepth; 687 | } 688 | 689 | // we dont show child if this one have elapsed time to 0.0 690 | // childs 691 | for (const auto& zone : zonesOrdered) { 692 | if (zone != nullptr) { 693 | pressed |= zone->m_DrawCircularFlameGraph(vRoot, vOutSelectedQuery, m_This, vDepth); 694 | } 695 | } 696 | } 697 | } 698 | 699 | return pressed; 700 | } 701 | 702 | //////////////////////////////////////////////////////////// 703 | /////////////////////// GL CONTEXT ///////////////////////// 704 | //////////////////////////////////////////////////////////// 705 | 706 | IAGPContextPtr InAppGpuGLContext::create(IAGP_GPU_CONTEXT vContext) { 707 | auto res = std::make_shared(vContext); 708 | res->m_This = res; 709 | return res; 710 | } 711 | 712 | InAppGpuGLContext::InAppGpuGLContext(IAGP_GPU_CONTEXT vContext) : m_Context(vContext) { 713 | } 714 | 715 | void InAppGpuGLContext::Clear() { 716 | m_RootZone.reset(); 717 | m_PendingUpdate.clear(); 718 | m_QueryIDToZone.clear(); 719 | m_DepthToLastZone.clear(); 720 | } 721 | 722 | void InAppGpuGLContext::Init() { 723 | } 724 | 725 | void InAppGpuGLContext::Unit() { 726 | Clear(); 727 | } 728 | 729 | void InAppGpuGLContext::Collect() { 730 | #ifdef IAGP_DEBUG_MODE_LOGGING 731 | IAGP_DEBUG_MODE_LOGGING("------ Collect Trhead (%i) -----", (intptr_t)m_Context); 732 | #endif 733 | 734 | auto it = m_PendingUpdate.begin(); 735 | while (!m_PendingUpdate.empty() && it != m_PendingUpdate.end()) { 736 | GLuint id = *it; 737 | GLuint value = 0; 738 | glGetQueryObjectuiv(id, GL_QUERY_RESULT_AVAILABLE, &value); 739 | const auto it_to_erase_eventually = it; 740 | ++it; 741 | if (value == GL_TRUE /* || id == m_RootZone->ids[0] || id == m_RootZone->ids[1]*/) { 742 | GLuint64 value64 = 0; 743 | glGetQueryObjectui64v(id, GL_QUERY_RESULT, &value64); 744 | if (m_QueryIDToZone.find(id) != m_QueryIDToZone.end()) { 745 | auto ptr = m_QueryIDToZone[id]; 746 | if (ptr != nullptr) { 747 | if (id == ptr->ids[0]) { 748 | ptr->SetStartTimeStamp(value64); 749 | } else if (id == ptr->ids[1]) { 750 | ptr->last_count = ptr->current_count; 751 | ptr->current_count = 0U; 752 | ptr->SetEndTimeStamp(value64); 753 | } else { 754 | DEBUG_BREAK; 755 | } 756 | } 757 | } 758 | m_PendingUpdate.erase(it_to_erase_eventually); 759 | } else { 760 | auto ptr = m_QueryIDToZone[id]; 761 | if (ptr != nullptr) { 762 | IAGP_LOG_ERROR_MESSAGE("%*s id not retrieved : %u", ptr->depth, "", id); 763 | } 764 | } 765 | } 766 | 767 | #ifdef IAGP_DEBUG_MODE_LOGGING 768 | IAGP_DEBUG_MODE_LOGGING("------ End Frame -----"); 769 | #endif 770 | } 771 | 772 | void InAppGpuGLContext::DrawFlamGraph(const InAppGpuGraphTypeEnum& vGraphType) { 773 | if (m_RootZone != nullptr) { 774 | if (!m_SelectedQuery.expired()) { 775 | auto ptr = m_SelectedQuery.lock(); 776 | if (ptr) { 777 | ptr->DrawBreadCrumbTrail(m_SelectedQuery); 778 | ptr->DrawFlamGraph(vGraphType, m_SelectedQuery); 779 | } 780 | } else { 781 | m_RootZone->DrawFlamGraph(vGraphType, m_SelectedQuery); 782 | } 783 | } 784 | } 785 | 786 | void InAppGpuGLContext::DrawDetails() { 787 | if (m_RootZone != nullptr) { 788 | m_RootZone->DrawDetails(); 789 | } 790 | } 791 | 792 | IAGPQueryZonePtr InAppGpuGLContext::GetQueryZoneForName(const void* vPtr, const std::string& vName, const std::string& vSection, 793 | const bool vIsRoot) { 794 | IAGPQueryZonePtr res = nullptr; 795 | 796 | ///////////////////////////////////////////// 797 | //////////////// CREATION /////////////////// 798 | ///////////////////////////////////////////// 799 | 800 | // there is many link issues with 'max' in cross compilation so we dont using it 801 | if (InAppGpuScopedZone::sCurrentDepth > InAppGpuScopedZone::sMaxDepth) { 802 | InAppGpuScopedZone::sMaxDepth = InAppGpuScopedZone::sCurrentDepth; 803 | } 804 | 805 | if (InAppGpuScopedZone::sCurrentDepth == 0) { // root zone 806 | #ifdef IAGP_DEBUG_MODE_LOGGING 807 | IAGP_DEBUG_MODE_LOGGING("------ Start Frame -----"); 808 | #endif 809 | m_DepthToLastZone.clear(); 810 | if (m_RootZone == nullptr) { 811 | res = InAppGpuQueryZone::create(m_Context, vName, vSection, vIsRoot); 812 | if (res != nullptr) { 813 | res->depth = InAppGpuScopedZone::sCurrentDepth; 814 | res->UpdateBreadCrumbTrail(); 815 | m_QueryIDToZone[res->ids[0]] = res; 816 | m_QueryIDToZone[res->ids[1]] = res; 817 | m_RootZone = res; 818 | #ifdef IAGP_DEBUG_MODE_LOGGING 819 | // IAGP_DEBUG_MODE_LOGGING("Profile : add zone %s at puDepth %u", vName.c_str(), InAppGpuScopedZone::sCurrentDepth); 820 | #endif 821 | } 822 | } else { 823 | res = m_RootZone; 824 | } 825 | } else { // else child zone 826 | auto root = m_GetQueryZoneFromDepth(InAppGpuScopedZone::sCurrentDepth - 1U); 827 | if (root != nullptr) { 828 | bool found = false; 829 | const auto& key_str = vSection + vName; 830 | const auto& ptr_iter = root->zonesDico.find(vPtr); 831 | if (ptr_iter == root->zonesDico.end()) { // not found 832 | found = false; 833 | } else { // found 834 | found = (ptr_iter->second.find(key_str) != ptr_iter->second.end()); // not found 835 | } 836 | if (!found) { // not found 837 | res = InAppGpuQueryZone::create(m_Context, vName, vSection, vIsRoot); 838 | if (res != nullptr) { 839 | res->parentPtr = root; 840 | res->rootPtr = m_RootZone; 841 | res->depth = InAppGpuScopedZone::sCurrentDepth; 842 | res->UpdateBreadCrumbTrail(); 843 | m_QueryIDToZone[res->ids[0]] = res; 844 | m_QueryIDToZone[res->ids[1]] = res; 845 | root->zonesDico[vPtr][key_str] = res; 846 | root->zonesOrdered.push_back(res); 847 | #ifdef IAGP_DEBUG_MODE_LOGGING 848 | // IAGP_DEBUG_MODE_LOGGING("Profile : add zone %s at puDepth %u", vName.c_str(), InAppGpuScopedZone::sCurrentDepth); 849 | #endif 850 | } else { 851 | DEBUG_BREAK; 852 | } 853 | } else { 854 | res = root->zonesDico[vPtr][key_str]; 855 | } 856 | } else { 857 | return res; // happen when profiling is activated inside a profiling zone 858 | } 859 | } 860 | 861 | ///////////////////////////////////////////// 862 | //////////////// UTILISATION //////////////// 863 | ///////////////////////////////////////////// 864 | 865 | if (res != nullptr) { 866 | m_SetQueryZoneForDepth(res, InAppGpuScopedZone::sCurrentDepth); 867 | 868 | if (res->name != vName) { 869 | // at depth 0 there is only one frame 870 | IAGP_LOG_DEBUG_ERROR_MESSAGE("was registerd at depth %u %s. but we got %s\nwe clear the profiler", // 871 | InAppGpuScopedZone::sCurrentDepth, res->name.c_str(), vName.c_str()); 872 | // maybe the scoped frame is taken outside of the main frame 873 | Clear(); 874 | } 875 | 876 | m_PendingUpdate.emplace(res->ids[0]); 877 | m_PendingUpdate.emplace(res->ids[1]); 878 | } 879 | 880 | return res; 881 | } 882 | 883 | void InAppGpuGLContext::m_SetQueryZoneForDepth(IAGPQueryZonePtr vInAppGpuQueryZone, GLuint vDepth) { 884 | m_DepthToLastZone[vDepth] = vInAppGpuQueryZone; 885 | } 886 | 887 | IAGPQueryZonePtr InAppGpuGLContext::m_GetQueryZoneFromDepth(GLuint vDepth) { 888 | IAGPQueryZonePtr res = nullptr; 889 | 890 | if (m_DepthToLastZone.find(vDepth) != m_DepthToLastZone.end()) { // found 891 | res = m_DepthToLastZone[vDepth]; 892 | } 893 | 894 | return res; 895 | } 896 | 897 | //////////////////////////////////////////////////////////// 898 | /////////////////////// GL PROFILER //////////////////////// 899 | //////////////////////////////////////////////////////////// 900 | 901 | bool InAppGpuProfiler::sIsActive = false; 902 | bool InAppGpuProfiler::sIsPaused = false; 903 | 904 | InAppGpuProfiler::InAppGpuProfiler() = default; 905 | InAppGpuProfiler::InAppGpuProfiler(const InAppGpuProfiler&) = default; 906 | 907 | InAppGpuProfiler& InAppGpuProfiler::operator=(const InAppGpuProfiler&) { 908 | return *this; 909 | }; 910 | 911 | InAppGpuProfiler::~InAppGpuProfiler() { 912 | Clear(); 913 | }; 914 | 915 | void InAppGpuProfiler::Clear() { 916 | m_Contexts.clear(); 917 | } 918 | 919 | void InAppGpuProfiler::Collect() { 920 | if (!sIsActive || sIsPaused) { 921 | return; 922 | } 923 | 924 | glFinish(); 925 | 926 | for (const auto& con : m_Contexts) { 927 | if (con.second != nullptr) { 928 | con.second->Collect(); 929 | } 930 | } 931 | } 932 | 933 | void InAppGpuProfiler::DrawFlamGraph(const char* vLabel, bool* pOpen, ImGuiWindowFlags vFlags) { 934 | if (m_ImGuiBeginFunctor != nullptr && m_ImGuiBeginFunctor(vLabel, pOpen, vFlags | ImGuiWindowFlags_MenuBar)) { 935 | DrawFlamGraphNoWin(); 936 | } 937 | if (m_ImGuiEndFunctor != nullptr) { 938 | m_ImGuiEndFunctor(); 939 | } 940 | 941 | DrawFlamGraphChilds(vFlags); 942 | 943 | DrawDetails(vFlags); 944 | } 945 | 946 | void InAppGpuProfiler::DrawFlamGraphNoWin() { 947 | if (sIsActive) { 948 | m_DrawMenuBar(); 949 | for (const auto& con : m_Contexts) { 950 | if (con.second != nullptr) { 951 | con.second->DrawFlamGraph(m_GraphType); 952 | } 953 | } 954 | } 955 | } 956 | 957 | void InAppGpuProfiler::DrawFlamGraphChilds(ImGuiWindowFlags vFlags) { 958 | m_SelectedQuery.reset(); 959 | m_QueryZoneToClose = -1; 960 | for (size_t idx = 0U; idx < iagp::InAppGpuQueryZone::sTabbedQueryZones.size(); ++idx) { 961 | auto ptr = iagp::InAppGpuQueryZone::sTabbedQueryZones[idx].lock(); 962 | if (ptr != nullptr) { 963 | bool opened = true; 964 | ImGui::SetNextWindowSizeConstraints(IAGP_SUB_WINDOW_MIN_SIZE, ImGui::GetIO().DisplaySize); 965 | if (m_ImGuiBeginFunctor != nullptr && m_ImGuiBeginFunctor(ptr->imGuiTitle.c_str(), &opened, vFlags)) { 966 | if (sIsActive) { 967 | ptr->DrawFlamGraph(iagp::InAppGpuProfiler::Instance()->GetGraphTypeRef(), m_SelectedQuery); 968 | } 969 | } 970 | if (m_ImGuiEndFunctor != nullptr) { 971 | m_ImGuiEndFunctor(); 972 | } 973 | if (!opened) { 974 | m_QueryZoneToClose = (int32_t)idx; 975 | } 976 | } 977 | } 978 | if (m_QueryZoneToClose > -1) { 979 | iagp::InAppGpuQueryZone::sTabbedQueryZones.erase( // 980 | iagp::InAppGpuQueryZone::sTabbedQueryZones.begin() + m_QueryZoneToClose); 981 | } 982 | } 983 | 984 | void InAppGpuProfiler::SetImGuiBeginFunctor(const ImGuiBeginFunctor& vImGuiBeginFunctor) { 985 | m_ImGuiBeginFunctor = vImGuiBeginFunctor; 986 | } 987 | 988 | void InAppGpuProfiler::SetImGuiEndFunctor(const ImGuiEndFunctor& vImGuiEndFunctor) { 989 | m_ImGuiEndFunctor = vImGuiEndFunctor; 990 | } 991 | 992 | void InAppGpuProfiler::m_DrawMenuBar() { 993 | if (ImGui::BeginMenuBar()) { 994 | if (InAppGpuScopedZone::sMaxDepth) { 995 | InAppGpuQueryZone::sMaxDepthToOpen = InAppGpuScopedZone::sMaxDepth; 996 | } 997 | 998 | IAGP_IMGUI_PLAY_PAUSE_BUTTON(sIsPaused); 999 | 1000 | if (IAGP_IMGUI_BUTTON("Details")) { 1001 | m_ShowDetails = !m_ShowDetails; 1002 | } 1003 | 1004 | #ifdef IAGP_DEV_MODE 1005 | ImGui::Checkbox("Logging", &InAppGpuQueryZone::sActivateLogger); 1006 | 1007 | if (ImGui::BeginMenu("Graph Types")) { 1008 | if (ImGui::MenuItem("Horizontal", nullptr, m_GraphType == iagp::InAppGpuGraphTypeEnum::IN_APP_GPU_HORIZONTAL)) { 1009 | m_GraphType = iagp::InAppGpuGraphTypeEnum::IN_APP_GPU_HORIZONTAL; 1010 | } 1011 | if (ImGui::MenuItem("Circular", nullptr, m_GraphType == iagp::InAppGpuGraphTypeEnum::IN_APP_GPU_CIRCULAR)) { 1012 | m_GraphType = iagp::InAppGpuGraphTypeEnum::IN_APP_GPU_CIRCULAR; 1013 | } 1014 | ImGui::EndMenu(); 1015 | } 1016 | #endif 1017 | 1018 | ImGui::EndMenuBar(); 1019 | } 1020 | } 1021 | 1022 | void InAppGpuProfiler::DrawDetails(ImGuiWindowFlags vFlags) { 1023 | if (m_ShowDetails) { 1024 | if (m_ImGuiBeginFunctor != nullptr && m_ImGuiBeginFunctor(IAGP_DETAILS_TITLE, &m_ShowDetails, vFlags)) { 1025 | DrawDetailsNoWin(); 1026 | } 1027 | if (m_ImGuiEndFunctor != nullptr) { 1028 | m_ImGuiEndFunctor(); 1029 | } 1030 | } 1031 | } 1032 | 1033 | void InAppGpuProfiler::DrawDetailsNoWin() { 1034 | if (!sIsActive) { 1035 | return; 1036 | } 1037 | 1038 | int32_t count_tables = 5; 1039 | #ifdef IAGP_SHOW_COUNT 1040 | ++count_tables; 1041 | #endif 1042 | 1043 | static ImGuiTableFlags flags = // 1044 | ImGuiTableFlags_SizingFixedFit | // 1045 | ImGuiTableFlags_RowBg | // 1046 | ImGuiTableFlags_Hideable | // 1047 | ImGuiTableFlags_ScrollY | // 1048 | ImGuiTableFlags_NoHostExtendY; 1049 | const auto& size = ImGui::GetContentRegionAvail(); 1050 | auto listViewID = ImGui::GetID("##InAppGpuProfiler_DrawDetails"); 1051 | if (ImGui::BeginTableEx("##InAppGpuProfiler_DrawDetails", listViewID, count_tables, flags, size, 0.0f)) { 1052 | ImGui::TableSetupColumn("Tree", ImGuiTableColumnFlags_WidthStretch); 1053 | #ifdef IAGP_SHOW_COUNT 1054 | ImGui::TableSetupColumn("Count"); 1055 | #endif 1056 | ImGui::TableSetupColumn("Elapsed time"); 1057 | ImGui::TableSetupColumn("Max fps"); 1058 | ImGui::TableSetupColumn("Start time"); 1059 | ImGui::TableSetupColumn("End time"); 1060 | ImGui::TableHeadersRow(); 1061 | for (const auto& con : m_Contexts) { 1062 | if (con.second != nullptr) { 1063 | con.second->DrawDetails(); 1064 | } 1065 | } 1066 | ImGui::EndTable(); 1067 | } 1068 | } 1069 | 1070 | IAGPContextPtr InAppGpuProfiler::GetContextPtr(IAGP_GPU_CONTEXT vThreadPtr) { 1071 | if (!sIsActive) { 1072 | return nullptr; 1073 | } 1074 | 1075 | if (vThreadPtr != nullptr) { 1076 | if (m_Contexts.find((intptr_t)vThreadPtr) == m_Contexts.end()) { 1077 | m_Contexts[(intptr_t)vThreadPtr] = InAppGpuGLContext::create(vThreadPtr); 1078 | } 1079 | 1080 | return m_Contexts[(intptr_t)vThreadPtr]; 1081 | } 1082 | 1083 | IAGP_LOG_ERROR_MESSAGE("GPU_CONTEXT vThreadPtr is NULL"); 1084 | 1085 | return nullptr; 1086 | } 1087 | 1088 | //////////////////////////////////////////////////////////// 1089 | /////////////////////// SCOPED ZONE //////////////////////// 1090 | //////////////////////////////////////////////////////////// 1091 | 1092 | // STATIC 1093 | uint32_t InAppGpuScopedZone::sCurrentDepth = 0U; 1094 | uint32_t InAppGpuScopedZone::sMaxDepth = 0U; 1095 | 1096 | // SCOPED ZONE 1097 | InAppGpuScopedZone::InAppGpuScopedZone(const bool vIsRoot, const void* vPtr, const std::string& vSection, const char* fmt, ...) { 1098 | if (InAppGpuProfiler::sIsActive) { 1099 | va_list args; 1100 | va_start(args, fmt); 1101 | static char TempBuffer[256]; 1102 | const int w = vsnprintf(TempBuffer, 256, fmt, args); 1103 | va_end(args); 1104 | if (w) { 1105 | auto context_ptr = InAppGpuProfiler::Instance()->GetContextPtr(IAGP_GET_CURRENT_CONTEXT()); 1106 | if (context_ptr != nullptr) { 1107 | const auto& label = std::string(TempBuffer, (size_t)w); 1108 | queryPtr = context_ptr->GetQueryZoneForName(vPtr, label, vSection, vIsRoot); 1109 | if (queryPtr != nullptr) { 1110 | glQueryCounter(queryPtr->ids[0], GL_TIMESTAMP); 1111 | #ifdef IAGP_DEBUG_MODE_LOGGING 1112 | IAGP_DEBUG_MODE_LOGGING("%*s begin : [%u:%u] (depth:%u) (%s)", // 1113 | queryPtr->depth, "", queryPtr->ids[0], queryPtr->ids[1], queryPtr->depth, label.c_str()); 1114 | #endif 1115 | sCurrentDepth++; 1116 | } 1117 | } 1118 | } 1119 | } 1120 | } 1121 | 1122 | InAppGpuScopedZone::~InAppGpuScopedZone() { 1123 | if (InAppGpuProfiler::sIsActive) { 1124 | if (queryPtr != nullptr) { 1125 | #ifdef IAGP_DEBUG_MODE_LOGGING 1126 | if (queryPtr->depth > 0) { 1127 | IAGP_DEBUG_MODE_LOGGING("%*s end : [%u:%u] (depth:%u)", // 1128 | (queryPtr->depth - 1U), "", queryPtr->ids[0], queryPtr->ids[1], queryPtr->depth); 1129 | } else { 1130 | IAGP_DEBUG_MODE_LOGGING("end : [%u:%u] (depth:%u)", // 1131 | queryPtr->ids[0], queryPtr->ids[1], 0); 1132 | } 1133 | #endif 1134 | glQueryCounter(queryPtr->ids[1], GL_TIMESTAMP); 1135 | ++queryPtr->current_count; 1136 | --sCurrentDepth; 1137 | } 1138 | } 1139 | } 1140 | 1141 | } // namespace iagp 1142 | -------------------------------------------------------------------------------- /iagp.h: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2021-2024 Stephane Cuillerdier (aka aiekick) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | #pragma once 26 | 27 | #if defined(__WIN32__) || defined(WIN32) || defined(_WIN32) || defined(__WIN64__) || defined(WIN64) || defined(_WIN64) || defined(_MSC_VER) 28 | #if defined(ImAppGpuProfiler_EXPORTS) 29 | #define IN_APP_GPU_PROFILER_API __declspec(dllexport) 30 | #elif defined(BUILD_IN_APP_GPU_PROFILER_SHARED_LIBS) 31 | #define IN_APP_GPU_PROFILER_API __declspec(dllimport) 32 | #else 33 | #define IN_APP_GPU_PROFILER_API 34 | #endif 35 | #else 36 | #define IN_APP_GPU_PROFILER_API 37 | #endif 38 | 39 | #ifdef IMGUI_INCLUDE 40 | #include IMGUI_INCLUDE 41 | #else // IMGUI_INCLUDE 42 | #include 43 | #endif // IMGUI_INCLUDE 44 | 45 | #ifndef CUSTOM_IN_APP_GPU_PROFILER_CONFIG 46 | #include "InAppGpuProfilerConfig.h" 47 | #else // CUSTOM_IN_APP_GPU_PROFILER_CONFIG 48 | #include CUSTOM_IN_APP_GPU_PROFILER_CONFIG 49 | #endif // CUSTOM_IN_APP_GPU_PROFILER_CONFIG 50 | 51 | #include 52 | #include 53 | #include 54 | #include 55 | #include 56 | #include 57 | #include 58 | #include 59 | 60 | #ifndef IMGUI_DEFINE_MATH_OPERATORS 61 | #define IMGUI_DEFINE_MATH_OPERATORS 62 | #endif // IMGUI_DEFINE_MATH_OPERATORS 63 | 64 | // a main zone for the frame must always been defined for the frame 65 | #define IAGPNewFrame(section, fmt, ...) \ 66 | auto __IAGP__ScopedMainZone = iagp::InAppGpuScopedZone(true, nullptr, section, fmt, ##__VA_ARGS__); \ 67 | (void)__IAGP__ScopedMainZone 68 | 69 | #define IAGPScoped(section, fmt, ...) \ 70 | auto __IAGP__ScopedSubZone = iagp::InAppGpuScopedZone(false, nullptr, section, fmt, ##__VA_ARGS__); \ 71 | (void)__IAGP__ScopedSubZone 72 | 73 | #define IAGPScopedPtr(ptr, section, fmt, ...) \ 74 | auto __IAGP__ScopedSubZone = iagp::InAppGpuScopedZone(false, ptr, section, fmt, ##__VA_ARGS__); \ 75 | (void)__IAGP__ScopedSubZone 76 | 77 | #define IAGPCollect iagp::InAppGpuProfiler::Instance()->Collect() 78 | 79 | #ifndef IAGP_RECURSIVE_LEVELS_COUNT 80 | #define IAGP_RECURSIVE_LEVELS_COUNT 20U 81 | #endif // RECURSIVE_LEVELS_COUNT 82 | 83 | #ifndef IAGP_MEAN_AVERAGE_LEVELS_COUNT 84 | #define IAGP_MEAN_AVERAGE_LEVELS_COUNT 60U 85 | #endif // MEAN_AVERAGE_LEVELS_COUNT 86 | 87 | #ifndef IAGP_GPU_CONTEXT 88 | #define IAGP_GPU_CONTEXT void* 89 | #endif // GPU_CONTEXT 90 | 91 | namespace iagp { 92 | 93 | class InAppGpuQueryZone; 94 | typedef std::shared_ptr IAGPQueryZonePtr; 95 | typedef std::weak_ptr IAGPQueryZoneWeak; 96 | 97 | class InAppGpuGLContext; 98 | typedef std::shared_ptr IAGPContextPtr; 99 | typedef std::weak_ptr IAGPContextWeak; 100 | 101 | enum InAppGpuGraphTypeEnum { 102 | IN_APP_GPU_HORIZONTAL = 0, 103 | IN_APP_GPU_CIRCULAR, 104 | IN_APP_GPU_Count 105 | }; 106 | 107 | template 108 | class InAppGpuAverageValue { 109 | private: 110 | static constexpr uint32_t sCountAverageValues = IAGP_MEAN_AVERAGE_LEVELS_COUNT; 111 | T m_PerFrame[sCountAverageValues] = {}; 112 | int m_PerFrameIdx = (T)0; 113 | T m_PerFrameAccum = (T)0; 114 | T m_AverageValue = (T)0; 115 | 116 | public: 117 | InAppGpuAverageValue(); 118 | void AddValue(T vValue); 119 | T GetAverage(); 120 | }; 121 | 122 | template 123 | InAppGpuAverageValue::InAppGpuAverageValue() { 124 | memset(m_PerFrame, 0, sizeof(T) * sCountAverageValues); 125 | m_PerFrameIdx = (T)0; 126 | m_PerFrameAccum = (T)0; 127 | m_AverageValue = (T)0; 128 | } 129 | 130 | template 131 | void InAppGpuAverageValue::AddValue(T vValue) { 132 | if (vValue < m_PerFrame[m_PerFrameIdx]) { 133 | memset(m_PerFrame, 0, sizeof(T) * sCountAverageValues); 134 | m_PerFrameIdx = (T)0; 135 | m_PerFrameAccum = (T)0; 136 | m_AverageValue = (T)0; 137 | } 138 | m_PerFrameAccum += vValue - m_PerFrame[m_PerFrameIdx]; 139 | m_PerFrame[m_PerFrameIdx] = vValue; 140 | m_PerFrameIdx = (m_PerFrameIdx + (T)1) % sCountAverageValues; 141 | if (m_PerFrameAccum > (T)0) { 142 | m_AverageValue = m_PerFrameAccum / (T)sCountAverageValues; 143 | } 144 | } 145 | 146 | template 147 | T InAppGpuAverageValue::GetAverage() { 148 | return m_AverageValue; 149 | } 150 | 151 | class IN_APP_GPU_PROFILER_API InAppGpuQueryZone { 152 | public: 153 | struct circularSettings { 154 | float count_point = 20.0f; // 60 for 2pi 155 | float scaleX = 1.0f; 156 | float scaleY = 1.0f; 157 | float base_radius = 50.0f; 158 | float space = 5.0f; 159 | float thick = 10.0f; 160 | }; 161 | 162 | public: 163 | static GLuint sMaxDepthToOpen; 164 | static bool sShowLeafMode; 165 | static float sContrastRatio; 166 | static bool sActivateLogger; 167 | static std::vector sTabbedQueryZones; 168 | static IAGPQueryZonePtr create(IAGP_GPU_CONTEXT vContext, const std::string& vName, const std::string& vSectionName, 169 | const bool vIsRoot = false); 170 | static circularSettings sCircularSettings; 171 | 172 | private: 173 | IAGPQueryZoneWeak m_This; 174 | IAGP_GPU_CONTEXT m_Context; 175 | bool m_IsRoot = false; 176 | double m_ElapsedTime = 0.0; 177 | double m_StartTime = 0.0; 178 | double m_EndTime = 0.0; 179 | GLuint m_StartFrameId = 0; 180 | GLuint m_EndFrameId = 0; 181 | GLuint64 m_StartTimeStamp = 0; 182 | GLuint64 m_EndTimeStamp = 0; 183 | bool m_Expanded = false; 184 | bool m_Highlighted = false; 185 | InAppGpuAverageValue m_AverageStartValue; 186 | InAppGpuAverageValue m_AverageEndValue; 187 | std::string m_BarLabel; 188 | std::string m_SectionName; 189 | ImVec4 cv4; 190 | ImVec4 hsv; 191 | 192 | // fil d'ariane 193 | std::array m_BreadCrumbTrail; // the parent cound is done by current depth 194 | 195 | // circular 196 | const float _1PI_ = 3.141592653589793238462643383279f; 197 | //const float _2PI_ = 6.283185307179586476925286766559f; 198 | const ImU32 m_BlackU32 = ImGui::GetColorU32(ImVec4(0, 0, 0, 1)); 199 | ImVec2 m_P0, m_P1, m_LP0, m_LP1; 200 | 201 | public: 202 | GLuint depth = 0U; // the depth of the QueryZone 203 | GLuint ids[2] = {0U, 0U}; 204 | std::vector zonesOrdered; 205 | std::unordered_map> zonesDico; // main container 206 | std::string name; 207 | std::string imGuiLabel; 208 | std::string imGuiTitle; 209 | IAGPQueryZonePtr parentPtr = nullptr; 210 | IAGPQueryZonePtr rootPtr = nullptr; 211 | GLuint current_count = 0U; 212 | GLuint last_count = 0U; 213 | 214 | public: 215 | InAppGpuQueryZone() = default; 216 | InAppGpuQueryZone(IAGP_GPU_CONTEXT vContext, const std::string& vName, const std::string& vSectionName, 217 | const bool vIsRoot = false); 218 | ~InAppGpuQueryZone(); 219 | void Clear(); 220 | void SetStartTimeStamp(const GLuint64& vValue); 221 | void SetEndTimeStamp(const GLuint64& vValue); 222 | void ComputeElapsedTime(); 223 | void DrawDetails(); 224 | bool DrawFlamGraph(InAppGpuGraphTypeEnum vGraphType, // 225 | IAGPQueryZoneWeak& vOutSelectedQuery, // 226 | IAGPQueryZoneWeak vParent = {}, // 227 | uint32_t vDepth = 0); 228 | void UpdateBreadCrumbTrail(); 229 | void DrawBreadCrumbTrail(IAGPQueryZoneWeak& vOutSelectedQuery); 230 | 231 | private: 232 | void m_DrawList_DrawBar(const char* vLabel, const ImRect& vRect, const ImVec4& vColor, const bool vHovered); 233 | bool m_ComputeRatios(IAGPQueryZonePtr vRoot, IAGPQueryZoneWeak vParent, uint32_t vDepth, float& vOutStartRatio, float& vOutSizeRatio); 234 | bool m_DrawHorizontalFlameGraph(IAGPQueryZonePtr vRoot, IAGPQueryZoneWeak& vOutSelectedQuery, IAGPQueryZoneWeak vParent, 235 | uint32_t vDepth); 236 | bool m_DrawCircularFlameGraph(IAGPQueryZonePtr vRoot, IAGPQueryZoneWeak& vOutSelectedQuery, IAGPQueryZoneWeak vParent, 237 | uint32_t vDepth); 238 | }; 239 | 240 | class IN_APP_GPU_PROFILER_API InAppGpuGLContext { 241 | private: 242 | IAGPContextWeak m_This; 243 | IAGP_GPU_CONTEXT m_Context; 244 | IAGPQueryZonePtr m_RootZone = nullptr; 245 | IAGPQueryZoneWeak m_SelectedQuery; // query to show the flamegraph in this context 246 | std::unordered_map m_QueryIDToZone; // Get the zone for a query id because a query have to id's : start and end 247 | std::unordered_map m_DepthToLastZone; // last zone registered at this depth 248 | std::set m_PendingUpdate; // some queries msut but retrieveds 249 | 250 | public: 251 | static IAGPContextPtr create(IAGP_GPU_CONTEXT vContext); 252 | 253 | public: 254 | InAppGpuGLContext(IAGP_GPU_CONTEXT vContext); 255 | void Clear(); 256 | void Init(); 257 | void Unit(); 258 | void Collect(); 259 | void DrawFlamGraph(const InAppGpuGraphTypeEnum& vGraphType); 260 | void DrawDetails(); 261 | IAGPQueryZonePtr GetQueryZoneForName(const void* vPtr, const std::string& vName, const std::string& vSection = "", const bool vIsRoot = false); 262 | 263 | private: 264 | void m_SetQueryZoneForDepth(IAGPQueryZonePtr vQueryZone, GLuint vDepth); 265 | IAGPQueryZonePtr m_GetQueryZoneFromDepth(GLuint vDepth); 266 | }; 267 | 268 | class IN_APP_GPU_PROFILER_API InAppGpuScopedZone { 269 | public: 270 | static GLuint sCurrentDepth; // current depth catched by Profiler 271 | static GLuint sMaxDepth; // max depth catched ever 272 | 273 | public: 274 | IAGPQueryZonePtr queryPtr = nullptr; 275 | 276 | public: 277 | InAppGpuScopedZone(const bool vIsRoot, const void* vPtr, const std::string& vSection, const char* fmt, ...); 278 | ~InAppGpuScopedZone(); 279 | }; 280 | 281 | class IN_APP_GPU_PROFILER_API InAppGpuProfiler { 282 | public: 283 | typedef std::function ImGuiBeginFunctor; 284 | typedef std::function ImGuiEndFunctor; 285 | 286 | public: 287 | static bool sIsActive; 288 | static bool sIsPaused; 289 | 290 | private: 291 | std::unordered_map m_Contexts; 292 | InAppGpuGraphTypeEnum m_GraphType = InAppGpuGraphTypeEnum::IN_APP_GPU_HORIZONTAL; 293 | IAGPQueryZoneWeak m_SelectedQuery; 294 | int32_t m_QueryZoneToClose = -1; 295 | ImGuiBeginFunctor m_ImGuiBeginFunctor = // 296 | [](const char* vLabel, bool* pOpen, ImGuiWindowFlags vFlags) -> bool { // 297 | return ImGui::Begin(vLabel, pOpen, vFlags); 298 | }; 299 | ImGuiEndFunctor m_ImGuiEndFunctor = []() { ImGui::End(); }; 300 | bool m_ShowDetails = false; 301 | 302 | public: 303 | void Clear(); 304 | void Collect(); 305 | void DrawFlamGraph(const char* vLabel, bool* pOpen, ImGuiWindowFlags vFlags = 0); 306 | void DrawFlamGraphNoWin(); 307 | void DrawFlamGraphChilds(ImGuiWindowFlags vFlags = 0); 308 | void SetImGuiBeginFunctor(const ImGuiBeginFunctor& vImGuiBeginFunctor); 309 | void SetImGuiEndFunctor(const ImGuiEndFunctor& vImGuiEndFunctor); 310 | void DrawDetails(ImGuiWindowFlags vFlags = 0); 311 | void DrawDetailsNoWin(); 312 | IAGPContextPtr GetContextPtr(IAGP_GPU_CONTEXT vContext); 313 | InAppGpuGraphTypeEnum& GetGraphTypeRef() { 314 | return m_GraphType; 315 | } 316 | 317 | private: 318 | void m_DrawMenuBar(); 319 | 320 | public: 321 | static InAppGpuProfiler* Instance() { 322 | static InAppGpuProfiler _instance; 323 | return &_instance; 324 | } 325 | 326 | protected: 327 | InAppGpuProfiler(); 328 | InAppGpuProfiler(const InAppGpuProfiler&); 329 | InAppGpuProfiler& operator=(const InAppGpuProfiler&); 330 | ~InAppGpuProfiler(); 331 | }; 332 | 333 | } // namespace iagp 334 | -------------------------------------------------------------------------------- /iagpConfig.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | //////////////////////////////////////////////////////////////// 4 | // YOU NEED TO DEFINE THESES VARS FOR USING IAGP /////////////// 5 | // you can check the demo app for an example use /////////////// 6 | // https://github.com/aiekick/InAppGpuProfiler/tree/DemoApp //// 7 | //////////////////////////////////////////////////////////////// 8 | 9 | // you need also to put your opengl laoder here 10 | // without that you will have many linker error 11 | // ex #include 12 | 13 | // you need also to put your imgui header path too 14 | // without that you will have many linker error 15 | // ex #include 16 | 17 | // you need also to put your context dependencies here if requried 18 | // by your get/set context fucntions 19 | // without that you will have many linker error 20 | // #define IAGP_GPU_CONTEXT void* 21 | // #define IAGP_GET_CURRENT_CONTEXT 22 | // #define IAGP_SET_CURRENT_CONTEXT SetCurrentContext 23 | 24 | //////////////////////////////////////////////////////////////// 25 | // OPTIONNAL /////////////////////////////////////////////////// 26 | //////////////////////////////////////////////////////////////// 27 | 28 | // the title of the profiler detail imgui windows 29 | //#define IAGP_DETAILS_TITLE "Profiler Details" 30 | 31 | // the max level of recursion for profiler queries 32 | //#define IAGP_RECURSIVE_LEVELS_COUNT 20U 33 | 34 | // the mean average level 35 | // all the values will be smoothed on 60 frames (1s of 60fps diosplay) 36 | //#define IAGP_MEAN_AVERAGE_LEVELS_COUNT 60U 37 | 38 | //the minimal size of imgui sub window, when you openif by click right on a progiler bar 39 | //#define IAGP_SUB_WINDOW_MIN_SIZE ImVec2(300, 100) 40 | 41 | // the imgui button to use in IAGP 42 | //#define IAGP_IMGUI_BUTTON ImGui::Button 43 | 44 | // the Imgui Play/Pause button to use 45 | //#define IAGP_IMGUI_PLAY_PAUSE_BUTTON 46 | //#define IAGP_IMGUI_PLAY_LABEL "Play" 47 | //#define IAGP_IMGUI_PAUSE_LABEL "Pause" 48 | //#define IAGP_IMGUI_PLAY_PAUSE_HELP "Play/Pause Profiling" 49 | 50 | // define your fucntion for log error message of IAGP 51 | //#define IAGP_LOG_ERROR_MESSAGE LogError 52 | 53 | // define your fucntion for log error message of IAGP only in debug 54 | //#define IAGP_LOG_DEBUG_ERROR_MESSAGE LogDebugError 55 | --------------------------------------------------------------------------------