From 9c819ba3e77834a1fee3802fc856e635de5c78df Mon Sep 17 00:00:00 2001 From: Jaremie Romer Date: Sun, 11 Jan 2026 21:44:03 -0600 Subject: [PATCH 1/8] Initial wiring up of Tracy as an alternative to Optick --- CMakeLists.txt | 9 ++- cmake/FetchDependencies.cmake | 25 ++++++- include/ncengine/debug/Profile.h | 65 ++++++++++++++++-- resources/shaders/compiled/ToonPixel.spv | Bin 14228 -> 14292 bytes source/ncengine/CMakeLists.txt | 9 ++- source/ncengine/engine/NcEngineImpl.cpp | 4 +- source/ncengine/graphics2/NcGraphicsImpl2.cpp | 2 +- .../subsystem/WireframeRendererSubsystem.cpp | 4 +- .../subsystem/particle/ParticleSubsystem.cpp | 6 +- source/ncjolt/ncjolt/Profiler.inl | 22 ++++-- 10 files changed, 121 insertions(+), 25 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6d64d0cf4..6f9969e19 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,7 +9,9 @@ option(NC_BUILD_TESTS "Enable building tests." OFF) option(NC_BUILD_INTEGRATION_TESTS "Enable building integration tests." OFF) option(NC_BUILD_NCCONVERT "Enable building nc-convert asset converter." ON) option(NC_RUNTIME_SHADER_COMPILATION "Enable compiling shaders at runtime" ON) -option(NC_PROFILING_ENABLED "Enable profiling with Optick" OFF) +option(NC_PROFILING_ENABLED "Enable profiling" OFF) +set(NC_PROFILER "Tracy" CACHE STRING "Profiler to use (Tracy or Optick)") +set_property(CACHE NC_PROFILER PROPERTY STRINGS "Tracy" "Optick") option(NC_PROD_BUILD "Only build NcEngine production binaries." OFF) # Variables @@ -59,6 +61,11 @@ endif() if(NC_PROFILING_ENABLED) list(APPEND NC_COMPILE_DEFINITIONS NC_PROFILING_ENABLED) + if(NC_PROFILER STREQUAL "Tracy") + list(APPEND NC_COMPILE_DEFINITIONS NC_USE_TRACY) + elseif(NC_PROFILER STREQUAL "Optick") + list(APPEND NC_COMPILE_DEFINITIONS NC_USE_OPTICK) + endif() endif() # Get dependencies diff --git a/cmake/FetchDependencies.cmake b/cmake/FetchDependencies.cmake index 4505beb39..beaaa06eb 100644 --- a/cmake/FetchDependencies.cmake +++ b/cmake/FetchDependencies.cmake @@ -72,7 +72,7 @@ FetchContent_Declare(taskflow # Optick set(OPTICK_INSTALL_TARGETS OFF CACHE BOOL "" FORCE) -if(${NC_PROFILING_ENABLED}) +if(NC_PROFILING_ENABLED AND NC_PROFILER STREQUAL "Optick") set(OPTICK_ENABLED ON CACHE BOOL "" FORCE) else() set(OPTICK_ENABLED OFF CACHE BOOL "" FORCE) @@ -84,6 +84,20 @@ FetchContent_Declare(optick GIT_SHALLOW TRUE ) +# Tracy +if(NC_PROFILING_ENABLED AND NC_PROFILER STREQUAL "Tracy") + set(TRACY_ENABLE ON CACHE BOOL "" FORCE) +else() + set(TRACY_ENABLE OFF CACHE BOOL "" FORCE) +endif() +set(TRACY_ON_DEMAND ON CACHE BOOL "" FORCE) + +FetchContent_Declare(tracy + GIT_REPOSITORY https://github.com/wolfpld/tracy.git + GIT_TAG v0.11.1 + GIT_SHALLOW TRUE +) + # Jolt set(CPP_EXCEPTIONS_ENABLED ON CACHE BOOL "" FORCE) set(CPP_RTTI_ENABLED ON CACHE BOOL "" FORCE) @@ -156,7 +170,7 @@ FetchContent_Declare(fmt ) # Fetch all required sources -FetchContent_MakeAvailable(taskflow optick JoltPhysics DirectXMath fmt DiligentCore DiligentTools) +FetchContent_MakeAvailable(taskflow optick tracy JoltPhysics DirectXMath fmt DiligentCore DiligentTools) # Silence warnings disable_warnings_for_headers(Taskflow) @@ -172,10 +186,15 @@ disable_warnings_for_target(Diligent-Imgui) if(NC_PROFILING_ENABLED AND NOT CMAKE_CXX_COMPILER_ID STREQUAL "GNU") target_compile_definitions(Jolt PUBLIC - -DJPH_EXTERNAL_PROFILE + JPH_EXTERNAL_PROFILE ) endif() +# Silence Tracy warnings +if(NC_PROFILING_ENABLED AND NC_PROFILER STREQUAL "Tracy") + disable_warnings_for_headers(TracyClient) +endif() + # Optional Dependencies if(NC_BUILD_NCCONVERT) diff --git a/include/ncengine/debug/Profile.h b/include/ncengine/debug/Profile.h index cb7d5e11d..2e45b9b78 100644 --- a/include/ncengine/debug/Profile.h +++ b/include/ncengine/debug/Profile.h @@ -6,12 +6,59 @@ #ifdef NC_PROFILING_ENABLED +#if defined(NC_USE_TRACY) + +#include "tracy/Tracy.hpp" + +namespace nc +{ +/** @brief Color and filter for a profile event (unused with Tracy, kept for API compatibility). */ +struct ProfileCategory +{ + static constexpr int None = 0; + static constexpr int Rendering = 1; + static constexpr int Physics = 2; + static constexpr int Audio = 3; + static constexpr int GameLogic = 4; + static constexpr int Animation = 5; + static constexpr int VFX = 6; +}; + +/** @cond internal */ +namespace detail +{ +struct TaskMeasurement +{ + explicit TaskMeasurement() + { + tracy::SetThreadName("NcEngine Worker"); + } + + ~TaskMeasurement() noexcept = default; +}; +} // namespace detail +} // namespace nc +/** @endcond */ + +/** @brief Mark frame boundary. */ +#define NC_PROFILE_FRAME(name) FrameMark + +/** @brief Profile a function or inner scope. */ +#define NC_PROFILE_SCOPE(name, category) ZoneScopedN(name) + +/** @brief Profile a task. Use this at the top level of a task instead of NC_PROFILE_SCOPE for proper thread tracking. */ +#define NC_PROFILE_TASK(name, category) \ + const auto _ncTaskMeasurement ## __LINE__ = nc::detail::TaskMeasurement{}; \ + NC_PROFILE_SCOPE(name, category) + +#elif defined(NC_USE_OPTICK) + #include "optick.h" namespace nc { /** @brief Color and filter for a profile event. */ -using ProfileCategory = Optick::Category; +using ProfileCategory = ProfileCategory; /** @cond internal */ namespace detail @@ -32,15 +79,23 @@ struct TaskMeasurement } // namespace nc /** @endcond */ +/** @brief Mark frame boundary. */ +#define NC_PROFILE_FRAME(name) OPTICK_FRAME(name) + /** @brief Profile a function or inner scope. */ -#define NC_PROFILE_SCOPE(name, category) OPTICK_CATEGORY(name, category); +#define NC_PROFILE_SCOPE(name, category) OPTICK_CATEGORY(name, category) /** @brief Profile a task. Use this at the top level of a task instead of NC_PROFILE_SCOPE for proper thread tracking. */ #define NC_PROFILE_TASK(name, category) \ const auto _ncTaskMeasurement ## __LINE__ = nc::detail::TaskMeasurement{}; \ - NC_PROFILE_SCOPE(name, category); + NC_PROFILE_SCOPE(name, category) + +#endif // NC_USE_TRACY / NC_USE_OPTICK -#else +#else // NC_PROFILING_ENABLED not defined + +#define NC_PROFILE_FRAME(name) #define NC_PROFILE_SCOPE(name, category) #define NC_PROFILE_TASK(name, category) -#endif + +#endif // NC_PROFILING_ENABLED diff --git a/resources/shaders/compiled/ToonPixel.spv b/resources/shaders/compiled/ToonPixel.spv index 4e0c2ec77576f8fd73a9e3addd00c9cef946312c..15e35f4adb0318e3c3757abfeec404c293a8ce3e 100644 GIT binary patch delta 150 zcmbP|eQ4cCauoBr-8DgfXx(Bm?Q4K>l_h-mzI! zs$4<|rfe@Os0=2dO#1y7`^a GG8O-d$!QT{O diff --git a/source/ncengine/CMakeLists.txt b/source/ncengine/CMakeLists.txt index 27f53af35..2fa604228 100644 --- a/source/ncengine/CMakeLists.txt +++ b/source/ncengine/CMakeLists.txt @@ -30,6 +30,13 @@ set_target_properties(${NC_ENGINE_LIB} PROPERTIES CMAKE_DEBUG_POSTFIX d ) +# Profiler library selection +if(NC_PROFILING_ENABLED AND NC_PROFILER STREQUAL "Tracy") + set(NC_PROFILER_LIB TracyClient) +else() + set(NC_PROFILER_LIB OptickCore) +endif() + # Link libraries target_link_libraries(${NC_ENGINE_LIB} PUBLIC @@ -40,7 +47,7 @@ target_link_libraries(${NC_ENGINE_LIB} glfw imgui Taskflow - OptickCore + ${NC_PROFILER_LIB} RtAudio ${DILIGENT_LIBRARIES} ) diff --git a/source/ncengine/engine/NcEngineImpl.cpp b/source/ncengine/engine/NcEngineImpl.cpp index fa4a0c976..72c4778af 100644 --- a/source/ncengine/engine/NcEngineImpl.cpp +++ b/source/ncengine/engine/NcEngineImpl.cpp @@ -9,7 +9,7 @@ #include "utility/Log.h" #include "window/Window.h" -#include "optick.h" +#include "ncengine/debug/Profile.h" #include #include @@ -171,7 +171,7 @@ void NcEngineImpl::Run() auto* ncScene = m_modules->Get(); auto update = [this, ncWindow = m_modules->Get()](float dt) { - OPTICK_FRAME("Main Thread"); + NC_PROFILE_FRAME("Main Thread"); time::SetDeltaTime(dt); input::Flush(); ncWindow->ProcessSystemMessages(); diff --git a/source/ncengine/graphics2/NcGraphicsImpl2.cpp b/source/ncengine/graphics2/NcGraphicsImpl2.cpp index 58ab4b955..23ad7b05b 100644 --- a/source/ncengine/graphics2/NcGraphicsImpl2.cpp +++ b/source/ncengine/graphics2/NcGraphicsImpl2.cpp @@ -532,7 +532,7 @@ void NcGraphicsImpl2::OnBuildTaskGraph(task::UpdateTasks& update, task::RenderTa void NcGraphicsImpl2::Run() { - NC_PROFILE_TASK("Render", Optick::Category::Rendering); + NC_PROFILE_TASK("Render", ProfileCategory::Rendering); if (m_resizeNeeded) { diff --git a/source/ncengine/graphics2/frontend/subsystem/WireframeRendererSubsystem.cpp b/source/ncengine/graphics2/frontend/subsystem/WireframeRendererSubsystem.cpp index 2c68dbcb3..990572211 100644 --- a/source/ncengine/graphics2/frontend/subsystem/WireframeRendererSubsystem.cpp +++ b/source/ncengine/graphics2/frontend/subsystem/WireframeRendererSubsystem.cpp @@ -9,7 +9,7 @@ #include "asset/AssetService.h" #include "ncmath/MatrixUtilities.h" -#include "optick.h" +#include "ncengine/debug/Profile.h" namespace { @@ -87,7 +87,7 @@ auto WireframeRendererSubsystem::BuildState(ecs::ExplicitEcs worldView) -> WireframeRendererRenderState { - OPTICK_CATEGORY("WireframeRendererSubsystem::Execute", Optick::Category::Rendering); + NC_PROFILE_SCOPE("WireframeRendererSubsystem::Execute", ProfileCategory::Rendering); auto state = WireframeRendererRenderState{}; for (auto& renderer : worldView.GetAll()) diff --git a/source/ncengine/graphics2/frontend/subsystem/particle/ParticleSubsystem.cpp b/source/ncengine/graphics2/frontend/subsystem/particle/ParticleSubsystem.cpp index 18ea2d554..3ce981d88 100644 --- a/source/ncengine/graphics2/frontend/subsystem/particle/ParticleSubsystem.cpp +++ b/source/ncengine/graphics2/frontend/subsystem/particle/ParticleSubsystem.cpp @@ -100,7 +100,7 @@ void ParticleSubsystem::Emit(Entity entity, size_t count) void ParticleSubsystem::Update(Camera* mainCamera) { - NC_PROFILE_TASK("ParticleSubystem::Update()", Optick::Category::VFX); + NC_PROFILE_TASK("ParticleSubystem::Update()", ProfileCategory::VFX); CommitPendingChanges(); const float dt = time::DeltaTime(); const auto [camPosition, camRotation, camForward] = GetCameraProperties(m_world, mainCamera); @@ -124,7 +124,7 @@ void ParticleSubsystem::Update(Camera* mainCamera) void ParticleSubsystem::CommitPendingChanges() { - NC_PROFILE_SCOPE("ParticleSubsystem::CommitPendingChanges()", Optick::Category::VFX); + NC_PROFILE_SCOPE("ParticleSubsystem::CommitPendingChanges()", ProfileCategory::VFX); m_emitterStates.insert( m_emitterStates.cend(), std::make_move_iterator(m_toAdd.begin()), @@ -170,7 +170,7 @@ void ParticleSubsystem::Clear() noexcept void ParticleSubsystem::SortEmitters(DirectX::FXMVECTOR cameraPosition) { - NC_PROFILE_SCOPE("ParticleSubsystem::SortEmitters()", Optick::Category::VFX); + NC_PROFILE_SCOPE("ParticleSubsystem::SortEmitters()", ProfileCategory::VFX); // Build up an index array for sorting to help minimize number of swaps and distance calculations auto permutation = std::vector{}; diff --git a/source/ncjolt/ncjolt/Profiler.inl b/source/ncjolt/ncjolt/Profiler.inl index f7e87562c..5a89359ee 100644 --- a/source/ncjolt/ncjolt/Profiler.inl +++ b/source/ncjolt/ncjolt/Profiler.inl @@ -6,8 +6,10 @@ #include "Jolt/Core/Profiler.h" // For 'real' targets, both jolt and nc profiling will be in sync. This just allows us to build tests when -// we have a profiled build of jolt without bringing in the optick dependency as well. -#ifdef NC_PROFILING_ENABLED +// we have a profiled build of jolt without bringing in the profiler dependency as well. +#if defined(NC_USE_TRACY) +#include "tracy/Tracy.hpp" +#elif defined(NC_USE_OPTICK) #include "optick.h" #endif @@ -15,12 +17,16 @@ JPH_NAMESPACE_BEGIN ExternalProfileMeasurement::ExternalProfileMeasurement([[maybe_unused]] const char* name, uint32) { -#ifdef NC_PROFILING_ENABLED +#if defined(NC_USE_TRACY) + // Tracy uses a different approach - we store the zone context in mUserData + static_assert(sizeof(tracy::ScopedZone) <= sizeof(mUserData)); + new (mUserData) tracy::ScopedZone(__LINE__, __FILE__, strlen(__FILE__), nullptr, 0, name, strlen(name), true); +#elif defined(NC_USE_OPTICK) // Optick macros use a static variable to hold a description and a local variable to start/stop. We need to manually // build a description based on the given name and emplace an event over our user data to take ownership. - constexpr auto category = Optick::Category::Physics; - static const auto color = Optick::Category::GetColor(category); - static const auto filter = Optick::Category::GetMask(category); + constexpr auto category = ProfileCategory::Physics; + static const auto color = ProfileCategory::GetColor(category); + static const auto filter = ProfileCategory::GetMask(category); // note: The use case for CreateShared is to make a copy of the string, which we don't care about, but it also // caches the description. Since we aren't caching in a static var, its considerably more performant. const auto description = Optick::EventDescription::CreateShared(name, __FILE__, __LINE__, color, filter); @@ -31,7 +37,9 @@ ExternalProfileMeasurement::ExternalProfileMeasurement([[maybe_unused]] const ch ExternalProfileMeasurement::~ExternalProfileMeasurement() { -#ifdef NC_PROFILING_ENABLED +#if defined(NC_USE_TRACY) + reinterpret_cast(&mUserData)->~ScopedZone(); +#elif defined(NC_USE_OPTICK) reinterpret_cast(&mUserData)->~Event(); #endif } From 4ec511a5a53c49a8704a93508f2886c3b6477c94 Mon Sep 17 00:00:00 2001 From: Jaremie Romer Date: Sun, 11 Jan 2026 22:29:31 -0600 Subject: [PATCH 2/8] Fixed versioning --- cmake/FetchDependencies.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/FetchDependencies.cmake b/cmake/FetchDependencies.cmake index beaaa06eb..176e798a9 100644 --- a/cmake/FetchDependencies.cmake +++ b/cmake/FetchDependencies.cmake @@ -94,7 +94,7 @@ set(TRACY_ON_DEMAND ON CACHE BOOL "" FORCE) FetchContent_Declare(tracy GIT_REPOSITORY https://github.com/wolfpld/tracy.git - GIT_TAG v0.11.1 + GIT_TAG v0.13.1 GIT_SHALLOW TRUE ) From d1a5f2929494c4a188f5f0d18c5ff4bce9ed9620 Mon Sep 17 00:00:00 2001 From: Jaremie Romer Date: Sun, 11 Jan 2026 22:47:25 -0600 Subject: [PATCH 3/8] Added additional instrumentation to ncgraphics2 --- source/ncengine/graphics2/diligent/UIBackend.cpp | 3 +++ source/ncengine/graphics2/diligent/pass/PassBackend.cpp | 3 ++- .../graphics2/diligent/resource/CubeMapBufferResource.cpp | 2 ++ .../graphics2/diligent/resource/EnvironmentBufferResource.cpp | 2 ++ .../graphics2/diligent/resource/SinkBufferResource.cpp | 2 ++ .../graphics2/diligent/resource/TextureBufferResource.cpp | 2 ++ .../graphics2/diligent/resource/WireframeBufferResource.cpp | 2 ++ source/ncengine/graphics2/frontend/GraphicsFrontend.cpp | 2 ++ .../ncengine/graphics2/frontend/subsystem/CameraSubsystem.cpp | 2 ++ .../ncengine/graphics2/frontend/subsystem/InstanceCache.inl | 2 ++ .../ncengine/graphics2/frontend/subsystem/LightSubsystem.cpp | 2 ++ .../graphics2/frontend/subsystem/PostProcessSubsystem.cpp | 2 ++ .../ncengine/graphics2/frontend/subsystem/TransformCache.cpp | 1 + source/ncengine/graphics2/frontend/subsystem/UISubsystem.cpp | 2 ++ .../frontend/subsystem/WireframeRendererSubsystem.cpp | 2 +- .../graphics2/frontend/subsystem/animation/BoneCache.cpp | 4 ++++ .../subsystem/animation/SkeletalAnimationSubsystem.cpp | 2 ++ .../graphics2/frontend/subsystem/particle/EmitterState.cpp | 2 ++ .../frontend/subsystem/particle/ParticleSubsystem.cpp | 1 + 19 files changed, 38 insertions(+), 2 deletions(-) diff --git a/source/ncengine/graphics2/diligent/UIBackend.cpp b/source/ncengine/graphics2/diligent/UIBackend.cpp index 3dfe38b87..6b5633263 100644 --- a/source/ncengine/graphics2/diligent/UIBackend.cpp +++ b/source/ncengine/graphics2/diligent/UIBackend.cpp @@ -1,5 +1,6 @@ #include "UIBackend.h" +#include "ncengine/debug/Profile.h" #include "imgui.h" #include "ImGuizmo.h" @@ -16,6 +17,7 @@ UIBackend::UIBackend(Diligent::IRenderDevice& device, void UIBackend::FrameBegin(Diligent::ISwapChain& swapChain) { + NC_PROFILE_SCOPE("UIBackend::FrameBegin", ProfileCategory::Rendering); const auto& scDesc = swapChain.GetDesc(); m_imguiBackend.NewFrame(scDesc.Width, scDesc.Height, scDesc.PreTransform); ImGuizmo::BeginFrame(); @@ -23,6 +25,7 @@ void UIBackend::FrameBegin(Diligent::ISwapChain& swapChain) void UIBackend::Render(Diligent::IDeviceContext& context) { + NC_PROFILE_SCOPE("UIBackend::Render", ProfileCategory::Rendering); m_imguiBackend.Render(&context); } diff --git a/source/ncengine/graphics2/diligent/pass/PassBackend.cpp b/source/ncengine/graphics2/diligent/pass/PassBackend.cpp index 3744fea9c..ca5b50786 100644 --- a/source/ncengine/graphics2/diligent/pass/PassBackend.cpp +++ b/source/ncengine/graphics2/diligent/pass/PassBackend.cpp @@ -212,6 +212,7 @@ void PassBackend::RenderShadowPass(IDeviceContext& context, const std::vector& skinnedBatches, const std::span& lights) { + NC_PROFILE_SCOPE("PassBackend::RenderShadowPass", ProfileCategory::Rendering); auto& sinkIndexBuffer = perPassResourceSignature.GetSinkIndexBufferResource(); auto& uniShadowMapsBuffer = perPassResourceSignature.GetUniShadowMapSinksResource(); auto& pointShadowMapsBuffer = perPassResourceSignature.GetPointShadowMapSinksResource(); @@ -418,8 +419,8 @@ void PassBackend::RenderParticle(IDeviceContext& context, PerPassResourceSignature& perPassResourceSignature, const ParticleRenderState& state, const Viewport& viewport) - { + NC_PROFILE_SCOPE("PassBackend::RenderParticle", ProfileCategory::Rendering); if (state.particleData.instances.empty() || !m_particlePass) { return; diff --git a/source/ncengine/graphics2/diligent/resource/CubeMapBufferResource.cpp b/source/ncengine/graphics2/diligent/resource/CubeMapBufferResource.cpp index e20b4f7a3..125890f4b 100644 --- a/source/ncengine/graphics2/diligent/resource/CubeMapBufferResource.cpp +++ b/source/ncengine/graphics2/diligent/resource/CubeMapBufferResource.cpp @@ -1,4 +1,5 @@ #include "CubeMapBufferResource.h" +#include "ncengine/debug/Profile.h" #include "ResourceTypes.h" #include "TextureLoader.h" @@ -36,6 +37,7 @@ void CubeMapBufferResource::Load(std::span cubeMaps, IDeviceContext& context, IRenderDevice& device) { + NC_PROFILE_SCOPE("CubeMapBufferResource::Load", ProfileCategory::Rendering); const auto cubeMapCount = cubeMaps.size(); if (cubeMapCount == 0) { diff --git a/source/ncengine/graphics2/diligent/resource/EnvironmentBufferResource.cpp b/source/ncengine/graphics2/diligent/resource/EnvironmentBufferResource.cpp index 63a93c797..82d965df7 100644 --- a/source/ncengine/graphics2/diligent/resource/EnvironmentBufferResource.cpp +++ b/source/ncengine/graphics2/diligent/resource/EnvironmentBufferResource.cpp @@ -1,5 +1,6 @@ #include "EnvironmentBufferResource.h" #include "graphics2/frontend/subsystem/CameraRenderState.h" +#include "ncengine/debug/Profile.h" #include "graphics2/frontend/subsystem/EnvironmentRenderState.h" #include "graphics2/frontend/subsystem/LightRenderState.h" #include "ncengine/graphics/Environment.h" @@ -25,6 +26,7 @@ void EnvironmentBufferResource::Update(Diligent::IDeviceContext& context, const LightRenderState& lightRenderState, const EnvironmentRenderState& environmentRenderState) { + NC_PROFILE_SCOPE("EnvironmentBufferResource::Update", ProfileCategory::Rendering); const auto data = GlobalEnvironmentData{ .cameraViewProjection = cameraState.viewProjection, .cameraInvProjection = cameraState.invProjection, diff --git a/source/ncengine/graphics2/diligent/resource/SinkBufferResource.cpp b/source/ncengine/graphics2/diligent/resource/SinkBufferResource.cpp index 0dea7fc97..8b54d77d2 100644 --- a/source/ncengine/graphics2/diligent/resource/SinkBufferResource.cpp +++ b/source/ncengine/graphics2/diligent/resource/SinkBufferResource.cpp @@ -1,5 +1,6 @@ #include "SinkBufferResource.h" #include "graphics2/diligent/pass/PassTypes.h" +#include "ncengine/debug/Profile.h" #include "ResourceTypes.h" #include "ncutility/NcError.h" @@ -210,6 +211,7 @@ void SinkBufferResource::Clear() void SinkBufferResource::Update() { + NC_PROFILE_SCOPE("SinkBufferResource::Update", ProfileCategory::Rendering); SetArrayRegion(m_variable, std::span(m_shaderResourceViews), 0u, m_shaderResourceViews.size()); } } // namespace nc::graphics diff --git a/source/ncengine/graphics2/diligent/resource/TextureBufferResource.cpp b/source/ncengine/graphics2/diligent/resource/TextureBufferResource.cpp index 59d99691e..e9d2abe32 100644 --- a/source/ncengine/graphics2/diligent/resource/TextureBufferResource.cpp +++ b/source/ncengine/graphics2/diligent/resource/TextureBufferResource.cpp @@ -1,4 +1,5 @@ #include "TextureBufferResource.h" +#include "ncengine/debug/Profile.h" #include "ResourceTypes.h" #include "TextureLoader.h" @@ -31,6 +32,7 @@ void TextureBufferResource::Load(std::span textures, Diligent::IDeviceContext& context, Diligent::IRenderDevice& device) { + NC_PROFILE_SCOPE("TextureBufferResource::Load", ProfileCategory::Rendering); if (!m_initialLoadComplete) { InitializeArray(context, device, m_variable, m_maxTextures); diff --git a/source/ncengine/graphics2/diligent/resource/WireframeBufferResource.cpp b/source/ncengine/graphics2/diligent/resource/WireframeBufferResource.cpp index 72fb74613..9f9944640 100644 --- a/source/ncengine/graphics2/diligent/resource/WireframeBufferResource.cpp +++ b/source/ncengine/graphics2/diligent/resource/WireframeBufferResource.cpp @@ -1,4 +1,5 @@ #include "WireframeBufferResource.h" +#include "ncengine/debug/Profile.h" namespace nc::graphics { @@ -19,6 +20,7 @@ WireframeBufferResource::WireframeBufferResource(Diligent::IDeviceContext& conte void WireframeBufferResource::Update(Diligent::IDeviceContext& context, const WireframeData& data) { + NC_PROFILE_SCOPE("WireframeBufferResource::Update", ProfileCategory::Rendering); m_buffer.Write(context, data); } } // namespace nc::graphics diff --git a/source/ncengine/graphics2/frontend/GraphicsFrontend.cpp b/source/ncengine/graphics2/frontend/GraphicsFrontend.cpp index d76cf83a2..cdcb35f46 100644 --- a/source/ncengine/graphics2/frontend/GraphicsFrontend.cpp +++ b/source/ncengine/graphics2/frontend/GraphicsFrontend.cpp @@ -1,11 +1,13 @@ #include "GraphicsFrontend.h" #include "FrontendRenderState.h" +#include "ncengine/debug/Profile.h" #include "ncengine/ecs/Ecs.h" namespace nc::graphics { auto GraphicsFrontend::BuildRenderState(ecs::Ecs world) -> FrontendRenderState { + NC_PROFILE_SCOPE("GraphicsFrontend::BuildRenderState", ProfileCategory::Rendering); return FrontendRenderState{ .cameraState = m_cameraSystem.BuildState(world), .environmentRenderState = m_environmentSystem.BuildState(), diff --git a/source/ncengine/graphics2/frontend/subsystem/CameraSubsystem.cpp b/source/ncengine/graphics2/frontend/subsystem/CameraSubsystem.cpp index 42823884e..01609d3a4 100644 --- a/source/ncengine/graphics2/frontend/subsystem/CameraSubsystem.cpp +++ b/source/ncengine/graphics2/frontend/subsystem/CameraSubsystem.cpp @@ -1,6 +1,7 @@ #include "CameraSubsystem.h" #include "CameraRenderState.h" +#include "ncengine/debug/Profile.h" #include "ncengine/ecs/Ecs.h" #include "ncengine/graphics/Camera.h" #include "ncengine/window/Window.h" @@ -20,6 +21,7 @@ namespace nc::graphics { auto CameraSubsystem::BuildState(ecs::ExplicitEcs ecs) -> CameraRenderState { + NC_PROFILE_SCOPE("CameraSubsystem::BuildState", ProfileCategory::Rendering); if (m_mainCamera) { const auto& transform = ecs.Get(m_mainCamera->ParentEntity()); diff --git a/source/ncengine/graphics2/frontend/subsystem/InstanceCache.inl b/source/ncengine/graphics2/frontend/subsystem/InstanceCache.inl index 888cc3762..2614a1dde 100644 --- a/source/ncengine/graphics2/frontend/subsystem/InstanceCache.inl +++ b/source/ncengine/graphics2/frontend/subsystem/InstanceCache.inl @@ -133,6 +133,7 @@ void InstanceCache::CommitPendingChanges() template auto InstanceCache::BuildState() -> BufferUpdateInfo { + NC_PROFILE_SCOPE("InstanceCache::BuildState", ProfileCategory::Rendering); // For simplicity, we're only tracking one dirty range from the first modified index to the buffer // end. This can result in more copies than necessary for non-shifting insertions and removals, but the // data is pretty cheap to copy. @@ -181,6 +182,7 @@ auto InstanceCache::BuildBatches(std::span pass template void InstanceCache::Purge() noexcept { + NC_PROFILE_SCOPE("InstanceCache::Purge", ProfileCategory::Rendering); CommitPendingChanges(); m_dirtyBegin = 0u; auto bufferEnd = 0u; diff --git a/source/ncengine/graphics2/frontend/subsystem/LightSubsystem.cpp b/source/ncengine/graphics2/frontend/subsystem/LightSubsystem.cpp index 9e9755caa..96cdbb81b 100644 --- a/source/ncengine/graphics2/frontend/subsystem/LightSubsystem.cpp +++ b/source/ncengine/graphics2/frontend/subsystem/LightSubsystem.cpp @@ -1,5 +1,6 @@ #include "LightSubsystem.h" +#include "ncengine/debug/Profile.h" #include "ncengine/ecs/Ecs.h" #include "ncengine/ecs/Transform.h" #include "ncengine/graphics/Light.h" @@ -37,6 +38,7 @@ namespace nc::graphics { auto LightSubsystem::BuildState(ecs::ExplicitEcs ecs) -> LightRenderState { + NC_PROFILE_SCOPE("LightSubsystem::BuildState", ProfileCategory::Rendering); m_lightData.clear(); m_lightMatrixData.clear(); auto lightMatrixIndex = 0u; diff --git a/source/ncengine/graphics2/frontend/subsystem/PostProcessSubsystem.cpp b/source/ncengine/graphics2/frontend/subsystem/PostProcessSubsystem.cpp index b311cc667..d56ec974c 100644 --- a/source/ncengine/graphics2/frontend/subsystem/PostProcessSubsystem.cpp +++ b/source/ncengine/graphics2/frontend/subsystem/PostProcessSubsystem.cpp @@ -1,4 +1,5 @@ #include "PostProcessSubsystem.h" +#include "ncengine/debug/Profile.h" #include "ncengine/graphics/GraphicsUtility.h" #include "ncutility/NcError.h" @@ -121,6 +122,7 @@ void PostProcessSubsystem::SetProperties(PostProcessEffectId effectId, auto PostProcessSubsystem::BuildState() -> PostProcessState { + NC_PROFILE_SCOPE("PostProcessSubsystem::BuildState", ProfileCategory::Rendering); return PostProcessState{ .toggledEffects = std::move(m_toggledEffects), .modifiedProperties = std::move(m_modifiedProperties) diff --git a/source/ncengine/graphics2/frontend/subsystem/TransformCache.cpp b/source/ncengine/graphics2/frontend/subsystem/TransformCache.cpp index dda6666c0..8811e841e 100644 --- a/source/ncengine/graphics2/frontend/subsystem/TransformCache.cpp +++ b/source/ncengine/graphics2/frontend/subsystem/TransformCache.cpp @@ -33,6 +33,7 @@ void TransformCache::RemoveInstance(TransformDataHandle instance) void TransformCache::CommitPendingChanges() { + NC_PROFILE_SCOPE("TransformCache::CommitPendingChanges", ProfileCategory::Rendering); m_buffer.CommitPendingChanges(); } diff --git a/source/ncengine/graphics2/frontend/subsystem/UISubsystem.cpp b/source/ncengine/graphics2/frontend/subsystem/UISubsystem.cpp index c1b2e3dae..f2dd80594 100644 --- a/source/ncengine/graphics2/frontend/subsystem/UISubsystem.cpp +++ b/source/ncengine/graphics2/frontend/subsystem/UISubsystem.cpp @@ -1,5 +1,6 @@ #include "UISubsystem.h" +#include "ncengine/debug/Profile.h" #include "ncengine/ui/IUI.h" #include "ncengine/ui/editor/Editor.h" @@ -26,6 +27,7 @@ void UISubsystem::SetClientUI(ui::IUI* ui) noexcept void UISubsystem::UpdateUI(ecs::Ecs world) { + NC_PROFILE_SCOPE("UISubsystem::UpdateUI", ProfileCategory::Rendering); m_editor->Draw(world); if (m_clientUI) { diff --git a/source/ncengine/graphics2/frontend/subsystem/WireframeRendererSubsystem.cpp b/source/ncengine/graphics2/frontend/subsystem/WireframeRendererSubsystem.cpp index 990572211..58d38f726 100644 --- a/source/ncengine/graphics2/frontend/subsystem/WireframeRendererSubsystem.cpp +++ b/source/ncengine/graphics2/frontend/subsystem/WireframeRendererSubsystem.cpp @@ -87,7 +87,7 @@ auto WireframeRendererSubsystem::BuildState(ecs::ExplicitEcs worldView) -> WireframeRendererRenderState { - NC_PROFILE_SCOPE("WireframeRendererSubsystem::Execute", ProfileCategory::Rendering); + NC_PROFILE_SCOPE("WireframeRendererSubsystem::BuildState", ProfileCategory::Rendering); auto state = WireframeRendererRenderState{}; for (auto& renderer : worldView.GetAll()) diff --git a/source/ncengine/graphics2/frontend/subsystem/animation/BoneCache.cpp b/source/ncengine/graphics2/frontend/subsystem/animation/BoneCache.cpp index 2a73528d0..cf4d6cf90 100644 --- a/source/ncengine/graphics2/frontend/subsystem/animation/BoneCache.cpp +++ b/source/ncengine/graphics2/frontend/subsystem/animation/BoneCache.cpp @@ -1,5 +1,6 @@ #include "BoneCache.h" +#include "ncengine/debug/Profile.h" #include "ncutility/NcError.h" #include @@ -119,6 +120,7 @@ void BoneCacheStaging::Purge() void BoneCache::CommitPendingChanges() { + NC_PROFILE_SCOPE("BoneCache::CommitPendingChanges", ProfileCategory::Animation); std::ranges::fill(m_data, BoneData{}); const auto newCapacity = m_staging.GetCapacity(); if (newCapacity > m_data.size()) @@ -129,6 +131,7 @@ void BoneCache::CommitPendingChanges() void BoneCache::UpdateRegion(BoneCacheHandle boneIndex, std::span bones) { + NC_PROFILE_SCOPE("BoneCache::UpdateRegion", ProfileCategory::Animation); NC_ASSERT(m_data.size() >= bones.size() + boneIndex, "BoneCache write out of bounds"); std::memcpy(m_data.data() + boneIndex, bones.data(), bones.size() * sizeof(BoneData)); } @@ -143,6 +146,7 @@ auto BoneCache::BuildUpdateInfo() -> BufferUpdateInfo void BoneCache::Purge() { + NC_PROFILE_SCOPE("BoneCache::Purge", ProfileCategory::Animation); m_staging.Purge(); m_data.resize(m_staging.GetCapacity()); m_data.shrink_to_fit(); diff --git a/source/ncengine/graphics2/frontend/subsystem/animation/SkeletalAnimationSubsystem.cpp b/source/ncengine/graphics2/frontend/subsystem/animation/SkeletalAnimationSubsystem.cpp index bef268066..ad5b38796 100644 --- a/source/ncengine/graphics2/frontend/subsystem/animation/SkeletalAnimationSubsystem.cpp +++ b/source/ncengine/graphics2/frontend/subsystem/animation/SkeletalAnimationSubsystem.cpp @@ -129,6 +129,7 @@ void SkeletalAnimationSubsystem::CalculateBoneMatrices() void SkeletalAnimationSubsystem::CommitPendingChanges() { + NC_PROFILE_SCOPE("SkeletalAnimationSubsystem::CommitPendingChanges", ProfileCategory::Animation); m_stateOrchestrator.Remove(m_removed); m_removed.clear(); m_boneCache.CommitPendingChanges(); @@ -136,6 +137,7 @@ void SkeletalAnimationSubsystem::CommitPendingChanges() auto SkeletalAnimationSubsystem::BuildState() -> SkeletalAnimationRenderState { + NC_PROFILE_SCOPE("SkeletalAnimationSubsystem::BuildState", ProfileCategory::Animation); return SkeletalAnimationRenderState{m_boneCache.BuildUpdateInfo()}; } diff --git a/source/ncengine/graphics2/frontend/subsystem/particle/EmitterState.cpp b/source/ncengine/graphics2/frontend/subsystem/particle/EmitterState.cpp index 533f69775..733bc7d4b 100644 --- a/source/ncengine/graphics2/frontend/subsystem/particle/EmitterState.cpp +++ b/source/ncengine/graphics2/frontend/subsystem/particle/EmitterState.cpp @@ -1,5 +1,6 @@ #include "EmitterState.h" #include "math/Random.h" +#include "ncengine/debug/Profile.h" #include "ncmath/Math.h" #include @@ -106,6 +107,7 @@ void EmitterState::Update(DirectX::FXMVECTOR position, DirectX::FXMVECTOR camForward, float dt) { + NC_PROFILE_SCOPE("EmitterState::Update", ProfileCategory::VFX); if (m_needsResize) { // We can't just reserve because the capacity is logically important. In the case of shrinking, we'd need diff --git a/source/ncengine/graphics2/frontend/subsystem/particle/ParticleSubsystem.cpp b/source/ncengine/graphics2/frontend/subsystem/particle/ParticleSubsystem.cpp index 3ce981d88..8831e8375 100644 --- a/source/ncengine/graphics2/frontend/subsystem/particle/ParticleSubsystem.cpp +++ b/source/ncengine/graphics2/frontend/subsystem/particle/ParticleSubsystem.cpp @@ -145,6 +145,7 @@ void ParticleSubsystem::CommitPendingChanges() auto ParticleSubsystem::BuildState() -> ParticleRenderState { + NC_PROFILE_SCOPE("ParticleSubsystem::BuildState", ProfileCategory::VFX); const auto count = std::min(static_cast(m_particleDataHostBuffer.size()), m_maxParticles); // we don't want to crash when exceeding maxParticles, just discard const auto updateInfo = count > 0u ? BufferUpdateInfo{m_particleDataHostBuffer, { {0, count} }} From b9461b61b181dd2ebd9851b2be0ef70a6e395481 Mon Sep 17 00:00:00 2001 From: Jaremie Romer Date: Mon, 19 Jan 2026 09:35:06 -0600 Subject: [PATCH 4/8] Ignoring benchmarks --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 91663d803..2a363a666 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,4 @@ imgui.ini /Folder.DotSettings.user /.editorconfig /CMakeSettings.json +Benchmarks_1_19_2026.tracy From 75055e596110feff1bfc8cec3c151ae05df82899 Mon Sep 17 00:00:00 2001 From: Jaremie Romer Date: Mon, 19 Jan 2026 11:18:16 -0600 Subject: [PATCH 5/8] Fixed Optick --- include/ncengine/debug/Profile.h | 3 +-- source/ncjolt/ncjolt/Profiler.inl | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/include/ncengine/debug/Profile.h b/include/ncengine/debug/Profile.h index 2e45b9b78..6acc25a2e 100644 --- a/include/ncengine/debug/Profile.h +++ b/include/ncengine/debug/Profile.h @@ -57,8 +57,7 @@ struct TaskMeasurement namespace nc { -/** @brief Color and filter for a profile event. */ -using ProfileCategory = ProfileCategory; +using ProfileCategory = Optick::Category; /** @cond internal */ namespace detail diff --git a/source/ncjolt/ncjolt/Profiler.inl b/source/ncjolt/ncjolt/Profiler.inl index 5a89359ee..907dbfa73 100644 --- a/source/ncjolt/ncjolt/Profiler.inl +++ b/source/ncjolt/ncjolt/Profiler.inl @@ -24,9 +24,9 @@ ExternalProfileMeasurement::ExternalProfileMeasurement([[maybe_unused]] const ch #elif defined(NC_USE_OPTICK) // Optick macros use a static variable to hold a description and a local variable to start/stop. We need to manually // build a description based on the given name and emplace an event over our user data to take ownership. - constexpr auto category = ProfileCategory::Physics; - static const auto color = ProfileCategory::GetColor(category); - static const auto filter = ProfileCategory::GetMask(category); + constexpr auto category = Optick::Category::Physics; + static const auto color = Optick::Category::GetColor(category); + static const auto filter = Optick::Category::GetMask(category); // note: The use case for CreateShared is to make a copy of the string, which we don't care about, but it also // caches the description. Since we aren't caching in a static var, its considerably more performant. const auto description = Optick::EventDescription::CreateShared(name, __FILE__, __LINE__, color, filter); From adf2e6b41f77905973ce30d2a679c7dd0c5cb616 Mon Sep 17 00:00:00 2001 From: Jaremie Romer Date: Mon, 19 Jan 2026 16:55:59 -0600 Subject: [PATCH 6/8] cleanup --- source/ncjolt/ncjolt/Profiler.inl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/ncjolt/ncjolt/Profiler.inl b/source/ncjolt/ncjolt/Profiler.inl index 907dbfa73..260863bdb 100644 --- a/source/ncjolt/ncjolt/Profiler.inl +++ b/source/ncjolt/ncjolt/Profiler.inl @@ -24,7 +24,7 @@ ExternalProfileMeasurement::ExternalProfileMeasurement([[maybe_unused]] const ch #elif defined(NC_USE_OPTICK) // Optick macros use a static variable to hold a description and a local variable to start/stop. We need to manually // build a description based on the given name and emplace an event over our user data to take ownership. - constexpr auto category = Optick::Category::Physics; + constexpr auto category = Optick::Category::Physics; static const auto color = Optick::Category::GetColor(category); static const auto filter = Optick::Category::GetMask(category); // note: The use case for CreateShared is to make a copy of the string, which we don't care about, but it also From f418e7a70400238b51637402208a4289e978c004 Mon Sep 17 00:00:00 2001 From: Jaremie Romer Date: Mon, 19 Jan 2026 16:57:04 -0600 Subject: [PATCH 7/8] cleanup --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 2a363a666..91663d803 100644 --- a/.gitignore +++ b/.gitignore @@ -36,4 +36,3 @@ imgui.ini /Folder.DotSettings.user /.editorconfig /CMakeSettings.json -Benchmarks_1_19_2026.tracy From 374afa518e3598e79af8ccf10c029b6a300395a4 Mon Sep 17 00:00:00 2001 From: Jaremie Romer Date: Mon, 19 Jan 2026 17:03:17 -0600 Subject: [PATCH 8/8] cleanup --- include/ncengine/debug/Profile.h | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/include/ncengine/debug/Profile.h b/include/ncengine/debug/Profile.h index 6acc25a2e..0726c4c6f 100644 --- a/include/ncengine/debug/Profile.h +++ b/include/ncengine/debug/Profile.h @@ -12,18 +12,6 @@ namespace nc { -/** @brief Color and filter for a profile event (unused with Tracy, kept for API compatibility). */ -struct ProfileCategory -{ - static constexpr int None = 0; - static constexpr int Rendering = 1; - static constexpr int Physics = 2; - static constexpr int Audio = 3; - static constexpr int GameLogic = 4; - static constexpr int Animation = 5; - static constexpr int VFX = 6; -}; - /** @cond internal */ namespace detail {