├── .gitignore ├── Image.jpg ├── fontawesome-webfont.ttf ├── ImGuiProfiler.vcxproj.user ├── .clang-format ├── ImGui ├── LICENSE.txt ├── imgui_impl_win32.h ├── imgui_impl_dx12.h ├── imconfig.h └── imstb_rectpack.h ├── ImGuiProfiler.sln ├── LICENSE ├── README.md ├── ImGuiProfiler.vcxproj.filters ├── ImGuiProfiler.vcxproj ├── Profiler.h ├── ProfilerWindow.cpp ├── main.cpp ├── Profiler.cpp └── IconsFontAwesome4.h /.gitignore: -------------------------------------------------------------------------------- 1 | .vs 2 | x64 3 | trace.json 4 | /.idea 5 | imgui.ini 6 | -------------------------------------------------------------------------------- /Image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simco50/TimelineProfiler/HEAD/Image.jpg -------------------------------------------------------------------------------- /fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simco50/TimelineProfiler/HEAD/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /ImGuiProfiler.vcxproj.user: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: Microsoft 3 | AccessModifierOffset: '-4' 4 | AlignConsecutiveAssignments: Consecutive 5 | AlignConsecutiveDeclarations: Consecutive 6 | AlignConsecutiveMacros: Consecutive 7 | AlignConsecutiveBitFields: Consecutive 8 | AlignEscapedNewlines: Left 9 | AlignTrailingComments: 'true' 10 | Cpp11BracedListStyle: 'false' 11 | PointerAlignment: Left 12 | TabWidth: '4' 13 | UseTab: Always 14 | AllowShortFunctionsOnASingleLine: Inline 15 | ColumnLimit: 0 16 | ... 17 | -------------------------------------------------------------------------------- /ImGui/LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2025 Omar Cornut 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 | -------------------------------------------------------------------------------- /ImGuiProfiler.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.6.33829.357 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ImGuiProfiler", "ImGuiProfiler.vcxproj", "{C89A478E-D4D0-4A65-8D3E-98AE7BD4929C}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Debug|x86 = Debug|x86 12 | Release|x64 = Release|x64 13 | Release|x86 = Release|x86 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {C89A478E-D4D0-4A65-8D3E-98AE7BD4929C}.Debug|x64.ActiveCfg = Debug|x64 17 | {C89A478E-D4D0-4A65-8D3E-98AE7BD4929C}.Debug|x64.Build.0 = Debug|x64 18 | {C89A478E-D4D0-4A65-8D3E-98AE7BD4929C}.Debug|x86.ActiveCfg = Debug|Win32 19 | {C89A478E-D4D0-4A65-8D3E-98AE7BD4929C}.Debug|x86.Build.0 = Debug|Win32 20 | {C89A478E-D4D0-4A65-8D3E-98AE7BD4929C}.Release|x64.ActiveCfg = Release|x64 21 | {C89A478E-D4D0-4A65-8D3E-98AE7BD4929C}.Release|x64.Build.0 = Release|x64 22 | {C89A478E-D4D0-4A65-8D3E-98AE7BD4929C}.Release|x86.ActiveCfg = Release|Win32 23 | {C89A478E-D4D0-4A65-8D3E-98AE7BD4929C}.Release|x86.Build.0 = Release|Win32 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {77272787-8C32-4F17-B9A0-C7FA5D1D7D25} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Simon Coenen 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 | 23 | -------------------------------------------------------------------------------------------- 24 | 25 | 26 | FontAwesomeCpp License 27 | 28 | Copyright (c) 2017 Juliette Foucaut and Doug Binks 29 | 30 | This software is provided 'as-is', without any express or implied 31 | warranty. In no event will the authors be held liable for any damages 32 | arising from the use of this software. 33 | 34 | Permission is granted to anyone to use this software for any purpose, 35 | including commercial applications, and to alter it and redistribute it 36 | freely, subject to the following restrictions: 37 | 38 | 1. The origin of this software must not be misrepresented; you must not 39 | claim that you wrote the original software. If you use this software 40 | in a product, an acknowledgment in the product documentation would be 41 | appreciated but is not required. 42 | 2. Altered source versions must be plainly marked as such, and must not be 43 | misrepresented as being the original software. 44 | 3. This notice may not be removed or altered from any source distribution.{\rtf1} -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TimelineProfiler 2 | Simple CPU/GPU profiler with ImGui HUD 3 | GPU profiler only supports D3D12 4 | 5 | ![](Image.jpg) 6 | 7 | ## Usage 8 | 9 | ### Setup 10 | 11 | Add files to project: 12 | - Profiler.h 13 | - Profiler.cpp 14 | - ProfilerWindow.cpp 15 | - IconsFontAwesome4.h 16 | - fontawesome-webfont.ttf 17 | 18 | Add the icon font and merge it with your main font 19 | ```c++ 20 | ImFontConfig fontConfig; 21 | fontConfig.MergeMode = true; 22 | fontConfig.GlyphMinAdvanceX = 15.0f 23 | static const ImWchar icon_ranges[] = { ICON_MIN_FA, ICON_MAX_FA, 0 }; 24 | io.Fonts->AddFontFromFileTTF(FONT_ICON_FILE_NAME_FA, 15.0f, &fontConfig, icon_ranges); 25 | ``` 26 | 27 | ### CPU Profiler 28 | 29 | #### Initialize 30 | 31 | ```c++ 32 | // Initialize 33 | gCPUProfiler.Initialize(historySize, maxNumEvents); 34 | ``` 35 | 36 | #### Shutdown 37 | 38 | ```c++ 39 | // Shutdown 40 | gCPUProfiler.Shutdown(); 41 | ``` 42 | 43 | #### Each frame 44 | ```c++ 45 | // Call at the start of each frame 46 | PROFILE_FRAME() 47 | ``` 48 | 49 | **CPU Event** 50 | 51 | `PROFILE_CPU_SCOPE()` to add a CPU event. Optionally specify a custom name 52 | 53 | 54 | **Registering a thread (optional)** 55 | 56 | `PROFILE_REGISTER_THREAD(name)` to register a thread. 57 | 58 | The registration order of threads will define the order in the timeline 59 | If a thread is not registered, it will lazy-register when an event is created first. 60 | 61 | 62 | ### GPU Profiler 63 | 64 | If you want to use the GPU profiler, initialize and shutdown as following, providing your command queues. 65 | 66 | #### Initialize 67 | 68 | ```c++ 69 | // Initialize 70 | Span queues; 71 | gGPUProfiler.Initialize(d3dDevice, queues, historySize, frameLatency, maxNumEvents, maxActiveCommandLists); 72 | ``` 73 | 74 | #### Shutdown 75 | 76 | ```c++ 77 | // Shutdown 78 | gGPUProfiler.Shutdown(); 79 | ``` 80 | 81 | #### Each frame 82 | ```c++ 83 | // Call at the start of each frame 84 | PROFILE_FRAME() 85 | ``` 86 | 87 | #### Each `ExecuteCommandLists` (not optional!) 88 | 89 | ```c++ 90 | ID3D12CommandQueue* queue = ...; 91 | Span cmdlists = ...; 92 | PROFILE_EXECUTE_COMMANDLISTS(queue, cmdlists); 93 | queue->ExecuteCommandLists(cmdlists.data(), cmdlists.size()); 94 | ``` 95 | 96 | #### Adding events 97 | 98 | `PROFILE_GPU_SCOPE(commandlist, name)` to add a GPU event. 99 | 100 | Specify the ID3D12GraphicsCommandList, and optionally a name. -------------------------------------------------------------------------------- /ImGuiProfiler.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Header Files 20 | 21 | 22 | Header Files 23 | 24 | 25 | Header Files 26 | 27 | 28 | Header Files 29 | 30 | 31 | Header Files 32 | 33 | 34 | Header Files 35 | 36 | 37 | Header Files 38 | 39 | 40 | Header Files 41 | 42 | 43 | Header Files 44 | 45 | 46 | Header Files 47 | 48 | 49 | Header Files 50 | 51 | 52 | 53 | 54 | Source Files 55 | 56 | 57 | Source Files 58 | 59 | 60 | Source Files 61 | 62 | 63 | Source Files 64 | 65 | 66 | Source Files 67 | 68 | 69 | Source Files 70 | 71 | 72 | Source Files 73 | 74 | 75 | Source Files 76 | 77 | 78 | Source Files 79 | 80 | 81 | Source Files 82 | 83 | 84 | -------------------------------------------------------------------------------- /ImGui/imgui_impl_win32.h: -------------------------------------------------------------------------------- 1 | // dear imgui: Platform Backend for Windows (standard windows API for 32-bits AND 64-bits applications) 2 | // This needs to be used along with a Renderer (e.g. DirectX11, OpenGL3, Vulkan..) 3 | 4 | // Implemented features: 5 | // [X] Platform: Clipboard support (for Win32 this is actually part of core dear imgui) 6 | // [X] Platform: Mouse support. Can discriminate Mouse/TouchScreen/Pen. 7 | // [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy VK_* values are obsolete since 1.87 and not supported since 1.91.5] 8 | // [X] Platform: Gamepad support. 9 | // [X] Platform: Mouse cursor shape and visibility (ImGuiBackendFlags_HasMouseCursors). Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. 10 | // [X] Platform: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'. 11 | 12 | // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. 13 | // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. 14 | // Learn about Dear ImGui: 15 | // - FAQ https://dearimgui.com/faq 16 | // - Getting Started https://dearimgui.com/getting-started 17 | // - Documentation https://dearimgui.com/docs (same as your local docs/ folder). 18 | // - Introduction, links and more at the top of imgui.cpp 19 | 20 | #pragma once 21 | #include "imgui.h" // IMGUI_IMPL_API 22 | #ifndef IMGUI_DISABLE 23 | 24 | // Follow "Getting Started" link and check examples/ folder to learn about using backends! 25 | IMGUI_IMPL_API bool ImGui_ImplWin32_Init(void* hwnd); 26 | IMGUI_IMPL_API bool ImGui_ImplWin32_InitForOpenGL(void* hwnd); 27 | IMGUI_IMPL_API void ImGui_ImplWin32_Shutdown(); 28 | IMGUI_IMPL_API void ImGui_ImplWin32_NewFrame(); 29 | 30 | // Win32 message handler your application need to call. 31 | // - Intentionally commented out in a '#if 0' block to avoid dragging dependencies on from this helper. 32 | // - You should COPY the line below into your .cpp code to forward declare the function and then you can call it. 33 | // - Call from your application's message handler. Keep calling your message handler unless this function returns TRUE. 34 | 35 | #if 0 36 | extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); 37 | #endif 38 | 39 | // DPI-related helpers (optional) 40 | // - Use to enable DPI awareness without having to create an application manifest. 41 | // - Your own app may already do this via a manifest or explicit calls. This is mostly useful for our examples/ apps. 42 | // - In theory we could call simple functions from Windows SDK such as SetProcessDPIAware(), SetProcessDpiAwareness(), etc. 43 | // but most of the functions provided by Microsoft require Windows 8.1/10+ SDK at compile time and Windows 8/10+ at runtime, 44 | // neither we want to require the user to have. So we dynamically select and load those functions to avoid dependencies. 45 | IMGUI_IMPL_API void ImGui_ImplWin32_EnableDpiAwareness(); 46 | IMGUI_IMPL_API float ImGui_ImplWin32_GetDpiScaleForHwnd(void* hwnd); // HWND hwnd 47 | IMGUI_IMPL_API float ImGui_ImplWin32_GetDpiScaleForMonitor(void* monitor); // HMONITOR monitor 48 | 49 | // Transparency related helpers (optional) [experimental] 50 | // - Use to enable alpha compositing transparency with the desktop. 51 | // - Use together with e.g. clearing your framebuffer with zero-alpha. 52 | IMGUI_IMPL_API void ImGui_ImplWin32_EnableAlphaCompositing(void* hwnd); // HWND hwnd 53 | 54 | #endif // #ifndef IMGUI_DISABLE 55 | -------------------------------------------------------------------------------- /ImGui/imgui_impl_dx12.h: -------------------------------------------------------------------------------- 1 | // dear imgui: Renderer Backend for DirectX12 2 | // This needs to be used along with a Platform Backend (e.g. Win32) 3 | 4 | // Implemented features: 5 | // [X] Renderer: User texture binding. Use 'D3D12_GPU_DESCRIPTOR_HANDLE' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! 6 | // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). 7 | // [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). 8 | // [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'. 9 | // [X] Renderer: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'. 10 | 11 | // The aim of imgui_impl_dx12.h/.cpp is to be usable in your engine without any modification. 12 | // IF YOU FEEL YOU NEED TO MAKE ANY CHANGE TO THIS CODE, please share them and your feedback at https://github.com/ocornut/imgui/ 13 | 14 | // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. 15 | // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. 16 | // Learn about Dear ImGui: 17 | // - FAQ https://dearimgui.com/faq 18 | // - Getting Started https://dearimgui.com/getting-started 19 | // - Documentation https://dearimgui.com/docs (same as your local docs/ folder). 20 | // - Introduction, links and more at the top of imgui.cpp 21 | 22 | #pragma once 23 | #include "imgui.h" // IMGUI_IMPL_API 24 | #ifndef IMGUI_DISABLE 25 | #include // DXGI_FORMAT 26 | #include // D3D12_CPU_DESCRIPTOR_HANDLE 27 | 28 | // Initialization data, for ImGui_ImplDX12_Init() 29 | struct ImGui_ImplDX12_InitInfo 30 | { 31 | ID3D12Device* Device; 32 | ID3D12CommandQueue* CommandQueue; // Command queue used for queuing texture uploads. 33 | int NumFramesInFlight; 34 | DXGI_FORMAT RTVFormat; // RenderTarget format. 35 | DXGI_FORMAT DSVFormat; // DepthStencilView format. 36 | void* UserData; 37 | 38 | // Allocating SRV descriptors for textures is up to the application, so we provide callbacks. 39 | // (current version of the backend will only allocate one descriptor, from 1.92 the backend will need to allocate more) 40 | ID3D12DescriptorHeap* SrvDescriptorHeap; 41 | void (*SrvDescriptorAllocFn)(ImGui_ImplDX12_InitInfo* info, D3D12_CPU_DESCRIPTOR_HANDLE* out_cpu_desc_handle, D3D12_GPU_DESCRIPTOR_HANDLE* out_gpu_desc_handle); 42 | void (*SrvDescriptorFreeFn)(ImGui_ImplDX12_InitInfo* info, D3D12_CPU_DESCRIPTOR_HANDLE cpu_desc_handle, D3D12_GPU_DESCRIPTOR_HANDLE gpu_desc_handle); 43 | #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS 44 | D3D12_CPU_DESCRIPTOR_HANDLE LegacySingleSrvCpuDescriptor; // To facilitate transition from single descriptor to allocator callback, you may use those. 45 | D3D12_GPU_DESCRIPTOR_HANDLE LegacySingleSrvGpuDescriptor; 46 | #endif 47 | 48 | ImGui_ImplDX12_InitInfo() { memset((void*)this, 0, sizeof(*this)); } 49 | }; 50 | 51 | // Follow "Getting Started" link and check examples/ folder to learn about using backends! 52 | IMGUI_IMPL_API bool ImGui_ImplDX12_Init(ImGui_ImplDX12_InitInfo* info); 53 | IMGUI_IMPL_API void ImGui_ImplDX12_Shutdown(); 54 | IMGUI_IMPL_API void ImGui_ImplDX12_NewFrame(); 55 | IMGUI_IMPL_API void ImGui_ImplDX12_RenderDrawData(ImDrawData* draw_data, ID3D12GraphicsCommandList* graphics_command_list); 56 | 57 | #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS 58 | // Legacy initialization API Obsoleted in 1.91.5 59 | // - font_srv_cpu_desc_handle and font_srv_gpu_desc_handle are handles to a single SRV descriptor to use for the internal font texture, they must be in 'srv_descriptor_heap' 60 | // - When we introduced the ImGui_ImplDX12_InitInfo struct we also added a 'ID3D12CommandQueue* CommandQueue' field. 61 | IMGUI_IMPL_API bool ImGui_ImplDX12_Init(ID3D12Device* device, int num_frames_in_flight, DXGI_FORMAT rtv_format, ID3D12DescriptorHeap* srv_descriptor_heap, D3D12_CPU_DESCRIPTOR_HANDLE font_srv_cpu_desc_handle, D3D12_GPU_DESCRIPTOR_HANDLE font_srv_gpu_desc_handle); 62 | #endif 63 | 64 | // Use if you want to reset your rendering device without losing Dear ImGui state. 65 | IMGUI_IMPL_API bool ImGui_ImplDX12_CreateDeviceObjects(); 66 | IMGUI_IMPL_API void ImGui_ImplDX12_InvalidateDeviceObjects(); 67 | 68 | // (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = NULL to handle this manually. 69 | IMGUI_IMPL_API void ImGui_ImplDX12_UpdateTexture(ImTextureData* tex); 70 | 71 | // [BETA] Selected render state data shared with callbacks. 72 | // This is temporarily stored in GetPlatformIO().Renderer_RenderState during the ImGui_ImplDX12_RenderDrawData() call. 73 | // (Please open an issue if you feel you need access to more data) 74 | struct ImGui_ImplDX12_RenderState 75 | { 76 | ID3D12Device* Device; 77 | ID3D12GraphicsCommandList* CommandList; 78 | }; 79 | 80 | #endif // #ifndef IMGUI_DISABLE 81 | -------------------------------------------------------------------------------- /ImGuiProfiler.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 16.0 48 | Win32Proj 49 | {c89a478e-d4d0-4a65-8d3e-98ae7bd4929c} 50 | ImGuiProfiler 51 | 10.0 52 | 53 | 54 | 55 | Application 56 | true 57 | v143 58 | Unicode 59 | 60 | 61 | Application 62 | false 63 | v143 64 | true 65 | Unicode 66 | 67 | 68 | Application 69 | true 70 | v143 71 | Unicode 72 | 73 | 74 | Application 75 | false 76 | v143 77 | true 78 | Unicode 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | Level3 101 | true 102 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 103 | true 104 | stdcpp20 105 | 106 | 107 | Console 108 | true 109 | dxgi.lib;d3d12.lib;%(AdditionalDependencies) 110 | 111 | 112 | 113 | 114 | Level3 115 | true 116 | true 117 | true 118 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 119 | true 120 | stdcpp20 121 | 122 | 123 | Console 124 | true 125 | true 126 | true 127 | dxgi.lib;d3d12.lib;%(AdditionalDependencies) 128 | 129 | 130 | 131 | 132 | Level3 133 | true 134 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 135 | true 136 | stdcpp20 137 | $(PROJECT_DIR)imgui;C:\Program Files\Superluminal\Performance\API\include\Superluminal;%(AdditionalIncludeDirectories) 138 | 139 | 140 | Console 141 | true 142 | dxgi.lib;d3d12.lib;%(AdditionalDependencies) 143 | 144 | 145 | 146 | 147 | Level3 148 | true 149 | true 150 | true 151 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 152 | true 153 | $(PROJECT_DIR)imgui;C:\Program Files\Superluminal\Performance\API\include\Superluminal;%(AdditionalIncludeDirectories) 154 | stdcpp20 155 | 156 | 157 | Console 158 | true 159 | true 160 | true 161 | dxgi.lib;d3d12.lib;%(AdditionalDependencies) 162 | 163 | 164 | 165 | 166 | 167 | -------------------------------------------------------------------------------- /ImGui/imconfig.h: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // DEAR IMGUI COMPILE-TIME OPTIONS 3 | // Runtime options (clipboard callbacks, enabling various features, etc.) can generally be set via the ImGuiIO structure. 4 | // You can use ImGui::SetAllocatorFunctions() before calling ImGui::CreateContext() to rewire memory allocation functions. 5 | //----------------------------------------------------------------------------- 6 | // A) You may edit imconfig.h (and not overwrite it when updating Dear ImGui, or maintain a patch/rebased branch with your modifications to it) 7 | // B) or '#define IMGUI_USER_CONFIG "my_imgui_config.h"' in your project and then add directives in your own file without touching this template. 8 | //----------------------------------------------------------------------------- 9 | // You need to make sure that configuration settings are defined consistently _everywhere_ Dear ImGui is used, which include the imgui*.cpp 10 | // files but also _any_ of your code that uses Dear ImGui. This is because some compile-time options have an affect on data structures. 11 | // Defining those options in imconfig.h will ensure every compilation unit gets to see the same data structure layouts. 12 | // Call IMGUI_CHECKVERSION() from your .cpp file to verify that the data structures your files are using are matching the ones imgui.cpp is using. 13 | //----------------------------------------------------------------------------- 14 | 15 | #pragma once 16 | 17 | //---- Define assertion handler. Defaults to calling assert(). 18 | // If your macro uses multiple statements, make sure is enclosed in a 'do { .. } while (0)' block so it can be used as a single statement. 19 | //#define IM_ASSERT(_EXPR) MyAssert(_EXPR) 20 | //#define IM_ASSERT(_EXPR) ((void)(_EXPR)) // Disable asserts 21 | 22 | //---- Define attributes of all API symbols declarations, e.g. for DLL under Windows 23 | // Using Dear ImGui via a shared library is not recommended, because of function call overhead and because we don't guarantee backward nor forward ABI compatibility. 24 | // DLL users: heaps and globals are not shared across DLL boundaries! You will need to call SetCurrentContext() + SetAllocatorFunctions() 25 | // for each static/DLL boundary you are calling from. Read "Context and Memory Allocators" section of imgui.cpp for more details. 26 | //#define IMGUI_API __declspec( dllexport ) 27 | //#define IMGUI_API __declspec( dllimport ) 28 | 29 | //---- Don't define obsolete functions/enums/behaviors. Consider enabling from time to time after updating to clean your code of obsolete function/names. 30 | //#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS 31 | //#define IMGUI_DISABLE_OBSOLETE_KEYIO // 1.87: disable legacy io.KeyMap[]+io.KeysDown[] in favor io.AddKeyEvent(). This will be folded into IMGUI_DISABLE_OBSOLETE_FUNCTIONS in a few versions. 32 | 33 | //---- Disable all of Dear ImGui or don't implement standard windows/tools. 34 | // It is very strongly recommended to NOT disable the demo windows and debug tool during development. They are extremely useful in day to day work. Please read comments in imgui_demo.cpp. 35 | //#define IMGUI_DISABLE // Disable everything: all headers and source files will be empty. 36 | //#define IMGUI_DISABLE_DEMO_WINDOWS // Disable demo windows: ShowDemoWindow()/ShowStyleEditor() will be empty. 37 | //#define IMGUI_DISABLE_DEBUG_TOOLS // Disable metrics/debugger and other debug tools: ShowMetricsWindow(), ShowDebugLogWindow() and ShowStackToolWindow() will be empty (this was called IMGUI_DISABLE_METRICS_WINDOW before 1.88). 38 | 39 | //---- Don't implement some functions to reduce linkage requirements. 40 | //#define IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS // [Win32] Don't implement default clipboard handler. Won't use and link with OpenClipboard/GetClipboardData/CloseClipboard etc. (user32.lib/.a, kernel32.lib/.a) 41 | //#define IMGUI_ENABLE_WIN32_DEFAULT_IME_FUNCTIONS // [Win32] [Default with Visual Studio] Implement default IME handler (require imm32.lib/.a, auto-link for Visual Studio, -limm32 on command-line for MinGW) 42 | //#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS // [Win32] [Default with non-Visual Studio compilers] Don't implement default IME handler (won't require imm32.lib/.a) 43 | //#define IMGUI_DISABLE_WIN32_FUNCTIONS // [Win32] Won't use and link with any Win32 function (clipboard, IME). 44 | //#define IMGUI_ENABLE_OSX_DEFAULT_CLIPBOARD_FUNCTIONS // [OSX] Implement default OSX clipboard handler (need to link with '-framework ApplicationServices', this is why this is not the default). 45 | //#define IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS // Don't implement ImFormatString/ImFormatStringV so you can implement them yourself (e.g. if you don't want to link with vsnprintf) 46 | //#define IMGUI_DISABLE_DEFAULT_MATH_FUNCTIONS // Don't implement ImFabs/ImSqrt/ImPow/ImFmod/ImCos/ImSin/ImAcos/ImAtan2 so you can implement them yourself. 47 | //#define IMGUI_DISABLE_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle at all (replace them with dummies) 48 | //#define IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle so you can implement them yourself if you don't want to link with fopen/fclose/fread/fwrite. This will also disable the LogToTTY() function. 49 | //#define IMGUI_DISABLE_DEFAULT_ALLOCATORS // Don't implement default allocators calling malloc()/free() to avoid linking with them. You will need to call ImGui::SetAllocatorFunctions(). 50 | //#define IMGUI_DISABLE_SSE // Disable use of SSE intrinsics even if available 51 | 52 | //---- Include imgui_user.h at the end of imgui.h as a convenience 53 | //#define IMGUI_INCLUDE_IMGUI_USER_H 54 | 55 | //---- Pack colors to BGRA8 instead of RGBA8 (to avoid converting from one to another) 56 | //#define IMGUI_USE_BGRA_PACKED_COLOR 57 | 58 | //---- Use 32-bit for ImWchar (default is 16-bit) to support unicode planes 1-16. (e.g. point beyond 0xFFFF like emoticons, dingbats, symbols, shapes, ancient languages, etc...) 59 | //#define IMGUI_USE_WCHAR32 60 | 61 | //---- Avoid multiple STB libraries implementations, or redefine path/filenames to prioritize another version 62 | // By default the embedded implementations are declared static and not available outside of Dear ImGui sources files. 63 | //#define IMGUI_STB_TRUETYPE_FILENAME "my_folder/stb_truetype.h" 64 | //#define IMGUI_STB_RECT_PACK_FILENAME "my_folder/stb_rect_pack.h" 65 | //#define IMGUI_STB_SPRINTF_FILENAME "my_folder/stb_sprintf.h" // only used if IMGUI_USE_STB_SPRINTF is defined. 66 | //#define IMGUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION 67 | //#define IMGUI_DISABLE_STB_RECT_PACK_IMPLEMENTATION 68 | //#define IMGUI_DISABLE_STB_SPRINTF_IMPLEMENTATION // only disabled if IMGUI_USE_STB_SPRINTF is defined. 69 | 70 | //---- Use stb_sprintf.h for a faster implementation of vsnprintf instead of the one from libc (unless IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS is defined) 71 | // Compatibility checks of arguments and formats done by clang and GCC will be disabled in order to support the extra formats provided by stb_sprintf.h. 72 | //#define IMGUI_USE_STB_SPRINTF 73 | 74 | //---- Use FreeType to build and rasterize the font atlas (instead of stb_truetype which is embedded by default in Dear ImGui) 75 | // Requires FreeType headers to be available in the include path. Requires program to be compiled with 'misc/freetype/imgui_freetype.cpp' (in this repository) + the FreeType library (not provided). 76 | // On Windows you may use vcpkg with 'vcpkg install freetype --triplet=x64-windows' + 'vcpkg integrate install'. 77 | //#define IMGUI_ENABLE_FREETYPE 78 | 79 | //---- Use FreeType+lunasvg library to render OpenType SVG fonts (SVGinOT) 80 | // Requires lunasvg headers to be available in the include path + program to be linked with the lunasvg library (not provided). 81 | // Only works in combination with IMGUI_ENABLE_FREETYPE. 82 | // (implementation is based on Freetype's rsvg-port.c which is licensed under CeCILL-C Free Software License Agreement) 83 | //#define IMGUI_ENABLE_FREETYPE_LUNASVG 84 | 85 | //---- Use stb_truetype to build and rasterize the font atlas (default) 86 | // The only purpose of this define is if you want force compilation of the stb_truetype backend ALONG with the FreeType backend. 87 | //#define IMGUI_ENABLE_STB_TRUETYPE 88 | 89 | //---- Define constructor and implicit cast operators to convert back<>forth between your math types and ImVec2/ImVec4. 90 | // This will be inlined as part of ImVec2 and ImVec4 class declarations. 91 | /* 92 | #define IM_VEC2_CLASS_EXTRA \ 93 | constexpr ImVec2(const MyVec2& f) : x(f.x), y(f.y) {} \ 94 | operator MyVec2() const { return MyVec2(x,y); } 95 | 96 | #define IM_VEC4_CLASS_EXTRA \ 97 | constexpr ImVec4(const MyVec4& f) : x(f.x), y(f.y), z(f.z), w(f.w) {} \ 98 | operator MyVec4() const { return MyVec4(x,y,z,w); } 99 | */ 100 | //---- ...Or use Dear ImGui's own very basic math operators. 101 | #define IMGUI_DEFINE_MATH_OPERATORS 102 | 103 | //---- Use 32-bit vertex indices (default is 16-bit) is one way to allow large meshes with more than 64K vertices. 104 | // Your renderer backend will need to support it (most example renderer backends support both 16/32-bit indices). 105 | // Another way to allow large meshes while keeping 16-bit indices is to handle ImDrawCmd::VtxOffset in your renderer. 106 | // Read about ImGuiBackendFlags_RendererHasVtxOffset for details. 107 | //#define ImDrawIdx unsigned int 108 | 109 | //---- Override ImDrawCallback signature (will need to modify renderer backends accordingly) 110 | //struct ImDrawList; 111 | //struct ImDrawCmd; 112 | //typedef void (*MyImDrawCallback)(const ImDrawList* draw_list, const ImDrawCmd* cmd, void* my_renderer_user_data); 113 | //#define ImDrawCallback MyImDrawCallback 114 | 115 | //---- Debug Tools: Macro to break in Debugger (we provide a default implementation of this in the codebase) 116 | // (use 'Metrics->Tools->Item Picker' to pick widgets with the mouse and break into them for easy debugging.) 117 | //#define IM_DEBUG_BREAK IM_ASSERT(0) 118 | //#define IM_DEBUG_BREAK __debugbreak() 119 | 120 | //---- Debug Tools: Enable slower asserts 121 | //#define IMGUI_DEBUG_PARANOID 122 | 123 | //---- Tip: You can add extra functions within the ImGui:: namespace from anywhere (e.g. your own sources/header files) 124 | /* 125 | namespace ImGui 126 | { 127 | void MyFunction(const char* name, MyMatrix44* mtx); 128 | } 129 | */ 130 | -------------------------------------------------------------------------------- /Profiler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef WITH_PROFILING 4 | #define WITH_PROFILING 1 5 | #endif 6 | 7 | #if !WITH_PROFILING 8 | 9 | #define PROFILE_REGISTER_THREAD(...) 10 | #define PROFILE_FRAME() 11 | #define PROFILE_EXECUTE_COMMANDLISTS(...) 12 | 13 | #define PROFILE_CPU_SCOPE(...) 14 | #define PROFILE_CPU_BEGIN(...) 15 | #define PROFILE_CPU_END() 16 | 17 | #define PROFILE_GPU_SCOPE(...) 18 | #define PROFILE_GPU_BEGIN(...) 19 | #define PROFILE_GPU_END() 20 | 21 | #define PROFILE_PRESENT(...) 22 | 23 | #else 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | void DrawProfilerHUD(); 35 | 36 | void HandleAssertMessage(const char* pMessage); 37 | 38 | template 39 | static void HandleAssertMessage(const char* pExpression, const char* pFileName, int line, const char* pFmt = nullptr, Args... args) 40 | { 41 | char message[1024]{}; 42 | if (pFmt) 43 | sprintf_s(message, pFmt, std::forward(args)...); 44 | char finalMessage[1024]{}; 45 | sprintf_s(finalMessage, "ASSERT FAILED:\nExpression: %s\nFile: %s:%d\n%s\n", pExpression, __FILE__, __LINE__, message); 46 | HandleAssertMessage(finalMessage); 47 | } 48 | 49 | #define gAssert(op, ...) \ 50 | do \ 51 | { \ 52 | bool result = (op); \ 53 | if (result == false) \ 54 | { \ 55 | HandleAssertMessage(#op, __FILE__, __LINE__, __VA_ARGS__); \ 56 | __debugbreak(); \ 57 | } \ 58 | } while (false) 59 | 60 | #define gVerify(op, expected, ...) \ 61 | do \ 62 | { \ 63 | auto r = (op); \ 64 | gAssert(r expected, __VA_ARGS__); \ 65 | } while (false) 66 | 67 | #define gVerifyHR(op) gVerify(op, == S_OK, "HRESULT Failed") 68 | #define gBoundCheck(val, minV, maxV) gAssert(val >= minV && val < maxV, "Value out of bounds") 69 | 70 | #define _STRINGIFY(a) #a 71 | #define STRINGIFY(a) _STRINGIFY(a) 72 | #define CONCAT_IMPL(x, y) x##y 73 | #define MACRO_CONCAT(x, y) CONCAT_IMPL(x, y) 74 | 75 | // Basic types 76 | using uint64 = uint64_t; 77 | using uint32 = uint32_t; 78 | using uint16 = uint16_t; 79 | using uint8 = uint8_t; 80 | template 81 | using Span = std::span; 82 | template 83 | using Array = std::vector; 84 | template 85 | using StaticArray = std::array; 86 | template 87 | using HashMap = std::unordered_map; 88 | using Mutex = std::mutex; 89 | 90 | struct URange 91 | { 92 | uint32 Begin = 0; 93 | uint32 End = 0; 94 | uint32 GetLength() const { return End - Begin; } 95 | }; 96 | 97 | // Forward declare D3D12 types 98 | struct ID3D12CommandList; 99 | struct ID3D12GraphicsCommandList; 100 | struct ID3D12CommandQueue; 101 | struct ID3D12Device; 102 | struct ID3D12Resource; 103 | struct ID3D12CommandAllocator; 104 | struct ID3D12QueryHeap; 105 | struct ID3D12Fence; 106 | struct IDXGISwapChain; 107 | using WinHandle = void*; 108 | 109 | 110 | /* 111 | General 112 | */ 113 | 114 | // Usage: 115 | // PROFILE_REGISTER_THREAD(const char* pName) 116 | // PROFILE_REGISTER_THREAD() 117 | #define PROFILE_REGISTER_THREAD(...) gProfiler.RegisterCurrentThread(__VA_ARGS__) 118 | 119 | /// Usage: 120 | // PROFILE_FRAME() 121 | #define PROFILE_FRAME() \ 122 | gProfiler.Tick(); \ 123 | gGPUProfiler.Tick() 124 | 125 | /// Usage: 126 | /// PROFILE_EXECUTE_COMMANDLISTS(ID3D12CommandQueue* pQueue, Span commandLists) 127 | #define PROFILE_EXECUTE_COMMANDLISTS(queue, cmdlists) gGPUProfiler.ExecuteCommandLists(queue, cmdlists) 128 | 129 | /* 130 | CPU Profiling 131 | */ 132 | 133 | // Usage: 134 | // PROFILE_CPU_SCOPE(const char* pName) 135 | // PROFILE_CPU_SCOPE() 136 | #define PROFILE_CPU_SCOPE(...) CPUProfileScope MACRO_CONCAT(profiler, __COUNTER__)(__FUNCTION__, __FILE__, __LINE__, __VA_ARGS__) 137 | 138 | // Usage: 139 | // PROFILE_CPU_BEGIN(const char* pName) 140 | // PROFILE_CPU_BEGIN() 141 | #define PROFILE_CPU_BEGIN(...) gProfiler.BeginEvent(__VA_ARGS__) 142 | // Usage: 143 | // PROFILE_CPU_END() 144 | #define PROFILE_CPU_END() gProfiler.EndEvent() 145 | 146 | 147 | #define PROFILE_PRESENT(swapchain) gProfiler.Present(swapchain) 148 | 149 | 150 | /* 151 | GPU Profiling 152 | */ 153 | 154 | // Usage: 155 | // PROFILE_GPU_SCOPE(ID3D12GraphicsCommandList* pCommandList, const char* pName) 156 | // PROFILE_GPU_SCOPE(ID3D12GraphicsCommandList* pCommandList) 157 | #define PROFILE_GPU_SCOPE(cmdlist, ...) GPUProfileScope MACRO_CONCAT(gpu_profiler, __COUNTER__)(__FUNCTION__, __FILE__, __LINE__, cmdlist, __VA_ARGS__) 158 | 159 | // Usage: 160 | // PROFILE_GPU_BEGIN(const char* pName, ID3D12GraphicsCommandList* pCommandList) 161 | #define PROFILE_GPU_BEGIN(cmdlist, name) gGPUProfiler.BeginEvent(cmdlist, name, __FILE__, __LINE__) 162 | 163 | // Usage: 164 | // PROFILE_GPU_END(ID3D12GraphicsCommandList* pCommandList) 165 | #define PROFILE_GPU_END(cmdlist) gGPUProfiler.EndEvent(cmdlist) 166 | 167 | 168 | 169 | template 170 | struct FixedArray 171 | { 172 | public: 173 | T& Pop() 174 | { 175 | gAssert(Length > 0); 176 | --Length; 177 | return Data[Length]; 178 | } 179 | 180 | T& Push() 181 | { 182 | Length++; 183 | gAssert(Length < N); 184 | return Data[Length - 1]; 185 | } 186 | 187 | T& Top() 188 | { 189 | gAssert(Length > 0); 190 | return Data[Length - 1]; 191 | } 192 | 193 | uint32 GetSize() const { return Length; } 194 | 195 | private: 196 | uint32 Length = 0; 197 | StaticArray Data{}; 198 | }; 199 | 200 | 201 | 202 | // Single event 203 | struct ProfilerEvent 204 | { 205 | const char* pName = nullptr; ///< Name of event 206 | const char* pFilePath = nullptr; ///< File path of location where event was started 207 | uint32 Color : 24 = 0xFFFFFF; ///< Color 208 | uint32 Depth : 8 = 0; ///< Stack depth of event 209 | uint32 LineNumber : 18 = 0; ///< Line number of file where event was started 210 | uint32 ThreadIndex : 10 = 0; ///< Index of thread this event is started on 211 | uint32 QueueIndex : 4 = 0; ///< GPU Queue Index (GPU-specific) 212 | uint64 TicksBegin = 0; ///< Begin CPU ticks 213 | uint64 TicksEnd = 0; ///< End CPU ticks 214 | 215 | bool IsValid() const { return TicksBegin != 0 && TicksEnd != 0; } 216 | uint32 GetColor() const { return Color | (0xFF << 24); } 217 | }; 218 | static_assert(std::has_unique_object_representations_v); 219 | 220 | 221 | // Data for a single frame of profiling events 222 | using ProfilerEventData = Array; 223 | 224 | 225 | //----------------------------------------------------------------------------- 226 | // [SECTION] GPU Profiler 227 | //----------------------------------------------------------------------------- 228 | 229 | extern class GPUProfiler gGPUProfiler; 230 | 231 | struct GPUProfilerCallbacks 232 | { 233 | using EventBeginFn = void (*)(const char* /*pName*/, ID3D12GraphicsCommandList* /*CommandList*/, void* /*pUserData*/); 234 | using EventEndFn = void (*)(ID3D12GraphicsCommandList* /*CommandList*/, void* /*pUserData*/); 235 | 236 | EventBeginFn OnEventBegin = nullptr; 237 | EventEndFn OnEventEnd = nullptr; 238 | void* pUserData = nullptr; 239 | }; 240 | 241 | class GPUProfiler 242 | { 243 | public: 244 | void Initialize(ID3D12Device* pDevice, Span queues, uint32 frameLatency); 245 | 246 | void Shutdown(); 247 | 248 | // Allocate and record a GPU event on the commandlist 249 | void BeginEvent(ID3D12GraphicsCommandList* pCmd, const char* pName, uint32 color, const char* pFilePath, uint32 lineNumber); 250 | 251 | // Allocate and record a GPU event on the commandlist 252 | void BeginEvent(ID3D12GraphicsCommandList* pCmd, const char* pName, uint32 color = 0) { BeginEvent(pCmd, pName, color, "", 0); } 253 | 254 | // Record a GPU event end on the commandlist 255 | void EndEvent(ID3D12GraphicsCommandList* pCmd); 256 | 257 | // Resolve the last frame and advance to the next frame. 258 | // Call at the START of the frame. 259 | void Tick(); 260 | 261 | // Notify profiler that these commandlists are executed on a particular queue 262 | void ExecuteCommandLists(const ID3D12CommandQueue* pQueue, Span commandLists); 263 | 264 | void SetPaused(bool paused) { m_PauseQueued = paused; } 265 | 266 | // Data of a single GPU queue. Allows converting GPU timestamps to CPU timestamps 267 | struct QueueInfo 268 | { 269 | char Name[128]{}; ///< Name of the queue 270 | ID3D12CommandQueue* pQueue = nullptr; ///< The D3D queue object 271 | uint64 GPUCalibrationTicks = 0; ///< The number of GPU ticks when the calibration was done 272 | uint64 CPUCalibrationTicks = 0; ///< The number of CPU ticks when the calibration was done 273 | uint64 GPUFrequency = 0; ///< The GPU tick frequency 274 | uint32 Index = 0; ///< Index of queue 275 | uint32 QueryHeapIndex = 0; ///< Query Heap index (Copy vs. Other queues) 276 | uint32 TrackIndex = 0; ///< The index in the tracks of the profiler 277 | }; 278 | 279 | Span GetQueues() const { return m_Queues; } 280 | 281 | void SetEventCallback(const GPUProfilerCallbacks& inCallbacks) { m_EventCallback = inCallbacks; } 282 | 283 | private: 284 | struct QueryHeap 285 | { 286 | public: 287 | void Initialize(ID3D12Device* pDevice, ID3D12CommandQueue* pResolveQueue, uint32 maxNumQueries, uint32 frameLatency); 288 | void Shutdown(); 289 | 290 | uint32 RecordQuery(ID3D12GraphicsCommandList* pCmd); 291 | uint32 Resolve(uint32 frameIndex); 292 | void Reset(uint32 frameIndex); 293 | bool IsFrameComplete(uint64 frameIndex); 294 | uint32 GetQueryCapacity() const { return m_MaxNumQueries; } 295 | 296 | Span GetQueryData(uint32 frameIndex) const 297 | { 298 | if (!IsInitialized()) 299 | return {}; 300 | 301 | uint32 frameBit = frameIndex % m_FrameLatency; 302 | return m_ReadbackData.subspan(frameBit * m_MaxNumQueries, m_MaxNumQueries); 303 | } 304 | 305 | bool IsInitialized() const { return m_pQueryHeap != nullptr; } 306 | ID3D12QueryHeap* GetHeap() const { return m_pQueryHeap; } 307 | 308 | private: 309 | Array m_CommandAllocators; ///< CommandAlloctors to resolve queries. 1 per frame 310 | uint32 m_MaxNumQueries = 0; ///< Max number of event queries 311 | uint32 m_FrameLatency = 0; ///< Number of GPU frame latency 312 | std::atomic m_QueryIndex = 0; ///< Current index of queries 313 | ID3D12GraphicsCommandList* m_pCommandList = nullptr; ///< CommandList to resolve queries 314 | ID3D12QueryHeap* m_pQueryHeap = nullptr; ///< Heap containing MaxNumQueries * FrameLatency queries 315 | ID3D12Resource* m_pReadbackResource = nullptr; ///< Readback resource storing resolved query dara 316 | Span m_ReadbackData = {}; ///< Mapped readback resource pointer 317 | ID3D12CommandQueue* m_pResolveQueue = nullptr; ///< Queue to resolve queries on 318 | ID3D12Fence* m_pResolveFence = nullptr; ///< Fence for tracking when queries are finished resolving 319 | uint64 m_LastCompletedFence = 0; ///< Last finish fence value 320 | }; 321 | 322 | // Data for a single frame of GPU queries. One for each frame latency 323 | struct QueryData 324 | { 325 | constexpr static uint32 cQueryIndexBits = 16u; 326 | constexpr static uint32 cMaxNumQueries = (1u << cQueryIndexBits) - 1u; 327 | 328 | struct QueryPair 329 | { 330 | uint32 QueryIndexBegin : cQueryIndexBits = 0xFFFF; 331 | uint32 QueryIndexEnd : cQueryIndexBits = 0xFFFF; 332 | 333 | bool IsValid() const { return QueryIndexBegin != 0xFFFF && QueryIndexEnd != 0xFFFF; } 334 | }; 335 | static_assert(sizeof(QueryPair) == sizeof(uint32)); 336 | 337 | struct Query 338 | { 339 | static constexpr uint32 EndEventFlag = 0xFFFE; 340 | static constexpr uint32 InvalidEventFlag = 0xFFFF; 341 | 342 | uint32 QueryIndex : QueryData::cQueryIndexBits = InvalidEventFlag; ///< The index into the query heap 343 | uint32 EventIndex : 16 = InvalidEventFlag; ///< The ProfilerEvent index. 0xFFFE is it is an "EndEvent" 344 | }; 345 | static_assert(sizeof(Query) == sizeof(uint32)); 346 | static_assert(std::has_unique_object_representations_v); 347 | 348 | Array Pairs; 349 | ProfilerEventData Events; 350 | uint32 NumEvents = 0; 351 | }; 352 | QueryData& GetQueryData(uint32 frameIndex) { return m_QueryData[frameIndex % m_FrameLatency]; } 353 | QueryData& GetQueryData() { return GetQueryData(m_FrameIndex); } 354 | 355 | // Contains the state for a tracked commandlist 356 | struct CommandListState 357 | { 358 | CommandListState(GPUProfiler* profiler, ID3D12CommandList* pCmd); 359 | ~CommandListState(); 360 | 361 | GPUProfiler* pProfiler = nullptr; 362 | ID3D12CommandList* pCommandList = nullptr; 363 | uint32 DestructionEventID = 0; 364 | Array Queries; 365 | }; 366 | 367 | CommandListState* GetState(ID3D12CommandList* pCmd, bool createIfNotFound); 368 | 369 | uint64 ConvertToCPUTicks(const QueueInfo& queue, uint64 gpuTicks) const 370 | { 371 | gAssert(gpuTicks >= queue.GPUCalibrationTicks); 372 | return queue.CPUCalibrationTicks + (gpuTicks - queue.GPUCalibrationTicks) * m_CPUTickFrequency / queue.GPUFrequency; 373 | } 374 | 375 | QueryHeap& GetHeap(bool isCopyQueue) { return isCopyQueue ? m_QueryHeaps[1] : m_QueryHeaps[0]; } 376 | 377 | bool m_IsInitialized = false; 378 | bool m_IsPaused = false; 379 | bool m_PauseQueued = false; 380 | 381 | Array m_QueryData; ///< Data containing all intermediate query event data. 1 per frame latency 382 | std::atomic m_EventIndex = 0; ///< Current event index 383 | uint32 m_FrameLatency = 0; ///< Max number of in-flight GPU frames 384 | uint32 m_FrameToReadback = 0; ///< Next frame to readback from 385 | uint32 m_FrameIndex = 0; ///< Current frame index 386 | StaticArray m_QueryHeaps; ///< GPU Query Heaps 387 | uint64 m_CPUTickFrequency = 0; ///< Tick frequency of CPU for QPC 388 | Mutex m_QueryRangeLock; 389 | 390 | WinHandle m_CommandListMapLock{}; ///< Lock for accessing commandlist state hashmap 391 | HashMap m_CommandListMap; ///< Maps commandlist to index 392 | 393 | static constexpr uint32 MAX_EVENT_DEPTH = 32; 394 | using ActiveEventStack = FixedArray; 395 | Array m_QueueEventStack; ///< Stack of active events for each command queue 396 | Array m_Queues; ///< All registered queues 397 | HashMap m_QueueIndexMap; ///< Map from command queue to index 398 | GPUProfilerCallbacks m_EventCallback; 399 | }; 400 | 401 | // Helper RAII-style structure to push and pop a GPU sample event 402 | struct GPUProfileScope 403 | { 404 | GPUProfileScope(const char* pFunction, const char* pFilePath, uint32 lineNumber, ID3D12GraphicsCommandList* pCmd, const char* pName) 405 | : pCmd(pCmd) 406 | { 407 | gGPUProfiler.BeginEvent(pCmd, pName, 0, pFilePath, lineNumber); 408 | } 409 | 410 | GPUProfileScope(const char* pFunction, const char* pFilePath, uint32 lineNumber, ID3D12GraphicsCommandList* pCmd) 411 | : pCmd(pCmd) 412 | { 413 | gGPUProfiler.BeginEvent(pCmd, pFunction, 0, pFilePath, lineNumber); 414 | } 415 | 416 | ~GPUProfileScope() 417 | { 418 | gGPUProfiler.EndEvent(pCmd); 419 | } 420 | 421 | GPUProfileScope(const GPUProfileScope&) = delete; 422 | GPUProfileScope& operator=(const GPUProfileScope&) = delete; 423 | 424 | private: 425 | ID3D12GraphicsCommandList* pCmd; 426 | }; 427 | 428 | 429 | 430 | //----------------------------------------------------------------------------- 431 | // [SECTION] CPU Profiler 432 | //----------------------------------------------------------------------------- 433 | 434 | // Global CPU Profiler 435 | extern class Profiler gProfiler; 436 | 437 | struct CPUProfilerCallbacks 438 | { 439 | using EventBeginFn = void (*)(const char* /*pName*/, void* /*pUserData*/); 440 | using EventEndFn = void (*)(void* /*pUserData*/); 441 | 442 | EventBeginFn OnEventBegin = nullptr; 443 | EventEndFn OnEventEnd = nullptr; 444 | void* pUserData = nullptr; 445 | }; 446 | 447 | // CPU Profiler 448 | // Also responsible for updating GPU profiler 449 | // Also responsible for drawing HUD 450 | class Profiler 451 | { 452 | public: 453 | void Initialize(uint32 historySize); 454 | 455 | void Shutdown(); 456 | 457 | // Start and push an event on the current thread 458 | void BeginEvent(const char* pName, uint32 color, const char* pFilePath, uint32 lineNumber); 459 | 460 | // Start and push an event on the current thread 461 | void BeginEvent(const char* pName, uint32 color = 0) { BeginEvent(pName, color, "", 0); } 462 | 463 | // End and pop the last pushed event on the current thread 464 | void EndEvent(); 465 | 466 | void AddEvent(uint32 trackIndex, const ProfilerEvent& event, uint32 frameIndex); 467 | 468 | void Present(IDXGISwapChain* pSwapChain); 469 | 470 | bool IsInitialized() const { return m_IsInitialized; } 471 | 472 | // Resolve the last frame and advance to the next frame. 473 | // Call at the START of the frame. 474 | void Tick(); 475 | 476 | // Initialize a thread with an optional name 477 | int RegisterCurrentThread(const char* pName = nullptr); 478 | 479 | struct EventTrack 480 | { 481 | enum class EType 482 | { 483 | CPU, 484 | GPU, 485 | Present, 486 | }; 487 | 488 | ProfilerEventData& GetFrameData(int frameIndex) { return Events[frameIndex % Events.size()]; } 489 | const ProfilerEventData& GetFrameData(int frameIndex) const { return Events[frameIndex % Events.size()]; } 490 | 491 | char Name[128]{}; ///< Name 492 | uint32 ID = 0; ///< ThreadID (or generic identifier) 493 | uint32 Index = 0; ///< Index in Tracks Array 494 | EType Type = EType::CPU; 495 | 496 | static constexpr int MAX_STACK_DEPTH = 32; 497 | FixedArray EventStack; 498 | Array Events; 499 | }; 500 | 501 | // Register a new track 502 | int RegisterTrack(const char* pName, EventTrack::EType type, uint32 id); 503 | 504 | URange GetFrameRange() const 505 | { 506 | uint32 begin = m_FrameIndex - std::min(m_FrameIndex, m_HistorySize) + 1; 507 | uint32 end = m_FrameIndex; 508 | return { .Begin = begin, .End = end }; 509 | } 510 | 511 | uint64 GetFirstFrameAnchorTicks() const { return m_BeginFrameTicks[(m_FrameIndex + m_BeginFrameTicks.size() + 1) % m_BeginFrameTicks.size()]; } 512 | 513 | Span GetTracks() const { return m_Tracks; } 514 | 515 | void SetEventCallback(const CPUProfilerCallbacks& inCallbacks) { m_EventCallback = inCallbacks; } 516 | void SetPaused(bool paused) { m_QueuedPaused = paused; } 517 | bool IsPaused() const { return m_Paused; } 518 | 519 | private: 520 | struct PresentEntry 521 | { 522 | constexpr static uint64 sQPCDroppedFrame = ~0ull; ///< QPC when frame is dropped (due to tearing) 523 | constexpr static uint64 sQPCMissedFrame = ~0ull - 1; ///< QPC when frame was missed on CPU (rare) 524 | 525 | uint64 PresentQPC = sQPCDroppedFrame; 526 | uint64 DisplayQPC = sQPCDroppedFrame; 527 | uint32 PresentID = ~0u; 528 | uint32 FrameIndex = 0; 529 | }; 530 | 531 | int& GetCurrentThreadTrackIndex() 532 | { 533 | static thread_local int index = -1; 534 | return index; 535 | } 536 | 537 | EventTrack& GetCurrentThreadTrack() 538 | { 539 | int index = GetCurrentThreadTrackIndex(); 540 | if (index == -1) 541 | index = RegisterCurrentThread(); 542 | return m_Tracks[index]; 543 | } 544 | 545 | PresentEntry* GetPresentEntry(uint32 presentID, bool isNewEntry) 546 | { 547 | PresentEntry& entry = m_PresentQueue[presentID % m_PresentQueue.size()]; 548 | if (entry.PresentID != presentID && !isNewEntry) 549 | return nullptr; 550 | return &entry; 551 | } 552 | 553 | IDXGISwapChain* m_pPresentSwapChain = nullptr; ///< The swapchain that was last presented with 554 | int m_PresentTrackIndex = -1; ///< The track index for the present timeline 555 | StaticArray m_PresentQueue = {}; ///< Queue to register information whenever Present is called 556 | uint32 m_LastQueuedPresentID = 0; ///< PresentID after the last Present was called 557 | uint32 m_LastQueriedPresentID = 0; ///< The last PresentID which GetFrameStatistics has provided data for 558 | uint32 m_LastProcessedValidPresentID = 0; ///< The last PresentID which GetFrameStatistics has provided data for 559 | uint32 m_LastSyncRefreshCount = 0; ///< The SyncRefreshCount of the last queried frame statistics 560 | uint32 m_LastProcessedPresentID = 0; ///< The last PresentID which was processed to an even 561 | uint32 m_MsToTicks = 0; ///< The amount of ticks in 1 ms 562 | 563 | CPUProfilerCallbacks m_EventCallback; 564 | Mutex m_ThreadDataLock; ///< Mutex for accessing thread data 565 | Array m_Tracks; 566 | Array m_BeginFrameTicks; 567 | uint32 m_HistorySize = 0; ///< History size 568 | uint32 m_FrameIndex = 0; ///< The current frame index 569 | bool m_Paused = false; ///< The current pause state 570 | bool m_QueuedPaused = false; ///< The queued pause state 571 | bool m_IsInitialized = false; 572 | }; 573 | 574 | // Helper RAII-style structure to push and pop a CPU sample region 575 | struct CPUProfileScope 576 | { 577 | CPUProfileScope(const char* pFunctionName, const char* pFilePath, uint32 lineNumber, const char* pName, uint32 color = 0) 578 | { 579 | gProfiler.BeginEvent(pName, color, pFilePath, lineNumber); 580 | } 581 | 582 | CPUProfileScope(const char* pFunctionName, const char* pFilePath, uint32 lineNumber, uint32 color = 0) 583 | { 584 | gProfiler.BeginEvent(pFunctionName, color, pFilePath, lineNumber); 585 | } 586 | 587 | ~CPUProfileScope() 588 | { 589 | gProfiler.EndEvent(); 590 | } 591 | 592 | CPUProfileScope(const CPUProfileScope&) = delete; 593 | CPUProfileScope& operator=(const CPUProfileScope&) = delete; 594 | }; 595 | 596 | #endif -------------------------------------------------------------------------------- /ImGui/imstb_rectpack.h: -------------------------------------------------------------------------------- 1 | // [DEAR IMGUI] 2 | // This is a slightly modified version of stb_rect_pack.h 1.01. 3 | // Grep for [DEAR IMGUI] to find the changes. 4 | // 5 | // stb_rect_pack.h - v1.01 - public domain - rectangle packing 6 | // Sean Barrett 2014 7 | // 8 | // Useful for e.g. packing rectangular textures into an atlas. 9 | // Does not do rotation. 10 | // 11 | // Before #including, 12 | // 13 | // #define STB_RECT_PACK_IMPLEMENTATION 14 | // 15 | // in the file that you want to have the implementation. 16 | // 17 | // Not necessarily the awesomest packing method, but better than 18 | // the totally naive one in stb_truetype (which is primarily what 19 | // this is meant to replace). 20 | // 21 | // Has only had a few tests run, may have issues. 22 | // 23 | // More docs to come. 24 | // 25 | // No memory allocations; uses qsort() and assert() from stdlib. 26 | // Can override those by defining STBRP_SORT and STBRP_ASSERT. 27 | // 28 | // This library currently uses the Skyline Bottom-Left algorithm. 29 | // 30 | // Please note: better rectangle packers are welcome! Please 31 | // implement them to the same API, but with a different init 32 | // function. 33 | // 34 | // Credits 35 | // 36 | // Library 37 | // Sean Barrett 38 | // Minor features 39 | // Martins Mozeiko 40 | // github:IntellectualKitty 41 | // 42 | // Bugfixes / warning fixes 43 | // Jeremy Jaussaud 44 | // Fabian Giesen 45 | // 46 | // Version history: 47 | // 48 | // 1.01 (2021-07-11) always use large rect mode, expose STBRP__MAXVAL in public section 49 | // 1.00 (2019-02-25) avoid small space waste; gracefully fail too-wide rectangles 50 | // 0.99 (2019-02-07) warning fixes 51 | // 0.11 (2017-03-03) return packing success/fail result 52 | // 0.10 (2016-10-25) remove cast-away-const to avoid warnings 53 | // 0.09 (2016-08-27) fix compiler warnings 54 | // 0.08 (2015-09-13) really fix bug with empty rects (w=0 or h=0) 55 | // 0.07 (2015-09-13) fix bug with empty rects (w=0 or h=0) 56 | // 0.06 (2015-04-15) added STBRP_SORT to allow replacing qsort 57 | // 0.05: added STBRP_ASSERT to allow replacing assert 58 | // 0.04: fixed minor bug in STBRP_LARGE_RECTS support 59 | // 0.01: initial release 60 | // 61 | // LICENSE 62 | // 63 | // See end of file for license information. 64 | 65 | ////////////////////////////////////////////////////////////////////////////// 66 | // 67 | // INCLUDE SECTION 68 | // 69 | 70 | #ifndef STB_INCLUDE_STB_RECT_PACK_H 71 | #define STB_INCLUDE_STB_RECT_PACK_H 72 | 73 | #define STB_RECT_PACK_VERSION 1 74 | 75 | #ifdef STBRP_STATIC 76 | #define STBRP_DEF static 77 | #else 78 | #define STBRP_DEF extern 79 | #endif 80 | 81 | #ifdef __cplusplus 82 | extern "C" { 83 | #endif 84 | 85 | typedef struct stbrp_context stbrp_context; 86 | typedef struct stbrp_node stbrp_node; 87 | typedef struct stbrp_rect stbrp_rect; 88 | 89 | typedef int stbrp_coord; 90 | 91 | #define STBRP__MAXVAL 0x7fffffff 92 | // Mostly for internal use, but this is the maximum supported coordinate value. 93 | 94 | STBRP_DEF int stbrp_pack_rects (stbrp_context *context, stbrp_rect *rects, int num_rects); 95 | // Assign packed locations to rectangles. The rectangles are of type 96 | // 'stbrp_rect' defined below, stored in the array 'rects', and there 97 | // are 'num_rects' many of them. 98 | // 99 | // Rectangles which are successfully packed have the 'was_packed' flag 100 | // set to a non-zero value and 'x' and 'y' store the minimum location 101 | // on each axis (i.e. bottom-left in cartesian coordinates, top-left 102 | // if you imagine y increasing downwards). Rectangles which do not fit 103 | // have the 'was_packed' flag set to 0. 104 | // 105 | // You should not try to access the 'rects' array from another thread 106 | // while this function is running, as the function temporarily reorders 107 | // the array while it executes. 108 | // 109 | // To pack into another rectangle, you need to call stbrp_init_target 110 | // again. To continue packing into the same rectangle, you can call 111 | // this function again. Calling this multiple times with multiple rect 112 | // arrays will probably produce worse packing results than calling it 113 | // a single time with the full rectangle array, but the option is 114 | // available. 115 | // 116 | // The function returns 1 if all of the rectangles were successfully 117 | // packed and 0 otherwise. 118 | 119 | struct stbrp_rect 120 | { 121 | // reserved for your use: 122 | int id; 123 | 124 | // input: 125 | stbrp_coord w, h; 126 | 127 | // output: 128 | stbrp_coord x, y; 129 | int was_packed; // non-zero if valid packing 130 | 131 | }; // 16 bytes, nominally 132 | 133 | 134 | STBRP_DEF void stbrp_init_target (stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes); 135 | // Initialize a rectangle packer to: 136 | // pack a rectangle that is 'width' by 'height' in dimensions 137 | // using temporary storage provided by the array 'nodes', which is 'num_nodes' long 138 | // 139 | // You must call this function every time you start packing into a new target. 140 | // 141 | // There is no "shutdown" function. The 'nodes' memory must stay valid for 142 | // the following stbrp_pack_rects() call (or calls), but can be freed after 143 | // the call (or calls) finish. 144 | // 145 | // Note: to guarantee best results, either: 146 | // 1. make sure 'num_nodes' >= 'width' 147 | // or 2. call stbrp_allow_out_of_mem() defined below with 'allow_out_of_mem = 1' 148 | // 149 | // If you don't do either of the above things, widths will be quantized to multiples 150 | // of small integers to guarantee the algorithm doesn't run out of temporary storage. 151 | // 152 | // If you do #2, then the non-quantized algorithm will be used, but the algorithm 153 | // may run out of temporary storage and be unable to pack some rectangles. 154 | 155 | STBRP_DEF void stbrp_setup_allow_out_of_mem (stbrp_context *context, int allow_out_of_mem); 156 | // Optionally call this function after init but before doing any packing to 157 | // change the handling of the out-of-temp-memory scenario, described above. 158 | // If you call init again, this will be reset to the default (false). 159 | 160 | 161 | STBRP_DEF void stbrp_setup_heuristic (stbrp_context *context, int heuristic); 162 | // Optionally select which packing heuristic the library should use. Different 163 | // heuristics will produce better/worse results for different data sets. 164 | // If you call init again, this will be reset to the default. 165 | 166 | enum 167 | { 168 | STBRP_HEURISTIC_Skyline_default=0, 169 | STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default, 170 | STBRP_HEURISTIC_Skyline_BF_sortHeight 171 | }; 172 | 173 | 174 | ////////////////////////////////////////////////////////////////////////////// 175 | // 176 | // the details of the following structures don't matter to you, but they must 177 | // be visible so you can handle the memory allocations for them 178 | 179 | struct stbrp_node 180 | { 181 | stbrp_coord x,y; 182 | stbrp_node *next; 183 | }; 184 | 185 | struct stbrp_context 186 | { 187 | int width; 188 | int height; 189 | int align; 190 | int init_mode; 191 | int heuristic; 192 | int num_nodes; 193 | stbrp_node *active_head; 194 | stbrp_node *free_head; 195 | stbrp_node extra[2]; // we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2' 196 | }; 197 | 198 | #ifdef __cplusplus 199 | } 200 | #endif 201 | 202 | #endif 203 | 204 | ////////////////////////////////////////////////////////////////////////////// 205 | // 206 | // IMPLEMENTATION SECTION 207 | // 208 | 209 | #ifdef STB_RECT_PACK_IMPLEMENTATION 210 | #ifndef STBRP_SORT 211 | #include 212 | #define STBRP_SORT qsort 213 | #endif 214 | 215 | #ifndef STBRP_ASSERT 216 | #include 217 | #define STBRP_ASSERT assert 218 | #endif 219 | 220 | #ifdef _MSC_VER 221 | #define STBRP__NOTUSED(v) (void)(v) 222 | #define STBRP__CDECL __cdecl 223 | #else 224 | #define STBRP__NOTUSED(v) (void)sizeof(v) 225 | #define STBRP__CDECL 226 | #endif 227 | 228 | enum 229 | { 230 | STBRP__INIT_skyline = 1 231 | }; 232 | 233 | STBRP_DEF void stbrp_setup_heuristic(stbrp_context *context, int heuristic) 234 | { 235 | switch (context->init_mode) { 236 | case STBRP__INIT_skyline: 237 | STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight); 238 | context->heuristic = heuristic; 239 | break; 240 | default: 241 | STBRP_ASSERT(0); 242 | } 243 | } 244 | 245 | STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context *context, int allow_out_of_mem) 246 | { 247 | if (allow_out_of_mem) 248 | // if it's ok to run out of memory, then don't bother aligning them; 249 | // this gives better packing, but may fail due to OOM (even though 250 | // the rectangles easily fit). @TODO a smarter approach would be to only 251 | // quantize once we've hit OOM, then we could get rid of this parameter. 252 | context->align = 1; 253 | else { 254 | // if it's not ok to run out of memory, then quantize the widths 255 | // so that num_nodes is always enough nodes. 256 | // 257 | // I.e. num_nodes * align >= width 258 | // align >= width / num_nodes 259 | // align = ceil(width/num_nodes) 260 | 261 | context->align = (context->width + context->num_nodes-1) / context->num_nodes; 262 | } 263 | } 264 | 265 | STBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes) 266 | { 267 | int i; 268 | 269 | for (i=0; i < num_nodes-1; ++i) 270 | nodes[i].next = &nodes[i+1]; 271 | nodes[i].next = NULL; 272 | context->init_mode = STBRP__INIT_skyline; 273 | context->heuristic = STBRP_HEURISTIC_Skyline_default; 274 | context->free_head = &nodes[0]; 275 | context->active_head = &context->extra[0]; 276 | context->width = width; 277 | context->height = height; 278 | context->num_nodes = num_nodes; 279 | stbrp_setup_allow_out_of_mem(context, 0); 280 | 281 | // node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly) 282 | context->extra[0].x = 0; 283 | context->extra[0].y = 0; 284 | context->extra[0].next = &context->extra[1]; 285 | context->extra[1].x = (stbrp_coord) width; 286 | context->extra[1].y = (1<<30); 287 | context->extra[1].next = NULL; 288 | } 289 | 290 | // find minimum y position if it starts at x1 291 | static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0, int width, int *pwaste) 292 | { 293 | stbrp_node *node = first; 294 | int x1 = x0 + width; 295 | int min_y, visited_width, waste_area; 296 | 297 | STBRP__NOTUSED(c); 298 | 299 | STBRP_ASSERT(first->x <= x0); 300 | 301 | #if 0 302 | // skip in case we're past the node 303 | while (node->next->x <= x0) 304 | ++node; 305 | #else 306 | STBRP_ASSERT(node->next->x > x0); // we ended up handling this in the caller for efficiency 307 | #endif 308 | 309 | STBRP_ASSERT(node->x <= x0); 310 | 311 | min_y = 0; 312 | waste_area = 0; 313 | visited_width = 0; 314 | while (node->x < x1) { 315 | if (node->y > min_y) { 316 | // raise min_y higher. 317 | // we've accounted for all waste up to min_y, 318 | // but we'll now add more waste for everything we've visted 319 | waste_area += visited_width * (node->y - min_y); 320 | min_y = node->y; 321 | // the first time through, visited_width might be reduced 322 | if (node->x < x0) 323 | visited_width += node->next->x - x0; 324 | else 325 | visited_width += node->next->x - node->x; 326 | } else { 327 | // add waste area 328 | int under_width = node->next->x - node->x; 329 | if (under_width + visited_width > width) 330 | under_width = width - visited_width; 331 | waste_area += under_width * (min_y - node->y); 332 | visited_width += under_width; 333 | } 334 | node = node->next; 335 | } 336 | 337 | *pwaste = waste_area; 338 | return min_y; 339 | } 340 | 341 | typedef struct 342 | { 343 | int x,y; 344 | stbrp_node **prev_link; 345 | } stbrp__findresult; 346 | 347 | static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int width, int height) 348 | { 349 | int best_waste = (1<<30), best_x, best_y = (1 << 30); 350 | stbrp__findresult fr; 351 | stbrp_node **prev, *node, *tail, **best = NULL; 352 | 353 | // align to multiple of c->align 354 | width = (width + c->align - 1); 355 | width -= width % c->align; 356 | STBRP_ASSERT(width % c->align == 0); 357 | 358 | // if it can't possibly fit, bail immediately 359 | if (width > c->width || height > c->height) { 360 | fr.prev_link = NULL; 361 | fr.x = fr.y = 0; 362 | return fr; 363 | } 364 | 365 | node = c->active_head; 366 | prev = &c->active_head; 367 | while (node->x + width <= c->width) { 368 | int y,waste; 369 | y = stbrp__skyline_find_min_y(c, node, node->x, width, &waste); 370 | if (c->heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight) { // actually just want to test BL 371 | // bottom left 372 | if (y < best_y) { 373 | best_y = y; 374 | best = prev; 375 | } 376 | } else { 377 | // best-fit 378 | if (y + height <= c->height) { 379 | // can only use it if it first vertically 380 | if (y < best_y || (y == best_y && waste < best_waste)) { 381 | best_y = y; 382 | best_waste = waste; 383 | best = prev; 384 | } 385 | } 386 | } 387 | prev = &node->next; 388 | node = node->next; 389 | } 390 | 391 | best_x = (best == NULL) ? 0 : (*best)->x; 392 | 393 | // if doing best-fit (BF), we also have to try aligning right edge to each node position 394 | // 395 | // e.g, if fitting 396 | // 397 | // ____________________ 398 | // |____________________| 399 | // 400 | // into 401 | // 402 | // | | 403 | // | ____________| 404 | // |____________| 405 | // 406 | // then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned 407 | // 408 | // This makes BF take about 2x the time 409 | 410 | if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) { 411 | tail = c->active_head; 412 | node = c->active_head; 413 | prev = &c->active_head; 414 | // find first node that's admissible 415 | while (tail->x < width) 416 | tail = tail->next; 417 | while (tail) { 418 | int xpos = tail->x - width; 419 | int y,waste; 420 | STBRP_ASSERT(xpos >= 0); 421 | // find the left position that matches this 422 | while (node->next->x <= xpos) { 423 | prev = &node->next; 424 | node = node->next; 425 | } 426 | STBRP_ASSERT(node->next->x > xpos && node->x <= xpos); 427 | y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste); 428 | if (y + height <= c->height) { 429 | if (y <= best_y) { 430 | if (y < best_y || waste < best_waste || (waste==best_waste && xpos < best_x)) { 431 | best_x = xpos; 432 | //STBRP_ASSERT(y <= best_y); [DEAR IMGUI] 433 | best_y = y; 434 | best_waste = waste; 435 | best = prev; 436 | } 437 | } 438 | } 439 | tail = tail->next; 440 | } 441 | } 442 | 443 | fr.prev_link = best; 444 | fr.x = best_x; 445 | fr.y = best_y; 446 | return fr; 447 | } 448 | 449 | static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, int width, int height) 450 | { 451 | // find best position according to heuristic 452 | stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height); 453 | stbrp_node *node, *cur; 454 | 455 | // bail if: 456 | // 1. it failed 457 | // 2. the best node doesn't fit (we don't always check this) 458 | // 3. we're out of memory 459 | if (res.prev_link == NULL || res.y + height > context->height || context->free_head == NULL) { 460 | res.prev_link = NULL; 461 | return res; 462 | } 463 | 464 | // on success, create new node 465 | node = context->free_head; 466 | node->x = (stbrp_coord) res.x; 467 | node->y = (stbrp_coord) (res.y + height); 468 | 469 | context->free_head = node->next; 470 | 471 | // insert the new node into the right starting point, and 472 | // let 'cur' point to the remaining nodes needing to be 473 | // stiched back in 474 | 475 | cur = *res.prev_link; 476 | if (cur->x < res.x) { 477 | // preserve the existing one, so start testing with the next one 478 | stbrp_node *next = cur->next; 479 | cur->next = node; 480 | cur = next; 481 | } else { 482 | *res.prev_link = node; 483 | } 484 | 485 | // from here, traverse cur and free the nodes, until we get to one 486 | // that shouldn't be freed 487 | while (cur->next && cur->next->x <= res.x + width) { 488 | stbrp_node *next = cur->next; 489 | // move the current node to the free list 490 | cur->next = context->free_head; 491 | context->free_head = cur; 492 | cur = next; 493 | } 494 | 495 | // stitch the list back in 496 | node->next = cur; 497 | 498 | if (cur->x < res.x + width) 499 | cur->x = (stbrp_coord) (res.x + width); 500 | 501 | #ifdef _DEBUG 502 | cur = context->active_head; 503 | while (cur->x < context->width) { 504 | STBRP_ASSERT(cur->x < cur->next->x); 505 | cur = cur->next; 506 | } 507 | STBRP_ASSERT(cur->next == NULL); 508 | 509 | { 510 | int count=0; 511 | cur = context->active_head; 512 | while (cur) { 513 | cur = cur->next; 514 | ++count; 515 | } 516 | cur = context->free_head; 517 | while (cur) { 518 | cur = cur->next; 519 | ++count; 520 | } 521 | STBRP_ASSERT(count == context->num_nodes+2); 522 | } 523 | #endif 524 | 525 | return res; 526 | } 527 | 528 | static int STBRP__CDECL rect_height_compare(const void *a, const void *b) 529 | { 530 | const stbrp_rect *p = (const stbrp_rect *) a; 531 | const stbrp_rect *q = (const stbrp_rect *) b; 532 | if (p->h > q->h) 533 | return -1; 534 | if (p->h < q->h) 535 | return 1; 536 | return (p->w > q->w) ? -1 : (p->w < q->w); 537 | } 538 | 539 | static int STBRP__CDECL rect_original_order(const void *a, const void *b) 540 | { 541 | const stbrp_rect *p = (const stbrp_rect *) a; 542 | const stbrp_rect *q = (const stbrp_rect *) b; 543 | return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed); 544 | } 545 | 546 | STBRP_DEF int stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int num_rects) 547 | { 548 | int i, all_rects_packed = 1; 549 | 550 | // we use the 'was_packed' field internally to allow sorting/unsorting 551 | for (i=0; i < num_rects; ++i) { 552 | rects[i].was_packed = i; 553 | } 554 | 555 | // sort according to heuristic 556 | STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_height_compare); 557 | 558 | for (i=0; i < num_rects; ++i) { 559 | if (rects[i].w == 0 || rects[i].h == 0) { 560 | rects[i].x = rects[i].y = 0; // empty rect needs no space 561 | } else { 562 | stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h); 563 | if (fr.prev_link) { 564 | rects[i].x = (stbrp_coord) fr.x; 565 | rects[i].y = (stbrp_coord) fr.y; 566 | } else { 567 | rects[i].x = rects[i].y = STBRP__MAXVAL; 568 | } 569 | } 570 | } 571 | 572 | // unsort 573 | STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_original_order); 574 | 575 | // set was_packed flags and all_rects_packed status 576 | for (i=0; i < num_rects; ++i) { 577 | rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL); 578 | if (!rects[i].was_packed) 579 | all_rects_packed = 0; 580 | } 581 | 582 | // return the all_rects_packed status 583 | return all_rects_packed; 584 | } 585 | #endif 586 | 587 | /* 588 | ------------------------------------------------------------------------------ 589 | This software is available under 2 licenses -- choose whichever you prefer. 590 | ------------------------------------------------------------------------------ 591 | ALTERNATIVE A - MIT License 592 | Copyright (c) 2017 Sean Barrett 593 | Permission is hereby granted, free of charge, to any person obtaining a copy of 594 | this software and associated documentation files (the "Software"), to deal in 595 | the Software without restriction, including without limitation the rights to 596 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 597 | of the Software, and to permit persons to whom the Software is furnished to do 598 | so, subject to the following conditions: 599 | The above copyright notice and this permission notice shall be included in all 600 | copies or substantial portions of the Software. 601 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 602 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 603 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 604 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 605 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 606 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 607 | SOFTWARE. 608 | ------------------------------------------------------------------------------ 609 | ALTERNATIVE B - Public Domain (www.unlicense.org) 610 | This is free and unencumbered software released into the public domain. 611 | Anyone is free to copy, modify, publish, use, compile, sell, or distribute this 612 | software, either in source code form or as a compiled binary, for any purpose, 613 | commercial or non-commercial, and by any means. 614 | In jurisdictions that recognize copyright laws, the author or authors of this 615 | software dedicate any and all copyright interest in the software to the public 616 | domain. We make this dedication for the benefit of the public at large and to 617 | the detriment of our heirs and successors. We intend this dedication to be an 618 | overt act of relinquishment in perpetuity of all present and future rights to 619 | this software under copyright law. 620 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 621 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 622 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 623 | AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 624 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 625 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 626 | ------------------------------------------------------------------------------ 627 | */ 628 | -------------------------------------------------------------------------------- /ProfilerWindow.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "Profiler.h" 3 | 4 | #if WITH_PROFILING 5 | 6 | #include "IconsFontAwesome4.h" 7 | #include "IconsFontAwesome4_data.h" 8 | #include "Roboto_data.h" 9 | #include 10 | #include 11 | #include 12 | 13 | #define NOMINMAX 14 | #include 15 | 16 | void HandleAssertMessage(const char* pMessage) 17 | { 18 | printf("%s", pMessage); 19 | OutputDebugStringA(pMessage); 20 | } 21 | 22 | class StringHash 23 | { 24 | private: 25 | static constexpr uint32 val_const{ 0x811c9dc5 }; 26 | static constexpr uint32 prime_const{ 0x1000193 }; 27 | 28 | static inline constexpr uint32 Hash_Internal(const char* const str, const uint32 value) noexcept 29 | { 30 | if (!str) 31 | return 0; 32 | return (str[0] == '\0') ? value : Hash_Internal(&str[1], (value ^ uint64(str[0])) * prime_const); 33 | } 34 | 35 | public: 36 | static inline constexpr StringHash Hash(const char* const str) noexcept 37 | { 38 | return StringHash(Hash_Internal(str, val_const)); 39 | } 40 | 41 | constexpr StringHash(uint32 value = 0) noexcept 42 | : m_Hash(value) 43 | { 44 | } 45 | 46 | constexpr StringHash(const char* const pText) noexcept 47 | : m_Hash(Hash_Internal(pText, val_const)) 48 | { 49 | } 50 | 51 | constexpr void Combine(uint32 other) 52 | { 53 | m_Hash ^= other + 0x9e3779b9 + (m_Hash << 6) + (m_Hash >> 2); 54 | } 55 | 56 | inline constexpr operator uint32() const { return m_Hash; } 57 | 58 | inline bool operator==(const StringHash& rhs) const = default; 59 | inline bool operator!=(const StringHash& rhs) const = default; 60 | inline bool operator<(const StringHash& rhs) const = default; 61 | inline bool operator>(const StringHash& rhs) const = default; 62 | 63 | uint32 m_Hash; 64 | }; 65 | 66 | struct StyleOptions 67 | { 68 | int MaxDepth = 10; 69 | float MaxTime = 200; 70 | 71 | float BarHeight = 1.5f; 72 | float BarPadding = 2; 73 | float ScrollBarSize = 15.0f; 74 | 75 | ImVec4 BarColorMultiplier = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); 76 | ImVec4 BGTextColor = ImVec4(0.5f, 0.5f, 0.5f, 1.0f); 77 | ImVec4 FGTextColor = ImVec4(0.9f, 0.9f, 0.9f, 1.0f); 78 | ImVec4 BarHighlightColor = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); 79 | 80 | bool DebugMode = false; 81 | 82 | float GetBarHeight() const 83 | { 84 | return BarHeight * ImGui::GetTextLineHeight(); 85 | } 86 | }; 87 | 88 | struct HUDContext 89 | { 90 | StyleOptions Style; 91 | ImFont* TextFont = nullptr; 92 | ImFont* IconFont = nullptr; 93 | 94 | float TimelineScale = 5.0f; 95 | ImVec2 TimelineOffset = ImVec2(0.0f, 0.0f); 96 | 97 | bool IsSelectingRange = false; 98 | float RangeSelectionStart = 0.0f; 99 | char SearchString[128]{}; 100 | bool IsPaused = false; 101 | 102 | struct SelectedStatData 103 | { 104 | StringHash Hash = {}; 105 | uint32 NumSamples = 0; 106 | 107 | float MovingAverageTime = 0; 108 | float MinTime = FLT_MAX; 109 | float MaxTime = 0.0f; 110 | 111 | void Set(uint32 hash) 112 | { 113 | Hash = hash; 114 | NumSamples = 0; 115 | MovingAverageTime = 0; 116 | MinTime = FLT_MAX; 117 | MaxTime = 0.0f; 118 | } 119 | 120 | void AddSample(float newSample) 121 | { 122 | ++NumSamples; 123 | MinTime = ImMin(newSample, MinTime); 124 | MaxTime = ImMax(newSample, MaxTime); 125 | MovingAverageTime = MovingAverageTime + (newSample - MovingAverageTime) / NumSamples; 126 | NumSamples %= 4096; 127 | } 128 | 129 | } SelectedEvent; 130 | }; 131 | 132 | static HUDContext gHUDContext; 133 | static HUDContext& Context() 134 | { 135 | return gHUDContext; 136 | } 137 | 138 | static void EditStyle(StyleOptions& style) 139 | { 140 | ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x * 0.7f); 141 | ImGui::SliderInt("Depth", &style.MaxDepth, 1, 12); 142 | ImGui::SliderFloat("Max Time", &style.MaxTime, 8, 500, "%.1f"); 143 | ImGui::SliderFloat("Bar Height", &style.BarHeight, 1, 4); 144 | ImGui::SliderFloat("Bar Padding", &style.BarPadding, 0, 5); 145 | ImGui::SliderFloat("Scroll Bar Size", &style.ScrollBarSize, 1.0f, 40.0f); 146 | ImGui::ColorEdit4("Bar Color Multiplier", &style.BarColorMultiplier.x); 147 | ImGui::ColorEdit4("Background Text Color", &style.BGTextColor.x); 148 | ImGui::ColorEdit4("Foreground Text Color", &style.FGTextColor.x); 149 | ImGui::ColorEdit4("Bar Highlight Color", &style.BarHighlightColor.x); 150 | ImGui::Separator(); 151 | ImGui::Checkbox("Debug Mode", &style.DebugMode); 152 | ImGui::PopItemWidth(); 153 | } 154 | 155 | static StringHash GetEventHash(const ProfilerEvent& event) 156 | { 157 | StringHash hash; 158 | hash.Combine(StringHash(event.pName)); 159 | hash.Combine(StringHash(event.pFilePath)); 160 | hash.Combine(event.LineNumber); 161 | hash.Combine(event.QueueIndex); 162 | return hash; 163 | } 164 | 165 | template 166 | static std::string Sprintf(const char* pFormat, Args... args) 167 | { 168 | static char buffer[1024]; 169 | sprintf_s(buffer, pFormat, std::forward(args)...); 170 | return buffer; 171 | } 172 | 173 | struct TraceContext 174 | { 175 | TraceContext() 176 | { 177 | QueryPerformanceCounter((LARGE_INTEGER*)&BaseTime); 178 | } 179 | 180 | std::ofstream TraceStream; 181 | uint64 BaseTime = 0; 182 | }; 183 | 184 | void BeginTrace(const char* pPath, TraceContext& context) 185 | { 186 | if (context.TraceStream.is_open()) 187 | return; 188 | 189 | context.TraceStream.open(pPath); 190 | context.TraceStream << "{\n\"traceEvents\": [\n"; 191 | 192 | context.TraceStream << Sprintf("{\"name\":\"process_name\",\"ph\":\"M\",\"pid\":0,\"args\":{\"name\":\"Track\"}},\n"); 193 | for (const Profiler::EventTrack& track : gProfiler.GetTracks()) 194 | { 195 | context.TraceStream << Sprintf("{\"name\":\"thread_name\",\"ph\":\"M\",\"pid\":0,\"tid\":%d,\"args\":{\"name\":\"%s\"}},\n", track.Index, track.Name); 196 | } 197 | } 198 | 199 | void UpdateTrace(TraceContext& context) 200 | { 201 | if (!context.TraceStream.is_open()) 202 | return; 203 | 204 | uint64 frequency = 0; 205 | QueryPerformanceFrequency((LARGE_INTEGER*)&frequency); 206 | const float TicksToMs = 1000.0f / frequency; 207 | 208 | URange cpuRange = gProfiler.GetFrameRange(); 209 | for (const Profiler::EventTrack& track : gProfiler.GetTracks()) 210 | { 211 | for (const ProfilerEvent& event : track.GetFrameData(cpuRange.Begin)) 212 | context.TraceStream << Sprintf("{\"pid\":0,\"tid\":%d,\"ts\":%d,\"dur\":%d,\"ph\":\"X\",\"name\":\"%s\"},\n", track.Index, (int)(1000 * TicksToMs * (event.TicksBegin - context.BaseTime)), (int)(1000 * TicksToMs * (event.TicksEnd - event.TicksBegin)), event.pName); 213 | } 214 | } 215 | 216 | void EndTrace(TraceContext& context) 217 | { 218 | if (!context.TraceStream.is_open()) 219 | return; 220 | 221 | context.TraceStream << "{}]\n}"; 222 | context.TraceStream.close(); 223 | } 224 | 225 | static void DrawProfilerTimeline(const ImVec2& size = ImVec2(0, 0)) 226 | { 227 | PROFILE_CPU_SCOPE(); 228 | 229 | HUDContext& context = gHUDContext; 230 | StyleOptions& style = context.Style; 231 | 232 | static TraceContext traceContext; 233 | 234 | UpdateTrace(traceContext); 235 | 236 | ImVec2 sizeActual = ImGui::CalcItemSize(size, ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y); 237 | 238 | ImRect timelineRect(ImGui::GetCursorScreenPos(), ImGui::GetCursorScreenPos() + sizeActual - ImVec2(200, 0)); 239 | ImGui::ItemSize(timelineRect.GetSize()); 240 | 241 | // The current (scaled) size of the timeline 242 | float timelineWidth = timelineRect.GetWidth() * context.TimelineScale; 243 | 244 | ImVec2 cursor = timelineRect.Min + context.TimelineOffset; 245 | ImVec2 cursorStart = cursor; 246 | ImDrawList* pDraw = ImGui::GetWindowDrawList(); 247 | 248 | ImGuiID timelineID = ImGui::GetID("Timeline"); 249 | timelineRect.Max -= ImVec2(style.ScrollBarSize, style.ScrollBarSize); 250 | if (ImGui::ItemAdd(timelineRect, timelineID)) 251 | { 252 | ImGui::PushClipRect(timelineRect.Min, timelineRect.Max, true); 253 | 254 | // How many ticks per ms 255 | uint64 frequency = 0; 256 | QueryPerformanceFrequency((LARGE_INTEGER*)&frequency); 257 | const float MsToTicks = (float)frequency / 1000.0f; 258 | const float TicksToMs = 1000.0f / frequency; 259 | 260 | // How many ticks are in the timeline 261 | float ticksInTimeline = MsToTicks * style.MaxTime; 262 | 263 | URange cpuRange = gProfiler.GetFrameRange(); 264 | uint64 beginAnchor = gProfiler.GetFirstFrameAnchorTicks(); 265 | 266 | // How many pixels is one tick 267 | const float TicksToPixels = timelineWidth / ticksInTimeline; 268 | 269 | // Add vertical bars for each ms interval 270 | /* 271 | 0 1 2 3 272 | | | | | 273 | | | | | 274 | | | | | 275 | */ 276 | pDraw->AddRectFilled(timelineRect.Min, ImVec2(timelineRect.Max.x, timelineRect.Min.y + style.GetBarHeight()), ImColor(0.0f, 0.0f, 0.0f, 0.1f)); 277 | pDraw->AddRect(timelineRect.Min - ImVec2(10, 0), ImVec2(timelineRect.Max.x + 10, timelineRect.Min.y + style.GetBarHeight()), ImColor(1.0f, 1.0f, 1.0f, 0.4f)); 278 | 279 | float minIntervalDistance = 80.0f; 280 | float msWidth = 1.0f * MsToTicks * TicksToPixels; 281 | float intervalSize = ceil(minIntervalDistance / msWidth * 2.0f) / 2.0f; 282 | 283 | int markerIdx = 0; 284 | for (float intervalTime = 0; intervalTime < style.MaxTime; intervalTime += intervalSize, ++markerIdx) 285 | { 286 | float x0 = intervalTime * MsToTicks * TicksToPixels; 287 | ImVec2 tickPos = ImVec2(cursor.x + x0, timelineRect.Min.y); 288 | pDraw->AddLine(tickPos + ImVec2(0, style.GetBarHeight() * 0.5f), tickPos + ImVec2(0, style.GetBarHeight()), ImColor(style.BGTextColor)); 289 | 290 | const char* pBarText; 291 | ImFormatStringToTempBuffer(&pBarText, nullptr, "%.1f ms", intervalTime); 292 | pDraw->AddText(tickPos + ImVec2(5, 0), ImColor(style.BGTextColor), pBarText); 293 | 294 | if (markerIdx % 2 == 0) 295 | pDraw->AddRectFilled(tickPos + ImVec2(0, style.GetBarHeight()), tickPos + ImVec2(intervalSize * MsToTicks * TicksToPixels, timelineRect.Max.y), ImColor(1.0f, 1.0f, 1.0f, 0.02f)); 296 | } 297 | 298 | cursor.y += style.GetBarHeight(); 299 | 300 | ImGui::PushClipRect(timelineRect.Min + ImVec2(0, style.GetBarHeight()), timelineRect.Max, true); 301 | 302 | ImRect clipRect = ImGui::GetCurrentWindow()->ClipRect; 303 | 304 | // Draw the bars for a list of profiling events 305 | /* 306 | [=== SomeFunction (1.2 ms) ===] 307 | */ 308 | bool anyHovered = false; 309 | auto DrawTrack = [&](Span events, uint32 frameIndex, uint32& outTrackDepth) { 310 | for (const ProfilerEvent& event : events) 311 | { 312 | // Skip events above the max depth 313 | if (!event.IsValid() || event.Depth >= (uint32)style.MaxDepth) 314 | continue; 315 | 316 | outTrackDepth = ImMax(outTrackDepth, (uint32)event.Depth + 1); 317 | 318 | bool hovered = false; 319 | bool clicked = false; 320 | if (event.TicksEnd > beginAnchor) 321 | { 322 | float startPos = (event.TicksBegin < beginAnchor ? 0 : event.TicksBegin - beginAnchor) * TicksToPixels; 323 | float endPos = (event.TicksEnd - beginAnchor) * TicksToPixels; 324 | float y = event.Depth * style.GetBarHeight(); 325 | ImRect itemRect = ImRect(cursor + ImVec2(startPos, y), cursor + ImVec2(endPos, y + style.GetBarHeight())); 326 | 327 | // Ensure a bar always has a width 328 | itemRect.Max.x = ImMax(itemRect.Max.x, itemRect.Min.x + 1); 329 | 330 | if (clipRect.Overlaps(itemRect)) 331 | { 332 | float ms = TicksToMs * (float)(event.TicksEnd - event.TicksBegin); 333 | 334 | ImColor color = ImColor(event.GetColor()) * style.BarColorMultiplier; 335 | ImColor textColor = style.FGTextColor; 336 | // Fade out the bars that don't match the filter 337 | if (context.SearchString[0] != 0 && !strstr(event.pName, context.SearchString)) 338 | { 339 | color.Value.w *= 0.3f; 340 | textColor.Value.w *= 0.5f; 341 | } 342 | 343 | // Darken the bottom 344 | ImColor colorBottom = color.Value * ImVec4(0.8f, 0.8f, 0.8f, 1.0f); 345 | 346 | if (!anyHovered && ImGui::IsMouseHoveringRect(itemRect.Min, itemRect.Max)) 347 | { 348 | hovered = true; 349 | anyHovered = true; 350 | 351 | if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) 352 | { 353 | clicked = true; 354 | } 355 | 356 | // If the bar is double-clicked, zoom in to make the bar fill the entire window 357 | if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) 358 | { 359 | // Zoom ration to make the bar fit the entire window 360 | float zoom = timelineWidth / itemRect.GetWidth(); 361 | context.TimelineScale = zoom; 362 | 363 | // Recompute the timeline size with new zoom 364 | float newTimelineWidth = timelineRect.GetWidth() * context.TimelineScale; 365 | float newTickScale = newTimelineWidth / ticksInTimeline; 366 | float newStartPos = newTickScale * (event.TicksBegin - beginAnchor); 367 | 368 | context.TimelineOffset.x = -newStartPos; 369 | } 370 | } 371 | 372 | // Draw the bar rect and outline if hovered 373 | 374 | // Only pad if bar is large enough 375 | float maxPaddingX = ImMax(itemRect.GetWidth() * 0.5f - 1.0f, 0.0f); 376 | const ImVec2 padding(ImMin(style.BarPadding, maxPaddingX), style.BarPadding); 377 | if (hovered) 378 | { 379 | ImColor highlightColor = color.Value * ImVec4(1.5f, 1.5f, 1.5f, 1.0f); 380 | color.Value = color.Value * ImVec4(1.2f, 1.2f, 1.2f, 1.0f); 381 | colorBottom.Value = colorBottom.Value * ImVec4(1.2f, 1.2f, 1.2f, 1.0f); 382 | pDraw->AddRectFilledMultiColor(itemRect.Min + padding, itemRect.Max - padding, color, color, colorBottom, colorBottom); 383 | pDraw->AddRect(itemRect.Min, itemRect.Max, highlightColor, 0.0f, ImDrawFlags_None, 3.0f); 384 | } 385 | else 386 | { 387 | pDraw->AddRectFilledMultiColor(itemRect.Min + padding, itemRect.Max - padding, color, color, colorBottom, colorBottom); 388 | } 389 | 390 | // If the bar size is large enough, draw the name of the bar on top 391 | if (itemRect.GetWidth() > 10.0f) 392 | { 393 | const char* pBarText = event.pName; 394 | ImVec2 textSize = ImGui::CalcTextSize(pBarText); 395 | if (textSize.x * 2 < itemRect.GetWidth()) 396 | { 397 | ImFormatStringToTempBuffer(&pBarText, nullptr, "%s (%.2f ms)", event.pName, ms); 398 | textSize = ImGui::CalcTextSize(pBarText); 399 | } 400 | 401 | itemRect.Expand(ImVec2(-2.0f, 0.0f)); 402 | 403 | ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.f, 0.f, 0.f, 0.5f)); 404 | itemRect.Translate(ImVec2(2.0f, 2.0f)); 405 | ImGui::RenderTextEllipsis(pDraw, itemRect.Min + ImMax(ImVec2(), ImVec2(itemRect.GetWidth(), style.GetBarHeight()) - textSize) * 0.5f, itemRect.Max, itemRect.Max.x, pBarText, nullptr, &textSize); 406 | ImGui::PopStyleColor(); 407 | 408 | itemRect.Translate(ImVec2(-2.0f, -2.0f)); 409 | ImGui::RenderTextEllipsis(pDraw, itemRect.Min + ImMax(ImVec2(), ImVec2(itemRect.GetWidth(), style.GetBarHeight()) - textSize) * 0.5f, itemRect.Max, itemRect.Max.x, pBarText, nullptr, &textSize); 410 | } 411 | } 412 | } 413 | 414 | if (hovered) 415 | { 416 | if (ImGui::BeginTooltip()) 417 | { 418 | ImGui::TextColored(ImVec4(1.0f, 0.7f, 0.4f, 1.0f), "%s | %.3f ms", event.pName, TicksToMs * (float)(event.TicksEnd - event.TicksBegin)); 419 | ImGui::Text("Frame %d", frameIndex); 420 | if (event.pFilePath && *event.pFilePath != 0) 421 | ImGui::Text("%s:%d", event.pFilePath, event.LineNumber); 422 | ImGui::EndTooltip(); 423 | } 424 | } 425 | if (clicked) 426 | { 427 | StringHash eventHash = GetEventHash(event); 428 | context.SelectedEvent.Set(GetEventHash(event)); 429 | } 430 | } 431 | }; 432 | 433 | // Add track name and expander 434 | /* 435 | (>) Main Thread [1234] 436 | */ 437 | auto TrackHeader = [&](const char* pName, uint32 id) { 438 | pDraw->AddRectFilled(ImVec2(timelineRect.Min.x, cursor.y), ImVec2(timelineRect.Max.x, cursor.y + style.GetBarHeight()), ImColor(0.0f, 0.0f, 0.0f, 0.3f)); 439 | 440 | bool isOpen = ImGui::GetCurrentWindow()->StateStorage.GetBool(id, true); 441 | ImVec2 trackTextCursor = ImVec2(timelineRect.Min.x, cursor.y); 442 | 443 | float caretSize = ImGui::GetTextLineHeight(); 444 | if (ImGui::ItemAdd(ImRect(trackTextCursor, trackTextCursor + ImVec2(caretSize, caretSize)), id)) 445 | { 446 | if (ImGui::IsItemHovered()) 447 | pDraw->AddRect(ImGui::GetItemRectMin() + ImVec2(2, 2), ImGui::GetItemRectMax() - ImVec2(2, 2), ImColor(style.BGTextColor), 3.0f); 448 | pDraw->AddText(ImGui::GetItemRectMin() + ImVec2(2, 2), ImColor(style.BGTextColor), isOpen ? ICON_FA_CARET_DOWN : ICON_FA_CARET_RIGHT); 449 | if (ImGui::ButtonBehavior(ImRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax()), id, nullptr, nullptr, ImGuiButtonFlags_MouseButtonLeft)) 450 | { 451 | isOpen = !isOpen; 452 | ImGui::GetCurrentWindow()->StateStorage.SetBool(id, isOpen); 453 | } 454 | } 455 | 456 | trackTextCursor.x += caretSize; 457 | pDraw->AddText(trackTextCursor, ImColor(style.BGTextColor), pName); 458 | cursor.y += style.GetBarHeight(); 459 | return isOpen; 460 | }; 461 | 462 | // Draw each track 463 | Span tracks = gProfiler.GetTracks(); 464 | 465 | // Sort by track type 466 | Array sorted_tracks; 467 | for (const Profiler::EventTrack& track : tracks) 468 | sorted_tracks.push_back(&track); 469 | std::sort(sorted_tracks.begin(), sorted_tracks.end(), [](const Profiler::EventTrack* a, const Profiler::EventTrack* b) { 470 | return (int)a->Type > (int)b->Type; 471 | }); 472 | 473 | for (const Profiler::EventTrack* pTrack : sorted_tracks) 474 | { 475 | PROFILE_CPU_SCOPE("Timeline Track"); 476 | 477 | // Add thread name for track 478 | const char* pHeaderText; 479 | ImFormatStringToTempBuffer(&pHeaderText, nullptr, "%s [%d]", pTrack->Name, pTrack->ID); 480 | 481 | if (TrackHeader(pHeaderText, ImGui::GetID(pTrack))) 482 | { 483 | uint32 trackDepth = 0; 484 | 485 | // Add a bar in the right place for each event 486 | /* 487 | |[=============] | 488 | | [======] | 489 | */ 490 | for (uint32 frameIndex = cpuRange.Begin; frameIndex < cpuRange.End; ++frameIndex) 491 | { 492 | DrawTrack(pTrack->GetFrameData(frameIndex), frameIndex, trackDepth); 493 | } 494 | cursor.y += trackDepth * style.GetBarHeight(); 495 | } 496 | 497 | // Add vertical line to end track 498 | pDraw->AddLine(ImVec2(timelineRect.Min.x, cursor.y), ImVec2(timelineRect.Max.x, cursor.y), ImColor(style.BGTextColor)); 499 | } 500 | 501 | // The final height of the timeline 502 | float timelineHeight = cursor.y - cursorStart.y; 503 | 504 | if (ImGui::IsWindowFocused()) 505 | { 506 | // Profile range 507 | // If not currently in selection, start selection when left mouse button is pressed 508 | if (!context.IsSelectingRange && ImGui::IsMouseHoveringRect(timelineRect.Min, timelineRect.Max)) 509 | { 510 | if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) 511 | { 512 | context.RangeSelectionStart = ImGui::GetMousePos().x; 513 | context.IsSelectingRange = true; 514 | } 515 | } 516 | else if (context.IsSelectingRange) 517 | { 518 | // If mouse button is released, exit measuring mode 519 | if (ImGui::IsMouseReleased(ImGuiMouseButton_Left)) 520 | { 521 | context.IsSelectingRange = false; 522 | } 523 | else 524 | { 525 | // Distance between mouse cursor and measuring start 526 | float distance = fabs(ImGui::GetMousePos().x - context.RangeSelectionStart); 527 | 528 | // Fade in based on distance 529 | float opacity = ImClamp(distance / 30.0f, 0.0f, 1.0f); 530 | if (opacity > 0.0f) 531 | { 532 | float time = (distance / TicksToPixels) * TicksToMs; 533 | 534 | // Draw measure region 535 | pDraw->AddRectFilled(ImVec2(context.RangeSelectionStart, timelineRect.Min.y), ImVec2(ImGui::GetMousePos().x, timelineRect.Max.y), ImColor(1.0f, 1.0f, 1.0f, 0.1f)); 536 | pDraw->AddLine(ImVec2(context.RangeSelectionStart, timelineRect.Min.y), ImVec2(context.RangeSelectionStart, timelineRect.Max.y), ImColor(1.0f, 1.0f, 1.0f, 0.3f), 3.0f); 537 | pDraw->AddLine(ImVec2(ImGui::GetMousePos().x, timelineRect.Min.y), ImVec2(ImGui::GetMousePos().x, timelineRect.Max.y), ImColor(1.0f, 1.0f, 1.0f, 0.3f), 3.0f); 538 | 539 | // Add line and arrows 540 | ImColor measureColor = style.FGTextColor; 541 | measureColor.Value.w *= opacity; 542 | ImVec2 lineStart = ImVec2(context.RangeSelectionStart, ImGui::GetMousePos().y); 543 | ImVec2 lineEnd = ImGui::GetMousePos(); 544 | if (lineStart.x > lineEnd.x) 545 | std::swap(lineStart.x, lineEnd.x); 546 | pDraw->AddLine(lineStart, lineEnd, measureColor); 547 | pDraw->AddLine(lineStart, lineStart + ImVec2(5, 5), measureColor); 548 | pDraw->AddLine(lineStart, lineStart + ImVec2(5, -5), measureColor); 549 | pDraw->AddLine(lineEnd, lineEnd + ImVec2(-5, 5), measureColor); 550 | pDraw->AddLine(lineEnd, lineEnd + ImVec2(-5, -5), measureColor); 551 | 552 | // Add text in the middle 553 | const char* pTimeText; 554 | ImFormatStringToTempBuffer(&pTimeText, nullptr, "Time: %.3f ms", time); 555 | ImVec2 textSize = ImGui::CalcTextSize(pTimeText); 556 | pDraw->AddText((lineEnd + lineStart) / 2 - ImVec2(textSize.x * 0.5f, textSize.y), measureColor, pTimeText); 557 | } 558 | } 559 | } 560 | 561 | // Zoom behavior 562 | float zoomDelta = 0.0f; 563 | if (ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl)) 564 | zoomDelta += ImGui::GetIO().MouseWheel / 5.0f; 565 | 566 | if (zoomDelta != 0) 567 | { 568 | // Logarithmic scale 569 | float logScale = logf(context.TimelineScale); 570 | logScale += zoomDelta; 571 | float newScale = ImClamp(expf(logScale), 1.0f, 100.0f); 572 | 573 | float scaleFactor = newScale / context.TimelineScale; 574 | context.TimelineScale *= scaleFactor; 575 | ImVec2 mousePos = ImGui::GetMousePos() - timelineRect.Min; 576 | context.TimelineOffset.x = mousePos.x - (mousePos.x - context.TimelineOffset.x) * scaleFactor; 577 | } 578 | } 579 | 580 | // Panning behavior 581 | bool held; 582 | ImGui::ButtonBehavior(timelineRect, timelineID, nullptr, &held, ImGuiButtonFlags_MouseButtonRight); 583 | if (held) 584 | context.TimelineOffset += ImGui::GetIO().MouseDelta; 585 | 586 | // Compute the new timeline size to correctly clamp the offset 587 | timelineWidth = timelineRect.GetWidth() * context.TimelineScale; 588 | context.TimelineOffset = ImClamp(context.TimelineOffset, ImMin(ImVec2(0.0f, 0.0f), timelineRect.GetSize() - ImVec2(timelineWidth, timelineHeight)), ImVec2(0.0f, 0.0f)); 589 | 590 | ImGui::PopClipRect(); 591 | ImGui::PopClipRect(); 592 | 593 | // Draw a debug rect around the timeline item and the whole (unclipped) timeline rect 594 | if (style.DebugMode) 595 | { 596 | pDraw->PushClipRectFullScreen(); 597 | pDraw->AddRect(cursorStart, cursorStart + ImVec2(timelineWidth, timelineHeight), ImColor(1.0f, 0.0f, 0.0f), 0.0f, ImDrawFlags_None, 3.0f); 598 | pDraw->AddRect(timelineRect.Min, timelineRect.Max, ImColor(0.0f, 1.0f, 0.0f), 0.0f, ImDrawFlags_None, 2.0f); 599 | pDraw->PopClipRect(); 600 | } 601 | 602 | // Add extra data to tooltip 603 | ImGui::SameLine(); 604 | 605 | ImGui::BeginGroup(); 606 | 607 | const char* pTracePath = "trace.json"; 608 | if (!traceContext.TraceStream.is_open()) 609 | { 610 | if (ImGui::Button("Begin Trace", ImVec2(ImGui::GetContentRegionAvail().x, 0))) 611 | { 612 | BeginTrace(pTracePath, traceContext); 613 | } 614 | } 615 | else 616 | { 617 | if (ImGui::Button("End Trace", ImVec2(ImGui::GetContentRegionAvail().x, 0))) 618 | { 619 | EndTrace(traceContext); 620 | } 621 | } 622 | 623 | HUDContext::SelectedStatData& selectedEvent = context.SelectedEvent; 624 | if ((uint32)selectedEvent.Hash != 0) 625 | { 626 | const char* pName = ""; 627 | float eventTime = 0; 628 | uint32 n = 0; 629 | for (uint32 i = cpuRange.Begin; i < cpuRange.End; ++i) 630 | { 631 | for (const Profiler::EventTrack& track : gProfiler.GetTracks()) 632 | { 633 | const ProfilerEventData& eventData = track.GetFrameData(i); 634 | for (const ProfilerEvent& event : eventData) 635 | { 636 | if (GetEventHash(event) == selectedEvent.Hash) 637 | { 638 | float time = TicksToMs * (float)(event.TicksEnd - event.TicksBegin); 639 | selectedEvent.AddSample(time); 640 | pName = event.pName; 641 | eventTime = time; 642 | ++n; 643 | } 644 | } 645 | } 646 | } 647 | 648 | if (eventTime) 649 | { 650 | ImGui::Text(pName); 651 | if (ImGui::BeginTable("TooltipTable", 2)) 652 | { 653 | ImGui::TableNextColumn(); 654 | ImGui::Text("Time:"); 655 | ImGui::TableNextColumn(); 656 | ImGui::Text("%.2f ms", eventTime); 657 | 658 | ImGui::TableNextColumn(); 659 | ImGui::Text("Occurances:"); 660 | ImGui::TableNextColumn(); 661 | ImGui::Text("%d", n); 662 | 663 | ImGui::TableNextColumn(); 664 | ImGui::Text("Moving Average:"); 665 | ImGui::TableNextColumn(); 666 | ImGui::Text("%.2f ms", selectedEvent.MovingAverageTime); 667 | 668 | ImGui::TableNextColumn(); 669 | ImGui::Text("Min/Max:"); 670 | ImGui::TableNextColumn(); 671 | ImGui::Text("%.2f/%.2f ms", selectedEvent.MinTime, selectedEvent.MaxTime); 672 | 673 | ImGui::EndTable(); 674 | } 675 | } 676 | } 677 | ImGui::EndGroup(); 678 | 679 | // Horizontal scroll bar 680 | ImS64 scrollH = -(ImS64)context.TimelineOffset.x; 681 | ImGui::ScrollbarEx(ImRect(ImVec2(timelineRect.Min.x, timelineRect.Max.y), ImVec2(timelineRect.Max.x + style.ScrollBarSize, timelineRect.Max.y + style.ScrollBarSize)), ImGui::GetID("ScrollH"), ImGuiAxis_X, &scrollH, (ImS64)timelineRect.GetSize().x, (ImS64)timelineWidth, ImDrawFlags_None); 682 | context.TimelineOffset.x = -(float)scrollH; 683 | 684 | // Vertical scroll bar 685 | ImS64 scrollV = -(ImS64)context.TimelineOffset.y; 686 | ImGui::ScrollbarEx(ImRect(ImVec2(timelineRect.Max.x, timelineRect.Min.y), ImVec2(timelineRect.Max.x + style.ScrollBarSize, timelineRect.Max.y)), ImGui::GetID("ScrollV"), ImGuiAxis_Y, &scrollV, (ImS64)timelineRect.GetSize().y, (ImS64)timelineHeight, ImDrawFlags_None); 687 | context.TimelineOffset.y = -(float)scrollV; 688 | } 689 | } 690 | 691 | 692 | void DrawProfilerHUD() 693 | { 694 | HUDContext& context = Context(); 695 | StyleOptions& style = context.Style; 696 | 697 | if (!context.IconFont) 698 | { 699 | { 700 | ImFontConfig fontConfig; 701 | fontConfig.MergeMode = false; 702 | strcpy_s(fontConfig.Name, "Roboto-Regular"); 703 | context.TextFont = ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(roboto_regular_compressed_data, roboto_regular_compressed_size, 0.0f, &fontConfig); 704 | } 705 | 706 | { 707 | ImFontConfig fontConfig; 708 | fontConfig.MergeMode = true; 709 | strcpy_s(fontConfig.Name, "FontAwesome"); 710 | static const ImWchar icon_ranges[] = { ICON_MIN_FA, ICON_MAX_FA, 0 }; 711 | context.IconFont = ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(font_awesome_compressed_data, font_awesome_compressed_size, 0.0f, &fontConfig, icon_ranges); 712 | } 713 | } 714 | 715 | ImGui::PushFont(context.TextFont); 716 | ImGui::PushStyleColor(ImGuiCol_Text, style.FGTextColor); 717 | 718 | if (gProfiler.IsPaused()) 719 | ImGui::Text("Paused"); 720 | else 721 | ImGui::Text("Press Space to pause"); 722 | 723 | ImGui::SameLine(100); 724 | 725 | ImGui::Dummy(ImVec2(30, 0)); 726 | ImGui::SameLine(); 727 | 728 | ImGui::Text("Filter"); 729 | ImGui::SetNextItemWidth(150); 730 | ImGui::SameLine(); 731 | ImGui::InputText("##Search", context.SearchString, ARRAYSIZE(context.SearchString)); 732 | ImGui::SameLine(); 733 | if (ImGui::Button(ICON_FA_TIMES "##clearfilter")) 734 | context.SearchString[0] = 0; 735 | ImGui::SameLine(); 736 | if (ImGui::Button(ICON_FA_PAINT_BRUSH "##styleeditor")) 737 | ImGui::OpenPopup("Style Editor"); 738 | 739 | if (ImGui::BeginPopup("Style Editor")) 740 | { 741 | EditStyle(style); 742 | ImGui::EndPopup(); 743 | } 744 | 745 | if (ImGui::IsWindowFocused() && ImGui::IsKeyPressed(ImGuiKey_Space)) 746 | context.IsPaused = !context.IsPaused; 747 | 748 | if (ImGui::IsWindowHovered() && (ImGui::IsMouseClicked(ImGuiMouseButton_Right) || ImGui::IsMouseClicked(ImGuiMouseButton_Left))) 749 | ImGui::SetWindowFocus(); 750 | 751 | gProfiler.SetPaused(context.IsPaused); 752 | gGPUProfiler.SetPaused(context.IsPaused); 753 | 754 | DrawProfilerTimeline(ImVec2(0, 0)); 755 | 756 | ImGui::PopStyleColor(); 757 | ImGui::PopFont(); 758 | } 759 | 760 | #endif 761 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | // Dear ImGui: standalone example application for Windows API + DirectX 12 2 | 3 | // Learn about Dear ImGui: 4 | // - FAQ https://dearimgui.com/faq 5 | // - Getting Started https://dearimgui.com/getting-started 6 | // - Documentation https://dearimgui.com/docs (same as your local docs/ folder). 7 | // - Introduction, links and more at the top of imgui.cpp 8 | 9 | #define NOMINMAX 10 | 11 | #include "imgui.h" 12 | #include "imgui_impl_win32.h" 13 | #include "imgui_impl_dx12.h" 14 | #include 15 | #include 16 | #include 17 | 18 | #include "Profiler.h" 19 | 20 | #ifdef _DEBUG 21 | #define DX12_ENABLE_DEBUG_LAYER 22 | #endif 23 | 24 | #ifdef DX12_ENABLE_DEBUG_LAYER 25 | #include 26 | #pragma comment(lib, "dxguid.lib") 27 | #endif 28 | 29 | //#define SUPERLUMINAL_API 30 | #ifdef SUPERLUMINAL_API 31 | #include 32 | static PerformanceAPI_Functions sSuperluminal{}; 33 | PerformanceAPI_ModuleHandle sSuperluminalHandle = nullptr; 34 | #endif 35 | 36 | // Config for example app 37 | static const int APP_NUM_FRAMES_IN_FLIGHT = 2; 38 | static const int APP_NUM_BACK_BUFFERS = 2; 39 | static const int APP_SRV_HEAP_SIZE = 64; 40 | 41 | struct FrameContext 42 | { 43 | ID3D12CommandAllocator* CommandAllocator; 44 | ID3D12CommandAllocator* PresentCommandAllocator; 45 | UINT64 FenceValue; 46 | }; 47 | 48 | // Simple free list based allocator 49 | struct ExampleDescriptorHeapAllocator 50 | { 51 | ID3D12DescriptorHeap* Heap = nullptr; 52 | D3D12_DESCRIPTOR_HEAP_TYPE HeapType = D3D12_DESCRIPTOR_HEAP_TYPE_NUM_TYPES; 53 | D3D12_CPU_DESCRIPTOR_HANDLE HeapStartCpu; 54 | D3D12_GPU_DESCRIPTOR_HANDLE HeapStartGpu; 55 | UINT HeapHandleIncrement; 56 | ImVector FreeIndices; 57 | 58 | void Create(ID3D12Device* device, ID3D12DescriptorHeap* heap) 59 | { 60 | IM_ASSERT(Heap == nullptr && FreeIndices.empty()); 61 | Heap = heap; 62 | D3D12_DESCRIPTOR_HEAP_DESC desc = heap->GetDesc(); 63 | HeapType = desc.Type; 64 | HeapStartCpu = Heap->GetCPUDescriptorHandleForHeapStart(); 65 | HeapStartGpu = Heap->GetGPUDescriptorHandleForHeapStart(); 66 | HeapHandleIncrement = device->GetDescriptorHandleIncrementSize(HeapType); 67 | FreeIndices.reserve((int)desc.NumDescriptors); 68 | for (int n = desc.NumDescriptors; n > 0; n--) 69 | FreeIndices.push_back(n - 1); 70 | } 71 | void Destroy() 72 | { 73 | Heap = nullptr; 74 | FreeIndices.clear(); 75 | } 76 | void Alloc(D3D12_CPU_DESCRIPTOR_HANDLE* out_cpu_desc_handle, D3D12_GPU_DESCRIPTOR_HANDLE* out_gpu_desc_handle) 77 | { 78 | IM_ASSERT(FreeIndices.Size > 0); 79 | int idx = FreeIndices.back(); 80 | FreeIndices.pop_back(); 81 | out_cpu_desc_handle->ptr = HeapStartCpu.ptr + (idx * HeapHandleIncrement); 82 | out_gpu_desc_handle->ptr = HeapStartGpu.ptr + (idx * HeapHandleIncrement); 83 | } 84 | void Free(D3D12_CPU_DESCRIPTOR_HANDLE out_cpu_desc_handle, D3D12_GPU_DESCRIPTOR_HANDLE out_gpu_desc_handle) 85 | { 86 | int cpu_idx = (int)((out_cpu_desc_handle.ptr - HeapStartCpu.ptr) / HeapHandleIncrement); 87 | int gpu_idx = (int)((out_gpu_desc_handle.ptr - HeapStartGpu.ptr) / HeapHandleIncrement); 88 | IM_ASSERT(cpu_idx == gpu_idx); 89 | FreeIndices.push_back(cpu_idx); 90 | } 91 | }; 92 | 93 | // Data 94 | static FrameContext g_frameContext[APP_NUM_FRAMES_IN_FLIGHT] = {}; 95 | static UINT g_frameIndex = 0; 96 | 97 | static ID3D12Device* g_pd3dDevice = nullptr; 98 | static ID3D12DescriptorHeap* g_pd3dRtvDescHeap = nullptr; 99 | static ID3D12DescriptorHeap* g_pd3dSrvDescHeap = nullptr; 100 | static ExampleDescriptorHeapAllocator g_pd3dSrvDescHeapAlloc = {}; 101 | static ID3D12CommandQueue* g_pd3dCommandQueue = nullptr; 102 | static ID3D12GraphicsCommandList* g_pd3dCommandList = nullptr; 103 | static ID3D12Fence* g_fence = nullptr; 104 | static HANDLE g_fenceEvent = nullptr; 105 | static UINT64 g_fenceLastSignaledValue = 0; 106 | static IDXGISwapChain3* g_pSwapChain = nullptr; 107 | static bool g_SwapChainTearingSupport = false; 108 | static bool g_SwapChainOccluded = false; 109 | static HANDLE g_hSwapChainWaitableObject = nullptr; 110 | static bool g_SwapChainEnableTearing = true; 111 | static bool g_SwapChainEnableVSync = true; 112 | static bool g_SwapChainEnableWaitableObject = true; 113 | static int g_SwapChainMaximumFrameLatency = 2; 114 | static ID3D12Resource* g_mainRenderTargetResource[APP_NUM_BACK_BUFFERS] = {}; 115 | static D3D12_CPU_DESCRIPTOR_HANDLE g_mainRenderTargetDescriptor[APP_NUM_BACK_BUFFERS] = {}; 116 | static HWND g_MainWindow = nullptr; 117 | 118 | static ID3D12Resource* g_GPUWorkloadResources[2]; 119 | static float g_CPUWorkload = 0.0f; 120 | static float g_CPUWorkloadVariance = 0.0f; 121 | static int g_GPUWorkload = 0; 122 | static float g_GPUWorkloadVariance = 0.0f; 123 | 124 | 125 | // Forward declarations of helper functions 126 | bool CreateDeviceD3D(HWND hWnd); 127 | void CleanupDeviceD3D(); 128 | bool CreateSwapchain(HWND hWnd); 129 | void CleanupRenderTarget(); 130 | void WaitForPendingOperations(); 131 | void WaitForSwapchain(); 132 | FrameContext* WaitForNextFrameContext(); 133 | LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); 134 | 135 | // Main code 136 | int main(int, char**) 137 | { 138 | // Make process DPI aware and obtain main monitor scale 139 | ImGui_ImplWin32_EnableDpiAwareness(); 140 | float main_scale = ImGui_ImplWin32_GetDpiScaleForMonitor(::MonitorFromPoint(POINT{ 0, 0 }, MONITOR_DEFAULTTOPRIMARY)); 141 | 142 | // Create application window 143 | WNDCLASSEXW wc = { sizeof(wc), CS_CLASSDC, WndProc, 0L, 0L, GetModuleHandle(nullptr), nullptr, nullptr, nullptr, nullptr, L"ImGui Example", nullptr }; 144 | ::RegisterClassExW(&wc); 145 | HWND hwnd = ::CreateWindowW(wc.lpszClassName, L"Dear ImGui DirectX12 Example", WS_OVERLAPPEDWINDOW, 100, 100, (int)(1280 * main_scale), (int)(800 * main_scale), nullptr, nullptr, wc.hInstance, nullptr); 146 | g_MainWindow = hwnd; 147 | 148 | // Initialize Direct3D 149 | if (!CreateDeviceD3D(hwnd)) 150 | { 151 | CleanupDeviceD3D(); 152 | ::UnregisterClassW(wc.lpszClassName, wc.hInstance); 153 | return 1; 154 | } 155 | 156 | // Show the window 157 | ::ShowWindow(hwnd, SW_SHOWDEFAULT); 158 | ::UpdateWindow(hwnd); 159 | 160 | // Setup Dear ImGui context 161 | IMGUI_CHECKVERSION(); 162 | ImGui::CreateContext(); 163 | ImGuiIO& io = ImGui::GetIO(); (void)io; 164 | io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls 165 | io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls 166 | io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Docking 167 | io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Enable Multi-Viewport / Platform Windows 168 | //io.ConfigViewportsNoAutoMerge = true; 169 | //io.ConfigViewportsNoTaskBarIcon = true; 170 | 171 | // Setup Dear ImGui style 172 | ImGui::StyleColorsDark(); 173 | //ImGui::StyleColorsLight(); 174 | 175 | // Setup scaling 176 | ImGuiStyle& style = ImGui::GetStyle(); 177 | style.ScaleAllSizes(main_scale); // Bake a fixed style scale. (until we have a solution for dynamic style scaling, changing this requires resetting Style + calling this again) 178 | style.FontScaleDpi = main_scale; // Set initial font scale. (using io.ConfigDpiScaleFonts=true makes this unnecessary. We leave both here for documentation purpose) 179 | io.ConfigDpiScaleFonts = true; // [Experimental] Automatically overwrite style.FontScaleDpi in Begin() when Monitor DPI changes. This will scale fonts but _NOT_ scale sizes/padding for now. 180 | io.ConfigDpiScaleViewports = true; // [Experimental] Scale Dear ImGui and Platform Windows when Monitor DPI changes. 181 | 182 | // When viewports are enabled we tweak WindowRounding/WindowBg so platform windows can look identical to regular ones. 183 | if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) 184 | { 185 | style.WindowRounding = 0.0f; 186 | style.Colors[ImGuiCol_WindowBg].w = 1.0f; 187 | } 188 | 189 | // Setup Platform/Renderer backends 190 | ImGui_ImplWin32_Init(hwnd); 191 | 192 | ImGui_ImplDX12_InitInfo init_info = {}; 193 | init_info.Device = g_pd3dDevice; 194 | init_info.CommandQueue = g_pd3dCommandQueue; 195 | init_info.NumFramesInFlight = APP_NUM_FRAMES_IN_FLIGHT; 196 | init_info.RTVFormat = DXGI_FORMAT_R8G8B8A8_UNORM; 197 | init_info.DSVFormat = DXGI_FORMAT_UNKNOWN; 198 | // Allocating SRV descriptors (for textures) is up to the application, so we provide callbacks. 199 | // (current version of the backend will only allocate one descriptor, future versions will need to allocate more) 200 | init_info.SrvDescriptorHeap = g_pd3dSrvDescHeap; 201 | init_info.SrvDescriptorAllocFn = [](ImGui_ImplDX12_InitInfo*, D3D12_CPU_DESCRIPTOR_HANDLE* out_cpu_handle, D3D12_GPU_DESCRIPTOR_HANDLE* out_gpu_handle) { return g_pd3dSrvDescHeapAlloc.Alloc(out_cpu_handle, out_gpu_handle); }; 202 | init_info.SrvDescriptorFreeFn = [](ImGui_ImplDX12_InitInfo*, D3D12_CPU_DESCRIPTOR_HANDLE cpu_handle, D3D12_GPU_DESCRIPTOR_HANDLE gpu_handle) { return g_pd3dSrvDescHeapAlloc.Free(cpu_handle, gpu_handle); }; 203 | ImGui_ImplDX12_Init(&init_info); 204 | 205 | gProfiler.Initialize(25); 206 | Span queues(&g_pd3dCommandQueue, 1); 207 | gGPUProfiler.Initialize(g_pd3dDevice, queues, 4); 208 | 209 | #ifdef SUPERLUMINAL_API 210 | sSuperluminalHandle = PerformanceAPI_LoadFrom(L"C:\\Program Files\\Superluminal\\Performance\\API\\dll\\x64\\PerformanceAPI.dll", &sSuperluminal); 211 | if (sSuperluminalHandle) 212 | { 213 | CPUProfilerCallbacks callbacks{ 214 | .OnEventBegin = [](const char* pName, void* /*pUserData*/) { sSuperluminal.BeginEvent(pName, nullptr, PERFORMANCEAPI_DEFAULT_COLOR); }, 215 | .OnEventEnd = [](void* /*pUserData*/) { sSuperluminal.EndEvent(); }, 216 | .pUserData = nullptr 217 | }; 218 | gProfiler.SetEventCallback(callbacks); 219 | } 220 | #endif 221 | 222 | // Before 1.91.6: our signature was using a single descriptor. From 1.92, specifying SrvDescriptorAllocFn/SrvDescriptorFreeFn will be required to benefit from new features. 223 | //ImGui_ImplDX12_Init(g_pd3dDevice, APP_NUM_FRAMES_IN_FLIGHT, DXGI_FORMAT_R8G8B8A8_UNORM, g_pd3dSrvDescHeap, g_pd3dSrvDescHeap->GetCPUDescriptorHandleForHeapStart(), g_pd3dSrvDescHeap->GetGPUDescriptorHandleForHeapStart()); 224 | 225 | // Load Fonts 226 | // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. 227 | // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple. 228 | // - If the file cannot be loaded, the function will return a nullptr. Please handle those errors in your application (e.g. use an assertion, or display an error and quit). 229 | // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. 230 | // - Read 'docs/FONTS.md' for more instructions and details. If you like the default font but want it to scale better, consider using the 'ProggyVector' from the same author! 231 | // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! 232 | style.FontSizeBase = 14.0f; 233 | //io.Fonts->AddFontDefault(); 234 | //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf"); 235 | //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf"); 236 | //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf"); 237 | //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf"); 238 | //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf"); 239 | //IM_ASSERT(font != nullptr); 240 | 241 | // Our state 242 | bool show_demo_window = true; 243 | bool show_another_window = false; 244 | ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); 245 | 246 | PROFILE_REGISTER_THREAD("Main Thread"); 247 | 248 | // Main loop 249 | bool done = false; 250 | while (!done) 251 | { 252 | PROFILE_FRAME(); 253 | 254 | { 255 | PROFILE_CPU_SCOPE("WndProc"); 256 | 257 | // Poll and handle messages (inputs, window resize, etc.) 258 | // See the WndProc() function below for our to dispatch events to the Win32 backend. 259 | MSG msg; 260 | while (::PeekMessage(&msg, nullptr, 0U, 0U, PM_REMOVE)) 261 | { 262 | ::TranslateMessage(&msg); 263 | ::DispatchMessage(&msg); 264 | if (msg.message == WM_QUIT) 265 | done = true; 266 | } 267 | } 268 | 269 | if (done) 270 | break; 271 | 272 | // Handle window screen locked 273 | if ((g_SwapChainOccluded && g_pSwapChain->Present(0, DXGI_PRESENT_TEST) == DXGI_STATUS_OCCLUDED) || ::IsIconic(hwnd)) 274 | { 275 | ::Sleep(10); 276 | continue; 277 | } 278 | g_SwapChainOccluded = false; 279 | 280 | WaitForSwapchain(); 281 | FrameContext* frameCtx = WaitForNextFrameContext(); 282 | 283 | // Start the Dear ImGui frame 284 | { 285 | PROFILE_CPU_SCOPE("NewFrame"); 286 | 287 | ImGui_ImplDX12_NewFrame(); 288 | ImGui_ImplWin32_NewFrame(); 289 | ImGui::NewFrame(); 290 | } 291 | 292 | { 293 | PROFILE_CPU_SCOPE("ProfilerHUD"); 294 | DrawProfilerHUD(); 295 | } 296 | 297 | if (show_demo_window) 298 | ImGui::ShowDemoWindow(&show_demo_window); 299 | 300 | { 301 | if (ImGui::Begin("Options")) 302 | { 303 | PROFILE_CPU_SCOPE("Options"); 304 | 305 | if (ImGui::SliderInt("Maximum Frame Latency", &g_SwapChainMaximumFrameLatency, 1, 4)) 306 | CreateSwapchain(g_MainWindow); 307 | if (ImGui::Checkbox("Waitable SwapChain", &g_SwapChainEnableWaitableObject)) 308 | CreateSwapchain(g_MainWindow); 309 | if(ImGui::Checkbox("VSync", &g_SwapChainEnableVSync)) 310 | CreateSwapchain(g_MainWindow); 311 | if(ImGui::Checkbox("Allow Tearing", &g_SwapChainEnableTearing)) 312 | CreateSwapchain(g_MainWindow); 313 | 314 | ImGui::SliderFloat("CPU Load", &g_CPUWorkload, 0.0f, 33.66f, "%.2f"); 315 | ImGui::SliderFloat("CPU Load Variance", &g_CPUWorkloadVariance, 0.0f, 1.0f, "%.2f"); 316 | ImGui::SliderInt("GPU Load", &g_GPUWorkload, 0, 500); 317 | ImGui::SliderFloat("GPU Load Variance", &g_GPUWorkloadVariance, 0.0f, 1.0f, "%.2f"); 318 | 319 | ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); 320 | } 321 | ImGui::End(); 322 | } 323 | 324 | float randomV_CPU = ((float)rand() / RAND_MAX) * 2 - 1; 325 | float cpu_workload = g_CPUWorkload + g_CPUWorkload * randomV_CPU * g_CPUWorkloadVariance; 326 | 327 | float randomV_GPU = ((float)rand() / RAND_MAX) * 2 - 1; 328 | int gpu_workload = (int)(g_GPUWorkload + g_GPUWorkload * randomV_GPU * g_GPUWorkloadVariance); 329 | 330 | if (cpu_workload > 0.0f) 331 | { 332 | PROFILE_CPU_SCOPE("CPU Load"); 333 | 334 | LARGE_INTEGER freq, start, now; 335 | QueryPerformanceFrequency(&freq); 336 | QueryPerformanceCounter(&start); 337 | 338 | double target = cpu_workload * 0.001f * freq.QuadPart; // 2 ms in ticks 339 | do 340 | { 341 | QueryPerformanceCounter(&now); 342 | } while ((now.QuadPart - start.QuadPart) < target); 343 | } 344 | 345 | // Rendering 346 | { 347 | PROFILE_CPU_SCOPE("ImGui Render"); 348 | ImGui::Render(); 349 | } 350 | 351 | UINT backBufferIdx = g_pSwapChain->GetCurrentBackBufferIndex(); 352 | 353 | frameCtx->CommandAllocator->Reset(); 354 | g_pd3dCommandList->Reset(frameCtx->CommandAllocator, nullptr); 355 | 356 | { 357 | PROFILE_GPU_SCOPE(g_pd3dCommandList, "GPU Workload"); 358 | for (int i = 0; i < gpu_workload; ++i) 359 | g_pd3dCommandList->CopyResource(g_GPUWorkloadResources[0], g_GPUWorkloadResources[1]); 360 | } 361 | 362 | g_pd3dCommandList->Close(); 363 | 364 | { 365 | Span cmdlists((ID3D12CommandList**)&g_pd3dCommandList, 1); 366 | PROFILE_EXECUTE_COMMANDLISTS(g_pd3dCommandQueue, cmdlists); 367 | g_pd3dCommandQueue->ExecuteCommandLists(1, (ID3D12CommandList* const*)&g_pd3dCommandList); 368 | } 369 | 370 | frameCtx->PresentCommandAllocator->Reset(); 371 | g_pd3dCommandList->Reset(frameCtx->CommandAllocator, nullptr); 372 | 373 | 374 | { 375 | PROFILE_GPU_SCOPE(g_pd3dCommandList, "Render"); 376 | 377 | D3D12_RESOURCE_BARRIER barrier = {}; 378 | barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; 379 | barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; 380 | barrier.Transition.pResource = g_mainRenderTargetResource[backBufferIdx]; 381 | barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; 382 | barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT; 383 | barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET; 384 | 385 | g_pd3dCommandList->ResourceBarrier(1, &barrier); 386 | 387 | // Render Dear ImGui graphics 388 | const float clear_color_with_alpha[4] = { clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w }; 389 | g_pd3dCommandList->ClearRenderTargetView(g_mainRenderTargetDescriptor[backBufferIdx], clear_color_with_alpha, 0, nullptr); 390 | g_pd3dCommandList->OMSetRenderTargets(1, &g_mainRenderTargetDescriptor[backBufferIdx], FALSE, nullptr); 391 | g_pd3dCommandList->SetDescriptorHeaps(1, &g_pd3dSrvDescHeap); 392 | ImGui_ImplDX12_RenderDrawData(ImGui::GetDrawData(), g_pd3dCommandList); 393 | barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET; 394 | barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT; 395 | g_pd3dCommandList->ResourceBarrier(1, &barrier); 396 | } 397 | 398 | g_pd3dCommandList->Close(); 399 | 400 | { 401 | Span cmdlists((ID3D12CommandList**)&g_pd3dCommandList, 1); 402 | PROFILE_EXECUTE_COMMANDLISTS(g_pd3dCommandQueue, cmdlists); 403 | g_pd3dCommandQueue->ExecuteCommandLists(1, (ID3D12CommandList* const*)&g_pd3dCommandList); 404 | } 405 | 406 | // Update and Render additional Platform Windows 407 | if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) 408 | { 409 | PROFILE_CPU_SCOPE("Update Viewports"); 410 | ImGui::UpdatePlatformWindows(); 411 | ImGui::RenderPlatformWindowsDefault(); 412 | } 413 | 414 | g_pd3dCommandQueue->Signal(g_fence, ++g_fenceLastSignaledValue); 415 | frameCtx->FenceValue = g_fenceLastSignaledValue; 416 | 417 | // Present 418 | { 419 | PROFILE_CPU_SCOPE("Present"); 420 | HRESULT hr = g_pSwapChain->Present(g_SwapChainEnableVSync ? 1 : 0, !g_SwapChainEnableVSync && g_SwapChainTearingSupport ? DXGI_PRESENT_ALLOW_TEARING : 0); // Present without vsync 421 | PROFILE_PRESENT(g_pSwapChain); 422 | g_SwapChainOccluded = (hr == DXGI_STATUS_OCCLUDED); 423 | g_frameIndex++; 424 | } 425 | } 426 | 427 | WaitForPendingOperations(); 428 | 429 | // Cleanup 430 | ImGui_ImplDX12_Shutdown(); 431 | ImGui_ImplWin32_Shutdown(); 432 | ImGui::DestroyContext(); 433 | 434 | gProfiler.Shutdown(); 435 | gGPUProfiler.Shutdown(); 436 | 437 | CleanupDeviceD3D(); 438 | ::DestroyWindow(hwnd); 439 | ::UnregisterClassW(wc.lpszClassName, wc.hInstance); 440 | 441 | return 0; 442 | } 443 | 444 | // Helper functions 445 | bool CreateDeviceD3D(HWND hWnd) 446 | { 447 | // [DEBUG] Enable debug interface 448 | #ifdef DX12_ENABLE_DEBUG_LAYER 449 | ID3D12Debug* pdx12Debug = nullptr; 450 | if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&pdx12Debug)))) 451 | pdx12Debug->EnableDebugLayer(); 452 | #endif 453 | 454 | // Create device 455 | D3D_FEATURE_LEVEL featureLevel = D3D_FEATURE_LEVEL_11_0; 456 | if (D3D12CreateDevice(nullptr, featureLevel, IID_PPV_ARGS(&g_pd3dDevice)) != S_OK) 457 | return false; 458 | 459 | // [DEBUG] Setup debug interface to break on any warnings/errors 460 | #ifdef DX12_ENABLE_DEBUG_LAYER 461 | if (pdx12Debug != nullptr) 462 | { 463 | ID3D12InfoQueue* pInfoQueue = nullptr; 464 | g_pd3dDevice->QueryInterface(IID_PPV_ARGS(&pInfoQueue)); 465 | pInfoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_ERROR, true); 466 | pInfoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_CORRUPTION, true); 467 | pInfoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_WARNING, true); 468 | pInfoQueue->Release(); 469 | pdx12Debug->Release(); 470 | } 471 | #endif 472 | 473 | { 474 | D3D12_DESCRIPTOR_HEAP_DESC desc = {}; 475 | desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV; 476 | desc.NumDescriptors = APP_NUM_BACK_BUFFERS; 477 | desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE; 478 | desc.NodeMask = 1; 479 | if (g_pd3dDevice->CreateDescriptorHeap(&desc, IID_PPV_ARGS(&g_pd3dRtvDescHeap)) != S_OK) 480 | return false; 481 | 482 | SIZE_T rtvDescriptorSize = g_pd3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV); 483 | D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = g_pd3dRtvDescHeap->GetCPUDescriptorHandleForHeapStart(); 484 | for (UINT i = 0; i < APP_NUM_BACK_BUFFERS; i++) 485 | { 486 | g_mainRenderTargetDescriptor[i] = rtvHandle; 487 | rtvHandle.ptr += rtvDescriptorSize; 488 | } 489 | } 490 | 491 | { 492 | D3D12_DESCRIPTOR_HEAP_DESC desc = {}; 493 | desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV; 494 | desc.NumDescriptors = APP_SRV_HEAP_SIZE; 495 | desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE; 496 | if (g_pd3dDevice->CreateDescriptorHeap(&desc, IID_PPV_ARGS(&g_pd3dSrvDescHeap)) != S_OK) 497 | return false; 498 | g_pd3dSrvDescHeapAlloc.Create(g_pd3dDevice, g_pd3dSrvDescHeap); 499 | } 500 | 501 | { 502 | D3D12_COMMAND_QUEUE_DESC desc = {}; 503 | desc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; 504 | desc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; 505 | desc.NodeMask = 1; 506 | if (g_pd3dDevice->CreateCommandQueue(&desc, IID_PPV_ARGS(&g_pd3dCommandQueue)) != S_OK) 507 | return false; 508 | } 509 | 510 | for (UINT i = 0; i < APP_NUM_FRAMES_IN_FLIGHT; i++) 511 | { 512 | if (g_pd3dDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&g_frameContext[i].CommandAllocator)) != S_OK) 513 | return false; 514 | if (g_pd3dDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&g_frameContext[i].PresentCommandAllocator)) != S_OK) 515 | return false; 516 | } 517 | 518 | if (g_pd3dDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, g_frameContext[0].CommandAllocator, nullptr, IID_PPV_ARGS(&g_pd3dCommandList)) != S_OK || 519 | g_pd3dCommandList->Close() != S_OK) 520 | return false; 521 | 522 | if (g_pd3dDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&g_fence)) != S_OK) 523 | return false; 524 | 525 | g_fenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); 526 | if (g_fenceEvent == nullptr) 527 | return false; 528 | 529 | 530 | D3D12_RESOURCE_DESC bufferDesc{ 531 | .Dimension = D3D12_RESOURCE_DIMENSION_BUFFER, 532 | .Alignment = 0, 533 | .Width = 32 * 1024 * 1024, 534 | .Height = 1, 535 | .DepthOrArraySize = 1, 536 | .MipLevels = 1, 537 | .Format = DXGI_FORMAT_UNKNOWN, 538 | .SampleDesc = { 539 | .Count = 1, 540 | .Quality = 0, 541 | }, 542 | .Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR, 543 | .Flags = D3D12_RESOURCE_FLAG_NONE, 544 | }; 545 | 546 | D3D12_HEAP_PROPERTIES heapProps{ 547 | .Type = D3D12_HEAP_TYPE_DEFAULT, 548 | .CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN, 549 | .MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN, 550 | .CreationNodeMask = 0, 551 | .VisibleNodeMask = 0, 552 | }; 553 | 554 | for (int i = 0; i < 2; ++i) 555 | g_pd3dDevice->CreateCommittedResource(&heapProps, D3D12_HEAP_FLAG_ALLOW_ALL_BUFFERS_AND_TEXTURES, &bufferDesc, D3D12_RESOURCE_STATE_COMMON, nullptr, IID_PPV_ARGS(&g_GPUWorkloadResources[i])); 556 | 557 | return true; 558 | } 559 | 560 | void CleanupDeviceD3D() 561 | { 562 | CleanupRenderTarget(); 563 | 564 | for (int i = 0; i < 2; ++i) 565 | g_GPUWorkloadResources[i]->Release(); 566 | 567 | if (g_pSwapChain) { g_pSwapChain->SetFullscreenState(false, nullptr); g_pSwapChain->Release(); g_pSwapChain = nullptr; } 568 | if (g_hSwapChainWaitableObject != nullptr) { CloseHandle(g_hSwapChainWaitableObject); } 569 | for (UINT i = 0; i < APP_NUM_FRAMES_IN_FLIGHT; i++) 570 | { 571 | if (g_frameContext[i].CommandAllocator) 572 | { 573 | g_frameContext[i].CommandAllocator->Release(); 574 | g_frameContext[i].CommandAllocator = nullptr; 575 | } 576 | if (g_frameContext[i].PresentCommandAllocator) 577 | { 578 | g_frameContext[i].PresentCommandAllocator->Release(); 579 | g_frameContext[i].PresentCommandAllocator = nullptr; 580 | } 581 | } 582 | if (g_pd3dCommandQueue) { g_pd3dCommandQueue->Release(); g_pd3dCommandQueue = nullptr; } 583 | if (g_pd3dCommandList) { g_pd3dCommandList->Release(); g_pd3dCommandList = nullptr; } 584 | if (g_pd3dRtvDescHeap) { g_pd3dRtvDescHeap->Release(); g_pd3dRtvDescHeap = nullptr; } 585 | if (g_pd3dSrvDescHeap) { g_pd3dSrvDescHeap->Release(); g_pd3dSrvDescHeap = nullptr; } 586 | if (g_fence) { g_fence->Release(); g_fence = nullptr; } 587 | if (g_fenceEvent) { CloseHandle(g_fenceEvent); g_fenceEvent = nullptr; } 588 | if (g_pd3dDevice) { g_pd3dDevice->Release(); g_pd3dDevice = nullptr; } 589 | 590 | #ifdef DX12_ENABLE_DEBUG_LAYER 591 | IDXGIDebug1* pDebug = nullptr; 592 | if (SUCCEEDED(DXGIGetDebugInterface1(0, IID_PPV_ARGS(&pDebug)))) 593 | { 594 | pDebug->ReportLiveObjects(DXGI_DEBUG_ALL, DXGI_DEBUG_RLO_SUMMARY); 595 | pDebug->Release(); 596 | } 597 | #endif 598 | } 599 | 600 | bool CreateSwapchain(HWND hWnd) 601 | { 602 | CleanupRenderTarget(); 603 | 604 | if (g_pSwapChain) 605 | { 606 | g_pSwapChain->Release(); 607 | g_pSwapChain = nullptr; 608 | } 609 | 610 | // Setup swap chain 611 | DXGI_SWAP_CHAIN_DESC1 sd; 612 | ZeroMemory(&sd, sizeof(sd)); 613 | sd.BufferCount = APP_NUM_BACK_BUFFERS; 614 | sd.Width = 0; 615 | sd.Height = 0; 616 | sd.Format = DXGI_FORMAT_R8G8B8A8_UNORM; 617 | sd.Flags = g_SwapChainEnableWaitableObject ? DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT : 0; 618 | sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; 619 | sd.SampleDesc.Count = 1; 620 | sd.SampleDesc.Quality = 0; 621 | sd.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; 622 | sd.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED; 623 | sd.Scaling = DXGI_SCALING_STRETCH; 624 | sd.Stereo = FALSE; 625 | 626 | IDXGIFactory5* dxgiFactory = nullptr; 627 | IDXGISwapChain1* swapChain1 = nullptr; 628 | if (CreateDXGIFactory1(IID_PPV_ARGS(&dxgiFactory)) != S_OK) 629 | return false; 630 | 631 | BOOL allow_tearing = FALSE; 632 | dxgiFactory->CheckFeatureSupport(DXGI_FEATURE_PRESENT_ALLOW_TEARING, &allow_tearing, sizeof(allow_tearing)); 633 | g_SwapChainTearingSupport = allow_tearing == TRUE && g_SwapChainEnableTearing; 634 | if (g_SwapChainTearingSupport) 635 | sd.Flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING; 636 | 637 | if (dxgiFactory->CreateSwapChainForHwnd(g_pd3dCommandQueue, hWnd, &sd, nullptr, nullptr, &swapChain1) != S_OK) 638 | return false; 639 | if (swapChain1->QueryInterface(IID_PPV_ARGS(&g_pSwapChain)) != S_OK) 640 | return false; 641 | if (g_SwapChainTearingSupport) 642 | dxgiFactory->MakeWindowAssociation(hWnd, DXGI_MWA_NO_ALT_ENTER); 643 | 644 | swapChain1->Release(); 645 | dxgiFactory->Release(); 646 | 647 | if (g_hSwapChainWaitableObject) 648 | CloseHandle(g_hSwapChainWaitableObject); 649 | 650 | if (g_SwapChainEnableWaitableObject) 651 | { 652 | g_pSwapChain->SetMaximumFrameLatency(g_SwapChainMaximumFrameLatency); 653 | g_hSwapChainWaitableObject = g_pSwapChain->GetFrameLatencyWaitableObject(); 654 | } 655 | else 656 | { 657 | g_hSwapChainWaitableObject = nullptr; 658 | } 659 | 660 | for (UINT i = 0; i < APP_NUM_BACK_BUFFERS; i++) 661 | { 662 | ID3D12Resource* pBackBuffer = nullptr; 663 | g_pSwapChain->GetBuffer(i, IID_PPV_ARGS(&pBackBuffer)); 664 | g_pd3dDevice->CreateRenderTargetView(pBackBuffer, nullptr, g_mainRenderTargetDescriptor[i]); 665 | g_mainRenderTargetResource[i] = pBackBuffer; 666 | } 667 | return true; 668 | } 669 | 670 | void WaitForSwapchain() 671 | { 672 | PROFILE_CPU_SCOPE(); 673 | if (g_hSwapChainWaitableObject) 674 | { 675 | ::WaitForSingleObject(g_hSwapChainWaitableObject, INFINITE); 676 | } 677 | } 678 | 679 | void CleanupRenderTarget() 680 | { 681 | WaitForPendingOperations(); 682 | 683 | for (UINT i = 0; i < APP_NUM_BACK_BUFFERS; i++) 684 | if (g_mainRenderTargetResource[i]) { g_mainRenderTargetResource[i]->Release(); g_mainRenderTargetResource[i] = nullptr; } 685 | } 686 | 687 | void WaitForPendingOperations() 688 | { 689 | g_pd3dCommandQueue->Signal(g_fence, ++g_fenceLastSignaledValue); 690 | 691 | g_fence->SetEventOnCompletion(g_fenceLastSignaledValue, g_fenceEvent); 692 | ::WaitForSingleObject(g_fenceEvent, INFINITE); 693 | } 694 | 695 | FrameContext* WaitForNextFrameContext() 696 | { 697 | PROFILE_CPU_SCOPE(); 698 | 699 | FrameContext* frame_context = &g_frameContext[g_frameIndex % APP_NUM_FRAMES_IN_FLIGHT]; 700 | if (g_fence->GetCompletedValue() < frame_context->FenceValue) 701 | { 702 | g_fence->SetEventOnCompletion(frame_context->FenceValue, g_fenceEvent); 703 | ::WaitForSingleObject(g_fenceEvent, INFINITE); 704 | } 705 | 706 | return frame_context; 707 | } 708 | 709 | // Forward declare message handler from imgui_impl_win32.cpp 710 | extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); 711 | 712 | // Win32 message handler 713 | // You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs. 714 | // - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data. 715 | // - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your copy of the keyboard data. 716 | // Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags. 717 | LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) 718 | { 719 | if (ImGui_ImplWin32_WndProcHandler(hWnd, msg, wParam, lParam)) 720 | return true; 721 | 722 | switch (msg) 723 | { 724 | case WM_SIZE: 725 | if (g_pd3dDevice != nullptr && wParam != SIZE_MINIMIZED) 726 | { 727 | CreateSwapchain(hWnd); 728 | } 729 | return 0; 730 | case WM_SYSCOMMAND: 731 | if ((wParam & 0xfff0) == SC_KEYMENU) // Disable ALT application menu 732 | return 0; 733 | break; 734 | case WM_DESTROY: 735 | ::PostQuitMessage(0); 736 | return 0; 737 | } 738 | return ::DefWindowProcW(hWnd, msg, wParam, lParam); 739 | } 740 | -------------------------------------------------------------------------------- /Profiler.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "Profiler.h" 3 | 4 | #if WITH_PROFILING 5 | 6 | #define NOMINMAX 7 | #include 8 | #include 9 | 10 | Profiler gProfiler; 11 | GPUProfiler gGPUProfiler; 12 | 13 | 14 | // Thread-safe page allocator that recycles pages based on ID 15 | struct ProfilerAllocator 16 | { 17 | public: 18 | static constexpr uint32 cPageSize = 2 * 1024; 19 | 20 | struct Page 21 | { 22 | uint32 ID = 0; 23 | uint32 Size = 0; 24 | 25 | void* GetData() 26 | { 27 | return static_cast(this + 1); 28 | } 29 | 30 | static Page* Create(uint32 size) 31 | { 32 | void* pData = new char[sizeof(Page) + size]; 33 | Page* pPage = new (pData) Page; 34 | pPage->Size = size; 35 | return pPage; 36 | } 37 | 38 | static void Release(Page* pPage) 39 | { 40 | char* pData = reinterpret_cast(pPage); 41 | delete[] pData; 42 | } 43 | }; 44 | static_assert(std::is_trivially_destructible_v); 45 | 46 | void Release() 47 | { 48 | while (!AllocatedPages.empty()) 49 | { 50 | Page* pPage = AllocatedPages.front(); 51 | Page::Release(pPage); 52 | AllocatedPages.pop(); 53 | } 54 | while (FreePages.empty()) 55 | { 56 | Page* pPage = FreePages.back(); 57 | Page::Release(pPage); 58 | FreePages.pop_back(); 59 | } 60 | } 61 | 62 | Page* AllocatePage(uint32 id) 63 | { 64 | std::lock_guard m(PageLock); 65 | 66 | Page* pPage = nullptr; 67 | if (FreePages.empty()) 68 | { 69 | pPage = Page::Create(cPageSize); 70 | ++NumPages; 71 | } 72 | else 73 | { 74 | pPage = FreePages.back(); 75 | FreePages.pop_back(); 76 | } 77 | pPage->ID = id; 78 | AllocatedPages.push(pPage); 79 | return pPage; 80 | } 81 | 82 | bool IsValidPage(uint32 id) 83 | { 84 | return id >= MinValidID; 85 | } 86 | 87 | void Evict(uint32 id) 88 | { 89 | std::lock_guard m(PageLock); 90 | 91 | gAssert(NumPages == FreePages.size() + AllocatedPages.size()); 92 | 93 | while (!AllocatedPages.empty()) 94 | { 95 | Page* pPage = AllocatedPages.front(); 96 | if (id >= pPage->ID) 97 | { 98 | FreePages.push_back(pPage); 99 | AllocatedPages.pop(); 100 | } 101 | else 102 | { 103 | break; 104 | } 105 | } 106 | 107 | MinValidID = id + 1; 108 | } 109 | 110 | private: 111 | std::mutex PageLock; 112 | Array FreePages; 113 | std::queue AllocatedPages; 114 | uint32 MinValidID = 0; 115 | int NumPages = 0; 116 | }; 117 | 118 | static ProfilerAllocator sProfilerAllocator; 119 | 120 | class SubAllocator 121 | { 122 | public: 123 | const char* String(const char* pStr, uint32 id) 124 | { 125 | uint32 len = (uint32)strlen(pStr) + 1; 126 | char* pData = (char*)Allocate(len, id); 127 | strcpy_s(pData, len, pStr); 128 | return pData; 129 | } 130 | 131 | void* Allocate(uint32 size, uint32 id) 132 | { 133 | if (pPage == nullptr || id > ID || !sProfilerAllocator.IsValidPage(ID) || Offset + size > pPage->Size) 134 | { 135 | ID = std::max(ID, id); 136 | pPage = sProfilerAllocator.AllocatePage(ID); 137 | Offset = 0; 138 | } 139 | void* pData = static_cast(pPage->GetData()) + Offset; 140 | Offset += size; 141 | return pData; 142 | } 143 | 144 | private: 145 | uint32 Offset = 0; 146 | uint32 ID = 0; 147 | ProfilerAllocator::Page* pPage = nullptr; 148 | }; 149 | static thread_local SubAllocator gAllocator; 150 | 151 | static uint32 HSV2RGB(float hue, float saturation, float value) 152 | { 153 | float R = std::max(std::min(fabs(hue * 6 - 3) - 1, 1.0f), 0.0f); 154 | float G = std::max(std::min(2 - fabs(hue * 6 - 2), 1.0f), 0.0f); 155 | float B = std::max(std::min(2 - fabs(hue * 6 - 4), 1.0f), 0.0f); 156 | 157 | R = ((R - 1) * saturation + 1) * value; 158 | G = ((G - 1) * saturation + 1) * value; 159 | B = ((B - 1) * saturation + 1) * value; 160 | 161 | return ((uint8)roundf(R * 255.0f) << 0) | 162 | ((uint8)roundf(G * 255.0f) << 8) | 163 | ((uint8)roundf(B * 255.0f) << 16) | 164 | 255 << 24; 165 | } 166 | 167 | static uint32 GetFrameColor(uint32 frameIndex) 168 | { 169 | return HSV2RGB(frameIndex % 10 / 10.0f, 0.5f, 0.5f); 170 | } 171 | 172 | static uint32 ColorFromString(const char* pStr) 173 | { 174 | const float saturation = 0.5f; 175 | const float value = 0.6f; 176 | float hue = (float)std::hash{}(pStr) / std::numeric_limits::max(); 177 | return HSV2RGB(hue, saturation, value); 178 | } 179 | 180 | 181 | //----------------------------------------------------------------------------- 182 | // [SECTION] GPU Profiler 183 | //----------------------------------------------------------------------------- 184 | 185 | static constexpr const char* GetCommandQueueName(D3D12_COMMAND_LIST_TYPE type) 186 | { 187 | switch (type) 188 | { 189 | case D3D12_COMMAND_LIST_TYPE_DIRECT: return "Direct Queue"; 190 | case D3D12_COMMAND_LIST_TYPE_COMPUTE: return "Compute Queue"; 191 | case D3D12_COMMAND_LIST_TYPE_COPY: return "Copy Queue"; 192 | case D3D12_COMMAND_LIST_TYPE_VIDEO_DECODE: return "Video Decode Queue"; 193 | case D3D12_COMMAND_LIST_TYPE_VIDEO_ENCODE: return "Video Encode Queue"; 194 | case D3D12_COMMAND_LIST_TYPE_VIDEO_PROCESS: return "Video Process Queue"; 195 | default: return "Unknown Queue"; 196 | } 197 | } 198 | 199 | void GPUProfiler::Initialize(ID3D12Device* pDevice, Span queues, uint32 frameLatency) 200 | { 201 | gAssert(frameLatency >= 1, "Frame Latency must be at least 1"); 202 | 203 | m_FrameLatency = frameLatency; 204 | 205 | InitializeSRWLock((PSRWLOCK)&m_CommandListMapLock); 206 | QueryPerformanceFrequency((LARGE_INTEGER*)&m_CPUTickFrequency); 207 | 208 | m_QueueEventStack.resize(queues.size()); 209 | for (uint32 queueIndex = 0; queueIndex < queues.size(); ++queueIndex) 210 | { 211 | ID3D12CommandQueue* pQueue = queues[queueIndex]; 212 | D3D12_COMMAND_QUEUE_DESC desc = pQueue->GetDesc(); 213 | 214 | m_QueueIndexMap[pQueue] = (uint32)m_Queues.size(); 215 | QueueInfo& queueInfo = m_Queues.emplace_back(); 216 | uint32 size = ARRAYSIZE(queueInfo.Name); 217 | constexpr GUID ID_D3DDebugObjectName = { 0x429b8c22, 0x9188, 0x4b0c, 0x87, 0x42, 0xac, 0xb0, 0xbf, 0x85, 0xc2, 0x00 }; 218 | if (FAILED(pQueue->GetPrivateData(ID_D3DDebugObjectName, &size, queueInfo.Name))) 219 | strcpy_s(queueInfo.Name, GetCommandQueueName(desc.Type)); 220 | queueInfo.pQueue = pQueue; 221 | queueInfo.Index = queueIndex; 222 | queueInfo.QueryHeapIndex = desc.Type == D3D12_COMMAND_LIST_TYPE_COPY ? 1 : 0; 223 | gVerifyHR(pQueue->GetClockCalibration(&queueInfo.GPUCalibrationTicks, &queueInfo.CPUCalibrationTicks)); 224 | gVerifyHR(pQueue->GetTimestampFrequency(&queueInfo.GPUFrequency)); 225 | 226 | if (!GetHeap(queueInfo.QueryHeapIndex).IsInitialized()) 227 | GetHeap(queueInfo.QueryHeapIndex).Initialize(pDevice, pQueue, QueryData::cMaxNumQueries, frameLatency); 228 | 229 | queueInfo.TrackIndex = gProfiler.RegisterTrack(queueInfo.Name, Profiler::EventTrack::EType::GPU, queueInfo.Index); 230 | } 231 | 232 | // Maximum number of events is the number of queries is the total capacity of all query heaps divided by 2 (a pair of queries per event) 233 | int queryCapacity = 0; 234 | for (QueryHeap& heap : m_QueryHeaps) 235 | queryCapacity += heap.GetQueryCapacity(); 236 | 237 | 238 | m_QueryData.resize(frameLatency); 239 | for (uint32 i = 0; i < frameLatency; ++i) 240 | { 241 | m_QueryData[i].Pairs.resize(queryCapacity / 2); 242 | m_QueryData[i].Events.resize(queryCapacity / 2); 243 | } 244 | 245 | m_IsInitialized = true; 246 | } 247 | 248 | void GPUProfiler::Shutdown() 249 | { 250 | for (QueryHeap& heap : m_QueryHeaps) 251 | heap.Shutdown(); 252 | 253 | for (auto& commandListState : m_CommandListMap) 254 | delete commandListState.second; 255 | m_CommandListMap.clear(); 256 | 257 | m_QueryData.clear(); 258 | 259 | m_Queues.clear(); 260 | m_QueueIndexMap.clear(); 261 | } 262 | 263 | void GPUProfiler::BeginEvent(ID3D12GraphicsCommandList* pCmd, const char* pName, uint32 color, const char* pFilePath, uint32 lineNumber) 264 | { 265 | if (!m_IsInitialized) 266 | return; 267 | 268 | if (m_EventCallback.OnEventBegin) 269 | m_EventCallback.OnEventBegin(pName, pCmd, m_EventCallback.pUserData); 270 | 271 | if (m_IsPaused) 272 | return; 273 | 274 | // Register a query on the commandlist 275 | CommandListState& cmdListState = *GetState(pCmd, true); 276 | QueryData::Query& cmdListQuery = cmdListState.Queries.emplace_back(); 277 | 278 | QueryData& queryData = GetQueryData(); 279 | 280 | // Allocate a query range. This stores a begin/end query index pair. (Also event index) 281 | uint32 eventIndex = m_EventIndex.fetch_add(1); 282 | if (eventIndex >= queryData.Events.size()) 283 | return; 284 | 285 | // Record a timestamp query and assign to the commandlist 286 | cmdListQuery.QueryIndex = GetHeap(pCmd->GetType()).RecordQuery(pCmd); 287 | cmdListQuery.EventIndex = eventIndex; 288 | 289 | // Allocate an event in the sample history 290 | ProfilerEvent& event = queryData.Events[eventIndex]; 291 | event.pName = gAllocator.String(pName, m_FrameIndex); 292 | event.pFilePath = pFilePath; 293 | event.LineNumber = lineNumber; 294 | event.Color = color == 0 ? ColorFromString(pName) : color; 295 | } 296 | 297 | void GPUProfiler::EndEvent(ID3D12GraphicsCommandList* pCmd) 298 | { 299 | if (!m_IsInitialized) 300 | return; 301 | 302 | if (m_EventCallback.OnEventEnd) 303 | m_EventCallback.OnEventEnd(pCmd, m_EventCallback.pUserData); 304 | 305 | if (m_IsPaused) 306 | return; 307 | 308 | // Record a timestamp query and assign to the commandlist 309 | CommandListState& cmdListState = *GetState(pCmd, true); 310 | QueryData::Query& query = cmdListState.Queries.emplace_back(); 311 | query.QueryIndex = GetHeap(pCmd->GetType()).RecordQuery(pCmd); 312 | query.EventIndex = QueryData::Query::EndEventFlag; // Range index to indicate this is an 'End' query 313 | } 314 | 315 | // Process the last frame and advance to the next 316 | void GPUProfiler::Tick() 317 | { 318 | if (!m_IsInitialized) 319 | return; 320 | 321 | for (ActiveEventStack& stack : m_QueueEventStack) 322 | gAssert(stack.GetSize() == 0, "EventStack for the CommandQueue should be empty. Forgot to `End()` %d Events", stack.GetSize()); 323 | 324 | // Poll query heap and populate event timings 325 | while (m_FrameToReadback < m_FrameIndex) 326 | { 327 | // Wait for all query heaps to have finished resolving the queries for the readback frame 328 | bool allHeapsReady = true; 329 | for (QueryHeap& heap : m_QueryHeaps) 330 | allHeapsReady &= heap.IsFrameComplete(m_FrameToReadback); 331 | if (!allHeapsReady) 332 | break; 333 | 334 | std::scoped_lock lock(m_QueryRangeLock); 335 | 336 | QueryData& queryData = GetQueryData(m_FrameToReadback); 337 | for (uint32 i = 0; i < queryData.NumEvents; ++i) 338 | { 339 | ProfilerEvent& event = queryData.Events[i]; 340 | QueryData::QueryPair& queryRange = queryData.Pairs[i]; 341 | gAssert(queryRange.IsValid()); 342 | 343 | const QueueInfo& queue = m_Queues[event.QueueIndex]; 344 | Span queries = m_QueryHeaps[queue.QueryHeapIndex].GetQueryData(m_FrameToReadback); 345 | 346 | // Convert to CPU ticks and assign to event 347 | event.TicksBegin = ConvertToCPUTicks(queue, queries[queryRange.QueryIndexBegin]); 348 | event.TicksEnd = ConvertToCPUTicks(queue, queries[queryRange.QueryIndexEnd]); 349 | 350 | // Invalidate 351 | queryRange = {}; 352 | 353 | gProfiler.AddEvent(queue.TrackIndex, event, m_FrameToReadback); 354 | } 355 | queryData.NumEvents = 0; 356 | 357 | ++m_FrameToReadback; 358 | } 359 | 360 | m_IsPaused = m_PauseQueued; 361 | if (m_IsPaused) 362 | return; 363 | 364 | for (const auto& data: m_CommandListMap) 365 | gAssert(data.second->Queries.empty(), "The Queries inside the commandlist is not empty. This is because ExecuteCommandLists was not called with this commandlist."); 366 | 367 | for (QueryHeap& heap : m_QueryHeaps) 368 | heap.Resolve(m_FrameIndex); 369 | 370 | ++m_FrameIndex; 371 | 372 | { 373 | for (QueryHeap& heap : m_QueryHeaps) 374 | heap.Reset(m_FrameIndex); 375 | 376 | m_EventIndex = 0; 377 | } 378 | } 379 | 380 | void GPUProfiler::ExecuteCommandLists(const ID3D12CommandQueue* pQueue, Span commandLists) 381 | { 382 | if (!m_IsInitialized) 383 | return; 384 | 385 | if (m_IsPaused) 386 | return; 387 | 388 | auto it = m_QueueIndexMap.find(pQueue); 389 | if (it == m_QueueIndexMap.end()) 390 | return; 391 | 392 | std::scoped_lock lock(m_QueryRangeLock); 393 | 394 | uint32 queueIndex = it->second; 395 | ActiveEventStack& eventStack = m_QueueEventStack[queueIndex]; 396 | QueryData& queryData = GetQueryData(); 397 | queryData.NumEvents = m_EventIndex; 398 | 399 | for (ID3D12CommandList* pCmd : commandLists) 400 | { 401 | CommandListState* pCommandListQueries = GetState(pCmd, false); 402 | if (pCommandListQueries) 403 | { 404 | for (QueryData::Query& query : pCommandListQueries->Queries) 405 | { 406 | if (query.EventIndex != QueryData::Query::EndEventFlag) 407 | { 408 | // If it's a "BeginEvent", add to the stack 409 | eventStack.Push() = query; 410 | if (query.EventIndex == QueryData::Query::InvalidEventFlag) 411 | continue; 412 | 413 | ProfilerEvent& sampleEvent = queryData.Events[query.EventIndex]; 414 | sampleEvent.QueueIndex = queueIndex; 415 | } 416 | else 417 | { 418 | // If it's an "EndEvent", pop top query from the stack and pair up 419 | gAssert(eventStack.GetSize() > 0, "Event Begin/End mismatch"); 420 | QueryData::Query beginEventQuery = eventStack.Pop(); 421 | if (beginEventQuery.EventIndex == QueryData::Query::InvalidEventFlag) 422 | continue; 423 | 424 | // Pair up Begin/End query indices 425 | QueryData::QueryPair& pair = queryData.Pairs[beginEventQuery.EventIndex]; 426 | pair.QueryIndexBegin = beginEventQuery.QueryIndex; 427 | pair.QueryIndexEnd = query.QueryIndex; 428 | 429 | // Compute event depth 430 | ProfilerEvent& sampleEvent = queryData.Events[beginEventQuery.EventIndex]; 431 | sampleEvent.Depth = eventStack.GetSize(); 432 | gAssert(sampleEvent.QueueIndex == queueIndex, "Begin/EndEvent must be recorded on the same queue"); 433 | } 434 | } 435 | pCommandListQueries->Queries.clear(); 436 | } 437 | } 438 | } 439 | 440 | 441 | GPUProfiler::CommandListState* GPUProfiler::GetState(ID3D12CommandList* pCmd, bool createIfNotFound) 442 | { 443 | // See if it's already tracked 444 | AcquireSRWLockShared((PSRWLOCK)&m_CommandListMapLock); 445 | auto it = m_CommandListMap.find(pCmd); 446 | CommandListState* pState = it != m_CommandListMap.end() ? it->second : nullptr; 447 | ReleaseSRWLockShared((PSRWLOCK)&m_CommandListMapLock); 448 | 449 | if (!pState && createIfNotFound) 450 | { 451 | // If not, register new commandlist 452 | // TODO: Pool CommandListStates in case ID3D12CommandLists are often recreated 453 | pState = new CommandListState(this, pCmd); 454 | 455 | AcquireSRWLockExclusive((PSRWLOCK)&m_CommandListMapLock); 456 | m_CommandListMap[pCmd] = pState; 457 | ReleaseSRWLockExclusive((PSRWLOCK)&m_CommandListMapLock); 458 | } 459 | return pState; 460 | } 461 | 462 | 463 | 464 | GPUProfiler::CommandListState::CommandListState(GPUProfiler* profiler, ID3D12CommandList* pCmd) 465 | : pProfiler(profiler), pCommandList(pCmd) 466 | { 467 | ID3DDestructionNotifier* destruction_notifier = nullptr; 468 | gVerifyHR(pCmd->QueryInterface(&destruction_notifier)); 469 | gVerifyHR(destruction_notifier->RegisterDestructionCallback([](void* pContext) { 470 | GPUProfiler::CommandListState* pState = (GPUProfiler::CommandListState*)pContext; 471 | 472 | AcquireSRWLockExclusive((PSRWLOCK)&pState->pProfiler->m_CommandListMapLock); 473 | pState->pProfiler->m_CommandListMap.erase(pState->pCommandList); 474 | ReleaseSRWLockExclusive((PSRWLOCK)&pState->pProfiler->m_CommandListMapLock); 475 | 476 | delete pState; 477 | }, this, &DestructionEventID)); 478 | 479 | destruction_notifier->Release(); 480 | } 481 | 482 | 483 | 484 | GPUProfiler::CommandListState::~CommandListState() 485 | { 486 | ID3DDestructionNotifier* destruction_notifier = nullptr; 487 | gVerifyHR(pCommandList->QueryInterface(&destruction_notifier)); 488 | gVerifyHR(destruction_notifier->UnregisterDestructionCallback(DestructionEventID)); 489 | destruction_notifier->Release(); 490 | } 491 | 492 | 493 | 494 | void GPUProfiler::QueryHeap::Initialize(ID3D12Device* pDevice, ID3D12CommandQueue* pResolveQueue, uint32 maxNumQueries, uint32 frameLatency) 495 | { 496 | gAssert(gProfiler.IsInitialized()); 497 | 498 | m_pResolveQueue = pResolveQueue; 499 | m_FrameLatency = frameLatency; 500 | m_MaxNumQueries = maxNumQueries; 501 | 502 | D3D12_COMMAND_QUEUE_DESC queueDesc = pResolveQueue->GetDesc(); 503 | 504 | D3D12_QUERY_HEAP_DESC heapDesc{ 505 | .Type = queueDesc.Type == D3D12_COMMAND_LIST_TYPE_COPY ? D3D12_QUERY_HEAP_TYPE_COPY_QUEUE_TIMESTAMP : D3D12_QUERY_HEAP_TYPE_TIMESTAMP, 506 | .Count = maxNumQueries, 507 | .NodeMask = 0x1, 508 | }; 509 | gVerifyHR(pDevice->CreateQueryHeap(&heapDesc, IID_PPV_ARGS(&m_pQueryHeap))); 510 | 511 | for (uint32 i = 0; i < frameLatency; ++i) 512 | gVerifyHR(pDevice->CreateCommandAllocator(queueDesc.Type, IID_PPV_ARGS(&m_CommandAllocators.emplace_back()))); 513 | gVerifyHR(pDevice->CreateCommandList(0x1, queueDesc.Type, m_CommandAllocators[0], nullptr, IID_PPV_ARGS(&m_pCommandList))); 514 | 515 | D3D12_RESOURCE_DESC readbackDesc{ 516 | .Dimension = D3D12_RESOURCE_DIMENSION_BUFFER, 517 | .Alignment = 0, 518 | .Width = (uint64)maxNumQueries * sizeof(uint64) * frameLatency, 519 | .Height = 1, 520 | .DepthOrArraySize = 1, 521 | .MipLevels = 1, 522 | .Format = DXGI_FORMAT_UNKNOWN, 523 | .SampleDesc = { 524 | .Count = 1, 525 | .Quality = 0, 526 | }, 527 | .Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR, 528 | .Flags = D3D12_RESOURCE_FLAG_NONE, 529 | }; 530 | 531 | D3D12_HEAP_PROPERTIES heapProps{ 532 | .Type = D3D12_HEAP_TYPE_READBACK, 533 | .CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN, 534 | .MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN, 535 | .CreationNodeMask = 0, 536 | .VisibleNodeMask = 0, 537 | }; 538 | 539 | gVerifyHR(pDevice->CreateCommittedResource(&heapProps, D3D12_HEAP_FLAG_NONE, &readbackDesc, D3D12_RESOURCE_STATE_COMMON, nullptr, IID_PPV_ARGS(&m_pReadbackResource))); 540 | void* pReadbackData = nullptr; 541 | gVerifyHR(m_pReadbackResource->Map(0, nullptr, &pReadbackData)); 542 | m_ReadbackData = Span(static_cast(pReadbackData), maxNumQueries * frameLatency); 543 | 544 | gVerifyHR(pDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&m_pResolveFence))); 545 | } 546 | 547 | void GPUProfiler::QueryHeap::Shutdown() 548 | { 549 | if (!IsInitialized()) 550 | return; 551 | 552 | for (ID3D12CommandAllocator* pAllocator : m_CommandAllocators) 553 | pAllocator->Release(); 554 | m_pCommandList->Release(); 555 | m_pQueryHeap->Release(); 556 | m_pReadbackResource->Release(); 557 | m_pResolveFence->Release(); 558 | } 559 | 560 | uint32 GPUProfiler::QueryHeap::RecordQuery(ID3D12GraphicsCommandList* pCmd) 561 | { 562 | uint32 index = m_QueryIndex.fetch_add(1); 563 | if (index >= m_MaxNumQueries) 564 | return 0xFFFFFFFF; 565 | 566 | pCmd->EndQuery(m_pQueryHeap, D3D12_QUERY_TYPE_TIMESTAMP, index); 567 | return index; 568 | } 569 | 570 | uint32 GPUProfiler::QueryHeap::Resolve(uint32 frameIndex) 571 | { 572 | if (!IsInitialized()) 573 | return 0; 574 | 575 | uint32 frameBit = frameIndex % m_FrameLatency; 576 | uint32 queryStart = frameBit * m_MaxNumQueries; 577 | uint32 numQueries = std::min(m_MaxNumQueries, (uint32)m_QueryIndex); 578 | m_pCommandList->ResolveQueryData(m_pQueryHeap, D3D12_QUERY_TYPE_TIMESTAMP, 0, numQueries, m_pReadbackResource, queryStart * sizeof(uint64)); 579 | gVerifyHR(m_pCommandList->Close()); 580 | ID3D12CommandList* pCmdLists[] = { m_pCommandList }; 581 | m_pResolveQueue->ExecuteCommandLists(1, pCmdLists); 582 | gVerifyHR(m_pResolveQueue->Signal(m_pResolveFence, frameIndex)); 583 | return numQueries; 584 | } 585 | 586 | void GPUProfiler::QueryHeap::Reset(uint32 frameIndex) 587 | { 588 | if (!IsInitialized()) 589 | return; 590 | 591 | // Don't advance to the next frame until the GPU has caught up until at least the frame latency 592 | if (frameIndex >= m_FrameLatency) 593 | { 594 | uint32 wait_frame = frameIndex - m_FrameLatency; 595 | if (!IsFrameComplete(wait_frame)) 596 | m_pResolveFence->SetEventOnCompletion(wait_frame, nullptr); 597 | } 598 | 599 | m_QueryIndex = 0; 600 | ID3D12CommandAllocator* pAllocator = m_CommandAllocators[frameIndex % m_FrameLatency]; 601 | gVerifyHR(pAllocator->Reset()); 602 | gVerifyHR(m_pCommandList->Reset(pAllocator, nullptr)); 603 | } 604 | 605 | bool GPUProfiler::QueryHeap::IsFrameComplete(uint64 frameIndex) 606 | { 607 | if (!IsInitialized()) 608 | return true; 609 | 610 | uint64 fenceValue = frameIndex; 611 | if (fenceValue <= m_LastCompletedFence && m_LastCompletedFence > 0) 612 | return true; 613 | m_LastCompletedFence = std::max(m_pResolveFence->GetCompletedValue(), m_LastCompletedFence); 614 | return fenceValue <= m_LastCompletedFence; 615 | } 616 | 617 | //----------------------------------------------------------------------------- 618 | // [SECTION] CPU Profiler 619 | //----------------------------------------------------------------------------- 620 | 621 | void Profiler::Initialize(uint32 historySize) 622 | { 623 | m_HistorySize = historySize; 624 | m_IsInitialized = true; 625 | m_BeginFrameTicks.resize(historySize); 626 | 627 | uint64 frequency = 0; 628 | QueryPerformanceFrequency((LARGE_INTEGER*)&frequency); 629 | m_MsToTicks = (uint32)(frequency / 1000); 630 | } 631 | 632 | void Profiler::Shutdown() 633 | { 634 | m_Tracks.clear(); 635 | m_BeginFrameTicks.clear(); 636 | 637 | sProfilerAllocator.Release(); 638 | } 639 | 640 | // Begin a new CPU event on the current thread 641 | void Profiler::BeginEvent(const char* pName, uint32 color, const char* pFilePath, uint32 lineNumber) 642 | { 643 | if (!m_IsInitialized) 644 | return; 645 | 646 | if (m_EventCallback.OnEventBegin) 647 | m_EventCallback.OnEventBegin(pName, m_EventCallback.pUserData); 648 | 649 | if (m_Paused) 650 | return; 651 | 652 | // Record new event 653 | EventTrack& track = GetCurrentThreadTrack(); 654 | ProfilerEventData& eventData = track.GetFrameData(m_FrameIndex); 655 | track.EventStack.Push() = (uint32)eventData.size(); 656 | 657 | ProfilerEvent& newEvent = eventData.emplace_back(); 658 | newEvent.Depth = track.EventStack.GetSize() - 1; 659 | newEvent.ThreadIndex = track.Index; 660 | newEvent.pName = gAllocator.String(pName, m_FrameIndex); 661 | newEvent.pFilePath = pFilePath; 662 | newEvent.LineNumber = lineNumber; 663 | newEvent.Color = color == 0 ? ColorFromString(pName) : color; 664 | QueryPerformanceCounter((LARGE_INTEGER*)(&newEvent.TicksBegin)); 665 | } 666 | 667 | // End the last pushed event on the current thread 668 | void Profiler::EndEvent() 669 | { 670 | if (!m_IsInitialized) 671 | return; 672 | 673 | if (m_EventCallback.OnEventEnd) 674 | m_EventCallback.OnEventEnd(m_EventCallback.pUserData); 675 | 676 | if (m_Paused) 677 | return; 678 | 679 | // End and pop an event of the stack 680 | EventTrack& track = GetCurrentThreadTrack(); 681 | 682 | gAssert(track.EventStack.GetSize() > 0, "Event mismatch. Called EndEvent more than BeginEvent"); 683 | uint32 eventIndex = track.EventStack.Pop(); 684 | ProfilerEvent& event = track.GetFrameData(m_FrameIndex)[eventIndex]; 685 | QueryPerformanceCounter((LARGE_INTEGER*)(&event.TicksEnd)); 686 | } 687 | 688 | 689 | void Profiler::AddEvent(uint32 trackIndex, const ProfilerEvent& event, uint32 frameIndex) 690 | { 691 | EventTrack& track = m_Tracks[trackIndex]; 692 | ProfilerEventData& events = track.GetFrameData(frameIndex); 693 | 694 | // Name must be copied 695 | ProfilerEvent newEvent = event; 696 | newEvent.pName = gAllocator.String(newEvent.pName, frameIndex); 697 | 698 | events.push_back(newEvent); 699 | } 700 | 701 | 702 | void Profiler::Present(IDXGISwapChain* pSwapChain) 703 | { 704 | if (m_PresentTrackIndex == -1) 705 | m_PresentTrackIndex = RegisterTrack("Present", EventTrack::EType::Present, 0); 706 | 707 | if (!m_Paused) 708 | { 709 | // Add an entry for the current present 710 | uint32 presentID; 711 | if (SUCCEEDED(pSwapChain->GetLastPresentCount(&presentID))) 712 | { 713 | // If the last queried present is larger than the current present, 714 | // that means the swapchain may have been recreated and we need to reset. 715 | if (m_LastQueuedPresentID > presentID || m_pPresentSwapChain != pSwapChain) 716 | { 717 | m_LastQueriedPresentID = 0; 718 | m_LastProcessedPresentID = 0; 719 | m_LastProcessedValidPresentID = 0; 720 | m_PresentQueue = {}; 721 | m_LastSyncRefreshCount = 0; 722 | m_pPresentSwapChain = pSwapChain; 723 | } 724 | 725 | PresentEntry* pEntry = GetPresentEntry(presentID, true); 726 | *pEntry = {}; 727 | QueryPerformanceCounter((LARGE_INTEGER*)&pEntry->PresentQPC); 728 | pEntry->PresentID = presentID; 729 | pEntry->FrameIndex = m_FrameIndex; 730 | 731 | m_LastQueuedPresentID = presentID; 732 | } 733 | 734 | DXGI_FRAME_STATISTICS frameStats{}; 735 | while (SUCCEEDED(pSwapChain->GetFrameStatistics(&frameStats)) && frameStats.PresentCount > m_LastQueriedPresentID) 736 | { 737 | gAssert(frameStats.SyncQPCTime.QuadPart != 0); 738 | 739 | // Update the entry that was presented. All the ones before that were not queried are considered dropped. 740 | PresentEntry* pEntry = GetPresentEntry(frameStats.PresentCount, false); 741 | if (pEntry) 742 | pEntry->DisplayQPC = frameStats.SyncQPCTime.QuadPart; 743 | 744 | // It's possible for the CPU to miss a refresh in the time multiple presents have been processed (and not dropped). 745 | // In this case, there isn't enough information to resolve it. Try to estimate, otherwise ignore the frame. 746 | uint32 numRefreshes = frameStats.SyncRefreshCount - m_LastSyncRefreshCount; 747 | if (frameStats.SyncRefreshCount > 0 && numRefreshes > 1) 748 | { 749 | // If the previous entry was not processed, it means we missed it in GetFrameStatistics 750 | PresentEntry* pPreviousEntry = GetPresentEntry(frameStats.PresentCount - 1, false); 751 | if (pPreviousEntry && pPreviousEntry->DisplayQPC == PresentEntry::sQPCDroppedFrame) 752 | { 753 | // If there was a valid present before the previous one, average the time to at least get some estimate 754 | PresentEntry* pPreviousPreviousEntry = GetPresentEntry(frameStats.PresentCount - 2, false); 755 | if (pPreviousPreviousEntry && pEntry) 756 | { 757 | // printf("MISSED FRAME: %d - ID %d\n", pPreviousEntry->FrameIndex, pPreviousEntry->PresentID); 758 | 759 | // Taking a guess: the sync happened one sync after the one before it (most likely timing to have been missed) 760 | uint64 duration = pEntry->DisplayQPC - pPreviousPreviousEntry->DisplayQPC; 761 | uint64 estimatedSyncTime = pPreviousPreviousEntry->DisplayQPC + duration / numRefreshes; 762 | 763 | pPreviousEntry->DisplayQPC = estimatedSyncTime; 764 | } 765 | else 766 | { 767 | // If there's no way to estimate, just drop it 768 | pPreviousEntry->DisplayQPC = PresentEntry::sQPCMissedFrame; 769 | } 770 | } 771 | } 772 | 773 | m_LastSyncRefreshCount = frameStats.SyncRefreshCount; 774 | m_LastQueriedPresentID = frameStats.PresentCount; 775 | } 776 | } 777 | 778 | uint32 stackSize = 0; 779 | 780 | // Process all entries up until the last present 781 | for (uint32 presentID = m_LastProcessedPresentID + 1; presentID < m_LastQueriedPresentID; ++presentID) 782 | { 783 | const PresentEntry* pToProcessEntry = GetPresentEntry(presentID, false); 784 | 785 | // Search from the last processed valid entry where the next one is 786 | const PresentEntry* pNextValidPresentEntry = nullptr; 787 | for (uint32 nextValidPresent = m_LastProcessedValidPresentID; nextValidPresent <= m_LastQueriedPresentID; ++nextValidPresent) 788 | { 789 | const PresentEntry* pEntry = GetPresentEntry(nextValidPresent, false); 790 | if (pEntry && pEntry->DisplayQPC != PresentEntry::sQPCDroppedFrame) 791 | { 792 | pNextValidPresentEntry = pEntry; 793 | m_LastProcessedValidPresentID = pEntry->PresentID; 794 | } 795 | } 796 | 797 | // No valid present yet, try again next frame 798 | if (!pNextValidPresentEntry) 799 | break; 800 | 801 | if (pToProcessEntry) 802 | { 803 | if (pToProcessEntry->DisplayQPC == PresentEntry::sQPCMissedFrame) 804 | { 805 | // Ignored frame (rare, see above) 806 | } 807 | else if (pToProcessEntry->DisplayQPC == PresentEntry::sQPCDroppedFrame) 808 | { 809 | ProfilerEvent event{ 810 | .pName = "Discarded", 811 | .Color = GetFrameColor(pToProcessEntry->FrameIndex), 812 | .Depth = 1, 813 | .TicksBegin = pNextValidPresentEntry->DisplayQPC, 814 | .TicksEnd = pNextValidPresentEntry->DisplayQPC + m_MsToTicks, 815 | }; 816 | AddEvent(m_PresentTrackIndex, event, pToProcessEntry->FrameIndex); 817 | } 818 | else 819 | { 820 | // If the timing of the refresh is the same as the current frame, stack the events. 821 | // Wait for the next frame with a different refresh time 822 | if (pNextValidPresentEntry->DisplayQPC == pToProcessEntry->DisplayQPC) 823 | break; 824 | 825 | ProfilerEvent event{ 826 | .pName = "Present", 827 | .Color = GetFrameColor(pToProcessEntry->FrameIndex), 828 | .Depth = stackSize, 829 | .TicksBegin = pToProcessEntry->DisplayQPC, 830 | .TicksEnd = pNextValidPresentEntry->DisplayQPC, 831 | }; 832 | ++stackSize; 833 | AddEvent(m_PresentTrackIndex, event, pToProcessEntry->FrameIndex); 834 | } 835 | } 836 | 837 | m_LastProcessedPresentID = presentID; 838 | } 839 | } 840 | 841 | 842 | 843 | // Process the last frame and advance 844 | void Profiler::Tick() 845 | { 846 | if (!m_IsInitialized) 847 | return; 848 | 849 | m_Paused = m_QueuedPaused; 850 | if (m_Paused) 851 | return; 852 | 853 | // End the "CPU Frame" event (except on frame 0) 854 | if (m_FrameIndex) 855 | EndEvent(); 856 | 857 | // Advance the frame and reset its data 858 | ++m_FrameIndex; 859 | 860 | std::scoped_lock lock(m_ThreadDataLock); 861 | for (EventTrack& track : m_Tracks) 862 | { 863 | ProfilerEventData& events = track.GetFrameData(m_FrameIndex); 864 | events.clear(); 865 | } 866 | 867 | QueryPerformanceCounter((LARGE_INTEGER*)&m_BeginFrameTicks[m_FrameIndex % m_BeginFrameTicks.size()]); 868 | 869 | // Begin a "CPU Frame" event 870 | BeginEvent("CPU Frame", GetFrameColor(m_FrameIndex)); 871 | 872 | if (m_FrameIndex >= m_HistorySize) 873 | sProfilerAllocator.Evict(m_FrameIndex - m_HistorySize); 874 | } 875 | 876 | 877 | 878 | // Register a new thread 879 | int Profiler::RegisterCurrentThread(const char* pName) 880 | { 881 | int& threadIndex = GetCurrentThreadTrackIndex(); 882 | 883 | const char* pLocalName = pName; 884 | char name[256]{}; 885 | if (!pName) 886 | { 887 | // If the name is not provided, retrieve it using GetThreadDescription() 888 | PWSTR pDescription = nullptr; 889 | if (SUCCEEDED(::GetThreadDescription(GetCurrentThread(), &pDescription))) 890 | WideCharToMultiByte(CP_UTF8, 0, pDescription, (int)wcslen(pDescription), name, ARRAYSIZE(name), nullptr, nullptr); 891 | pLocalName = name; 892 | } 893 | 894 | if (threadIndex == -1) 895 | { 896 | threadIndex = RegisterTrack(pLocalName, EventTrack::EType::CPU, GetCurrentThreadId()); 897 | } 898 | else 899 | { 900 | strcpy_s(m_Tracks[threadIndex].Name, pLocalName); 901 | } 902 | return threadIndex; 903 | } 904 | 905 | 906 | // Register a new track 907 | int Profiler::RegisterTrack(const char* pName, EventTrack::EType type, uint32 id) 908 | { 909 | std::scoped_lock lock(m_ThreadDataLock); 910 | 911 | EventTrack& data = m_Tracks.emplace_back(); 912 | strcpy_s(data.Name, ARRAYSIZE(data.Name), pName); 913 | data.ID = id; 914 | data.Index = (uint32)m_Tracks.size() - 1; 915 | data.Type = type; 916 | data.Events.resize(m_HistorySize); 917 | 918 | return data.Index; 919 | } 920 | 921 | 922 | #endif 923 | -------------------------------------------------------------------------------- /IconsFontAwesome4.h: -------------------------------------------------------------------------------- 1 | // Generated by https://github.com/juliettef/IconFontCppHeaders script GenerateIconFontCppHeaders.py for languages C and C++ 2 | // from https://github.com/FortAwesome/Font-Awesome/raw/4.x/src/icons.yml 3 | // for use with https://github.com/FortAwesome/Font-Awesome/blob/4.x/fonts/fontawesome-webfont.ttf 4 | #pragma once 5 | 6 | #define FONT_ICON_FILE_NAME_FA "fontawesome-webfont.ttf" 7 | 8 | #define ICON_MIN_FA 0xf000 9 | #define ICON_MAX_16_FA 0xf2e0 10 | #define ICON_MAX_FA 0xf2e0 11 | #define ICON_FA_GLASS "\xef\x80\x80" // U+f000 12 | #define ICON_FA_MUSIC "\xef\x80\x81" // U+f001 13 | #define ICON_FA_SEARCH "\xef\x80\x82" // U+f002 14 | #define ICON_FA_ENVELOPE_O "\xef\x80\x83" // U+f003 15 | #define ICON_FA_HEART "\xef\x80\x84" // U+f004 16 | #define ICON_FA_STAR "\xef\x80\x85" // U+f005 17 | #define ICON_FA_STAR_O "\xef\x80\x86" // U+f006 18 | #define ICON_FA_USER "\xef\x80\x87" // U+f007 19 | #define ICON_FA_FILM "\xef\x80\x88" // U+f008 20 | #define ICON_FA_TH_LARGE "\xef\x80\x89" // U+f009 21 | #define ICON_FA_TH "\xef\x80\x8a" // U+f00a 22 | #define ICON_FA_TH_LIST "\xef\x80\x8b" // U+f00b 23 | #define ICON_FA_CHECK "\xef\x80\x8c" // U+f00c 24 | #define ICON_FA_TIMES "\xef\x80\x8d" // U+f00d 25 | #define ICON_FA_SEARCH_PLUS "\xef\x80\x8e" // U+f00e 26 | #define ICON_FA_SEARCH_MINUS "\xef\x80\x90" // U+f010 27 | #define ICON_FA_POWER_OFF "\xef\x80\x91" // U+f011 28 | #define ICON_FA_SIGNAL "\xef\x80\x92" // U+f012 29 | #define ICON_FA_COG "\xef\x80\x93" // U+f013 30 | #define ICON_FA_TRASH_O "\xef\x80\x94" // U+f014 31 | #define ICON_FA_HOME "\xef\x80\x95" // U+f015 32 | #define ICON_FA_FILE_O "\xef\x80\x96" // U+f016 33 | #define ICON_FA_CLOCK_O "\xef\x80\x97" // U+f017 34 | #define ICON_FA_ROAD "\xef\x80\x98" // U+f018 35 | #define ICON_FA_DOWNLOAD "\xef\x80\x99" // U+f019 36 | #define ICON_FA_ARROW_CIRCLE_O_DOWN "\xef\x80\x9a" // U+f01a 37 | #define ICON_FA_ARROW_CIRCLE_O_UP "\xef\x80\x9b" // U+f01b 38 | #define ICON_FA_INBOX "\xef\x80\x9c" // U+f01c 39 | #define ICON_FA_PLAY_CIRCLE_O "\xef\x80\x9d" // U+f01d 40 | #define ICON_FA_REPEAT "\xef\x80\x9e" // U+f01e 41 | #define ICON_FA_REFRESH "\xef\x80\xa1" // U+f021 42 | #define ICON_FA_LIST_ALT "\xef\x80\xa2" // U+f022 43 | #define ICON_FA_LOCK "\xef\x80\xa3" // U+f023 44 | #define ICON_FA_FLAG "\xef\x80\xa4" // U+f024 45 | #define ICON_FA_HEADPHONES "\xef\x80\xa5" // U+f025 46 | #define ICON_FA_VOLUME_OFF "\xef\x80\xa6" // U+f026 47 | #define ICON_FA_VOLUME_DOWN "\xef\x80\xa7" // U+f027 48 | #define ICON_FA_VOLUME_UP "\xef\x80\xa8" // U+f028 49 | #define ICON_FA_QRCODE "\xef\x80\xa9" // U+f029 50 | #define ICON_FA_BARCODE "\xef\x80\xaa" // U+f02a 51 | #define ICON_FA_TAG "\xef\x80\xab" // U+f02b 52 | #define ICON_FA_TAGS "\xef\x80\xac" // U+f02c 53 | #define ICON_FA_BOOK "\xef\x80\xad" // U+f02d 54 | #define ICON_FA_BOOKMARK "\xef\x80\xae" // U+f02e 55 | #define ICON_FA_PRINT "\xef\x80\xaf" // U+f02f 56 | #define ICON_FA_CAMERA "\xef\x80\xb0" // U+f030 57 | #define ICON_FA_FONT "\xef\x80\xb1" // U+f031 58 | #define ICON_FA_BOLD "\xef\x80\xb2" // U+f032 59 | #define ICON_FA_ITALIC "\xef\x80\xb3" // U+f033 60 | #define ICON_FA_TEXT_HEIGHT "\xef\x80\xb4" // U+f034 61 | #define ICON_FA_TEXT_WIDTH "\xef\x80\xb5" // U+f035 62 | #define ICON_FA_ALIGN_LEFT "\xef\x80\xb6" // U+f036 63 | #define ICON_FA_ALIGN_CENTER "\xef\x80\xb7" // U+f037 64 | #define ICON_FA_ALIGN_RIGHT "\xef\x80\xb8" // U+f038 65 | #define ICON_FA_ALIGN_JUSTIFY "\xef\x80\xb9" // U+f039 66 | #define ICON_FA_LIST "\xef\x80\xba" // U+f03a 67 | #define ICON_FA_OUTDENT "\xef\x80\xbb" // U+f03b 68 | #define ICON_FA_INDENT "\xef\x80\xbc" // U+f03c 69 | #define ICON_FA_VIDEO_CAMERA "\xef\x80\xbd" // U+f03d 70 | #define ICON_FA_PICTURE_O "\xef\x80\xbe" // U+f03e 71 | #define ICON_FA_PENCIL "\xef\x81\x80" // U+f040 72 | #define ICON_FA_MAP_MARKER "\xef\x81\x81" // U+f041 73 | #define ICON_FA_ADJUST "\xef\x81\x82" // U+f042 74 | #define ICON_FA_TINT "\xef\x81\x83" // U+f043 75 | #define ICON_FA_PENCIL_SQUARE_O "\xef\x81\x84" // U+f044 76 | #define ICON_FA_SHARE_SQUARE_O "\xef\x81\x85" // U+f045 77 | #define ICON_FA_CHECK_SQUARE_O "\xef\x81\x86" // U+f046 78 | #define ICON_FA_ARROWS "\xef\x81\x87" // U+f047 79 | #define ICON_FA_STEP_BACKWARD "\xef\x81\x88" // U+f048 80 | #define ICON_FA_FAST_BACKWARD "\xef\x81\x89" // U+f049 81 | #define ICON_FA_BACKWARD "\xef\x81\x8a" // U+f04a 82 | #define ICON_FA_PLAY "\xef\x81\x8b" // U+f04b 83 | #define ICON_FA_PAUSE "\xef\x81\x8c" // U+f04c 84 | #define ICON_FA_STOP "\xef\x81\x8d" // U+f04d 85 | #define ICON_FA_FORWARD "\xef\x81\x8e" // U+f04e 86 | #define ICON_FA_FAST_FORWARD "\xef\x81\x90" // U+f050 87 | #define ICON_FA_STEP_FORWARD "\xef\x81\x91" // U+f051 88 | #define ICON_FA_EJECT "\xef\x81\x92" // U+f052 89 | #define ICON_FA_CHEVRON_LEFT "\xef\x81\x93" // U+f053 90 | #define ICON_FA_CHEVRON_RIGHT "\xef\x81\x94" // U+f054 91 | #define ICON_FA_PLUS_CIRCLE "\xef\x81\x95" // U+f055 92 | #define ICON_FA_MINUS_CIRCLE "\xef\x81\x96" // U+f056 93 | #define ICON_FA_TIMES_CIRCLE "\xef\x81\x97" // U+f057 94 | #define ICON_FA_CHECK_CIRCLE "\xef\x81\x98" // U+f058 95 | #define ICON_FA_QUESTION_CIRCLE "\xef\x81\x99" // U+f059 96 | #define ICON_FA_INFO_CIRCLE "\xef\x81\x9a" // U+f05a 97 | #define ICON_FA_CROSSHAIRS "\xef\x81\x9b" // U+f05b 98 | #define ICON_FA_TIMES_CIRCLE_O "\xef\x81\x9c" // U+f05c 99 | #define ICON_FA_CHECK_CIRCLE_O "\xef\x81\x9d" // U+f05d 100 | #define ICON_FA_BAN "\xef\x81\x9e" // U+f05e 101 | #define ICON_FA_ARROW_LEFT "\xef\x81\xa0" // U+f060 102 | #define ICON_FA_ARROW_RIGHT "\xef\x81\xa1" // U+f061 103 | #define ICON_FA_ARROW_UP "\xef\x81\xa2" // U+f062 104 | #define ICON_FA_ARROW_DOWN "\xef\x81\xa3" // U+f063 105 | #define ICON_FA_SHARE "\xef\x81\xa4" // U+f064 106 | #define ICON_FA_EXPAND "\xef\x81\xa5" // U+f065 107 | #define ICON_FA_COMPRESS "\xef\x81\xa6" // U+f066 108 | #define ICON_FA_PLUS "\xef\x81\xa7" // U+f067 109 | #define ICON_FA_MINUS "\xef\x81\xa8" // U+f068 110 | #define ICON_FA_ASTERISK "\xef\x81\xa9" // U+f069 111 | #define ICON_FA_EXCLAMATION_CIRCLE "\xef\x81\xaa" // U+f06a 112 | #define ICON_FA_GIFT "\xef\x81\xab" // U+f06b 113 | #define ICON_FA_LEAF "\xef\x81\xac" // U+f06c 114 | #define ICON_FA_FIRE "\xef\x81\xad" // U+f06d 115 | #define ICON_FA_EYE "\xef\x81\xae" // U+f06e 116 | #define ICON_FA_EYE_SLASH "\xef\x81\xb0" // U+f070 117 | #define ICON_FA_EXCLAMATION_TRIANGLE "\xef\x81\xb1" // U+f071 118 | #define ICON_FA_PLANE "\xef\x81\xb2" // U+f072 119 | #define ICON_FA_CALENDAR "\xef\x81\xb3" // U+f073 120 | #define ICON_FA_RANDOM "\xef\x81\xb4" // U+f074 121 | #define ICON_FA_COMMENT "\xef\x81\xb5" // U+f075 122 | #define ICON_FA_MAGNET "\xef\x81\xb6" // U+f076 123 | #define ICON_FA_CHEVRON_UP "\xef\x81\xb7" // U+f077 124 | #define ICON_FA_CHEVRON_DOWN "\xef\x81\xb8" // U+f078 125 | #define ICON_FA_RETWEET "\xef\x81\xb9" // U+f079 126 | #define ICON_FA_SHOPPING_CART "\xef\x81\xba" // U+f07a 127 | #define ICON_FA_FOLDER "\xef\x81\xbb" // U+f07b 128 | #define ICON_FA_FOLDER_OPEN "\xef\x81\xbc" // U+f07c 129 | #define ICON_FA_ARROWS_V "\xef\x81\xbd" // U+f07d 130 | #define ICON_FA_ARROWS_H "\xef\x81\xbe" // U+f07e 131 | #define ICON_FA_BAR_CHART "\xef\x82\x80" // U+f080 132 | #define ICON_FA_TWITTER_SQUARE "\xef\x82\x81" // U+f081 133 | #define ICON_FA_FACEBOOK_SQUARE "\xef\x82\x82" // U+f082 134 | #define ICON_FA_CAMERA_RETRO "\xef\x82\x83" // U+f083 135 | #define ICON_FA_KEY "\xef\x82\x84" // U+f084 136 | #define ICON_FA_COGS "\xef\x82\x85" // U+f085 137 | #define ICON_FA_COMMENTS "\xef\x82\x86" // U+f086 138 | #define ICON_FA_THUMBS_O_UP "\xef\x82\x87" // U+f087 139 | #define ICON_FA_THUMBS_O_DOWN "\xef\x82\x88" // U+f088 140 | #define ICON_FA_STAR_HALF "\xef\x82\x89" // U+f089 141 | #define ICON_FA_HEART_O "\xef\x82\x8a" // U+f08a 142 | #define ICON_FA_SIGN_OUT "\xef\x82\x8b" // U+f08b 143 | #define ICON_FA_LINKEDIN_SQUARE "\xef\x82\x8c" // U+f08c 144 | #define ICON_FA_THUMB_TACK "\xef\x82\x8d" // U+f08d 145 | #define ICON_FA_EXTERNAL_LINK "\xef\x82\x8e" // U+f08e 146 | #define ICON_FA_SIGN_IN "\xef\x82\x90" // U+f090 147 | #define ICON_FA_TROPHY "\xef\x82\x91" // U+f091 148 | #define ICON_FA_GITHUB_SQUARE "\xef\x82\x92" // U+f092 149 | #define ICON_FA_UPLOAD "\xef\x82\x93" // U+f093 150 | #define ICON_FA_LEMON_O "\xef\x82\x94" // U+f094 151 | #define ICON_FA_PHONE "\xef\x82\x95" // U+f095 152 | #define ICON_FA_SQUARE_O "\xef\x82\x96" // U+f096 153 | #define ICON_FA_BOOKMARK_O "\xef\x82\x97" // U+f097 154 | #define ICON_FA_PHONE_SQUARE "\xef\x82\x98" // U+f098 155 | #define ICON_FA_TWITTER "\xef\x82\x99" // U+f099 156 | #define ICON_FA_FACEBOOK "\xef\x82\x9a" // U+f09a 157 | #define ICON_FA_GITHUB "\xef\x82\x9b" // U+f09b 158 | #define ICON_FA_UNLOCK "\xef\x82\x9c" // U+f09c 159 | #define ICON_FA_CREDIT_CARD "\xef\x82\x9d" // U+f09d 160 | #define ICON_FA_RSS "\xef\x82\x9e" // U+f09e 161 | #define ICON_FA_HDD_O "\xef\x82\xa0" // U+f0a0 162 | #define ICON_FA_BULLHORN "\xef\x82\xa1" // U+f0a1 163 | #define ICON_FA_BELL "\xef\x83\xb3" // U+f0f3 164 | #define ICON_FA_CERTIFICATE "\xef\x82\xa3" // U+f0a3 165 | #define ICON_FA_HAND_O_RIGHT "\xef\x82\xa4" // U+f0a4 166 | #define ICON_FA_HAND_O_LEFT "\xef\x82\xa5" // U+f0a5 167 | #define ICON_FA_HAND_O_UP "\xef\x82\xa6" // U+f0a6 168 | #define ICON_FA_HAND_O_DOWN "\xef\x82\xa7" // U+f0a7 169 | #define ICON_FA_ARROW_CIRCLE_LEFT "\xef\x82\xa8" // U+f0a8 170 | #define ICON_FA_ARROW_CIRCLE_RIGHT "\xef\x82\xa9" // U+f0a9 171 | #define ICON_FA_ARROW_CIRCLE_UP "\xef\x82\xaa" // U+f0aa 172 | #define ICON_FA_ARROW_CIRCLE_DOWN "\xef\x82\xab" // U+f0ab 173 | #define ICON_FA_GLOBE "\xef\x82\xac" // U+f0ac 174 | #define ICON_FA_WRENCH "\xef\x82\xad" // U+f0ad 175 | #define ICON_FA_TASKS "\xef\x82\xae" // U+f0ae 176 | #define ICON_FA_FILTER "\xef\x82\xb0" // U+f0b0 177 | #define ICON_FA_BRIEFCASE "\xef\x82\xb1" // U+f0b1 178 | #define ICON_FA_ARROWS_ALT "\xef\x82\xb2" // U+f0b2 179 | #define ICON_FA_USERS "\xef\x83\x80" // U+f0c0 180 | #define ICON_FA_LINK "\xef\x83\x81" // U+f0c1 181 | #define ICON_FA_CLOUD "\xef\x83\x82" // U+f0c2 182 | #define ICON_FA_FLASK "\xef\x83\x83" // U+f0c3 183 | #define ICON_FA_SCISSORS "\xef\x83\x84" // U+f0c4 184 | #define ICON_FA_FILES_O "\xef\x83\x85" // U+f0c5 185 | #define ICON_FA_PAPERCLIP "\xef\x83\x86" // U+f0c6 186 | #define ICON_FA_FLOPPY_O "\xef\x83\x87" // U+f0c7 187 | #define ICON_FA_SQUARE "\xef\x83\x88" // U+f0c8 188 | #define ICON_FA_BARS "\xef\x83\x89" // U+f0c9 189 | #define ICON_FA_LIST_UL "\xef\x83\x8a" // U+f0ca 190 | #define ICON_FA_LIST_OL "\xef\x83\x8b" // U+f0cb 191 | #define ICON_FA_STRIKETHROUGH "\xef\x83\x8c" // U+f0cc 192 | #define ICON_FA_UNDERLINE "\xef\x83\x8d" // U+f0cd 193 | #define ICON_FA_TABLE "\xef\x83\x8e" // U+f0ce 194 | #define ICON_FA_MAGIC "\xef\x83\x90" // U+f0d0 195 | #define ICON_FA_TRUCK "\xef\x83\x91" // U+f0d1 196 | #define ICON_FA_PINTEREST "\xef\x83\x92" // U+f0d2 197 | #define ICON_FA_PINTEREST_SQUARE "\xef\x83\x93" // U+f0d3 198 | #define ICON_FA_GOOGLE_PLUS_SQUARE "\xef\x83\x94" // U+f0d4 199 | #define ICON_FA_GOOGLE_PLUS "\xef\x83\x95" // U+f0d5 200 | #define ICON_FA_MONEY "\xef\x83\x96" // U+f0d6 201 | #define ICON_FA_CARET_DOWN "\xef\x83\x97" // U+f0d7 202 | #define ICON_FA_CARET_UP "\xef\x83\x98" // U+f0d8 203 | #define ICON_FA_CARET_LEFT "\xef\x83\x99" // U+f0d9 204 | #define ICON_FA_CARET_RIGHT "\xef\x83\x9a" // U+f0da 205 | #define ICON_FA_COLUMNS "\xef\x83\x9b" // U+f0db 206 | #define ICON_FA_SORT "\xef\x83\x9c" // U+f0dc 207 | #define ICON_FA_SORT_DESC "\xef\x83\x9d" // U+f0dd 208 | #define ICON_FA_SORT_ASC "\xef\x83\x9e" // U+f0de 209 | #define ICON_FA_ENVELOPE "\xef\x83\xa0" // U+f0e0 210 | #define ICON_FA_LINKEDIN "\xef\x83\xa1" // U+f0e1 211 | #define ICON_FA_UNDO "\xef\x83\xa2" // U+f0e2 212 | #define ICON_FA_GAVEL "\xef\x83\xa3" // U+f0e3 213 | #define ICON_FA_TACHOMETER "\xef\x83\xa4" // U+f0e4 214 | #define ICON_FA_COMMENT_O "\xef\x83\xa5" // U+f0e5 215 | #define ICON_FA_COMMENTS_O "\xef\x83\xa6" // U+f0e6 216 | #define ICON_FA_BOLT "\xef\x83\xa7" // U+f0e7 217 | #define ICON_FA_SITEMAP "\xef\x83\xa8" // U+f0e8 218 | #define ICON_FA_UMBRELLA "\xef\x83\xa9" // U+f0e9 219 | #define ICON_FA_CLIPBOARD "\xef\x83\xaa" // U+f0ea 220 | #define ICON_FA_LIGHTBULB_O "\xef\x83\xab" // U+f0eb 221 | #define ICON_FA_EXCHANGE "\xef\x83\xac" // U+f0ec 222 | #define ICON_FA_CLOUD_DOWNLOAD "\xef\x83\xad" // U+f0ed 223 | #define ICON_FA_CLOUD_UPLOAD "\xef\x83\xae" // U+f0ee 224 | #define ICON_FA_USER_MD "\xef\x83\xb0" // U+f0f0 225 | #define ICON_FA_STETHOSCOPE "\xef\x83\xb1" // U+f0f1 226 | #define ICON_FA_SUITCASE "\xef\x83\xb2" // U+f0f2 227 | #define ICON_FA_BELL_O "\xef\x82\xa2" // U+f0a2 228 | #define ICON_FA_COFFEE "\xef\x83\xb4" // U+f0f4 229 | #define ICON_FA_CUTLERY "\xef\x83\xb5" // U+f0f5 230 | #define ICON_FA_FILE_TEXT_O "\xef\x83\xb6" // U+f0f6 231 | #define ICON_FA_BUILDING_O "\xef\x83\xb7" // U+f0f7 232 | #define ICON_FA_HOSPITAL_O "\xef\x83\xb8" // U+f0f8 233 | #define ICON_FA_AMBULANCE "\xef\x83\xb9" // U+f0f9 234 | #define ICON_FA_MEDKIT "\xef\x83\xba" // U+f0fa 235 | #define ICON_FA_FIGHTER_JET "\xef\x83\xbb" // U+f0fb 236 | #define ICON_FA_BEER "\xef\x83\xbc" // U+f0fc 237 | #define ICON_FA_H_SQUARE "\xef\x83\xbd" // U+f0fd 238 | #define ICON_FA_PLUS_SQUARE "\xef\x83\xbe" // U+f0fe 239 | #define ICON_FA_ANGLE_DOUBLE_LEFT "\xef\x84\x80" // U+f100 240 | #define ICON_FA_ANGLE_DOUBLE_RIGHT "\xef\x84\x81" // U+f101 241 | #define ICON_FA_ANGLE_DOUBLE_UP "\xef\x84\x82" // U+f102 242 | #define ICON_FA_ANGLE_DOUBLE_DOWN "\xef\x84\x83" // U+f103 243 | #define ICON_FA_ANGLE_LEFT "\xef\x84\x84" // U+f104 244 | #define ICON_FA_ANGLE_RIGHT "\xef\x84\x85" // U+f105 245 | #define ICON_FA_ANGLE_UP "\xef\x84\x86" // U+f106 246 | #define ICON_FA_ANGLE_DOWN "\xef\x84\x87" // U+f107 247 | #define ICON_FA_DESKTOP "\xef\x84\x88" // U+f108 248 | #define ICON_FA_LAPTOP "\xef\x84\x89" // U+f109 249 | #define ICON_FA_TABLET "\xef\x84\x8a" // U+f10a 250 | #define ICON_FA_MOBILE "\xef\x84\x8b" // U+f10b 251 | #define ICON_FA_CIRCLE_O "\xef\x84\x8c" // U+f10c 252 | #define ICON_FA_QUOTE_LEFT "\xef\x84\x8d" // U+f10d 253 | #define ICON_FA_QUOTE_RIGHT "\xef\x84\x8e" // U+f10e 254 | #define ICON_FA_SPINNER "\xef\x84\x90" // U+f110 255 | #define ICON_FA_CIRCLE "\xef\x84\x91" // U+f111 256 | #define ICON_FA_REPLY "\xef\x84\x92" // U+f112 257 | #define ICON_FA_GITHUB_ALT "\xef\x84\x93" // U+f113 258 | #define ICON_FA_FOLDER_O "\xef\x84\x94" // U+f114 259 | #define ICON_FA_FOLDER_OPEN_O "\xef\x84\x95" // U+f115 260 | #define ICON_FA_SMILE_O "\xef\x84\x98" // U+f118 261 | #define ICON_FA_FROWN_O "\xef\x84\x99" // U+f119 262 | #define ICON_FA_MEH_O "\xef\x84\x9a" // U+f11a 263 | #define ICON_FA_GAMEPAD "\xef\x84\x9b" // U+f11b 264 | #define ICON_FA_KEYBOARD_O "\xef\x84\x9c" // U+f11c 265 | #define ICON_FA_FLAG_O "\xef\x84\x9d" // U+f11d 266 | #define ICON_FA_FLAG_CHECKERED "\xef\x84\x9e" // U+f11e 267 | #define ICON_FA_TERMINAL "\xef\x84\xa0" // U+f120 268 | #define ICON_FA_CODE "\xef\x84\xa1" // U+f121 269 | #define ICON_FA_REPLY_ALL "\xef\x84\xa2" // U+f122 270 | #define ICON_FA_STAR_HALF_O "\xef\x84\xa3" // U+f123 271 | #define ICON_FA_LOCATION_ARROW "\xef\x84\xa4" // U+f124 272 | #define ICON_FA_CROP "\xef\x84\xa5" // U+f125 273 | #define ICON_FA_CODE_FORK "\xef\x84\xa6" // U+f126 274 | #define ICON_FA_CHAIN_BROKEN "\xef\x84\xa7" // U+f127 275 | #define ICON_FA_QUESTION "\xef\x84\xa8" // U+f128 276 | #define ICON_FA_INFO "\xef\x84\xa9" // U+f129 277 | #define ICON_FA_EXCLAMATION "\xef\x84\xaa" // U+f12a 278 | #define ICON_FA_SUPERSCRIPT "\xef\x84\xab" // U+f12b 279 | #define ICON_FA_SUBSCRIPT "\xef\x84\xac" // U+f12c 280 | #define ICON_FA_ERASER "\xef\x84\xad" // U+f12d 281 | #define ICON_FA_PUZZLE_PIECE "\xef\x84\xae" // U+f12e 282 | #define ICON_FA_MICROPHONE "\xef\x84\xb0" // U+f130 283 | #define ICON_FA_MICROPHONE_SLASH "\xef\x84\xb1" // U+f131 284 | #define ICON_FA_SHIELD "\xef\x84\xb2" // U+f132 285 | #define ICON_FA_CALENDAR_O "\xef\x84\xb3" // U+f133 286 | #define ICON_FA_FIRE_EXTINGUISHER "\xef\x84\xb4" // U+f134 287 | #define ICON_FA_ROCKET "\xef\x84\xb5" // U+f135 288 | #define ICON_FA_MAXCDN "\xef\x84\xb6" // U+f136 289 | #define ICON_FA_CHEVRON_CIRCLE_LEFT "\xef\x84\xb7" // U+f137 290 | #define ICON_FA_CHEVRON_CIRCLE_RIGHT "\xef\x84\xb8" // U+f138 291 | #define ICON_FA_CHEVRON_CIRCLE_UP "\xef\x84\xb9" // U+f139 292 | #define ICON_FA_CHEVRON_CIRCLE_DOWN "\xef\x84\xba" // U+f13a 293 | #define ICON_FA_HTML5 "\xef\x84\xbb" // U+f13b 294 | #define ICON_FA_CSS3 "\xef\x84\xbc" // U+f13c 295 | #define ICON_FA_ANCHOR "\xef\x84\xbd" // U+f13d 296 | #define ICON_FA_UNLOCK_ALT "\xef\x84\xbe" // U+f13e 297 | #define ICON_FA_BULLSEYE "\xef\x85\x80" // U+f140 298 | #define ICON_FA_ELLIPSIS_H "\xef\x85\x81" // U+f141 299 | #define ICON_FA_ELLIPSIS_V "\xef\x85\x82" // U+f142 300 | #define ICON_FA_RSS_SQUARE "\xef\x85\x83" // U+f143 301 | #define ICON_FA_PLAY_CIRCLE "\xef\x85\x84" // U+f144 302 | #define ICON_FA_TICKET "\xef\x85\x85" // U+f145 303 | #define ICON_FA_MINUS_SQUARE "\xef\x85\x86" // U+f146 304 | #define ICON_FA_MINUS_SQUARE_O "\xef\x85\x87" // U+f147 305 | #define ICON_FA_LEVEL_UP "\xef\x85\x88" // U+f148 306 | #define ICON_FA_LEVEL_DOWN "\xef\x85\x89" // U+f149 307 | #define ICON_FA_CHECK_SQUARE "\xef\x85\x8a" // U+f14a 308 | #define ICON_FA_PENCIL_SQUARE "\xef\x85\x8b" // U+f14b 309 | #define ICON_FA_EXTERNAL_LINK_SQUARE "\xef\x85\x8c" // U+f14c 310 | #define ICON_FA_SHARE_SQUARE "\xef\x85\x8d" // U+f14d 311 | #define ICON_FA_COMPASS "\xef\x85\x8e" // U+f14e 312 | #define ICON_FA_CARET_SQUARE_O_DOWN "\xef\x85\x90" // U+f150 313 | #define ICON_FA_CARET_SQUARE_O_UP "\xef\x85\x91" // U+f151 314 | #define ICON_FA_CARET_SQUARE_O_RIGHT "\xef\x85\x92" // U+f152 315 | #define ICON_FA_EUR "\xef\x85\x93" // U+f153 316 | #define ICON_FA_GBP "\xef\x85\x94" // U+f154 317 | #define ICON_FA_USD "\xef\x85\x95" // U+f155 318 | #define ICON_FA_INR "\xef\x85\x96" // U+f156 319 | #define ICON_FA_JPY "\xef\x85\x97" // U+f157 320 | #define ICON_FA_RUB "\xef\x85\x98" // U+f158 321 | #define ICON_FA_KRW "\xef\x85\x99" // U+f159 322 | #define ICON_FA_BTC "\xef\x85\x9a" // U+f15a 323 | #define ICON_FA_FILE "\xef\x85\x9b" // U+f15b 324 | #define ICON_FA_FILE_TEXT "\xef\x85\x9c" // U+f15c 325 | #define ICON_FA_SORT_ALPHA_ASC "\xef\x85\x9d" // U+f15d 326 | #define ICON_FA_SORT_ALPHA_DESC "\xef\x85\x9e" // U+f15e 327 | #define ICON_FA_SORT_AMOUNT_ASC "\xef\x85\xa0" // U+f160 328 | #define ICON_FA_SORT_AMOUNT_DESC "\xef\x85\xa1" // U+f161 329 | #define ICON_FA_SORT_NUMERIC_ASC "\xef\x85\xa2" // U+f162 330 | #define ICON_FA_SORT_NUMERIC_DESC "\xef\x85\xa3" // U+f163 331 | #define ICON_FA_THUMBS_UP "\xef\x85\xa4" // U+f164 332 | #define ICON_FA_THUMBS_DOWN "\xef\x85\xa5" // U+f165 333 | #define ICON_FA_YOUTUBE_SQUARE "\xef\x85\xa6" // U+f166 334 | #define ICON_FA_YOUTUBE "\xef\x85\xa7" // U+f167 335 | #define ICON_FA_XING "\xef\x85\xa8" // U+f168 336 | #define ICON_FA_XING_SQUARE "\xef\x85\xa9" // U+f169 337 | #define ICON_FA_YOUTUBE_PLAY "\xef\x85\xaa" // U+f16a 338 | #define ICON_FA_DROPBOX "\xef\x85\xab" // U+f16b 339 | #define ICON_FA_STACK_OVERFLOW "\xef\x85\xac" // U+f16c 340 | #define ICON_FA_INSTAGRAM "\xef\x85\xad" // U+f16d 341 | #define ICON_FA_FLICKR "\xef\x85\xae" // U+f16e 342 | #define ICON_FA_ADN "\xef\x85\xb0" // U+f170 343 | #define ICON_FA_BITBUCKET "\xef\x85\xb1" // U+f171 344 | #define ICON_FA_BITBUCKET_SQUARE "\xef\x85\xb2" // U+f172 345 | #define ICON_FA_TUMBLR "\xef\x85\xb3" // U+f173 346 | #define ICON_FA_TUMBLR_SQUARE "\xef\x85\xb4" // U+f174 347 | #define ICON_FA_LONG_ARROW_DOWN "\xef\x85\xb5" // U+f175 348 | #define ICON_FA_LONG_ARROW_UP "\xef\x85\xb6" // U+f176 349 | #define ICON_FA_LONG_ARROW_LEFT "\xef\x85\xb7" // U+f177 350 | #define ICON_FA_LONG_ARROW_RIGHT "\xef\x85\xb8" // U+f178 351 | #define ICON_FA_APPLE "\xef\x85\xb9" // U+f179 352 | #define ICON_FA_WINDOWS "\xef\x85\xba" // U+f17a 353 | #define ICON_FA_ANDROID "\xef\x85\xbb" // U+f17b 354 | #define ICON_FA_LINUX "\xef\x85\xbc" // U+f17c 355 | #define ICON_FA_DRIBBBLE "\xef\x85\xbd" // U+f17d 356 | #define ICON_FA_SKYPE "\xef\x85\xbe" // U+f17e 357 | #define ICON_FA_FOURSQUARE "\xef\x86\x80" // U+f180 358 | #define ICON_FA_TRELLO "\xef\x86\x81" // U+f181 359 | #define ICON_FA_FEMALE "\xef\x86\x82" // U+f182 360 | #define ICON_FA_MALE "\xef\x86\x83" // U+f183 361 | #define ICON_FA_GRATIPAY "\xef\x86\x84" // U+f184 362 | #define ICON_FA_SUN_O "\xef\x86\x85" // U+f185 363 | #define ICON_FA_MOON_O "\xef\x86\x86" // U+f186 364 | #define ICON_FA_ARCHIVE "\xef\x86\x87" // U+f187 365 | #define ICON_FA_BUG "\xef\x86\x88" // U+f188 366 | #define ICON_FA_VK "\xef\x86\x89" // U+f189 367 | #define ICON_FA_WEIBO "\xef\x86\x8a" // U+f18a 368 | #define ICON_FA_RENREN "\xef\x86\x8b" // U+f18b 369 | #define ICON_FA_PAGELINES "\xef\x86\x8c" // U+f18c 370 | #define ICON_FA_STACK_EXCHANGE "\xef\x86\x8d" // U+f18d 371 | #define ICON_FA_ARROW_CIRCLE_O_RIGHT "\xef\x86\x8e" // U+f18e 372 | #define ICON_FA_ARROW_CIRCLE_O_LEFT "\xef\x86\x90" // U+f190 373 | #define ICON_FA_CARET_SQUARE_O_LEFT "\xef\x86\x91" // U+f191 374 | #define ICON_FA_DOT_CIRCLE_O "\xef\x86\x92" // U+f192 375 | #define ICON_FA_WHEELCHAIR "\xef\x86\x93" // U+f193 376 | #define ICON_FA_VIMEO_SQUARE "\xef\x86\x94" // U+f194 377 | #define ICON_FA_TRY "\xef\x86\x95" // U+f195 378 | #define ICON_FA_PLUS_SQUARE_O "\xef\x86\x96" // U+f196 379 | #define ICON_FA_SPACE_SHUTTLE "\xef\x86\x97" // U+f197 380 | #define ICON_FA_SLACK "\xef\x86\x98" // U+f198 381 | #define ICON_FA_ENVELOPE_SQUARE "\xef\x86\x99" // U+f199 382 | #define ICON_FA_WORDPRESS "\xef\x86\x9a" // U+f19a 383 | #define ICON_FA_OPENID "\xef\x86\x9b" // U+f19b 384 | #define ICON_FA_UNIVERSITY "\xef\x86\x9c" // U+f19c 385 | #define ICON_FA_GRADUATION_CAP "\xef\x86\x9d" // U+f19d 386 | #define ICON_FA_YAHOO "\xef\x86\x9e" // U+f19e 387 | #define ICON_FA_GOOGLE "\xef\x86\xa0" // U+f1a0 388 | #define ICON_FA_REDDIT "\xef\x86\xa1" // U+f1a1 389 | #define ICON_FA_REDDIT_SQUARE "\xef\x86\xa2" // U+f1a2 390 | #define ICON_FA_STUMBLEUPON_CIRCLE "\xef\x86\xa3" // U+f1a3 391 | #define ICON_FA_STUMBLEUPON "\xef\x86\xa4" // U+f1a4 392 | #define ICON_FA_DELICIOUS "\xef\x86\xa5" // U+f1a5 393 | #define ICON_FA_DIGG "\xef\x86\xa6" // U+f1a6 394 | #define ICON_FA_PIED_PIPER_PP "\xef\x86\xa7" // U+f1a7 395 | #define ICON_FA_PIED_PIPER_ALT "\xef\x86\xa8" // U+f1a8 396 | #define ICON_FA_DRUPAL "\xef\x86\xa9" // U+f1a9 397 | #define ICON_FA_JOOMLA "\xef\x86\xaa" // U+f1aa 398 | #define ICON_FA_LANGUAGE "\xef\x86\xab" // U+f1ab 399 | #define ICON_FA_FAX "\xef\x86\xac" // U+f1ac 400 | #define ICON_FA_BUILDING "\xef\x86\xad" // U+f1ad 401 | #define ICON_FA_CHILD "\xef\x86\xae" // U+f1ae 402 | #define ICON_FA_PAW "\xef\x86\xb0" // U+f1b0 403 | #define ICON_FA_SPOON "\xef\x86\xb1" // U+f1b1 404 | #define ICON_FA_CUBE "\xef\x86\xb2" // U+f1b2 405 | #define ICON_FA_CUBES "\xef\x86\xb3" // U+f1b3 406 | #define ICON_FA_BEHANCE "\xef\x86\xb4" // U+f1b4 407 | #define ICON_FA_BEHANCE_SQUARE "\xef\x86\xb5" // U+f1b5 408 | #define ICON_FA_STEAM "\xef\x86\xb6" // U+f1b6 409 | #define ICON_FA_STEAM_SQUARE "\xef\x86\xb7" // U+f1b7 410 | #define ICON_FA_RECYCLE "\xef\x86\xb8" // U+f1b8 411 | #define ICON_FA_CAR "\xef\x86\xb9" // U+f1b9 412 | #define ICON_FA_TAXI "\xef\x86\xba" // U+f1ba 413 | #define ICON_FA_TREE "\xef\x86\xbb" // U+f1bb 414 | #define ICON_FA_SPOTIFY "\xef\x86\xbc" // U+f1bc 415 | #define ICON_FA_DEVIANTART "\xef\x86\xbd" // U+f1bd 416 | #define ICON_FA_SOUNDCLOUD "\xef\x86\xbe" // U+f1be 417 | #define ICON_FA_DATABASE "\xef\x87\x80" // U+f1c0 418 | #define ICON_FA_FILE_PDF_O "\xef\x87\x81" // U+f1c1 419 | #define ICON_FA_FILE_WORD_O "\xef\x87\x82" // U+f1c2 420 | #define ICON_FA_FILE_EXCEL_O "\xef\x87\x83" // U+f1c3 421 | #define ICON_FA_FILE_POWERPOINT_O "\xef\x87\x84" // U+f1c4 422 | #define ICON_FA_FILE_IMAGE_O "\xef\x87\x85" // U+f1c5 423 | #define ICON_FA_FILE_ARCHIVE_O "\xef\x87\x86" // U+f1c6 424 | #define ICON_FA_FILE_AUDIO_O "\xef\x87\x87" // U+f1c7 425 | #define ICON_FA_FILE_VIDEO_O "\xef\x87\x88" // U+f1c8 426 | #define ICON_FA_FILE_CODE_O "\xef\x87\x89" // U+f1c9 427 | #define ICON_FA_VINE "\xef\x87\x8a" // U+f1ca 428 | #define ICON_FA_CODEPEN "\xef\x87\x8b" // U+f1cb 429 | #define ICON_FA_JSFIDDLE "\xef\x87\x8c" // U+f1cc 430 | #define ICON_FA_LIFE_RING "\xef\x87\x8d" // U+f1cd 431 | #define ICON_FA_CIRCLE_O_NOTCH "\xef\x87\x8e" // U+f1ce 432 | #define ICON_FA_REBEL "\xef\x87\x90" // U+f1d0 433 | #define ICON_FA_EMPIRE "\xef\x87\x91" // U+f1d1 434 | #define ICON_FA_GIT_SQUARE "\xef\x87\x92" // U+f1d2 435 | #define ICON_FA_GIT "\xef\x87\x93" // U+f1d3 436 | #define ICON_FA_HACKER_NEWS "\xef\x87\x94" // U+f1d4 437 | #define ICON_FA_TENCENT_WEIBO "\xef\x87\x95" // U+f1d5 438 | #define ICON_FA_QQ "\xef\x87\x96" // U+f1d6 439 | #define ICON_FA_WEIXIN "\xef\x87\x97" // U+f1d7 440 | #define ICON_FA_PAPER_PLANE "\xef\x87\x98" // U+f1d8 441 | #define ICON_FA_PAPER_PLANE_O "\xef\x87\x99" // U+f1d9 442 | #define ICON_FA_HISTORY "\xef\x87\x9a" // U+f1da 443 | #define ICON_FA_CIRCLE_THIN "\xef\x87\x9b" // U+f1db 444 | #define ICON_FA_HEADER "\xef\x87\x9c" // U+f1dc 445 | #define ICON_FA_PARAGRAPH "\xef\x87\x9d" // U+f1dd 446 | #define ICON_FA_SLIDERS "\xef\x87\x9e" // U+f1de 447 | #define ICON_FA_SHARE_ALT "\xef\x87\xa0" // U+f1e0 448 | #define ICON_FA_SHARE_ALT_SQUARE "\xef\x87\xa1" // U+f1e1 449 | #define ICON_FA_BOMB "\xef\x87\xa2" // U+f1e2 450 | #define ICON_FA_FUTBOL_O "\xef\x87\xa3" // U+f1e3 451 | #define ICON_FA_TTY "\xef\x87\xa4" // U+f1e4 452 | #define ICON_FA_BINOCULARS "\xef\x87\xa5" // U+f1e5 453 | #define ICON_FA_PLUG "\xef\x87\xa6" // U+f1e6 454 | #define ICON_FA_SLIDESHARE "\xef\x87\xa7" // U+f1e7 455 | #define ICON_FA_TWITCH "\xef\x87\xa8" // U+f1e8 456 | #define ICON_FA_YELP "\xef\x87\xa9" // U+f1e9 457 | #define ICON_FA_NEWSPAPER_O "\xef\x87\xaa" // U+f1ea 458 | #define ICON_FA_WIFI "\xef\x87\xab" // U+f1eb 459 | #define ICON_FA_CALCULATOR "\xef\x87\xac" // U+f1ec 460 | #define ICON_FA_PAYPAL "\xef\x87\xad" // U+f1ed 461 | #define ICON_FA_GOOGLE_WALLET "\xef\x87\xae" // U+f1ee 462 | #define ICON_FA_CC_VISA "\xef\x87\xb0" // U+f1f0 463 | #define ICON_FA_CC_MASTERCARD "\xef\x87\xb1" // U+f1f1 464 | #define ICON_FA_CC_DISCOVER "\xef\x87\xb2" // U+f1f2 465 | #define ICON_FA_CC_AMEX "\xef\x87\xb3" // U+f1f3 466 | #define ICON_FA_CC_PAYPAL "\xef\x87\xb4" // U+f1f4 467 | #define ICON_FA_CC_STRIPE "\xef\x87\xb5" // U+f1f5 468 | #define ICON_FA_BELL_SLASH "\xef\x87\xb6" // U+f1f6 469 | #define ICON_FA_BELL_SLASH_O "\xef\x87\xb7" // U+f1f7 470 | #define ICON_FA_TRASH "\xef\x87\xb8" // U+f1f8 471 | #define ICON_FA_COPYRIGHT "\xef\x87\xb9" // U+f1f9 472 | #define ICON_FA_AT "\xef\x87\xba" // U+f1fa 473 | #define ICON_FA_EYEDROPPER "\xef\x87\xbb" // U+f1fb 474 | #define ICON_FA_PAINT_BRUSH "\xef\x87\xbc" // U+f1fc 475 | #define ICON_FA_BIRTHDAY_CAKE "\xef\x87\xbd" // U+f1fd 476 | #define ICON_FA_AREA_CHART "\xef\x87\xbe" // U+f1fe 477 | #define ICON_FA_PIE_CHART "\xef\x88\x80" // U+f200 478 | #define ICON_FA_LINE_CHART "\xef\x88\x81" // U+f201 479 | #define ICON_FA_LASTFM "\xef\x88\x82" // U+f202 480 | #define ICON_FA_LASTFM_SQUARE "\xef\x88\x83" // U+f203 481 | #define ICON_FA_TOGGLE_OFF "\xef\x88\x84" // U+f204 482 | #define ICON_FA_TOGGLE_ON "\xef\x88\x85" // U+f205 483 | #define ICON_FA_BICYCLE "\xef\x88\x86" // U+f206 484 | #define ICON_FA_BUS "\xef\x88\x87" // U+f207 485 | #define ICON_FA_IOXHOST "\xef\x88\x88" // U+f208 486 | #define ICON_FA_ANGELLIST "\xef\x88\x89" // U+f209 487 | #define ICON_FA_CC "\xef\x88\x8a" // U+f20a 488 | #define ICON_FA_ILS "\xef\x88\x8b" // U+f20b 489 | #define ICON_FA_MEANPATH "\xef\x88\x8c" // U+f20c 490 | #define ICON_FA_BUYSELLADS "\xef\x88\x8d" // U+f20d 491 | #define ICON_FA_CONNECTDEVELOP "\xef\x88\x8e" // U+f20e 492 | #define ICON_FA_DASHCUBE "\xef\x88\x90" // U+f210 493 | #define ICON_FA_FORUMBEE "\xef\x88\x91" // U+f211 494 | #define ICON_FA_LEANPUB "\xef\x88\x92" // U+f212 495 | #define ICON_FA_SELLSY "\xef\x88\x93" // U+f213 496 | #define ICON_FA_SHIRTSINBULK "\xef\x88\x94" // U+f214 497 | #define ICON_FA_SIMPLYBUILT "\xef\x88\x95" // U+f215 498 | #define ICON_FA_SKYATLAS "\xef\x88\x96" // U+f216 499 | #define ICON_FA_CART_PLUS "\xef\x88\x97" // U+f217 500 | #define ICON_FA_CART_ARROW_DOWN "\xef\x88\x98" // U+f218 501 | #define ICON_FA_DIAMOND "\xef\x88\x99" // U+f219 502 | #define ICON_FA_SHIP "\xef\x88\x9a" // U+f21a 503 | #define ICON_FA_USER_SECRET "\xef\x88\x9b" // U+f21b 504 | #define ICON_FA_MOTORCYCLE "\xef\x88\x9c" // U+f21c 505 | #define ICON_FA_STREET_VIEW "\xef\x88\x9d" // U+f21d 506 | #define ICON_FA_HEARTBEAT "\xef\x88\x9e" // U+f21e 507 | #define ICON_FA_VENUS "\xef\x88\xa1" // U+f221 508 | #define ICON_FA_MARS "\xef\x88\xa2" // U+f222 509 | #define ICON_FA_MERCURY "\xef\x88\xa3" // U+f223 510 | #define ICON_FA_TRANSGENDER "\xef\x88\xa4" // U+f224 511 | #define ICON_FA_TRANSGENDER_ALT "\xef\x88\xa5" // U+f225 512 | #define ICON_FA_VENUS_DOUBLE "\xef\x88\xa6" // U+f226 513 | #define ICON_FA_MARS_DOUBLE "\xef\x88\xa7" // U+f227 514 | #define ICON_FA_VENUS_MARS "\xef\x88\xa8" // U+f228 515 | #define ICON_FA_MARS_STROKE "\xef\x88\xa9" // U+f229 516 | #define ICON_FA_MARS_STROKE_V "\xef\x88\xaa" // U+f22a 517 | #define ICON_FA_MARS_STROKE_H "\xef\x88\xab" // U+f22b 518 | #define ICON_FA_NEUTER "\xef\x88\xac" // U+f22c 519 | #define ICON_FA_GENDERLESS "\xef\x88\xad" // U+f22d 520 | #define ICON_FA_FACEBOOK_OFFICIAL "\xef\x88\xb0" // U+f230 521 | #define ICON_FA_PINTEREST_P "\xef\x88\xb1" // U+f231 522 | #define ICON_FA_WHATSAPP "\xef\x88\xb2" // U+f232 523 | #define ICON_FA_SERVER "\xef\x88\xb3" // U+f233 524 | #define ICON_FA_USER_PLUS "\xef\x88\xb4" // U+f234 525 | #define ICON_FA_USER_TIMES "\xef\x88\xb5" // U+f235 526 | #define ICON_FA_BED "\xef\x88\xb6" // U+f236 527 | #define ICON_FA_VIACOIN "\xef\x88\xb7" // U+f237 528 | #define ICON_FA_TRAIN "\xef\x88\xb8" // U+f238 529 | #define ICON_FA_SUBWAY "\xef\x88\xb9" // U+f239 530 | #define ICON_FA_MEDIUM "\xef\x88\xba" // U+f23a 531 | #define ICON_FA_Y_COMBINATOR "\xef\x88\xbb" // U+f23b 532 | #define ICON_FA_OPTIN_MONSTER "\xef\x88\xbc" // U+f23c 533 | #define ICON_FA_OPENCART "\xef\x88\xbd" // U+f23d 534 | #define ICON_FA_EXPEDITEDSSL "\xef\x88\xbe" // U+f23e 535 | #define ICON_FA_BATTERY_FULL "\xef\x89\x80" // U+f240 536 | #define ICON_FA_BATTERY_THREE_QUARTERS "\xef\x89\x81" // U+f241 537 | #define ICON_FA_BATTERY_HALF "\xef\x89\x82" // U+f242 538 | #define ICON_FA_BATTERY_QUARTER "\xef\x89\x83" // U+f243 539 | #define ICON_FA_BATTERY_EMPTY "\xef\x89\x84" // U+f244 540 | #define ICON_FA_MOUSE_POINTER "\xef\x89\x85" // U+f245 541 | #define ICON_FA_I_CURSOR "\xef\x89\x86" // U+f246 542 | #define ICON_FA_OBJECT_GROUP "\xef\x89\x87" // U+f247 543 | #define ICON_FA_OBJECT_UNGROUP "\xef\x89\x88" // U+f248 544 | #define ICON_FA_STICKY_NOTE "\xef\x89\x89" // U+f249 545 | #define ICON_FA_STICKY_NOTE_O "\xef\x89\x8a" // U+f24a 546 | #define ICON_FA_CC_JCB "\xef\x89\x8b" // U+f24b 547 | #define ICON_FA_CC_DINERS_CLUB "\xef\x89\x8c" // U+f24c 548 | #define ICON_FA_CLONE "\xef\x89\x8d" // U+f24d 549 | #define ICON_FA_BALANCE_SCALE "\xef\x89\x8e" // U+f24e 550 | #define ICON_FA_HOURGLASS_O "\xef\x89\x90" // U+f250 551 | #define ICON_FA_HOURGLASS_START "\xef\x89\x91" // U+f251 552 | #define ICON_FA_HOURGLASS_HALF "\xef\x89\x92" // U+f252 553 | #define ICON_FA_HOURGLASS_END "\xef\x89\x93" // U+f253 554 | #define ICON_FA_HOURGLASS "\xef\x89\x94" // U+f254 555 | #define ICON_FA_HAND_ROCK_O "\xef\x89\x95" // U+f255 556 | #define ICON_FA_HAND_PAPER_O "\xef\x89\x96" // U+f256 557 | #define ICON_FA_HAND_SCISSORS_O "\xef\x89\x97" // U+f257 558 | #define ICON_FA_HAND_LIZARD_O "\xef\x89\x98" // U+f258 559 | #define ICON_FA_HAND_SPOCK_O "\xef\x89\x99" // U+f259 560 | #define ICON_FA_HAND_POINTER_O "\xef\x89\x9a" // U+f25a 561 | #define ICON_FA_HAND_PEACE_O "\xef\x89\x9b" // U+f25b 562 | #define ICON_FA_TRADEMARK "\xef\x89\x9c" // U+f25c 563 | #define ICON_FA_REGISTERED "\xef\x89\x9d" // U+f25d 564 | #define ICON_FA_CREATIVE_COMMONS "\xef\x89\x9e" // U+f25e 565 | #define ICON_FA_GG "\xef\x89\xa0" // U+f260 566 | #define ICON_FA_GG_CIRCLE "\xef\x89\xa1" // U+f261 567 | #define ICON_FA_TRIPADVISOR "\xef\x89\xa2" // U+f262 568 | #define ICON_FA_ODNOKLASSNIKI "\xef\x89\xa3" // U+f263 569 | #define ICON_FA_ODNOKLASSNIKI_SQUARE "\xef\x89\xa4" // U+f264 570 | #define ICON_FA_GET_POCKET "\xef\x89\xa5" // U+f265 571 | #define ICON_FA_WIKIPEDIA_W "\xef\x89\xa6" // U+f266 572 | #define ICON_FA_SAFARI "\xef\x89\xa7" // U+f267 573 | #define ICON_FA_CHROME "\xef\x89\xa8" // U+f268 574 | #define ICON_FA_FIREFOX "\xef\x89\xa9" // U+f269 575 | #define ICON_FA_OPERA "\xef\x89\xaa" // U+f26a 576 | #define ICON_FA_INTERNET_EXPLORER "\xef\x89\xab" // U+f26b 577 | #define ICON_FA_TELEVISION "\xef\x89\xac" // U+f26c 578 | #define ICON_FA_CONTAO "\xef\x89\xad" // U+f26d 579 | #define ICON_FA_500PX "\xef\x89\xae" // U+f26e 580 | #define ICON_FA_AMAZON "\xef\x89\xb0" // U+f270 581 | #define ICON_FA_CALENDAR_PLUS_O "\xef\x89\xb1" // U+f271 582 | #define ICON_FA_CALENDAR_MINUS_O "\xef\x89\xb2" // U+f272 583 | #define ICON_FA_CALENDAR_TIMES_O "\xef\x89\xb3" // U+f273 584 | #define ICON_FA_CALENDAR_CHECK_O "\xef\x89\xb4" // U+f274 585 | #define ICON_FA_INDUSTRY "\xef\x89\xb5" // U+f275 586 | #define ICON_FA_MAP_PIN "\xef\x89\xb6" // U+f276 587 | #define ICON_FA_MAP_SIGNS "\xef\x89\xb7" // U+f277 588 | #define ICON_FA_MAP_O "\xef\x89\xb8" // U+f278 589 | #define ICON_FA_MAP "\xef\x89\xb9" // U+f279 590 | #define ICON_FA_COMMENTING "\xef\x89\xba" // U+f27a 591 | #define ICON_FA_COMMENTING_O "\xef\x89\xbb" // U+f27b 592 | #define ICON_FA_HOUZZ "\xef\x89\xbc" // U+f27c 593 | #define ICON_FA_VIMEO "\xef\x89\xbd" // U+f27d 594 | #define ICON_FA_BLACK_TIE "\xef\x89\xbe" // U+f27e 595 | #define ICON_FA_FONTICONS "\xef\x8a\x80" // U+f280 596 | #define ICON_FA_REDDIT_ALIEN "\xef\x8a\x81" // U+f281 597 | #define ICON_FA_EDGE "\xef\x8a\x82" // U+f282 598 | #define ICON_FA_CREDIT_CARD_ALT "\xef\x8a\x83" // U+f283 599 | #define ICON_FA_CODIEPIE "\xef\x8a\x84" // U+f284 600 | #define ICON_FA_MODX "\xef\x8a\x85" // U+f285 601 | #define ICON_FA_FORT_AWESOME "\xef\x8a\x86" // U+f286 602 | #define ICON_FA_USB "\xef\x8a\x87" // U+f287 603 | #define ICON_FA_PRODUCT_HUNT "\xef\x8a\x88" // U+f288 604 | #define ICON_FA_MIXCLOUD "\xef\x8a\x89" // U+f289 605 | #define ICON_FA_SCRIBD "\xef\x8a\x8a" // U+f28a 606 | #define ICON_FA_PAUSE_CIRCLE "\xef\x8a\x8b" // U+f28b 607 | #define ICON_FA_PAUSE_CIRCLE_O "\xef\x8a\x8c" // U+f28c 608 | #define ICON_FA_STOP_CIRCLE "\xef\x8a\x8d" // U+f28d 609 | #define ICON_FA_STOP_CIRCLE_O "\xef\x8a\x8e" // U+f28e 610 | #define ICON_FA_SHOPPING_BAG "\xef\x8a\x90" // U+f290 611 | #define ICON_FA_SHOPPING_BASKET "\xef\x8a\x91" // U+f291 612 | #define ICON_FA_HASHTAG "\xef\x8a\x92" // U+f292 613 | #define ICON_FA_BLUETOOTH "\xef\x8a\x93" // U+f293 614 | #define ICON_FA_BLUETOOTH_B "\xef\x8a\x94" // U+f294 615 | #define ICON_FA_PERCENT "\xef\x8a\x95" // U+f295 616 | #define ICON_FA_GITLAB "\xef\x8a\x96" // U+f296 617 | #define ICON_FA_WPBEGINNER "\xef\x8a\x97" // U+f297 618 | #define ICON_FA_WPFORMS "\xef\x8a\x98" // U+f298 619 | #define ICON_FA_ENVIRA "\xef\x8a\x99" // U+f299 620 | #define ICON_FA_UNIVERSAL_ACCESS "\xef\x8a\x9a" // U+f29a 621 | #define ICON_FA_WHEELCHAIR_ALT "\xef\x8a\x9b" // U+f29b 622 | #define ICON_FA_QUESTION_CIRCLE_O "\xef\x8a\x9c" // U+f29c 623 | #define ICON_FA_BLIND "\xef\x8a\x9d" // U+f29d 624 | #define ICON_FA_AUDIO_DESCRIPTION "\xef\x8a\x9e" // U+f29e 625 | #define ICON_FA_VOLUME_CONTROL_PHONE "\xef\x8a\xa0" // U+f2a0 626 | #define ICON_FA_BRAILLE "\xef\x8a\xa1" // U+f2a1 627 | #define ICON_FA_ASSISTIVE_LISTENING_SYSTEMS "\xef\x8a\xa2" // U+f2a2 628 | #define ICON_FA_AMERICAN_SIGN_LANGUAGE_INTERPRETING "\xef\x8a\xa3" // U+f2a3 629 | #define ICON_FA_DEAF "\xef\x8a\xa4" // U+f2a4 630 | #define ICON_FA_GLIDE "\xef\x8a\xa5" // U+f2a5 631 | #define ICON_FA_GLIDE_G "\xef\x8a\xa6" // U+f2a6 632 | #define ICON_FA_SIGN_LANGUAGE "\xef\x8a\xa7" // U+f2a7 633 | #define ICON_FA_LOW_VISION "\xef\x8a\xa8" // U+f2a8 634 | #define ICON_FA_VIADEO "\xef\x8a\xa9" // U+f2a9 635 | #define ICON_FA_VIADEO_SQUARE "\xef\x8a\xaa" // U+f2aa 636 | #define ICON_FA_SNAPCHAT "\xef\x8a\xab" // U+f2ab 637 | #define ICON_FA_SNAPCHAT_GHOST "\xef\x8a\xac" // U+f2ac 638 | #define ICON_FA_SNAPCHAT_SQUARE "\xef\x8a\xad" // U+f2ad 639 | #define ICON_FA_PIED_PIPER "\xef\x8a\xae" // U+f2ae 640 | #define ICON_FA_FIRST_ORDER "\xef\x8a\xb0" // U+f2b0 641 | #define ICON_FA_YOAST "\xef\x8a\xb1" // U+f2b1 642 | #define ICON_FA_THEMEISLE "\xef\x8a\xb2" // U+f2b2 643 | #define ICON_FA_GOOGLE_PLUS_OFFICIAL "\xef\x8a\xb3" // U+f2b3 644 | #define ICON_FA_FONT_AWESOME "\xef\x8a\xb4" // U+f2b4 645 | #define ICON_FA_HANDSHAKE_O "\xef\x8a\xb5" // U+f2b5 646 | #define ICON_FA_ENVELOPE_OPEN "\xef\x8a\xb6" // U+f2b6 647 | #define ICON_FA_ENVELOPE_OPEN_O "\xef\x8a\xb7" // U+f2b7 648 | #define ICON_FA_LINODE "\xef\x8a\xb8" // U+f2b8 649 | #define ICON_FA_ADDRESS_BOOK "\xef\x8a\xb9" // U+f2b9 650 | #define ICON_FA_ADDRESS_BOOK_O "\xef\x8a\xba" // U+f2ba 651 | #define ICON_FA_ADDRESS_CARD "\xef\x8a\xbb" // U+f2bb 652 | #define ICON_FA_ADDRESS_CARD_O "\xef\x8a\xbc" // U+f2bc 653 | #define ICON_FA_USER_CIRCLE "\xef\x8a\xbd" // U+f2bd 654 | #define ICON_FA_USER_CIRCLE_O "\xef\x8a\xbe" // U+f2be 655 | #define ICON_FA_USER_O "\xef\x8b\x80" // U+f2c0 656 | #define ICON_FA_ID_BADGE "\xef\x8b\x81" // U+f2c1 657 | #define ICON_FA_ID_CARD "\xef\x8b\x82" // U+f2c2 658 | #define ICON_FA_ID_CARD_O "\xef\x8b\x83" // U+f2c3 659 | #define ICON_FA_QUORA "\xef\x8b\x84" // U+f2c4 660 | #define ICON_FA_FREE_CODE_CAMP "\xef\x8b\x85" // U+f2c5 661 | #define ICON_FA_TELEGRAM "\xef\x8b\x86" // U+f2c6 662 | #define ICON_FA_THERMOMETER_FULL "\xef\x8b\x87" // U+f2c7 663 | #define ICON_FA_THERMOMETER_THREE_QUARTERS "\xef\x8b\x88" // U+f2c8 664 | #define ICON_FA_THERMOMETER_HALF "\xef\x8b\x89" // U+f2c9 665 | #define ICON_FA_THERMOMETER_QUARTER "\xef\x8b\x8a" // U+f2ca 666 | #define ICON_FA_THERMOMETER_EMPTY "\xef\x8b\x8b" // U+f2cb 667 | #define ICON_FA_SHOWER "\xef\x8b\x8c" // U+f2cc 668 | #define ICON_FA_BATH "\xef\x8b\x8d" // U+f2cd 669 | #define ICON_FA_PODCAST "\xef\x8b\x8e" // U+f2ce 670 | #define ICON_FA_WINDOW_MAXIMIZE "\xef\x8b\x90" // U+f2d0 671 | #define ICON_FA_WINDOW_MINIMIZE "\xef\x8b\x91" // U+f2d1 672 | #define ICON_FA_WINDOW_RESTORE "\xef\x8b\x92" // U+f2d2 673 | #define ICON_FA_WINDOW_CLOSE "\xef\x8b\x93" // U+f2d3 674 | #define ICON_FA_WINDOW_CLOSE_O "\xef\x8b\x94" // U+f2d4 675 | #define ICON_FA_BANDCAMP "\xef\x8b\x95" // U+f2d5 676 | #define ICON_FA_GRAV "\xef\x8b\x96" // U+f2d6 677 | #define ICON_FA_ETSY "\xef\x8b\x97" // U+f2d7 678 | #define ICON_FA_IMDB "\xef\x8b\x98" // U+f2d8 679 | #define ICON_FA_RAVELRY "\xef\x8b\x99" // U+f2d9 680 | #define ICON_FA_EERCAST "\xef\x8b\x9a" // U+f2da 681 | #define ICON_FA_MICROCHIP "\xef\x8b\x9b" // U+f2db 682 | #define ICON_FA_SNOWFLAKE_O "\xef\x8b\x9c" // U+f2dc 683 | #define ICON_FA_SUPERPOWERS "\xef\x8b\x9d" // U+f2dd 684 | #define ICON_FA_WPEXPLORER "\xef\x8b\x9e" // U+f2de 685 | #define ICON_FA_MEETUP "\xef\x8b\xa0" // U+f2e0 686 | --------------------------------------------------------------------------------