From 8d66cbdb9b55b92c3fa80cbdfdc5a2439802e83f Mon Sep 17 00:00:00 2001 From: stm <14291421+stephanmeesters@users.noreply.github.com> Date: Mon, 26 Jan 2026 23:10:08 +0100 Subject: [PATCH 1/6] feat(profiling): Include Tracy profiler library in the solution (#2202) --- CMakePresets.json | 8 +++++--- Core/CMakeLists.txt | 4 ++++ Core/Libraries/Include/rts/profile.h | 14 +++++++++++++- GeneralsMD/Code/GameEngine/CMakeLists.txt | 6 ++++++ GeneralsMD/Code/GameEngineDevice/CMakeLists.txt | 5 +++++ GeneralsMD/Code/Main/CMakeLists.txt | 6 ++++++ cmake/config-build.cmake | 16 +++++++++++++++- cmake/tracy.cmake | 7 +++++++ vcpkg-lock.json | 6 ++++++ vcpkg.json | 5 +++-- 10 files changed, 70 insertions(+), 7 deletions(-) create mode 100644 cmake/tracy.cmake diff --git a/CMakePresets.json b/CMakePresets.json index 3b0a69e7261..00c89038f22 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -109,7 +109,8 @@ "inherits": "win32", "displayName": "Windows 32bit Profile", "cacheVariables": { - "RTS_BUILD_OPTION_PROFILE": "ON" + "RTS_BUILD_OPTION_PROFILE": "ON", + "RTS_BUILD_OPTION_PROFILE_TRACY": "ON" } }, { @@ -143,7 +144,8 @@ "inherits": "win32-vcpkg", "displayName": "Windows 32bit VCPKG Profile", "cacheVariables": { - "RTS_BUILD_OPTION_PROFILE": "ON" + "RTS_BUILD_OPTION_PROFILE": "ON", + "RTS_BUILD_OPTION_PROFILE_TRACY": "ON" } }, { @@ -488,4 +490,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/Core/CMakeLists.txt b/Core/CMakeLists.txt index ccf2513e5e6..949270c2f4a 100644 --- a/Core/CMakeLists.txt +++ b/Core/CMakeLists.txt @@ -31,6 +31,10 @@ target_link_libraries(corei_always_no_pch INTERFACE resources ) +if(RTS_BUILD_OPTION_PROFILE AND RTS_BUILD_OPTION_PROFILE_TRACY) + target_link_libraries(corei_libraries_include INTERFACE Tracy::TracyClient) +endif() + # Set where the build results will end up set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/Core/Libraries/Include/rts/profile.h b/Core/Libraries/Include/rts/profile.h index 06faf42d045..233e8f1e027 100644 --- a/Core/Libraries/Include/rts/profile.h +++ b/Core/Libraries/Include/rts/profile.h @@ -27,6 +27,18 @@ // Proxy header for profile module ////////////////////////////////////////////////////////////////////////////// -# pragma once +#pragma once +#ifdef TRACY_ENABLE +#include +#define TRACY_FRAMEIMAGE_SIZE 256 +#else #include "../../Source/profile/profile.h" +#define ZoneScopedN(name) ((void)0) +#define ZoneScopedNC(name, color) ((void)0) +#define TracyPlot(name, value) ((void)0) +#define FrameMark ((void)0) +#define FrameMarkNamed(name) ((void)0) +#define FrameImage(image, width, height, offset, flip) ((void)0) +#define TracyMessage(txt, size) ((void)0) +#endif diff --git a/GeneralsMD/Code/GameEngine/CMakeLists.txt b/GeneralsMD/Code/GameEngine/CMakeLists.txt index 189ab821ffb..c4003bd2c63 100644 --- a/GeneralsMD/Code/GameEngine/CMakeLists.txt +++ b/GeneralsMD/Code/GameEngine/CMakeLists.txt @@ -1169,6 +1169,12 @@ target_link_libraries(z_gameengine PRIVATE zi_always ) +if(RTS_BUILD_OPTION_PROFILE AND RTS_BUILD_OPTION_PROFILE_TRACY) + target_link_libraries(z_gameengine PRIVATE + Tracy::TracyClient + ) +endif() + target_link_libraries(z_gameengine PUBLIC corei_gameengine_public z_wwvegas diff --git a/GeneralsMD/Code/GameEngineDevice/CMakeLists.txt b/GeneralsMD/Code/GameEngineDevice/CMakeLists.txt index db9be4f3cdc..a4cb4a9e450 100644 --- a/GeneralsMD/Code/GameEngineDevice/CMakeLists.txt +++ b/GeneralsMD/Code/GameEngineDevice/CMakeLists.txt @@ -216,6 +216,11 @@ target_link_libraries(z_gameenginedevice PRIVATE zi_always zi_main ) +if(RTS_BUILD_OPTION_PROFILE AND RTS_BUILD_OPTION_PROFILE_TRACY) + target_link_libraries(z_gameenginedevice PRIVATE + Tracy::TracyClient + ) +endif() target_link_libraries(z_gameenginedevice PUBLIC corei_gameenginedevice_public diff --git a/GeneralsMD/Code/Main/CMakeLists.txt b/GeneralsMD/Code/Main/CMakeLists.txt index d6518f442ba..964f9eed535 100644 --- a/GeneralsMD/Code/Main/CMakeLists.txt +++ b/GeneralsMD/Code/Main/CMakeLists.txt @@ -25,6 +25,12 @@ target_link_libraries(z_generals PRIVATE zi_always ) +if(RTS_BUILD_OPTION_PROFILE AND RTS_BUILD_OPTION_PROFILE_TRACY) + target_link_libraries(z_generals PRIVATE + Tracy::TracyClient + ) +endif() + # TODO Originally referred to build host and user, replace with git info perhaps? file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/GeneratedVersion.h "#pragma once diff --git a/cmake/config-build.cmake b/cmake/config-build.cmake index 6401c458be0..3308759c526 100644 --- a/cmake/config-build.cmake +++ b/cmake/config-build.cmake @@ -8,6 +8,7 @@ option(RTS_BUILD_OPTION_DEBUG "Build code with the \"Debug\" configuration." OFF option(RTS_BUILD_OPTION_ASAN "Build code with Address Sanitizer." OFF) option(RTS_BUILD_OPTION_VC6_FULL_DEBUG "Build VC6 with full debug info." OFF) option(RTS_BUILD_OPTION_FFMPEG "Enable FFmpeg support" OFF) +option(RTS_BUILD_OPTION_PROFILE_TRACY "Enable Tracy profiling for profile builds." OFF) if(NOT RTS_BUILD_ZEROHOUR AND NOT RTS_BUILD_GENERALS) set(RTS_BUILD_ZEROHOUR TRUE) @@ -71,5 +72,18 @@ else() endif() if(RTS_BUILD_OPTION_PROFILE) - target_compile_definitions(core_config INTERFACE RTS_PROFILE) + if(RTS_BUILD_OPTION_PROFILE_TRACY) + find_package(Tracy CONFIG QUIET) + if(NOT Tracy_FOUND) + include(${CMAKE_CURRENT_LIST_DIR}/tracy.cmake) + endif() + if(NOT TARGET Tracy::TracyClient) + message(FATAL_ERROR "Tracy is enabled but Tracy::TracyClient was not found.") + endif() + target_compile_definitions(core_config INTERFACE + TRACY_ENABLE + ) + else () + target_compile_definitions(core_config INTERFACE RTS_PROFILE) + endif() endif() diff --git a/cmake/tracy.cmake b/cmake/tracy.cmake new file mode 100644 index 00000000000..f22f28f7517 --- /dev/null +++ b/cmake/tracy.cmake @@ -0,0 +1,7 @@ +FetchContent_Declare( + tracy + GIT_REPOSITORY https://github.com/wolfpld/tracy + GIT_TAG v0.13.1 +) + +FetchContent_MakeAvailable(tracy) diff --git a/vcpkg-lock.json b/vcpkg-lock.json index 422998f0378..16b16ed1760 100644 --- a/vcpkg-lock.json +++ b/vcpkg-lock.json @@ -42,6 +42,12 @@ "version-string": "1.3.1", "port-version": 0, "git-tree": "3f05e04b9aededb96786a911a16193cdb711f0c9" + }, + { + "name": "tracy", + "version-string": "0.11.1", + "port-version": 2, + "git-tree": "ff802e1b85c20b5f276c4b82aaa60fc933444ab2" } ] } diff --git a/vcpkg.json b/vcpkg.json index 011b913c8aa..0bfaa0abfdc 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -3,6 +3,7 @@ "builtin-baseline": "b02e341c927f16d991edbd915d8ea43eac52096c", "dependencies": [ "zlib", - "ffmpeg" + "ffmpeg", + "tracy" ] - } \ No newline at end of file + } From 579a8b16302be78607e3de3ce9dd1e8077bab2f2 Mon Sep 17 00:00:00 2001 From: stm <14291421+stephanmeesters@users.noreply.github.com> Date: Mon, 26 Jan 2026 23:14:41 +0100 Subject: [PATCH 2/6] feat(profiling): Add frame image capturing to Tracy profiling (#2202) --- .../Include/W3DDevice/GameClient/W3DDisplay.h | 20 +- .../W3DDevice/GameClient/W3DDisplay.cpp | 183 ++++++++++++++++++ 2 files changed, 202 insertions(+), 1 deletion(-) diff --git a/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplay.h b/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplay.h index dc997134a9f..8235146346c 100644 --- a/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplay.h +++ b/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplay.h @@ -45,7 +45,10 @@ class Render2DClass; class RTS3DScene; class RTS2DScene; class RTS3DInterfaceScene; - +class TextureClass; +class SurfaceClass; +struct IDirect3DTexture8; +struct IDirect3DSurface8; //============================================================================= /** W3D implementation of the game display which is responsible for creating @@ -79,6 +82,7 @@ class W3DDisplay : public Display virtual void enableClipping( Bool onoff ) { m_isClippedEnabled = onoff; } virtual void step(); ///< Do one fixed time step + virtual void draw( void ); ///< redraw the entire display /// @todo Replace these light management routines with a LightManager singleton @@ -172,6 +176,20 @@ class W3DDisplay : public Display Int64 m_timerAtCumuFPSStart; #endif +#ifdef TRACY_ENABLE + void InitTracyCaptureImage(); + void ResetTracyCaptureImage(); + void TracyCaptureImage(); + TextureClass *m_tracyRenderTarget; + SurfaceClass *m_tracySurfaceClass; + IDirect3DTexture8 *m_tracyIntermediateTexture; + DWORD m_tracySwizzleShader; +#else + inline void InitTracyCaptureImage() {}; + inline void TracyCaptureImage() {}; + inline void ResetTracyCaptureImage() {}; +#endif + enum { FPS, ///< debug display frames per second diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp index 41f566c9dfc..50875257901 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp @@ -39,6 +39,7 @@ static void drawFramerateBar(void); #include #include #include +#include // USER INCLUDES ////////////////////////////////////////////////////////////// #include "Common/FramePacer.h" @@ -109,6 +110,8 @@ static void drawFramerateBar(void); #include "WinMain.h" +#include + // DEFINE AND ENUMS /////////////////////////////////////////////////////////// @@ -411,6 +414,7 @@ W3DDisplay::W3DDisplay() //============================================================================= W3DDisplay::~W3DDisplay() { + ResetTracyCaptureImage(); // get rid of the debug display delete m_debugDisplay; @@ -541,6 +545,8 @@ void W3DDisplay::setGamma(Real gamma, Real bright, Real contrast, Bool calibrate //============================================================================= Bool W3DDisplay::setDisplayMode( UnsignedInt xres, UnsignedInt yres, UnsignedInt bitdepth, Bool windowed ) { + ResetTracyCaptureImage(); + if (WW3D_ERROR_OK == WW3D::Set_Device_Resolution(xres,yres,bitdepth,windowed,true)) { Render2DClass::Set_Screen_Resolution(RectClass(0, 0, xres, yres)); @@ -1656,6 +1662,182 @@ void W3DDisplay::step() //DECLARE_PERF_TIMER(BigAssRenderLoop) +#ifdef TRACY_ENABLE +void W3DDisplay::InitTracyCaptureImage() +{ + ResetTracyCaptureImage(); + + // allocate render target + m_tracyRenderTarget = DX8Wrapper::Create_Render_Target(TRACY_FRAMEIMAGE_SIZE, TRACY_FRAMEIMAGE_SIZE, WW3D_FORMAT_A8R8G8B8); + + // allocate surface class + const Real aspectRatio = static_cast(getHeight()) / static_cast(getWidth()); + unsigned int tracyImageHeight = MIN( + static_cast(WWMath::Round(static_cast(TRACY_FRAMEIMAGE_SIZE) * aspectRatio)), + TRACY_FRAMEIMAGE_SIZE); + m_tracySurfaceClass = NEW_REF(SurfaceClass, (TRACY_FRAMEIMAGE_SIZE, tracyImageHeight, WW3D_FORMAT_A8R8G8B8)); + + // allocate intermediate texture + SurfaceClass *backBuffer = DX8Wrapper::_Get_DX8_Back_Buffer(); + IDirect3DSurface8 *backBufferSurface = backBuffer->Peek_D3D_Surface(); + D3DSURFACE_DESC backBufferSurfaceDesc; + backBufferSurface->GetDesc(&backBufferSurfaceDesc); + IDirect3DDevice8 *device = DX8Wrapper::_Get_D3D_Device8(); + device->CreateTexture( + backBufferSurfaceDesc.Width, + backBufferSurfaceDesc.Height, + 1, + D3DUSAGE_RENDERTARGET, + backBufferSurfaceDesc.Format, + D3DPOOL_DEFAULT, + &m_tracyIntermediateTexture); + + // swizzle shader + // TheSuperHackers @todo In DX9 with ps2.0 this shader will be much simpler + ID3DXBuffer *compiledShader = nullptr; + static const char *shader = + "ps.1.4\n" + "texld r0, t0\n" + "mov r1.a, r0.r\n" + "mov r2.a, r0.g\n" + "mov r3.a, r0.b\n" + "mul r0.rgb, r3.a, c0\n" + "mad r0.rgb, r2.a, c1, r0\n" + "mad r0.rgb, r1.a, c2, r0\n"; + HRESULT hr = D3DXAssembleShader(shader, strlen(shader), 0, nullptr, &compiledShader, nullptr); + if (hr == 0) + { + hr = DX8Wrapper::_Get_D3D_Device8()->CreatePixelShader((DWORD*)compiledShader->GetBufferPointer(), &m_tracySwizzleShader); + compiledShader->Release(); + } +} + +void W3DDisplay::ResetTracyCaptureImage() +{ + if (m_tracySurfaceClass) + { + REF_PTR_RELEASE(m_tracySurfaceClass); + } + if (m_tracyIntermediateTexture) + { + m_tracyIntermediateTexture->Release(); + m_tracyIntermediateTexture = nullptr; + } + if (m_tracyRenderTarget) + { + REF_PTR_RELEASE(m_tracyRenderTarget); + } + if (m_tracySwizzleShader) + { + DX8Wrapper::_Get_D3D_Device8()->DeletePixelShader(m_tracySwizzleShader); + m_tracySwizzleShader = 0; + } +} + +void W3DDisplay::TracyCaptureImage() +{ + if (!m_tracySurfaceClass || !m_tracyIntermediateTexture || !m_tracyRenderTarget) + { + InitTracyCaptureImage(); + } + + // copy the backbuffer to an intermediate texture on the GPU + // TheSuperHackers @todo In DX9 it should be possible to sample the backbuffer directly and simplify this + SurfaceClass *backBuffer = DX8Wrapper::_Get_DX8_Back_Buffer(); + IDirect3DSurface8 *backBufferSurface = backBuffer->Peek_D3D_Surface(); + D3DSURFACE_DESC backBufferSurfaceDesc; + backBufferSurface->GetDesc(&backBufferSurfaceDesc); + + IDirect3DSurface8 *tracyBackbufferTextureSurface; + m_tracyIntermediateTexture->GetSurfaceLevel(0, &tracyBackbufferTextureSurface); + DX8Wrapper::_Copy_DX8_Rects(backBufferSurface, nullptr, 0, + tracyBackbufferTextureSurface, nullptr); + tracyBackbufferTextureSurface->Release(); + tracyBackbufferTextureSurface = nullptr; + + backBufferSurface->Release(); + backBufferSurface = nullptr; + REF_PTR_RELEASE(backBuffer); + + // set render target to a small surface + IDirect3DSurface8 *smallRenderTargetSurface = m_tracyRenderTarget->Get_D3D_Surface_Level(); + DX8Wrapper::Set_Render_Target(smallRenderTargetSurface, false); + + // set viewport + IDirect3DDevice8 *device = DX8Wrapper::_Get_D3D_Device8(); + D3DVIEWPORT8 restoreViewport; + device->GetViewport(&restoreViewport); + + SurfaceClass::SurfaceDescription smallRenderDesc; + m_tracySurfaceClass->Get_Description(smallRenderDesc); + + D3DVIEWPORT8 viewport; + viewport.X = 0; + viewport.Y = 0; + viewport.Width = TRACY_FRAMEIMAGE_SIZE; + viewport.Height = smallRenderDesc.Height; + viewport.MinZ = 0.0f; + viewport.MaxZ = 1.0f; + DX8Wrapper::Set_Viewport(&viewport); + + // bind shader to convert BGRA to RGBA to match the format that Tracy expects + DX8Wrapper::Set_Pixel_Shader(m_tracySwizzleShader); + // TheSuperHackers @todo In DX9 with ps2.0 we wont need these pixel shader constants + if (m_tracySwizzleShader != 0) { + static const float kMaskR[4] = {1.0f, 0.0f, 0.0f, 0.0f}; + static const float kMaskG[4] = {0.0f, 1.0f, 0.0f, 0.0f}; + static const float kMaskB[4] = {0.0f, 0.0f, 1.0f, 0.0f}; + device->SetPixelShaderConstant(0, kMaskR, 1); + device->SetPixelShaderConstant(1, kMaskG, 1); + device->SetPixelShaderConstant(2, kMaskB, 1); + } + + // draw texture scaled-down onto a small surface + struct QuadVertex + { + float x, y, z, rhw; + DWORD color; + float u; + float v; + } vtx[4]; + const float left = -0.5f; + const float top = -0.5f; + const float right = static_cast(TRACY_FRAMEIMAGE_SIZE) - 0.5f; + const float bottom = static_cast(smallRenderDesc.Height) - 0.5f; + vtx[0] = {right, bottom, 0.0f, 1.0f, 0xffffffff, 1.0f, 1.0f}; + vtx[1] = {right, top, 0.0f, 1.0f, 0xffffffff, 1.0f, 0.0f}; + vtx[2] = {left, bottom, 0.0f, 1.0f, 0xffffffff, 0.0f, 1.0f}; + vtx[3] = {left, top, 0.0f, 1.0f, 0xffffffff, 0.0f, 0.0f}; + DX8Wrapper::Set_DX8_Texture(0, m_tracyIntermediateTexture); + DX8Wrapper::Set_Vertex_Shader(D3DFVF_XYZRHW | D3DFVF_DIFFUSE | D3DFVF_TEX1); + device->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, vtx, sizeof(QuadVertex)); + DX8Wrapper::Set_Pixel_Shader(0); + DX8Wrapper::Set_DX8_Texture(0, nullptr); + DX8Wrapper::Set_Viewport(&restoreViewport); + DX8Wrapper::Set_Render_Target((IDirect3DSurface8 *) nullptr); + + // copy the small surface pixels from GPU to CPU + RECT srcRect = { 0, 0, TRACY_FRAMEIMAGE_SIZE, smallRenderDesc.Height }; + POINT dstPoint = { 0, 0 }; + DX8Wrapper::_Copy_DX8_Rects( + smallRenderTargetSurface, + &srcRect, + 1, + m_tracySurfaceClass->Peek_D3D_Surface(), + &dstPoint); + smallRenderTargetSurface->Release(); + + // send pixels to tracy + int pitch = 0; + void *bits = m_tracySurfaceClass->Lock(&pitch); + if (bits) + { + FrameImage(bits, TRACY_FRAMEIMAGE_SIZE, smallRenderDesc.Height, 0, false); + m_tracySurfaceClass->Unlock(); + } +} +#endif + // W3DDisplay::draw =========================================================== /** Draw the entire W3D Display */ //============================================================================= @@ -1941,6 +2123,7 @@ void W3DDisplay::draw( void ) TheGraphDraw->render(); TheGraphDraw->clear(); #endif + TracyCaptureImage(); // render is all done! WW3D::End_Render(); } From e0397e4f735d04bdc1faadc9155bcb6dee7bf43c Mon Sep 17 00:00:00 2001 From: stm <14291421+stephanmeesters@users.noreply.github.com> Date: Mon, 26 Jan 2026 23:17:35 +0100 Subject: [PATCH 3/6] feat(profiling): Add messages to Tracy profiling on game start and game end (#2202) --- .../Source/GameLogic/System/GameLogic.cpp | 8 ++++++++ .../Source/GameLogic/System/GameLogicDispatch.cpp | 15 +++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index 569b01943c6..c8c43b81e15 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -1171,6 +1171,14 @@ void GameLogic::startNewGame( Bool loadingSaveGame ) m_rankLevelLimit = 1000; // this is reset every game. +#ifdef TRACY_ENABLE + { + char msg[512]; + snprintf(msg, sizeof(msg), "GameStart: %s", TheGlobalData->m_mapName.str()); + TracyMessage(msg, strlen(msg)); + } +#endif + // // only reset the next object ID allocater counter when we're not loading a save game. // for save games, we read this value out of the save game file and it is important diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp index 5a8d617e393..aa290f9ac41 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp @@ -78,6 +78,7 @@ #include "GameClient/GUICallbacks.h" #include "GameClient/InGameUI.h" #include "GameClient/KeyDefs.h" + #include "GameClient/Mouse.h" #include "GameClient/ParticleSys.h" #include "GameClient/Shell.h" @@ -86,6 +87,7 @@ #include "GameNetwork/NetworkInterface.h" +#include @@ -251,6 +253,19 @@ void GameLogic::clearGameData( Bool showScoreScreen ) setClearingGameData( TRUE ); +#ifdef TRACY_ENABLE + { + char msg[512]; + int len = snprintf(msg, sizeof(msg), "GameEnd: %s", TheGlobalData->m_mapName.str()); + if (len > 0) + { + if (len > (int)sizeof(msg) - 1) + len = (int)sizeof(msg) - 1; + TracyMessage(msg, (size_t)len); + } + } +#endif + // m_background = TheWindowManager->winCreateLayout("Menus/BlankWindow.wnd"); // DEBUG_ASSERTCRASH(m_background,("We Couldn't Load Menus/BlankWindow.wnd")); // m_background->hide(FALSE); From cb0eee3dd3af1d114204216b60b7e5cce212ecfa Mon Sep 17 00:00:00 2001 From: stm <14291421+stephanmeesters@users.noreply.github.com> Date: Mon, 26 Jan 2026 23:41:13 +0100 Subject: [PATCH 4/6] feat(profiling): Add trace zones and frame marks to Tracy profiling (#2202) --- .../Source/Common/FrameRateLimit.cpp | 2 + .../Source/W3DDevice/GameClient/W3DView.cpp | 7 ++ .../GameEngine/Source/GameClient/Display.cpp | 4 ++ .../Source/GameClient/GameClient.cpp | 13 ++++ .../Source/GameLogic/AI/AIPathfind.cpp | 4 ++ .../GameLogic/Object/Update/AIUpdate.cpp | 5 ++ .../Source/GameLogic/System/GameLogic.cpp | 19 ++++- .../W3DDevice/GameClient/W3DDisplay.cpp | 23 +++++- .../Source/W3DDevice/GameClient/W3DScene.cpp | 72 +++++++++++-------- .../Source/WWVegas/WW3D2/dx8wrapper.cpp | 2 + 10 files changed, 120 insertions(+), 31 deletions(-) diff --git a/Core/GameEngine/Source/Common/FrameRateLimit.cpp b/Core/GameEngine/Source/Common/FrameRateLimit.cpp index b8c7f1ff7a9..b85f3201d49 100644 --- a/Core/GameEngine/Source/Common/FrameRateLimit.cpp +++ b/Core/GameEngine/Source/Common/FrameRateLimit.cpp @@ -19,6 +19,7 @@ #include "PreRTS.h" #include "Common/FrameRateLimit.h" +#include "rts/profile.h" FrameRateLimit::FrameRateLimit() { @@ -32,6 +33,7 @@ FrameRateLimit::FrameRateLimit() Real FrameRateLimit::wait(UnsignedInt maxFps) { + ZoneScopedN("FrameRateLimit::wait"); LARGE_INTEGER tick; QueryPerformanceCounter(&tick); double elapsedSeconds = static_cast(tick.QuadPart - m_start) / m_freq; diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp index efd4c3a4d83..8d4751eb4d4 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp @@ -99,6 +99,7 @@ are using timeGetTime, but we can remove that when we have our own timer */ +#include // 30 fps @@ -1550,6 +1551,7 @@ void W3DView::drawView( void ) void W3DView::draw( void ) { //USE_PERF_TIMER(W3DView_drawView) + ZoneScopedN("Render::DisplayDraw::W3DView"); Bool skipRender = false; Bool doExtraRender = false; CustomScenePassModes customScenePassMode = SCENE_PASS_DEFAULT; @@ -1559,6 +1561,7 @@ void W3DView::draw( void ) m_viewFilter > FT_NULL_FILTER && m_viewFilter < FT_MAX) { + ZoneScopedN("Render::DisplayDraw::FilterPre"); // Most likely will redirect rendering to a texture. preRenderResult=W3DShaderManager::filterPreRender(m_viewFilter, skipRender, customScenePassMode); if (!skipRender && getCameraLock()) @@ -1574,6 +1577,7 @@ void W3DView::draw( void ) if (!skipRender) { + ZoneScopedN("Render::DisplayDraw::SceneRender"); // Render 3D scene from our camera W3DDisplay::m_3DScene->setCustomPassMode(customScenePassMode); if (m_isWireFrameEnabled) @@ -1587,6 +1591,7 @@ void W3DView::draw( void ) m_viewFilter > FT_NULL_FILTER && m_viewFilter < FT_MAX) { + ZoneScopedN("Render::DisplayDraw::FilterPost"); Coord2D deltaScroll; calcDeltaScroll(deltaScroll); Bool continueTheEffect = false; @@ -1620,6 +1625,7 @@ void W3DView::draw( void ) //was turned off by filterPostRender(). if (doExtraRender) { + ZoneScopedN("Render::DisplayDraw::ExtraRender"); //Reset to normal scene rendering. //The pass that rendered into a texture may have left the z-buffer in a weird state //so clear it before rendering normal scene. @@ -1633,6 +1639,7 @@ void W3DView::draw( void ) if( TheGlobalData->m_debugAI ) { + ZoneScopedN("Render::DisplayDraw::DebugAI"); if (TheAI->pathfinder()->getDebugPath()) { // setup screen clipping region diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/Display.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/Display.cpp index ee436052f4d..cc2c7570965 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/Display.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/Display.cpp @@ -37,6 +37,8 @@ //#include "GameLogic/ScriptEngine.h" //#include "GameLogic/GameLogic.h" +#include + /// The Display singleton instance. Display *TheDisplay = nullptr; @@ -110,6 +112,7 @@ void Display::attachView( View *view ) */ void Display::drawViews( void ) { + ZoneScopedN("Render::W3DDisplay::DrawViews"); for( View *v = m_viewList; v; v = v->getNextView() ) v->drawView(); @@ -122,6 +125,7 @@ void Display::drawViews( void ) */ void Display::updateViews( void ) { + ZoneScopedN("Render::W3DDisplay::UpdateViews"); for( View *v = m_viewList; v; v = v->getNextView() ) v->updateView(); diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp index 583721ff51e..dc2aad239ae 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp @@ -85,6 +85,8 @@ #include "GameLogic/Object.h" #include "GameLogic/ScriptEngine.h" // For TheScriptEngine - jkmcd +#include + #define DRAWABLE_HASH_SIZE 8192 /// The GameClient singleton instance @@ -510,6 +512,7 @@ DECLARE_PERF_TIMER(GameClient_draw) void GameClient::update( void ) { USE_PERF_TIMER(GameClient_update) + FrameMark; // create the FRAME_TICK message GameMessage *frameMsg = TheMessageStream->appendMessage( GameMessage::MSG_FRAME_TICK ); frameMsg->appendTimestampArgument( getFrame() ); @@ -617,9 +620,12 @@ void GameClient::update( void ) TheInGameUI->setCameraTrackingDrawable( FALSE ); } + ZoneScopedNC("Render", 0x2196F3); + if(TheGlobalData->m_playIntro || TheGlobalData->m_afterIntro) { // redraw all views, update the GUI + ZoneScopedN("Render::IntroDisplay"); TheDisplay->DRAW(); TheDisplay->UPDATE(); return; @@ -627,11 +633,13 @@ void GameClient::update( void ) // update the window system itself { + ZoneScopedN("Render::WindowManager"); TheWindowManager->UPDATE(); } // update the video player { + ZoneScopedN("Render::VideoPlayer"); TheVideoPlayer->UPDATE(); } @@ -742,11 +750,13 @@ void GameClient::update( void ) // update the terrain visuals { + ZoneScopedN("Render::TerrainVisual"); TheTerrainVisual->UPDATE(); } // update display { + ZoneScopedN("Render::DisplayUpdate"); TheDisplay->UPDATE(); } @@ -761,16 +771,19 @@ void GameClient::update( void ) { // let display string factory handle its update + ZoneScopedN("Render::DisplayStrings"); TheDisplayStringManager->update(); } { // update the shell + ZoneScopedN("Render::Shell"); TheShell->UPDATE(); } { // update the in game UI + ZoneScopedN("Render::InGameUI"); TheInGameUI->UPDATE(); } } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp index fea307e8310..90349e40101 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp @@ -52,6 +52,8 @@ #include "Common/UnitTimings.h" //Contains the DO_UNIT_TIMINGS define jba. +#include + #define no_INTENSE_DEBUG #define DEBUG_QPF @@ -5754,6 +5756,7 @@ Path *Pathfinder::getAircraftPath( const Object *obj, const Coord3D *to ) void Pathfinder::processPathfindQueue(void) { //USE_PERF_TIMER(processPathfindQueue) + ZoneScopedN("Pathfinder::processPathfindQueue"); if (!m_isMapReady) { return; } @@ -5775,6 +5778,7 @@ void Pathfinder::processPathfindQueue(void) #endif m_zoneManager.needToCalculateZones()) { + ZoneScopedN("Pathfinder::calculateZones"); m_zoneManager.calculateZones(m_map, m_layers, m_extent); return; } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp index 133cc07a229..e9b08f6930d 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp @@ -71,6 +71,8 @@ #include "GameLogic/Weapon.h" #include "Common/Radar.h" // For TheRadar +#include + #define SLEEPY_AI @@ -395,6 +397,7 @@ except by the pathfinder during pathfind queue processing. jba */ //------------------------------------------------------------------------------------------------- void AIUpdateInterface::doPathfind( PathfindServicesInterface *pathfinder ) { + ZoneScopedN("AIUpdateInterface::doPathfind"); if (!m_waitingForPath) { return; } @@ -1667,6 +1670,7 @@ Bool AIUpdateInterface::computeQuickPath( const Coord3D *destination ) */ Bool AIUpdateInterface::computePath( PathfindServicesInterface *pathServices, Coord3D *destination ) { + ZoneScopedN("AIUpdateInterface::computePath"); if (!m_isBlockedAndStuck) { destroyPath(); @@ -1781,6 +1785,7 @@ Bool AIUpdateInterface::computePath( PathfindServicesInterface *pathServices, Co */ Bool AIUpdateInterface::computeAttackPath( PathfindServicesInterface *pathServices, const Object *victim, const Coord3D* victimPos ) { + ZoneScopedN("AIUpdateInterface::computeAttackPath"); //CRCDEBUG_LOG(("AIUpdateInterface::computeAttackPath() for object %d", getObject()->getID())); // See if it has been too soon. if (m_pathTimestamp >= TheGameLogic->getFrame()-2) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index c8c43b81e15..5691b8d1f0e 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -1102,6 +1102,7 @@ void GameLogic::setGameMode( GameMode mode ) // ------------------------------------------------------------------------------------------------ void GameLogic::startNewGame( Bool loadingSaveGame ) { + ZoneScopedN("GameLogic::startNewGame"); #ifdef DUMP_PERF_STATS __int64 startTime64; @@ -3610,6 +3611,7 @@ extern __int64 Total_Load_3D_Assets; void GameLogic::update( void ) { USE_PERF_TIMER(GameLogic_update) + ZoneScopedNC("GameLogic::update", 0x4CAF50); LatchRestore inUpdateLatch(m_isInUpdate, TRUE); #ifdef DO_UNIT_TIMINGS @@ -3658,12 +3660,14 @@ void GameLogic::update( void ) // update (execute) scripts { + ZoneScopedN("GameLogic::ScriptEngine"); TheScriptEngine->UPDATE(); } // Note - TerrainLogic update needs to happen after ScriptEngine update, but before object updates. jba. // This way changes in bridges are noted in the script engine before being cleared in TerrainLogic->update { + ZoneScopedN("GameLogic::TerrainLogic"); TheTerrainLogic->UPDATE(); } @@ -3681,6 +3685,7 @@ void GameLogic::update( void ) if (generateForSolo || generateForMP) { + ZoneScopedN("GameLogic::CRC"); m_CRC = getCRC( CRC_RECALC ); bool isPlayback = (TheRecorder && TheRecorder->isPlaybackMode()); @@ -3702,21 +3707,25 @@ void GameLogic::update( void ) // collect stats if(TheStatsCollector) { + ZoneScopedN("GameLogic::StatsCollector"); TheStatsCollector->update(); } // Update the Recorder { + ZoneScopedN("GameLogic::Recorder"); TheRecorder->UPDATE(); } // process client commands { + ZoneScopedN("GameLogic::CommandList"); processCommandList( TheCommandList ); } #ifdef ALLOW_NONSLEEPY_UPDATES { + ZoneScopedN("GameLogic::NormalUpdates"); for (std::list::const_iterator it = m_normalUpdates.begin(); it != m_normalUpdates.end(); ++it) { UpdateModulePtr u = *it; @@ -3741,6 +3750,7 @@ void GameLogic::update( void ) #endif { + ZoneScopedN("GameLogic::SleepyUpdates"); while (!m_sleepyUpdates.empty()) { UpdateModulePtr u = peekSleepyUpdate(); @@ -3787,16 +3797,19 @@ void GameLogic::update( void ) // update the Artificial Intelligence system { + ZoneScopedN("GameLogic::AI"); TheAI->UPDATE(); } // production updates { + ZoneScopedN("GameLogic::BuildAssistant"); TheBuildAssistant->UPDATE(); } // update partition info { + ZoneScopedN("GameLogic::Partition"); ThePartitionManager->UPDATE(); } @@ -3805,7 +3818,10 @@ void GameLogic::update( void ) // // destroy all pending objects - processDestroyList(); + { + ZoneScopedN("GameLogic::DestroyList"); + processDestroyList(); + } // reset the command list, destroying all messages TheCommandList->reset(); @@ -3816,6 +3832,7 @@ void GameLogic::update( void ) { //Handle disabled statii (and re-enable objects once frame matches) + ZoneScopedN("GameLogic::DisabledStatus"); for( Object *obj = m_objList; obj; obj = obj->getNextObject() ) { if( obj->isDisabled() ) diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp index 50875257901..45442e16bbf 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp @@ -1517,6 +1517,7 @@ void W3DDisplay::drawDebugStats( void ) //============================================================================= void W3DDisplay::drawFPSStats( void ) { + ZoneScopedN("Render::W3DDisplay::FPSStats"); Int x = 3; Int y = 20; Color textColor = GameMakeColor( 255, 255, 255, 255 ); @@ -1561,6 +1562,7 @@ void W3DDisplay::drawCurrentDebugDisplay( void ) //============================================================================= void W3DDisplay::calculateTerrainLOD( void ) { + ZoneScopedN("Render::W3DDisplay::TerrainLOD"); const Int NUM_SAMPLES=20; const Int NUM_TO_DISCARD=5; @@ -1741,6 +1743,8 @@ void W3DDisplay::TracyCaptureImage() InitTracyCaptureImage(); } + ZoneScopedN("Render::W3DDisplay::TracyFrameImage"); + // copy the backbuffer to an intermediate texture on the GPU // TheSuperHackers @todo In DX9 it should be possible to sample the backbuffer directly and simplify this SurfaceClass *backBuffer = DX8Wrapper::_Get_DX8_Back_Buffer(); @@ -1898,6 +1902,7 @@ void W3DDisplay::draw( void ) #endif ) { + ZoneScopedN("Render::W3DDisplay::DebugStats"); gatherDebugStats(); } #ifdef EXTENDED_STATS @@ -1947,6 +1952,7 @@ void W3DDisplay::draw( void ) if(TheGlobalData->m_loadScreenRender != TRUE) { + ZoneScopedN("Render::W3DDisplay::PreRenderUpdates"); if (TheTerrainTracksRenderObjClassSystem) TheTerrainTracksRenderObjClassSystem->update(); @@ -1991,7 +1997,9 @@ void W3DDisplay::draw( void ) //trying to refresh the visible terrain geometry. // if(TheGlobalData->m_loadScreenRender != TRUE) updateViews(); - TheParticleSystemManager->update();//LORENZEN AND WILCZYNSKI MOVED THIS FROM ITS NATIVE POSITION, ABOVE + { + ZoneScopedN("Render::W3DDisplay::ParticleUpdate"); + TheParticleSystemManager->update();//LORENZEN AND WILCZYNSKI MOVED THIS FROM ITS NATIVE POSITION, ABOVE //FOR THE PURPOSE OF LETTING THE PARTICLE SYSTEM LOOK UP THE RENDER OBJECT"S //TRANSFORM MATRIX, WHILE IT IS STILL VALID (HAVING DONE ITS CLIENT TRANSFORMS //BUT NOT YET RESETTING TOT HE LOGICAL TRANSFORM) @@ -1999,15 +2007,22 @@ void W3DDisplay::draw( void ) //MOVE WITH THE CLIENT TRANSFORMS, NOW. //REVOLUTIONARY! //-LORENZEN + } if (TheWaterRenderObj && TheGlobalData->m_waterType == 2) + { + ZoneScopedN("Render::W3DDisplay::WaterRTT"); TheWaterRenderObj->updateRenderTargetTextures(primaryW3DView->get3DCamera()); //do a render into each texture + } //Can't render into textures while rendering to screen so these textures need to be updated //before we enter main rendering loop. if (TheW3DProjectedShadowManager) + { + ZoneScopedN("Render::W3DDisplay::ShadowRTT"); TheW3DProjectedShadowManager->updateRenderTargetTextures(); + } } Debug_Statistics::End_Statistics(); //record number of polygons rendered in RenderTargetTextures. @@ -2030,6 +2045,7 @@ void W3DDisplay::draw( void ) if(TheGlobalData->m_loadScreenRender == TRUE) { + ZoneScopedN("Render::W3DDisplay::LoadScreen"); TheInGameUI->draw(); if( TheMouse ) TheMouse->draw(); //keep applying the current cursor style so it remains hidden if needed. @@ -2045,7 +2061,10 @@ void W3DDisplay::draw( void ) drawViews(); // draw the user interface - TheInGameUI->DRAW(); + { + ZoneScopedN("Render::W3DDisplay::InGameUI"); + TheInGameUI->DRAW(); + } // end of video example code diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScene.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScene.cpp index 08b6041b156..b33b55261b9 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScene.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScene.cpp @@ -66,6 +66,8 @@ #include "WW3D2/shdlib.h" +#include + /////////////////////////////////////////////////////////////////////////////// // DEFINITIONS //////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// @@ -981,6 +983,7 @@ void RTS3DScene::Render(RenderInfoClass & rinfo) { if (m_customPassMode == SCENE_PASS_DEFAULT) { + ZoneScopedN("Render::W3DScene::PassDefault"); //Regular rendering pass with no effects updatePlayerColorPasses();///@todo: this probably doesn't need to be done each frame. updateFixedLightEnvironments(rinfo); @@ -989,6 +992,7 @@ void RTS3DScene::Render(RenderInfoClass & rinfo) } else if (m_customPassMode == SCENE_PASS_ALPHA_MASK) { + ZoneScopedN("Render::W3DScene::PassAlphaMask"); //a projected alpha texture which will later be used to determine where //wireframe should be visible. ///@todo: Clearing to black may not be needed if the scene already did the clear. @@ -1012,6 +1016,7 @@ void RTS3DScene::Render(RenderInfoClass & rinfo) Bool old_enable=WW3D::Is_Texturing_Enabled(); if (Get_Extra_Pass_Polygon_Mode() == EXTRA_PASS_CLEAR_LINE) { + ZoneScopedN("Render::W3DScene::PassClearLine"); //render scene with solid black color but have destination alpha store //a projected alpha texture which will later be used to determine where //wireframe should be visible. @@ -1058,6 +1063,7 @@ void RTS3DScene::Render(RenderInfoClass & rinfo) } else { + ZoneScopedN("Render::W3DScene::PassLegacy"); //old W3D custom rendering code. //Disable writes to color buffer to save memory bandwidth - we only need Z. @@ -1102,6 +1108,7 @@ void RTS3DScene::Render(RenderInfoClass & rinfo) //============================================================================= void RTS3DScene::Customized_Render( RenderInfoClass &rinfo ) { + ZoneScopedN("Render::W3DScene::Customized"); #ifdef DIRTY_CONDITION_FLAGS StDrawableDirtyStuffLocker lockDirtyStuff; #endif @@ -1126,21 +1133,25 @@ void RTS3DScene::Customized_Render( RenderInfoClass &rinfo ) RefRenderObjListIterator it(&UpdateList); // allow all objects in the update list to do their "every frame" processing - for (it.First(); !it.Is_Done(); it.Next()) { - RenderObjClass * robj = it.Peek_Obj(); - if (robj->Class_ID() == RenderObjClass::CLASSID_TILEMAP) - terrainObject=robj; //found terrain object, store for later. - if (!ShaderClass::Is_Backface_Culling_Inverted()) { - // If we are doing water mirror, we draw with backface culling inverted. In this case, - // we only want to call On_Frame_Update if we aren't drawing water, as otherwise - // we get 2 frame updates per frame, and it screws up the particle emitters. - it.Peek_Obj()->On_Frame_Update(); + { + ZoneScopedN("Render::W3DScene::UpdateList"); + for (it.First(); !it.Is_Done(); it.Next()) { + RenderObjClass * robj = it.Peek_Obj(); + if (robj->Class_ID() == RenderObjClass::CLASSID_TILEMAP) + terrainObject=robj; //found terrain object, store for later. + if (!ShaderClass::Is_Backface_Culling_Inverted()) { + // If we are doing water mirror, we draw with backface culling inverted. In this case, + // we only want to call On_Frame_Update if we aren't drawing water, as otherwise + // we get 2 frame updates per frame, and it screws up the particle emitters. + it.Peek_Obj()->On_Frame_Update(); + } } } //terrain needs to be rendered first if (terrainObject) // Don't check visibility - terrain is always visible. jba. { + ZoneScopedN("Render::W3DScene::Terrain"); robj=terrainObject; rinfo.light_environment = nullptr; // Terrain is self lit. rinfo.Camera.Set_User_Data(this); //pass the scene to terrain via user data. @@ -1170,26 +1181,29 @@ void RTS3DScene::Customized_Render( RenderInfoClass &rinfo ) #endif // loop through all render objects in the list: - for (it.First(&RenderList); !it.Is_Done();) { - // get the render object - robj = it.Peek_Obj(); - it.Next(); //advance to next object in case this one gets deleted during renderOneObject(). - - if (robj->Class_ID() == RenderObjClass::CLASSID_TILEMAP) - continue; //we already rendered terrain - - if (robj->Is_Really_Visible()) { - DrawableInfo *drawInfo = (DrawableInfo *)robj->Get_User_Data(); - Drawable *draw=nullptr; - if (drawInfo) - draw = drawInfo->m_drawable; -#ifdef USE_NON_STENCIL_OCCLUSION - if (!(draw && drawInfo->m_flags & DrawableInfo::ERF_DELAYED_RENDER)) //model rendering is delayed for some reason until end of normal scene -#else - if (!(draw && drawInfo->m_flags & (DrawableInfo::ERF_DELAYED_RENDER|DrawableInfo::ERF_POTENTIAL_OCCLUDER|DrawableInfo::ERF_IS_NON_OCCLUDER_OR_OCCLUDEE))) //in this mode we delay almost all objects in order to do correct sorting with stencil. -#endif - renderOneObject(rinfo, robj, localPlayerIndex); + ZoneScopedN("Render::W3DScene::RenderList"); + for (it.First(&RenderList); !it.Is_Done();) + { + // get the render object + robj = it.Peek_Obj(); + it.Next(); //advance to next object in case this one gets deleted during renderOneObject(). + + if (robj->Class_ID() == RenderObjClass::CLASSID_TILEMAP) + continue; //we already rendered terrain + + if (robj->Is_Really_Visible()) { + DrawableInfo *drawInfo = (DrawableInfo *)robj->Get_User_Data(); + Drawable *draw=nullptr; + if (drawInfo) + draw = drawInfo->m_drawable; + #ifdef USE_NON_STENCIL_OCCLUSION + if (!(draw && drawInfo->m_flags & DrawableInfo::ERF_DELAYED_RENDER)) //model rendering is delayed for some reason until end of normal scene + #else + if (!(draw && drawInfo->m_flags & (DrawableInfo::ERF_DELAYED_RENDER|DrawableInfo::ERF_POTENTIAL_OCCLUDER|DrawableInfo::ERF_IS_NON_OCCLUDER_OR_OCCLUDEE))) //in this mode we delay almost all objects in order to do correct sorting with stencil. + #endif + renderOneObject(rinfo, robj, localPlayerIndex); + } } } @@ -1198,6 +1212,7 @@ void RTS3DScene::Customized_Render( RenderInfoClass &rinfo ) if (TheW3DShadowManager && terrainObject && !ShaderClass::Is_Backface_Culling_Inverted() && Get_Extra_Pass_Polygon_Mode() == EXTRA_PASS_DISABLE) { + ZoneScopedN("Render::W3DScene::Shadows"); TheW3DShadowManager->queueShadows(TRUE); } @@ -1205,6 +1220,7 @@ void RTS3DScene::Customized_Render( RenderInfoClass &rinfo ) if (terrainObject != nullptr && TheParticleSystemManager != nullptr && Get_Extra_Pass_Polygon_Mode() == EXTRA_PASS_DISABLE) { + ZoneScopedN("Render::W3DScene::Particles"); TheParticleSystemManager->queueParticleRender(); } } diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp index c3f82d48ce1..5dcf3eac6ff 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp @@ -84,6 +84,7 @@ #include "dx8texman.h" #include "bound.h" #include "DbgHelpGuard.h" +#include #include "shdlib.h" @@ -1735,6 +1736,7 @@ void DX8Wrapper::End_Scene(bool flip_frames) HRESULT hr; { WWPROFILE("DX8Device::Present()"); + ZoneScopedN("Render::VSync"); hr=_Get_D3D_Device8()->Present(nullptr, nullptr, nullptr, nullptr); } From bec7e8643904e5b68ad14d14d639de626fa27b93 Mon Sep 17 00:00:00 2001 From: stm <14291421+stephanmeesters@users.noreply.github.com> Date: Mon, 26 Jan 2026 23:53:21 +0100 Subject: [PATCH 5/6] feat(profiling): Add plots for logical frame number and pathfinding cells/paths to Tracy profiling (#2202) --- .../Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp | 10 +++++----- .../GameEngine/Source/GameLogic/System/GameLogic.cpp | 2 ++ 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp index 90349e40101..7c81e79c153 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp @@ -5796,9 +5796,7 @@ void Pathfinder::processPathfindQueue(void) m_logicalExtent = bounds; m_cumulativeCellsAllocated = 0; // Number of pathfind cells examined. -#ifdef DEBUG_QPF Int pathsFound = 0; -#endif while (m_cumulativeCellsAllocated < PATHFIND_CELLS_PER_FRAME && m_queuePRTail!=m_queuePRHead) { Object *obj = TheGameLogic->findObjectByID(m_queuedPathfindRequests[m_queuePRHead]); @@ -5807,9 +5805,7 @@ void Pathfinder::processPathfindQueue(void) AIUpdateInterface *ai = obj->getAIUpdateInterface(); if (ai) { ai->doPathfind(this); -#ifdef DEBUG_QPF pathsFound++; -#endif } } m_queuePRHead = m_queuePRHead+1; @@ -5817,8 +5813,12 @@ void Pathfinder::processPathfindQueue(void) m_queuePRHead = 0; } } - if (pathsFound>0) { + if (pathsFound > 0) { + TracyPlot("PathfindCells", (double)m_cumulativeCellsAllocated); + TracyPlot("PathfindPaths", (double)pathsFound); + } #ifdef DEBUG_QPF + if (pathsFound>0) { #ifdef DEBUG_LOGGING QueryPerformanceCounter((LARGE_INTEGER *)&endTime64); timeToUpdate = ((double)(endTime64-startTime64) / (double)(freq64)); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index 5691b8d1f0e..5419654e8a4 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -3658,6 +3658,8 @@ void GameLogic::update( void ) UnsignedInt now = getFrame(); TheGameClient->setFrame(now); + TracyPlot("LogicFrame", static_cast(now)); + // update (execute) scripts { ZoneScopedN("GameLogic::ScriptEngine"); From 8ede7497d921a5ca45c679ae7bc6dd8ed845d20a Mon Sep 17 00:00:00 2001 From: stm <14291421+stephanmeesters@users.noreply.github.com> Date: Tue, 27 Jan 2026 00:46:33 +0100 Subject: [PATCH 6/6] docs(readme): Add section about profiling (#2202) --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 84093136b79..c9100305ab2 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,12 @@ through `VCPKG_BINARY_SOURCES=clear;files,/vcpkg-bincache,readwrite` that folder), so the first CI build warms the cache and subsequent builds pull prebuilt binaries instead of re-compiling everything. +### Profiling + +Tracy profiling is supported in the CMake presets `win32-profile` and `win32-vcpkg-profile`. +Use `tracy-profiler.exe` from [Tracy v0.13.1](https://github.com/wolfpld/tracy/releases/tag/v0.13.1). For `win32-vcpkg-profile`, use [Tracy v0.11.1](https://github.com/wolfpld/tracy/releases/tag/v0.11.1). +If you get an error when using Tracy, try removing `dbghelp.dll` from the game binary directory. + ## Contributing We welcome contributions to the project! If you’re interested in contributing, you need to have knowledge of C++. Join