From b50876e77c59a6d64e5899de552adbdd4a42c36d Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Mon, 23 Mar 2026 16:58:41 -0700 Subject: [PATCH 01/67] eliminate NewFrame, and account for "abandoned" and "out-of-order" timestamp queries --- public/tracy/TracyD3D12.hpp | 213 ++++++++++++++++++++++-------------- 1 file changed, 131 insertions(+), 82 deletions(-) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index a3594689..095a4a42 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -36,42 +36,48 @@ using TracyD3D12Ctx = void*; #include "../client/TracyProfiler.hpp" #include "../client/TracyCallstack.hpp" +#include +#include +#include +#include #include +#include #include #include #include -#include + +#ifndef TRACY_D3D12_TIMESTAMP_COLLECT_TIMEOUT +#define TRACY_D3D12_TIMESTAMP_COLLECT_TIMEOUT 0.200f +#endif #define TracyD3D12Panic(msg, ...) do { assert(false && "TracyD3D12: " msg); tracy::Profiler::LogString( tracy::MessageSourceType::Tracy, tracy::MessageSeverity::Error, tracy::Color::Red4, 0, msg ); __VA_ARGS__; } while(false); namespace tracy { - struct D3D12QueryPayload - { - uint32_t m_queryIdStart = 0; - uint32_t m_queryCount = 0; - }; - // Command queue context. class D3D12QueueCtx { friend class D3D12ZoneScope; + static constexpr UINT64 InvalidTimestamp = 0; // arbitrary choice (afaik, it's technically a valid timestamp value) + + uint8_t m_contextId = 255; // TODO: apparently, 255 means "invalid id"; is this documented somewhere? + + std::mutex m_collectionMutex; + ID3D12Device* m_device = nullptr; ID3D12CommandQueue* m_queue = nullptr; - uint8_t m_contextId = 255; // TODO: apparently, 255 means "invalid id"; is this documented somewhere? ID3D12QueryHeap* m_queryHeap = nullptr; ID3D12Resource* m_readbackBuffer = nullptr; - // In-progress payload. - uint32_t m_queryLimit = 0; - std::atomic m_queryCounter = 0; - uint32_t m_previousQueryCounter = 0; + using atomic_counter = std::atomic; + atomic_counter m_queryCounter = 0; + atomic_counter m_previousCheckpoint = 0; - uint32_t m_activePayload = 0; - ID3D12Fence* m_payloadFence = nullptr; - std::queue m_payloadQueue; + uint32_t m_queryLimit = 0; + + std::vector m_queryRequestTime; UINT64 m_prevCalibrationTicksCPU = 0; @@ -173,11 +179,23 @@ namespace tracy TracyD3D12Panic("Failed to create query readback buffer.", return); } - if (FAILED(device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&m_payloadFence)))) { - TracyD3D12Panic("Failed to create payload fence.", return); + D3D12_RANGE zeroRange{ 0, m_queryLimit * sizeof(UINT64) }; + void* buffer = nullptr; + if (FAILED(m_readbackBuffer->Map(0, &zeroRange, &buffer))) + { + TracyD3D12Panic("Failed to map readback buffer for initialization.", return); + } + UINT64* timestampBuffer = static_cast(buffer); + for (uint64_t i = 0; i < m_queryLimit; ++i) + { + timestampBuffer[i] = InvalidTimestamp; + } + m_readbackBuffer->Unmap(0, &zeroRange); } + m_queryRequestTime.resize(m_queryLimit); + float period = [queue]() { uint64_t timestampFrequency; @@ -205,19 +223,19 @@ namespace tracy cpuTimestamp = Profiler::GetTime(); - // all checked: ready to roll + // All setup/init checks completed: ready to create the context. m_contextId = GetGpuCtxCounter().fetch_add(1); ZoneValue(int64_t(m_contextId)); auto* item = Profiler::QueueSerial(); MemWrite(&item->hdr.type, QueueType::GpuNewContext); - MemWrite(&item->gpuNewContext.cpuTime, cpuTimestamp); - MemWrite(&item->gpuNewContext.gpuTime, gpuTimestamp); - MemWrite(&item->gpuNewContext.thread, decltype(item->gpuNewContext.thread)(0)); // #TODO: why 0 instead of GetThreadHandle()? - MemWrite(&item->gpuNewContext.period, period); - MemWrite(&item->gpuNewContext.context, GetId()); - MemWrite(&item->gpuNewContext.flags, GpuContextCalibration); - MemWrite(&item->gpuNewContext.type, GpuContextType::Direct3D12); + MemWrite(&item->gpuNewContext.cpuTime, static_cast(cpuTimestamp)); + MemWrite(&item->gpuNewContext.gpuTime, static_cast(gpuTimestamp)); + MemWrite(&item->gpuNewContext.thread, static_cast(0)); // zero means the context is not associated with a specific thread + MemWrite(&item->gpuNewContext.period, static_cast(period)); + MemWrite(&item->gpuNewContext.context, static_cast(GetId())); + MemWrite(&item->gpuNewContext.flags, static_cast(GpuContextCalibration)); + MemWrite(&item->gpuNewContext.type, static_cast(GpuContextType::Direct3D12)); SubmitQueueItem(item); } @@ -225,28 +243,14 @@ namespace tracy { ZoneScopedC(Color::Red4); ZoneValue(int64_t(m_contextId)); - // collect all pending timestamps - while (m_payloadFence->GetCompletedValue() != m_activePayload) - /* busy-wait ... */; - Collect(); - m_payloadFence->Release(); - m_readbackBuffer->Release(); - m_queryHeap->Release(); - } - - void NewFrame() - { - uint32_t queryCounter = m_queryCounter.exchange(0); - m_payloadQueue.emplace(D3D12QueryPayload{ m_previousQueryCounter, queryCounter }); - m_previousQueryCounter += queryCounter; - - if (m_previousQueryCounter >= m_queryLimit) + while (m_previousCheckpoint.load() != m_queryCounter.load()) { - m_previousQueryCounter -= m_queryLimit; + Collect(); } - m_queue->Signal(m_payloadFence, ++m_activePayload); + m_readbackBuffer->Release(); + m_queryHeap->Release(); } void Name( const char* name, uint16_t len ) @@ -267,79 +271,123 @@ namespace tracy #ifdef TRACY_ON_DEMAND if (!GetProfiler().IsConnected()) { - m_queryCounter = 0; - + m_previousCheckpoint = m_queryCounter.load(); return; } #endif ZoneScopedC(Color::Red4); ZoneValue(uint64_t(m_contextId)); - // Find out what payloads are available. - const auto newestReadyPayload = m_payloadFence->GetCompletedValue(); - const auto payloadCount = m_payloadQueue.size() - (m_activePayload - newestReadyPayload); - - if (!payloadCount) + // Only one thread is allowed to collect timestamps at any given time + // but there's no need to block contending threads + if (!m_collectionMutex.try_lock()) { - return; // No payloads are available yet, exit out. + return; + } + std::unique_lock lock (m_collectionMutex, std::adopt_lock); + + // Establish a range of queries to collect: + // the range starts at the "previous checkpoint" (known to have been resolved already), + // and can extend up to the last query id emitted so far. + uint64_t begin = m_previousCheckpoint.load(); + uint64_t latestCheckpoint = m_queryCounter.load(); + + uint32_t count = RingCount(begin, latestCheckpoint); + if (count == 0) + { + return; // no pending timestamp queries + } + + // paranoid check... + if (count >= RingSize()) + { + TracyD3D12Panic("Collect: FULL! too many pending timestamp queries.", return); } - D3D12_RANGE mapRange{ 0, m_queryLimit * sizeof(uint64_t) }; - - // Map the readback buffer so we can fetch the query data from the GPU. + D3D12_RANGE mapRange{ 0, m_queryLimit * sizeof(UINT64) }; void* readbackBufferMapping = nullptr; - if (FAILED(m_readbackBuffer->Map(0, &mapRange, &readbackBufferMapping))) { - TracyD3D12Panic("Failed to map readback buffer.", return); + TracyD3D12Panic("Collect: failed to map timestamp buffer.", return); } + UINT64* timestampBuffer = static_cast(readbackBufferMapping); - auto* timestampData = static_cast(readbackBufferMapping); - - for (uint32_t i = 0; i < payloadCount; ++i) + for (uint64_t i = begin; i != latestCheckpoint; ++i) { - const auto& payload = m_payloadQueue.front(); + const uint32_t queryId = RingIndex(i); + UINT64& gpuTimestamp = timestampBuffer[queryId]; - for (uint32_t j = 0; j < payload.m_queryCount; ++j) + if (gpuTimestamp == InvalidTimestamp) { - const auto counter = (payload.m_queryIdStart + j) % m_queryLimit; - const auto timestamp = timestampData[counter]; - const auto queryId = counter; - - auto* item = Profiler::QueueSerial(); - MemWrite(&item->hdr.type, QueueType::GpuTime); - MemWrite(&item->gpuTime.gpuTime, timestamp); - MemWrite(&item->gpuTime.queryId, static_cast(queryId)); - MemWrite(&item->gpuTime.context, GetId()); - - Profiler::QueueSerialFinish(); + // drop the timestamp query if it's been in flight for too long + using Clock = std::chrono::high_resolution_clock; + auto now = Clock::now(); + auto start = m_queryRequestTime[queryId]; + auto timeout = std::chrono::duration{TRACY_D3D12_TIMESTAMP_COLLECT_TIMEOUT}; + if (now - start >= timeout) + { + TracyPlot("TracyD3D12 timeout", int64_t(0)); + TracyPlot("TracyD3D12 timeout", int64_t(1)); + TracyPlot("TracyD3D12 timeout", int64_t(0)); + m_previousCheckpoint.store(i+1, std::memory_order_relaxed); + // TODO: may need to emit a "bogus" GpuTime just to provide + // a "match" for the query ids that have been instrumented + continue; + } + // otherwise, let subsequent Collect() calls handle it + break; } - m_payloadQueue.pop(); + auto* item = Profiler::QueueSerial(); + MemWrite(&item->hdr.type, QueueType::GpuTime); + MemWrite(&item->gpuTime.gpuTime, static_cast(gpuTimestamp)); + MemWrite(&item->gpuTime.queryId, static_cast(queryId)); + MemWrite(&item->gpuTime.context, GetId()); + Profiler::QueueSerialFinish(); + + gpuTimestamp = InvalidTimestamp; + m_previousCheckpoint.store(i+1, std::memory_order_relaxed); } - m_readbackBuffer->Unmap(0, nullptr); + m_readbackBuffer->Unmap(0, &mapRange); - // Recalibrate to account for drift. RecalibrateClocks(); } private: + tracy_force_inline uint32_t RingSize() const + { + return m_queryLimit; + } + + tracy_force_inline uint32_t RingIndex(uint64_t logicalSlot) const + { + return static_cast(logicalSlot % RingSize()); + } + + tracy_force_inline uint32_t RingCount(uint64_t begin, uint64_t end) const + { + return static_cast(end - begin); + } + tracy_force_inline uint32_t NextQueryId() { - uint32_t queryCounter = m_queryCounter.fetch_add(2); - if (queryCounter >= m_queryLimit) + const uint64_t beginSlot = m_queryCounter.fetch_add(2, std::memory_order_relaxed); + if (RingCount(m_previousCheckpoint.load(), beginSlot) >= RingSize()) { ZoneScopedC(Color::Red4); ZoneValue(int64_t(m_contextId)); TracyD3D12Panic("Submitted too many GPU queries!"); - // TODO: get rid of NewFrame() and make collection "circular" - // TODO: decide what to do when "full" (collect, or return an error-id?) + // TODO: decide what to do when "full" (Collect(), or return an arbitrary error-id?) } - const uint32_t id = (m_previousQueryCounter + queryCounter) % m_queryLimit; + const uint32_t r0 = RingIndex(beginSlot); + const uint32_t r1 = RingIndex(beginSlot + 1); + const auto t = std::chrono::high_resolution_clock::now(); + m_queryRequestTime[r0] = t; + m_queryRequestTime[r1] = t; - return id; + return r0; } tracy_force_inline uint8_t GetId() const @@ -479,6 +527,7 @@ namespace tracy } #undef TracyD3D12Panic +#undef TRACY_D3D12_TIMESTAMP_COLLECT_TIMEOUT using TracyD3D12Ctx = tracy::D3D12QueueCtx*; @@ -486,7 +535,7 @@ using TracyD3D12Ctx = tracy::D3D12QueueCtx*; #define TracyD3D12Destroy(ctx) tracy::DestroyD3D12Context(ctx); #define TracyD3D12ContextName(ctx, name, size) ctx->Name(name, size); -#define TracyD3D12NewFrame(ctx) ctx->NewFrame(); +#define TracyD3D12NewFrame(ctx) ((void)(ctx)) #define TracyD3D12UnnamedZone ___tracy_gpu_d3d12_zone #define TracyD3D12SrcLocSymbol TracyConcat(__tracy_d3d12_source_location,TracyLine) From 1c2c1b5c1738fee2b9f78387cf18302f6538c840 Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Tue, 24 Mar 2026 08:12:57 -0700 Subject: [PATCH 02/67] comments and eminders --- public/tracy/TracyD3D12.hpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index 095a4a42..e46840e5 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -191,6 +191,7 @@ namespace tracy { timestampBuffer[i] = InvalidTimestamp; } + // TODO: any advantages to making this Map() persist? m_readbackBuffer->Unmap(0, &zeroRange); } @@ -244,6 +245,7 @@ namespace tracy ZoneScopedC(Color::Red4); ZoneValue(int64_t(m_contextId)); + // wait for all pending queries to be collected (busy-wait...) while (m_previousCheckpoint.load() != m_queryCounter.load()) { Collect(); @@ -345,7 +347,7 @@ namespace tracy MemWrite(&item->gpuTime.context, GetId()); Profiler::QueueSerialFinish(); - gpuTimestamp = InvalidTimestamp; + gpuTimestamp = InvalidTimestamp; // "slow" write (to a readback heap memory) m_previousCheckpoint.store(i+1, std::memory_order_relaxed); } @@ -506,6 +508,7 @@ namespace tracy MemWrite(&item->gpuZoneEnd.context, m_ctx->GetId()); Profiler::QueueSerialFinish(); + // TODO: maybe move this to Collect()? m_cmdList->ResolveQueryData(m_ctx->m_queryHeap, D3D12_QUERY_TYPE_TIMESTAMP, m_queryId, 2, m_ctx->m_readbackBuffer, m_queryId * sizeof(uint64_t)); } }; From b0b3c8a335e35f2a6f407efd0eddd545c6fe8b35 Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Tue, 24 Mar 2026 16:21:42 -0700 Subject: [PATCH 03/67] collect timestamps in pairs --- public/tracy/TracyD3D12.hpp | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index e46840e5..a3ff4c51 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -314,19 +314,20 @@ namespace tracy } UINT64* timestampBuffer = static_cast(readbackBufferMapping); - for (uint64_t i = begin; i != latestCheckpoint; ++i) + // Process timestamp queries two at a time, as (start, end) pairs + for (uint64_t i = begin; i != latestCheckpoint; i += 2) { const uint32_t queryId = RingIndex(i); - UINT64& gpuTimestamp = timestampBuffer[queryId]; - if (gpuTimestamp == InvalidTimestamp) + if (timestampBuffer[queryId+1] == InvalidTimestamp) { // drop the timestamp query if it's been in flight for too long using Clock = std::chrono::high_resolution_clock; auto now = Clock::now(); - auto start = m_queryRequestTime[queryId]; + auto start = m_queryRequestTime[queryId+1]; + auto elapsed = now - start; auto timeout = std::chrono::duration{TRACY_D3D12_TIMESTAMP_COLLECT_TIMEOUT}; - if (now - start >= timeout) + if (elapsed >= timeout) { TracyPlot("TracyD3D12 timeout", int64_t(0)); TracyPlot("TracyD3D12 timeout", int64_t(1)); @@ -340,15 +341,12 @@ namespace tracy break; } - auto* item = Profiler::QueueSerial(); - MemWrite(&item->hdr.type, QueueType::GpuTime); - MemWrite(&item->gpuTime.gpuTime, static_cast(gpuTimestamp)); - MemWrite(&item->gpuTime.queryId, static_cast(queryId)); - MemWrite(&item->gpuTime.context, GetId()); - Profiler::QueueSerialFinish(); + EmitGpuTime(timestampBuffer[queryId], queryId); + EmitGpuTime(timestampBuffer[queryId+1], queryId+1); + // "slow" write (to a readback heap memory) + timestampBuffer[queryId] = timestampBuffer[queryId+1] = InvalidTimestamp; - gpuTimestamp = InvalidTimestamp; // "slow" write (to a readback heap memory) - m_previousCheckpoint.store(i+1, std::memory_order_relaxed); + m_previousCheckpoint.store(i+2, std::memory_order_relaxed); } m_readbackBuffer->Unmap(0, &mapRange); @@ -357,6 +355,16 @@ namespace tracy } private: + tracy_force_inline void EmitGpuTime(UINT64 gpuTimestamp, uint32_t queryId) + { + auto* item = Profiler::QueueSerial(); + MemWrite(&item->hdr.type, QueueType::GpuTime); + MemWrite(&item->gpuTime.gpuTime, static_cast(gpuTimestamp)); + MemWrite(&item->gpuTime.queryId, static_cast(queryId)); + MemWrite(&item->gpuTime.context, GetId()); + Profiler::QueueSerialFinish(); + } + tracy_force_inline uint32_t RingSize() const { return m_queryLimit; From 7e0855d56b3680c1efc9c16c3d910daba0f00090 Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Tue, 24 Mar 2026 16:24:00 -0700 Subject: [PATCH 04/67] keep track of the last known emitted gpu timestamp, and use it to emit makeshift timestamps for dropped timestamps --- public/tracy/TracyD3D12.hpp | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index a3ff4c51..a6a171a1 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -81,6 +81,9 @@ namespace tracy UINT64 m_prevCalibrationTicksCPU = 0; + // Last absolute GPU counter passed to EmitGpuTime (authoritative for delta stream vs Tracy's refGpu). + UINT64 m_lastEmittedGpuTimestamp = 0; + void RecalibrateClocks() { UINT64 cpuTimestamp; @@ -329,12 +332,11 @@ namespace tracy auto timeout = std::chrono::duration{TRACY_D3D12_TIMESTAMP_COLLECT_TIMEOUT}; if (elapsed >= timeout) { - TracyPlot("TracyD3D12 timeout", int64_t(0)); - TracyPlot("TracyD3D12 timeout", int64_t(1)); - TracyPlot("TracyD3D12 timeout", int64_t(0)); - m_previousCheckpoint.store(i+1, std::memory_order_relaxed); - // TODO: may need to emit a "bogus" GpuTime just to provide - // a "match" for the query ids that have been instrumented + // emit a "bogus" GpuTime just to provide a "match" for the query ids + // that have been instrumented (this way, the UI does not freak out) + EmitGpuTime(m_lastEmittedGpuTimestamp, queryId); + EmitGpuTime(m_lastEmittedGpuTimestamp, queryId+1); + m_previousCheckpoint.store(i+2, std::memory_order_relaxed); continue; } // otherwise, let subsequent Collect() calls handle it @@ -363,6 +365,9 @@ namespace tracy MemWrite(&item->gpuTime.queryId, static_cast(queryId)); MemWrite(&item->gpuTime.context, GetId()); Profiler::QueueSerialFinish(); + // Tracy converts gpuTime to deltas by updating refGpu on each GpuTime event, + // so we must consistently track the same value here. + m_lastEmittedGpuTimestamp = gpuTimestamp; } tracy_force_inline uint32_t RingSize() const From e7a503c47f5e9af916fec3e585b88f4fcc68933b Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Tue, 24 Mar 2026 16:24:24 -0700 Subject: [PATCH 05/67] misc --- public/tracy/TracyD3D12.hpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index a6a171a1..9a33decf 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -96,6 +96,10 @@ namespace tracy int64_t cpuDeltaTicks = cpuTimestamp - m_prevCalibrationTicksCPU; if (cpuDeltaTicks > 0) { + // WARNING: technically, we should not emit a GpuCalibration event if the GPU counter + // did not move, to prevent division by a gpuDelta of zero in later on (in the server). + // In practice, GetClockCalibration() should be advancing CPU and GPU together. + static const int64_t nanosecodsPerTick = int64_t(1000000000) / GetFrequencyQpc(); int64_t cpuDeltaNS = cpuDeltaTicks * nanosecodsPerTick; // Save the device cpu timestamp, not the Tracy profiler timestamp: @@ -332,6 +336,12 @@ namespace tracy auto timeout = std::chrono::duration{TRACY_D3D12_TIMESTAMP_COLLECT_TIMEOUT}; if (elapsed >= timeout) { + ZoneScopedNC("[drop]", Color::Red4); + ZoneValue(int64_t(queryId)); + ZoneValue(int64_t(m_lastEmittedGpuTimestamp)); + TracyPlot("TracyD3D12 timeout", 0.0); + TracyPlot("TracyD3D12 timeout", std::chrono::duration{elapsed}.count()); + TracyPlot("TracyD3D12 timeout", 0.0); // emit a "bogus" GpuTime just to provide a "match" for the query ids // that have been instrumented (this way, the UI does not freak out) EmitGpuTime(m_lastEmittedGpuTimestamp, queryId); From db9e82691c4477d70abb80451f39911d89516ab0 Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Tue, 24 Mar 2026 16:38:45 -0700 Subject: [PATCH 06/67] debugging the UI freaking out --- profiler/src/profiler/TracyView_ZoneInfo.cpp | 6 ++++++ public/tracy/TracyD3D12.hpp | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/profiler/src/profiler/TracyView_ZoneInfo.cpp b/profiler/src/profiler/TracyView_ZoneInfo.cpp index 0f5ff3c1..a466bf2c 100644 --- a/profiler/src/profiler/TracyView_ZoneInfo.cpp +++ b/profiler/src/profiler/TracyView_ZoneInfo.cpp @@ -2025,6 +2025,12 @@ void View::ZoneTooltip( const GpuEvent& ev ) } } + ImGui::Separator(); + TextDisabledUnformatted( "GPU zone debug" ); + TextFocused( "GPU begin (Tracy ns):", RealToString( ev.GpuStart() ) ); + TextFocused( "GPU end (Tracy ns):", RealToString( end ) ); + TextFocused( "Begin query id:", RealToString( ev.query_id ) ); + ImGui::EndTooltip(); } diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index 9a33decf..a0777c54 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -47,7 +47,7 @@ using TracyD3D12Ctx = void*; #include #ifndef TRACY_D3D12_TIMESTAMP_COLLECT_TIMEOUT -#define TRACY_D3D12_TIMESTAMP_COLLECT_TIMEOUT 0.200f +#define TRACY_D3D12_TIMESTAMP_COLLECT_TIMEOUT 0.050f #endif #define TracyD3D12Panic(msg, ...) do { assert(false && "TracyD3D12: " msg); tracy::Profiler::LogString( tracy::MessageSourceType::Tracy, tracy::MessageSeverity::Error, tracy::Color::Red4, 0, msg ); __VA_ARGS__; } while(false); From 43b8a98091d0364ef2a1b1610dc18594f3536898 Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Fri, 27 Mar 2026 07:25:42 -0700 Subject: [PATCH 07/67] timestamp aging and race debugging --- public/client/TracyProfiler.cpp | 15 +++++++ public/tracy/TracyD3D12.hpp | 71 ++++++++++++++++++++++----------- server/TracyEvent.hpp | 16 +++++++- server/TracyWorker.cpp | 12 ++++++ 4 files changed, 89 insertions(+), 25 deletions(-) diff --git a/public/client/TracyProfiler.cpp b/public/client/TracyProfiler.cpp index 7d527517..86d6ed4f 100644 --- a/public/client/TracyProfiler.cpp +++ b/public/client/TracyProfiler.cpp @@ -84,6 +84,8 @@ #include "TracySysTrace.hpp" #include "../tracy/TracyC.h" +#include "../tracy/Tracy.hpp" + #if defined TRACY_MANUAL_LIFETIME && !defined(TRACY_DELAYED_INIT) # error "TRACY_MANUAL_LIFETIME requires enabled TRACY_DELAYED_INIT" #endif @@ -2854,6 +2856,15 @@ Profiler::DequeueStatus Profiler::DequeueContextSwitches( tracy::moodycamel::Con default: assert( false ); break; \ } +static void DebugDump(QueueGpuTime& item, int64_t dt, int64_t refGpu) +{ + static FILE* hFileDump = fopen("gpu-time-client.dump", "wb"); + fwrite(&item, sizeof(item), 1, hFileDump); + fwrite(&dt, sizeof(dt), 1, hFileDump); + fwrite(&refGpu, sizeof(refGpu), 1, hFileDump); + fflush(hFileDump); +} + Profiler::DequeueStatus Profiler::DequeueSerial() { { @@ -3002,8 +3013,12 @@ Profiler::DequeueStatus Profiler::DequeueSerial() { int64_t t = MemRead( &item->gpuTime.gpuTime ); int64_t dt = t - refGpu; + DebugDump(item->gpuTime, dt, refGpu), refGpu = t; MemWrite( &item->gpuTime.gpuTime, dt ); + TracyPlot("Tracy refGpu", int64_t(0)); + TracyPlot("Tracy refGpu", dt); + TracyPlot("Tracy refGpu", int64_t(0)); break; } case QueueType::GpuContextName: diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index a0777c54..ffb6e609 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -47,7 +47,7 @@ using TracyD3D12Ctx = void*; #include #ifndef TRACY_D3D12_TIMESTAMP_COLLECT_TIMEOUT -#define TRACY_D3D12_TIMESTAMP_COLLECT_TIMEOUT 0.050f +#define TRACY_D3D12_TIMESTAMP_COLLECT_TIMEOUT 0.020f #endif #define TracyD3D12Panic(msg, ...) do { assert(false && "TracyD3D12: " msg); tracy::Profiler::LogString( tracy::MessageSourceType::Tracy, tracy::MessageSeverity::Error, tracy::Color::Red4, 0, msg ); __VA_ARGS__; } while(false); @@ -77,7 +77,8 @@ namespace tracy uint32_t m_queryLimit = 0; - std::vector m_queryRequestTime; + using AgeClock = std::chrono::high_resolution_clock; + std::vector m_queryRequestTime; UINT64 m_prevCalibrationTicksCPU = 0; @@ -186,6 +187,8 @@ namespace tracy TracyD3D12Panic("Failed to create query readback buffer.", return); } + m_queryRequestTime.resize(m_queryLimit); + { D3D12_RANGE zeroRange{ 0, m_queryLimit * sizeof(UINT64) }; void* buffer = nullptr; @@ -197,13 +200,12 @@ namespace tracy for (uint64_t i = 0; i < m_queryLimit; ++i) { timestampBuffer[i] = InvalidTimestamp; + m_queryRequestTime[i] = AgeClock::time_point::max(); } // TODO: any advantages to making this Map() persist? m_readbackBuffer->Unmap(0, &zeroRange); } - m_queryRequestTime.resize(m_queryLimit); - float period = [queue]() { uint64_t timestampFrequency; @@ -225,6 +227,7 @@ namespace tracy { TracyD3D12Panic("Failed to get queue clock calibration.", return); } + m_lastEmittedGpuTimestamp = gpuTimestamp; // Save the device cpu timestamp, not the profiler's timestamp. m_prevCalibrationTicksCPU = cpuTimestamp; @@ -252,6 +255,9 @@ namespace tracy ZoneScopedC(Color::Red4); ZoneValue(int64_t(m_contextId)); + // TODO: could use queue->Signal() to inject a progress point in the queue + // and the immediately wait for the signal, in order to avoid busy-waiting + // wait for all pending queries to be collected (busy-wait...) while (m_previousCheckpoint.load() != m_queryCounter.load()) { @@ -329,11 +335,14 @@ namespace tracy if (timestampBuffer[queryId+1] == InvalidTimestamp) { // drop the timestamp query if it's been in flight for too long - using Clock = std::chrono::high_resolution_clock; - auto now = Clock::now(); + auto now = AgeClock::now(); auto start = m_queryRequestTime[queryId+1]; auto elapsed = now - start; - auto timeout = std::chrono::duration{TRACY_D3D12_TIMESTAMP_COLLECT_TIMEOUT}; + // "elastic" timeout, based on how "empty" the ring buffer is + // (the "emptier" it is, the more "relaxed" the timeout can be) + double fudge = RingSize() / double(count); + auto timeout = std::chrono::duration(TRACY_D3D12_TIMESTAMP_COLLECT_TIMEOUT); + //timeout *= fudge; if (elapsed >= timeout) { ZoneScopedNC("[drop]", Color::Red4); @@ -342,28 +351,35 @@ namespace tracy TracyPlot("TracyD3D12 timeout", 0.0); TracyPlot("TracyD3D12 timeout", std::chrono::duration{elapsed}.count()); TracyPlot("TracyD3D12 timeout", 0.0); + m_queryRequestTime[queryId+0] = AgeClock::time_point::max(); + m_queryRequestTime[queryId+1] = AgeClock::time_point::max(); // emit a "bogus" GpuTime just to provide a "match" for the query ids // that have been instrumented (this way, the UI does not freak out) - EmitGpuTime(m_lastEmittedGpuTimestamp, queryId); - EmitGpuTime(m_lastEmittedGpuTimestamp, queryId+1); - m_previousCheckpoint.store(i+2, std::memory_order_relaxed); + EmitGpuTime(m_lastEmittedGpuTimestamp+1000, queryId+0); + EmitGpuTime(m_lastEmittedGpuTimestamp+1000, queryId+1); + m_previousCheckpoint.store(i+2); continue; } // otherwise, let subsequent Collect() calls handle it break; } - EmitGpuTime(timestampBuffer[queryId], queryId); + EmitGpuTime(timestampBuffer[queryId+0], queryId+0); EmitGpuTime(timestampBuffer[queryId+1], queryId+1); - // "slow" write (to a readback heap memory) - timestampBuffer[queryId] = timestampBuffer[queryId+1] = InvalidTimestamp; - m_previousCheckpoint.store(i+2, std::memory_order_relaxed); + // "slow" write (to a readback heap memory) + timestampBuffer[queryId+0] = InvalidTimestamp; + timestampBuffer[queryId+1] = InvalidTimestamp; + + m_queryRequestTime[queryId+0] = AgeClock::time_point::max(); + m_queryRequestTime[queryId+1] = AgeClock::time_point::max(); + + m_previousCheckpoint.store(i+2); } m_readbackBuffer->Unmap(0, &mapRange); - RecalibrateClocks(); + //RecalibrateClocks(); } private: @@ -397,8 +413,8 @@ namespace tracy tracy_force_inline uint32_t NextQueryId() { - const uint64_t beginSlot = m_queryCounter.fetch_add(2, std::memory_order_relaxed); - if (RingCount(m_previousCheckpoint.load(), beginSlot) >= RingSize()) + const uint64_t seqIdx = m_queryCounter.fetch_add(2, std::memory_order_relaxed); + if (RingCount(m_previousCheckpoint.load(), seqIdx) >= RingSize()) { ZoneScopedC(Color::Red4); ZoneValue(int64_t(m_contextId)); @@ -406,13 +422,22 @@ namespace tracy // TODO: decide what to do when "full" (Collect(), or return an arbitrary error-id?) } - const uint32_t r0 = RingIndex(beginSlot); - const uint32_t r1 = RingIndex(beginSlot + 1); - const auto t = std::chrono::high_resolution_clock::now(); - m_queryRequestTime[r0] = t; - m_queryRequestTime[r1] = t; + const uint32_t queryId = RingIndex(seqIdx); - return r0; + if (m_queryRequestTime[queryId+0] != AgeClock::time_point::max()) { + TracyD3D12Panic("Unexpected Request Time! (+0)"); + } + if (m_queryRequestTime[queryId+1] != AgeClock::time_point::max()) { + TracyD3D12Panic("Unexpected Request Time! (+1)"); + } + + const auto t = AgeClock::now(); + m_queryRequestTime[queryId+0] = t; + m_queryRequestTime[queryId+1] = t; + + TracyPlot("TracyD3D12 QueryID", int64_t(queryId)); + + return queryId; } tracy_force_inline uint8_t GetId() const diff --git a/server/TracyEvent.hpp b/server/TracyEvent.hpp index 075479a5..97e0675d 100644 --- a/server/TracyEvent.hpp +++ b/server/TracyEvent.hpp @@ -380,9 +380,21 @@ struct GpuEvent tracy_force_inline int64_t CpuEnd() const { return int64_t( _cpuEnd_thread ) >> 16; } tracy_force_inline void SetCpuEnd( int64_t cpuEnd ) { assert( cpuEnd < (int64_t)( 1ull << 47 ) ); memcpy( ((char*)&_cpuEnd_thread)+2, &cpuEnd, 4 ); memcpy( ((char*)&_cpuEnd_thread)+6, ((char*)&cpuEnd)+4, 2 ); } tracy_force_inline int64_t GpuStart() const { return int64_t( _gpuStart_child1 ) >> 16; } - tracy_force_inline void SetGpuStart( int64_t gpuStart ) { /*assert( gpuStart < (int64_t)( 1ull << 47 ) );*/ memcpy( ((char*)&_gpuStart_child1)+2, &gpuStart, 4 ); memcpy( ((char*)&_gpuStart_child1)+6, ((char*)&gpuStart)+4, 2 ); } + tracy_force_inline void SetGpuStart( int64_t gpuStart ) { + // https://github.com/wolfpld/tracy/commit/c99dc5c43141e3359b10fb7e25c1e2e9ab91b82f + /*assert( gpuStart < (int64_t)( 1ull << 47 ) );*/ + memcpy( ((char*)&_gpuStart_child1)+2, &gpuStart, 4 ); + memcpy( ((char*)&_gpuStart_child1)+6, ((char*)&gpuStart)+4, 2 ); + } tracy_force_inline int64_t GpuEnd() const { return int64_t( _gpuEnd_child2 ) >> 16; } - tracy_force_inline void SetGpuEnd( int64_t gpuEnd ) { assert( gpuEnd < (int64_t)( 1ull << 47 ) ); memcpy( ((char*)&_gpuEnd_child2)+2, &gpuEnd, 4 ); memcpy( ((char*)&_gpuEnd_child2)+6, ((char*)&gpuEnd)+4, 2 ); } + tracy_force_inline void SetGpuEnd( int64_t gpuEnd ) { + //assert( gpuEnd < (int64_t)( 1ull << 47 ) ); + if (gpuEnd >= (int64_t)(1ull << 47)) { + fprintf(stderr, "ERROR: unexpected GpuEnd timestamp: %llu\n", gpuEnd); + } + memcpy( ((char*)&_gpuEnd_child2)+2, &gpuEnd, 4 ); + memcpy( ((char*)&_gpuEnd_child2)+6, ((char*)&gpuEnd)+4, 2 ); + } tracy_force_inline int16_t SrcLoc() const { return int16_t( _cpuStart_srcloc & 0xFFFF ); } tracy_force_inline void SetSrcLoc( int16_t srcloc ) { memcpy( &_cpuStart_srcloc, &srcloc, 2 ); } tracy_force_inline uint16_t Thread() const { return uint16_t( _cpuEnd_thread & 0xFFFF ); } diff --git a/server/TracyWorker.cpp b/server/TracyWorker.cpp index 21bd21ba..e458acdd 100644 --- a/server/TracyWorker.cpp +++ b/server/TracyWorker.cpp @@ -5941,12 +5941,23 @@ void Worker::ProcessGpuZoneEnd( const QueueGpuZoneEnd& ev, bool serial ) if( m_data.lastTime < time ) m_data.lastTime = time; } +static void DebugDump(const QueueGpuTime& item, const int64_t refGpu) +{ + static FILE* hFileDump = fopen("gpu-time-server.dump", "wb"); + fwrite(&item, sizeof(item), 1, hFileDump); + fwrite(&refGpu, sizeof(refGpu), 1, hFileDump); + fflush(hFileDump); +} + void Worker::ProcessGpuTime( const QueueGpuTime& ev ) { auto ctx = m_gpuCtxMap[ev.context]; assert( ctx ); + DebugDump(ev, m_refTimeGpu); + int64_t tgpu = RefTime( m_refTimeGpu, ev.gpuTime ); + /* if( tgpu < ctx->lastGpuTime - ( 1u << 31 ) ) { if( ctx->overflow == 0 ) @@ -5960,6 +5971,7 @@ void Worker::ProcessGpuTime( const QueueGpuTime& ev ) { tgpu += ctx->overflow * ctx->overflowMul; } + */ int64_t gpuTime; if( !ctx->hasPeriod ) From d8aaa91379fb6bdcac97393803b26fb047e20e55 Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Fri, 27 Mar 2026 14:08:27 -0700 Subject: [PATCH 08/67] disabling debug dump --- public/client/TracyProfiler.cpp | 8 ++++---- server/TracyWorker.cpp | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/public/client/TracyProfiler.cpp b/public/client/TracyProfiler.cpp index 86d6ed4f..0ba118da 100644 --- a/public/client/TracyProfiler.cpp +++ b/public/client/TracyProfiler.cpp @@ -2863,6 +2863,9 @@ static void DebugDump(QueueGpuTime& item, int64_t dt, int64_t refGpu) fwrite(&dt, sizeof(dt), 1, hFileDump); fwrite(&refGpu, sizeof(refGpu), 1, hFileDump); fflush(hFileDump); + TracyPlot("Tracy refGpu", int64_t(0)); + TracyPlot("Tracy refGpu", dt); + TracyPlot("Tracy refGpu", int64_t(0)); } Profiler::DequeueStatus Profiler::DequeueSerial() @@ -3013,12 +3016,9 @@ Profiler::DequeueStatus Profiler::DequeueSerial() { int64_t t = MemRead( &item->gpuTime.gpuTime ); int64_t dt = t - refGpu; - DebugDump(item->gpuTime, dt, refGpu), + //DebugDump(item->gpuTime, dt, refGpu), refGpu = t; MemWrite( &item->gpuTime.gpuTime, dt ); - TracyPlot("Tracy refGpu", int64_t(0)); - TracyPlot("Tracy refGpu", dt); - TracyPlot("Tracy refGpu", int64_t(0)); break; } case QueueType::GpuContextName: diff --git a/server/TracyWorker.cpp b/server/TracyWorker.cpp index e458acdd..4b25bb3e 100644 --- a/server/TracyWorker.cpp +++ b/server/TracyWorker.cpp @@ -5954,7 +5954,7 @@ void Worker::ProcessGpuTime( const QueueGpuTime& ev ) auto ctx = m_gpuCtxMap[ev.context]; assert( ctx ); - DebugDump(ev, m_refTimeGpu); + //DebugDump(ev, m_refTimeGpu); int64_t tgpu = RefTime( m_refTimeGpu, ev.gpuTime ); /* From 4f44b954abad0a6cdfbeb7850754b1463c0c2e24 Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Fri, 27 Mar 2026 14:09:39 -0700 Subject: [PATCH 09/67] reveting overflow suppresion --- server/TracyWorker.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/server/TracyWorker.cpp b/server/TracyWorker.cpp index 4b25bb3e..b7055d22 100644 --- a/server/TracyWorker.cpp +++ b/server/TracyWorker.cpp @@ -5957,7 +5957,6 @@ void Worker::ProcessGpuTime( const QueueGpuTime& ev ) //DebugDump(ev, m_refTimeGpu); int64_t tgpu = RefTime( m_refTimeGpu, ev.gpuTime ); - /* if( tgpu < ctx->lastGpuTime - ( 1u << 31 ) ) { if( ctx->overflow == 0 ) @@ -5971,7 +5970,6 @@ void Worker::ProcessGpuTime( const QueueGpuTime& ev ) { tgpu += ctx->overflow * ctx->overflowMul; } - */ int64_t gpuTime; if( !ctx->hasPeriod ) From 65cb45ea971cb52cb9f08b6956e024b4ebd72462 Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Fri, 27 Mar 2026 14:11:13 -0700 Subject: [PATCH 10/67] re-enable range assert --- server/TracyEvent.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/TracyEvent.hpp b/server/TracyEvent.hpp index 97e0675d..6c8bb3d0 100644 --- a/server/TracyEvent.hpp +++ b/server/TracyEvent.hpp @@ -388,10 +388,10 @@ struct GpuEvent } tracy_force_inline int64_t GpuEnd() const { return int64_t( _gpuEnd_child2 ) >> 16; } tracy_force_inline void SetGpuEnd( int64_t gpuEnd ) { - //assert( gpuEnd < (int64_t)( 1ull << 47 ) ); if (gpuEnd >= (int64_t)(1ull << 47)) { fprintf(stderr, "ERROR: unexpected GpuEnd timestamp: %llu\n", gpuEnd); } + assert( gpuEnd < (int64_t)( 1ull << 47 ) ); memcpy( ((char*)&_gpuEnd_child2)+2, &gpuEnd, 4 ); memcpy( ((char*)&_gpuEnd_child2)+6, ((char*)&gpuEnd)+4, 2 ); } From e5cdb8b36157d68cc400fdc4fa40df350f873f80 Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Fri, 27 Mar 2026 14:11:47 -0700 Subject: [PATCH 11/67] persistent map, debug toggle, etc --- public/tracy/TracyD3D12.hpp | 88 +++++++++++++++++++++++++------------ 1 file changed, 60 insertions(+), 28 deletions(-) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index ffb6e609..502defa0 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -47,11 +47,20 @@ using TracyD3D12Ctx = void*; #include #ifndef TRACY_D3D12_TIMESTAMP_COLLECT_TIMEOUT -#define TRACY_D3D12_TIMESTAMP_COLLECT_TIMEOUT 0.020f +#define TRACY_D3D12_TIMESTAMP_COLLECT_TIMEOUT 0.005f #endif +#define TRACY_D3D12_DEBUG (1) +#define TRACY_D3D12_PERSISTENT_TIMESTAMP_BUFFER (1) + #define TracyD3D12Panic(msg, ...) do { assert(false && "TracyD3D12: " msg); tracy::Profiler::LogString( tracy::MessageSourceType::Tracy, tracy::MessageSeverity::Error, tracy::Color::Red4, 0, msg ); __VA_ARGS__; } while(false); +#if TRACY_D3D12_DEBUG +#define TracyD3D12Debug(...) __VA_ARGS__ +#else +#define TracyD3D12Debug(...) +#endif + namespace tracy { @@ -71,6 +80,10 @@ namespace tracy ID3D12QueryHeap* m_queryHeap = nullptr; ID3D12Resource* m_readbackBuffer = nullptr; +#if TRACY_D3D12_PERSISTENT_TIMESTAMP_BUFFER + UINT64* m_timestampBuffer = nullptr; +#endif + using atomic_counter = std::atomic; atomic_counter m_queryCounter = 0; atomic_counter m_previousCheckpoint = 0; @@ -203,7 +216,11 @@ namespace tracy m_queryRequestTime[i] = AgeClock::time_point::max(); } // TODO: any advantages to making this Map() persist? +#if TRACY_D3D12_PERSISTENT_TIMESTAMP_BUFFER + m_timestampBuffer = timestampBuffer; +#else m_readbackBuffer->Unmap(0, &zeroRange); +#endif } float period = [queue]() @@ -264,6 +281,10 @@ namespace tracy Collect(); } +#if TRACY_D3D12_PERSISTENT_TIMESTAMP_BUFFER + D3D12_RANGE fullRange { 0, m_queryLimit * sizeof(UINT64) }; + m_readbackBuffer->Unmap(0, &fullRange); +#endif m_readbackBuffer->Release(); m_queryHeap->Release(); } @@ -319,6 +340,9 @@ namespace tracy TracyD3D12Panic("Collect: FULL! too many pending timestamp queries.", return); } +#if TRACY_D3D12_PERSISTENT_TIMESTAMP_BUFFER + UINT64* timestampBuffer = m_timestampBuffer; +#else D3D12_RANGE mapRange{ 0, m_queryLimit * sizeof(UINT64) }; void* readbackBufferMapping = nullptr; if (FAILED(m_readbackBuffer->Map(0, &mapRange, &readbackBufferMapping))) @@ -326,15 +350,18 @@ namespace tracy TracyD3D12Panic("Collect: failed to map timestamp buffer.", return); } UINT64* timestampBuffer = static_cast(readbackBufferMapping); +#endif // Process timestamp queries two at a time, as (start, end) pairs for (uint64_t i = begin; i != latestCheckpoint; i += 2) { const uint32_t queryId = RingIndex(i); + UINT64 gpuZoneBeginTimestamp = timestampBuffer[queryId]; + UINT64 gpuZoneEndTimestamp = timestampBuffer[queryId+1]; if (timestampBuffer[queryId+1] == InvalidTimestamp) { - // drop the timestamp query if it's been in flight for too long + // determine for how long the query has been in flight auto now = AgeClock::now(); auto start = m_queryRequestTime[queryId+1]; auto elapsed = now - start; @@ -343,41 +370,44 @@ namespace tracy double fudge = RingSize() / double(count); auto timeout = std::chrono::duration(TRACY_D3D12_TIMESTAMP_COLLECT_TIMEOUT); //timeout *= fudge; - if (elapsed >= timeout) - { + // if it's a recent query, let subsequent Collect() calls handle it + if (elapsed < timeout) + break; + // otherwise, give up on waiting and drop it + TracyD3D12Debug( ZoneScopedNC("[drop]", Color::Red4); ZoneValue(int64_t(queryId)); ZoneValue(int64_t(m_lastEmittedGpuTimestamp)); TracyPlot("TracyD3D12 timeout", 0.0); TracyPlot("TracyD3D12 timeout", std::chrono::duration{elapsed}.count()); TracyPlot("TracyD3D12 timeout", 0.0); - m_queryRequestTime[queryId+0] = AgeClock::time_point::max(); - m_queryRequestTime[queryId+1] = AgeClock::time_point::max(); - // emit a "bogus" GpuTime just to provide a "match" for the query ids - // that have been instrumented (this way, the UI does not freak out) - EmitGpuTime(m_lastEmittedGpuTimestamp+1000, queryId+0); - EmitGpuTime(m_lastEmittedGpuTimestamp+1000, queryId+1); - m_previousCheckpoint.store(i+2); - continue; - } - // otherwise, let subsequent Collect() calls handle it - break; + ); + // emit a "bogus" GpuTime just to provide a "match" for the query ids + // that have been instrumented (this way, the UI does not freak out) + gpuZoneBeginTimestamp = m_lastEmittedGpuTimestamp; + gpuZoneEndTimestamp = m_lastEmittedGpuTimestamp+1; } - EmitGpuTime(timestampBuffer[queryId+0], queryId+0); - EmitGpuTime(timestampBuffer[queryId+1], queryId+1); + EmitGpuTime(gpuZoneBeginTimestamp, queryId); + EmitGpuTime(gpuZoneEndTimestamp, queryId+1); - // "slow" write (to a readback heap memory) + // "slow" write (to timestampBuffer, backed by "readback" heap memory) timestampBuffer[queryId+0] = InvalidTimestamp; timestampBuffer[queryId+1] = InvalidTimestamp; - m_queryRequestTime[queryId+0] = AgeClock::time_point::max(); m_queryRequestTime[queryId+1] = AgeClock::time_point::max(); + TracyD3D12Debug( + TracyFreeN(reinterpret_cast(uintptr_t(queryId+0)), "TracyD3D12 Query"); + TracyFreeN(reinterpret_cast(uintptr_t(queryId+1)), "TracyD3D12 Query"); + ); + m_previousCheckpoint.store(i+2); } +#if !TRACY_D3D12_PERSISTENT_TIMESTAMP_BUFFER m_readbackBuffer->Unmap(0, &mapRange); +#endif //RecalibrateClocks(); } @@ -424,18 +454,18 @@ namespace tracy const uint32_t queryId = RingIndex(seqIdx); - if (m_queryRequestTime[queryId+0] != AgeClock::time_point::max()) { - TracyD3D12Panic("Unexpected Request Time! (+0)"); - } - if (m_queryRequestTime[queryId+1] != AgeClock::time_point::max()) { - TracyD3D12Panic("Unexpected Request Time! (+1)"); - } - const auto t = AgeClock::now(); m_queryRequestTime[queryId+0] = t; m_queryRequestTime[queryId+1] = t; +#if TRACY_D3D12_PERSISTENT_TIMESTAMP_BUFFER + m_timestampBuffer[queryId+0] = InvalidTimestamp; + m_timestampBuffer[queryId+1] = InvalidTimestamp; +#endif - TracyPlot("TracyD3D12 QueryID", int64_t(queryId)); + TracyD3D12Debug( + TracyAllocN(reinterpret_cast(uintptr_t(queryId+0)), 1, "TracyD3D12 Query"); + TracyAllocN(reinterpret_cast(uintptr_t(queryId+1)), 1, "TracyD3D12 Query"); + ); return queryId; } @@ -577,8 +607,10 @@ namespace tracy } -#undef TracyD3D12Panic +#undef TRACY_D3D12_DEBUG +#undef TRACY_D3D12_PERSISTENT_TIMESTAMP_BUFFER #undef TRACY_D3D12_TIMESTAMP_COLLECT_TIMEOUT +#undef TracyD3D12Panic using TracyD3D12Ctx = tracy::D3D12QueueCtx*; From 37628ed305693b573acf8031774a011940a65888 Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Fri, 27 Mar 2026 16:46:55 -0700 Subject: [PATCH 12/67] explaining the race condition --- public/tracy/TracyD3D12.hpp | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index 502defa0..9d830346 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -274,6 +274,7 @@ namespace tracy // TODO: could use queue->Signal() to inject a progress point in the queue // and the immediately wait for the signal, in order to avoid busy-waiting + // (need to create an ID3D12Fence and associate an Event object to it) // wait for all pending queries to be collected (busy-wait...) while (m_previousCheckpoint.load() != m_queryCounter.load()) @@ -375,7 +376,7 @@ namespace tracy break; // otherwise, give up on waiting and drop it TracyD3D12Debug( - ZoneScopedNC("[drop]", Color::Red4); + ZoneScopedNC("tracy::D3D12QueueCtx::Collect::[drop]", Color::Red4); ZoneValue(int64_t(queryId)); ZoneValue(int64_t(m_lastEmittedGpuTimestamp)); TracyPlot("TracyD3D12 timeout", 0.0); @@ -391,7 +392,13 @@ namespace tracy EmitGpuTime(gpuZoneBeginTimestamp, queryId); EmitGpuTime(gpuZoneEndTimestamp, queryId+1); - // "slow" write (to timestampBuffer, backed by "readback" heap memory) + // Reset timestamps to the InvalidTimestamp sentinel value to establish + // a pre-condition for reusing the slots. + // ("slow" write since the timestamp buffer is in "readback" heap memory) + // WARN: this does not eliminate the potential race with the GPU! + // If the query is legit (i.e., it has been submitted to the GPU queue + // for execution), it will resolve eventually, overwriting the sentinel + // value written below in the slot. timestampBuffer[queryId+0] = InvalidTimestamp; timestampBuffer[queryId+1] = InvalidTimestamp; m_queryRequestTime[queryId+0] = AgeClock::time_point::max(); @@ -402,6 +409,8 @@ namespace tracy TracyFreeN(reinterpret_cast(uintptr_t(queryId+1)), "TracyD3D12 Query"); ); + // as soon as the checkpoint is incremented, + // NextQueryId() is free to reuse the query ids m_previousCheckpoint.store(i+2); } @@ -443,6 +452,19 @@ namespace tracy tracy_force_inline uint32_t NextQueryId() { + // WARN: the moment m_queryCounter is incremented, Collect() will have + // instant visibility of the query pair and will attempt to collect it! + // Under normal circumstances, this is fine: the corresponding id slots + // would have been rset to the InvalidTimestamp sentinel value already, + // right after the timestamp slots being collected. + // However, if Collect() decides to timeout-drop a timestamp query that + // was indeed submitted to the GPU queue for execution, the dropped query + // will eventually be resolved by the GPU and will arrive "late" at its + // corrsponding query slot (the GPU will write asynchronously to it). + // The "new" query pair here will have matching slots with the late query + // pair of "before". Given that Collect() will "sense" the new query pair + // immediately upon m_queryCounter being incremented, the late timestamp + // query may be collected as if it was the timestamp of the new query... const uint64_t seqIdx = m_queryCounter.fetch_add(2, std::memory_order_relaxed); if (RingCount(m_previousCheckpoint.load(), seqIdx) >= RingSize()) { @@ -458,6 +480,11 @@ namespace tracy m_queryRequestTime[queryId+0] = t; m_queryRequestTime[queryId+1] = t; #if TRACY_D3D12_PERSISTENT_TIMESTAMP_BUFFER + // WARN: resetting the timestamps here helps with the timestamp "race", + // but it's palliative... The GPU may finish the "late" query after the + // sentinel assignment below, and Collect() will not know whether the + // next valid timestamp it sees in the slot belongs to the late query + // or to this new query... m_timestampBuffer[queryId+0] = InvalidTimestamp; m_timestampBuffer[queryId+1] = InvalidTimestamp; #endif From cd1a2c7c6ed08cf7a5ee4ce8d0036f0c260867ce Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Sat, 28 Mar 2026 12:58:16 -0700 Subject: [PATCH 13/67] re-enabbling post-collect calibration; rephrasing comments about race condition --- public/tracy/TracyD3D12.hpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index 9d830346..8d324cf6 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -418,7 +418,7 @@ namespace tracy m_readbackBuffer->Unmap(0, &mapRange); #endif - //RecalibrateClocks(); + RecalibrateClocks(); } private: @@ -471,7 +471,9 @@ namespace tracy ZoneScopedC(Color::Red4); ZoneValue(int64_t(m_contextId)); TracyD3D12Panic("Submitted too many GPU queries!"); - // TODO: decide what to do when "full" (Collect(), or return an arbitrary error-id?) + // TODO: decide what to do when "full" + // (maybe a loop with a few iterations attempting to Collect() queries + // and if it fails, return some "invalid query" id) } const uint32_t queryId = RingIndex(seqIdx); @@ -480,11 +482,11 @@ namespace tracy m_queryRequestTime[queryId+0] = t; m_queryRequestTime[queryId+1] = t; #if TRACY_D3D12_PERSISTENT_TIMESTAMP_BUFFER - // WARN: resetting the timestamps here helps with the timestamp "race", - // but it's palliative... The GPU may finish the "late" query after the - // sentinel assignment below, and Collect() will not know whether the - // next valid timestamp it sees in the slot belongs to the late query - // or to this new query... + // WARN: resetting the timestamps here reduces the window of the race + // condition, but does not eliminate it. The GPU may still finish the + // "late" query after assigning the sentinel value below... Collect() + // will not know whether the next valid timestamp it sees in the slot + // belongs to the late query or to this new query... m_timestampBuffer[queryId+0] = InvalidTimestamp; m_timestampBuffer[queryId+1] = InvalidTimestamp; #endif From 4f62a115fab90734e45880ef840316035d638a8a Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Sat, 28 Mar 2026 16:21:59 -0700 Subject: [PATCH 14/67] adopting a "collect window" scheme to avoid race conditions and heavy synchronization --- public/tracy/TracyD3D12.hpp | 169 ++++++++++++++++++------------------ 1 file changed, 86 insertions(+), 83 deletions(-) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index 8d324cf6..2ea24b21 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -69,8 +69,6 @@ namespace tracy { friend class D3D12ZoneScope; - static constexpr UINT64 InvalidTimestamp = 0; // arbitrary choice (afaik, it's technically a valid timestamp value) - uint8_t m_contextId = 255; // TODO: apparently, 255 means "invalid id"; is this documented somewhere? std::mutex m_collectionMutex; @@ -90,8 +88,16 @@ namespace tracy uint32_t m_queryLimit = 0; - using AgeClock = std::chrono::high_resolution_clock; - std::vector m_queryRequestTime; + struct CollectWindow + { + using AgeClock = std::chrono::high_resolution_clock; + static constexpr uint64_t capacity = 4 * 1024; + uint64_t rangeBegin = 0; + uint64_t rangeEnd = 0; + AgeClock::time_point ageStart = AgeClock::time_point::max(); + UINT64 latestKnownGpuTimestamp = 0; + UINT64 shadowBuffer[capacity] = {}; + } m_window; UINT64 m_prevCalibrationTicksCPU = 0; @@ -200,8 +206,6 @@ namespace tracy TracyD3D12Panic("Failed to create query readback buffer.", return); } - m_queryRequestTime.resize(m_queryLimit); - { D3D12_RANGE zeroRange{ 0, m_queryLimit * sizeof(UINT64) }; void* buffer = nullptr; @@ -212,8 +216,7 @@ namespace tracy UINT64* timestampBuffer = static_cast(buffer); for (uint64_t i = 0; i < m_queryLimit; ++i) { - timestampBuffer[i] = InvalidTimestamp; - m_queryRequestTime[i] = AgeClock::time_point::max(); + timestampBuffer[i] = 0; } // TODO: any advantages to making this Map() persist? #if TRACY_D3D12_PERSISTENT_TIMESTAMP_BUFFER @@ -246,6 +249,10 @@ namespace tracy } m_lastEmittedGpuTimestamp = gpuTimestamp; + // initialize CollectWindow + UpdateLatestKnownGpuTimestamp(gpuTimestamp); + AdvanceCollectWindow(); + // Save the device cpu timestamp, not the profiler's timestamp. m_prevCalibrationTicksCPU = cpuTimestamp; @@ -277,6 +284,8 @@ namespace tracy // (need to create an ID3D12Fence and associate an Event object to it) // wait for all pending queries to be collected (busy-wait...) + // TODO: need to relax the while condition, since now m_previousCheckpoint + // advances in batches of the window size, but not the query counter... while (m_previousCheckpoint.load() != m_queryCounter.load()) { Collect(); @@ -308,6 +317,7 @@ namespace tracy #ifdef TRACY_ON_DEMAND if (!GetProfiler().IsConnected()) { + // TODO: reset the collect window here m_previousCheckpoint = m_queryCounter.load(); return; } @@ -318,29 +328,24 @@ namespace tracy // Only one thread is allowed to collect timestamps at any given time // but there's no need to block contending threads if (!m_collectionMutex.try_lock()) - { return; - } std::unique_lock lock (m_collectionMutex, std::adopt_lock); - // Establish a range of queries to collect: - // the range starts at the "previous checkpoint" (known to have been resolved already), - // and can extend up to the last query id emitted so far. - uint64_t begin = m_previousCheckpoint.load(); - uint64_t latestCheckpoint = m_queryCounter.load(); + auto now = CollectWindow::AgeClock::now(); - uint32_t count = RingCount(begin, latestCheckpoint); - if (count == 0) + uint64_t windowSize = m_window.rangeEnd - m_window.rangeBegin; + if (windowSize < m_window.capacity) { - return; // no pending timestamp queries - } - - // paranoid check... - if (count >= RingSize()) - { - TracyD3D12Panic("Collect: FULL! too many pending timestamp queries.", return); + uint64_t latestIssued = m_queryCounter.load(); + uint64_t trail = latestIssued - m_window.rangeBegin; + windowSize = std::min(trail, m_window.capacity); + m_window.rangeEnd = m_window.rangeBegin + windowSize; + m_window.ageStart = now; } + if (windowSize == 0) + return; + #if TRACY_D3D12_PERSISTENT_TIMESTAMP_BUFFER UINT64* timestampBuffer = m_timestampBuffer; #else @@ -353,65 +358,54 @@ namespace tracy UINT64* timestampBuffer = static_cast(readbackBufferMapping); #endif - // Process timestamp queries two at a time, as (start, end) pairs - for (uint64_t i = begin; i != latestCheckpoint; i += 2) + auto timeout = std::chrono::duration(TRACY_D3D12_TIMESTAMP_COLLECT_TIMEOUT); + auto windowAge = now - m_window.ageStart; + + // iterate over every slot (pair) in the window + int unresolved = 0; + for (uint64_t i = m_window.rangeBegin; i != m_window.rangeEnd; i += 2) { - const uint32_t queryId = RingIndex(i); + uint32_t queryId = RingIndex(i); + uint32_t shadowIdx = static_cast(i - m_window.rangeBegin); + UINT64 gpuZoneBeginTimestamp = timestampBuffer[queryId]; UINT64 gpuZoneEndTimestamp = timestampBuffer[queryId+1]; - - if (timestampBuffer[queryId+1] == InvalidTimestamp) + int64_t diff = static_cast(gpuZoneEndTimestamp - m_window.shadowBuffer[shadowIdx+1]); + if (diff == 0) + continue; + if (diff < 0) { - // determine for how long the query has been in flight - auto now = AgeClock::now(); - auto start = m_queryRequestTime[queryId+1]; - auto elapsed = now - start; - // "elastic" timeout, based on how "empty" the ring buffer is - // (the "emptier" it is, the more "relaxed" the timeout can be) - double fudge = RingSize() / double(count); - auto timeout = std::chrono::duration(TRACY_D3D12_TIMESTAMP_COLLECT_TIMEOUT); - //timeout *= fudge; - // if it's a recent query, let subsequent Collect() calls handle it - if (elapsed < timeout) - break; - // otherwise, give up on waiting and drop it + ++unresolved; + if (windowAge < timeout) + continue; + // timed-out: drop it TracyD3D12Debug( ZoneScopedNC("tracy::D3D12QueueCtx::Collect::[drop]", Color::Red4); ZoneValue(int64_t(queryId)); - ZoneValue(int64_t(m_lastEmittedGpuTimestamp)); - TracyPlot("TracyD3D12 timeout", 0.0); - TracyPlot("TracyD3D12 timeout", std::chrono::duration{elapsed}.count()); - TracyPlot("TracyD3D12 timeout", 0.0); ); - // emit a "bogus" GpuTime just to provide a "match" for the query ids - // that have been instrumented (this way, the UI does not freak out) - gpuZoneBeginTimestamp = m_lastEmittedGpuTimestamp; - gpuZoneEndTimestamp = m_lastEmittedGpuTimestamp+1; + // must still emit a bogus GpuTime to avoid problems with + // the server/profiler internal tracking/matching logic + gpuZoneBeginTimestamp = m_window.latestKnownGpuTimestamp; + gpuZoneEndTimestamp = gpuZoneBeginTimestamp + 1; } EmitGpuTime(gpuZoneBeginTimestamp, queryId); EmitGpuTime(gpuZoneEndTimestamp, queryId+1); + m_window.shadowBuffer[shadowIdx] = gpuZoneBeginTimestamp; + m_window.shadowBuffer[shadowIdx+1] = gpuZoneEndTimestamp; + // TODO: maybe only update timestamp for "resolved" queries + UpdateLatestKnownGpuTimestamp(gpuZoneEndTimestamp); + } - // Reset timestamps to the InvalidTimestamp sentinel value to establish - // a pre-condition for reusing the slots. - // ("slow" write since the timestamp buffer is in "readback" heap memory) - // WARN: this does not eliminate the potential race with the GPU! - // If the query is legit (i.e., it has been submitted to the GPU queue - // for execution), it will resolve eventually, overwriting the sentinel - // value written below in the slot. - timestampBuffer[queryId+0] = InvalidTimestamp; - timestampBuffer[queryId+1] = InvalidTimestamp; - m_queryRequestTime[queryId+0] = AgeClock::time_point::max(); - m_queryRequestTime[queryId+1] = AgeClock::time_point::max(); - - TracyD3D12Debug( - TracyFreeN(reinterpret_cast(uintptr_t(queryId+0)), "TracyD3D12 Query"); - TracyFreeN(reinterpret_cast(uintptr_t(queryId+1)), "TracyD3D12 Query"); - ); - - // as soon as the checkpoint is incremented, - // NextQueryId() is free to reuse the query ids - m_previousCheckpoint.store(i+2); + if (windowSize == m_window.capacity) + { + if ((unresolved == 0) || (windowAge >= timeout)) + { + AdvanceCollectWindow(); + // Publish new checkpoint — NextQueryId can now reuse these slots. + m_previousCheckpoint.store(m_window.rangeBegin); + // TODO: start collecting the next window immediately... + } } #if !TRACY_D3D12_PERSISTENT_TIMESTAMP_BUFFER @@ -422,6 +416,25 @@ namespace tracy } private: + // Update the window's latest known GPU timestamp only if ts is strictly "ahead" + // of the current value. Uses a signed-difference comparison to be robust against + // counter wrap-around (though UINT64 at any conceivable clock rate won't wrap + // within the lifetime of the process). + tracy_force_inline void UpdateLatestKnownGpuTimestamp(UINT64 timestamp) + { + int64_t diff = static_cast(timestamp - m_window.latestKnownGpuTimestamp); + if (diff > 0) + m_window.latestKnownGpuTimestamp = timestamp; + } + + void AdvanceCollectWindow() + { + m_window.rangeBegin = m_window.rangeEnd; + m_window.ageStart = CollectWindow::AgeClock::time_point::max(); + for (auto& shadow : m_window.shadowBuffer) + shadow = m_window.latestKnownGpuTimestamp; + } + tracy_force_inline void EmitGpuTime(UINT64 gpuTimestamp, uint32_t queryId) { auto* item = Profiler::QueueSerial(); @@ -433,6 +446,9 @@ namespace tracy // Tracy converts gpuTime to deltas by updating refGpu on each GpuTime event, // so we must consistently track the same value here. m_lastEmittedGpuTimestamp = gpuTimestamp; + TracyD3D12Debug( + TracyFreeN(reinterpret_cast(uintptr_t(queryId)), "TracyD3D12 Query"); + ); } tracy_force_inline uint32_t RingSize() const @@ -478,19 +494,6 @@ namespace tracy const uint32_t queryId = RingIndex(seqIdx); - const auto t = AgeClock::now(); - m_queryRequestTime[queryId+0] = t; - m_queryRequestTime[queryId+1] = t; -#if TRACY_D3D12_PERSISTENT_TIMESTAMP_BUFFER - // WARN: resetting the timestamps here reduces the window of the race - // condition, but does not eliminate it. The GPU may still finish the - // "late" query after assigning the sentinel value below... Collect() - // will not know whether the next valid timestamp it sees in the slot - // belongs to the late query or to this new query... - m_timestampBuffer[queryId+0] = InvalidTimestamp; - m_timestampBuffer[queryId+1] = InvalidTimestamp; -#endif - TracyD3D12Debug( TracyAllocN(reinterpret_cast(uintptr_t(queryId+0)), 1, "TracyD3D12 Query"); TracyAllocN(reinterpret_cast(uintptr_t(queryId+1)), 1, "TracyD3D12 Query"); From e1357d7a1b8742b9ba8753517bdf8ddf4ab5f367 Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Sun, 29 Mar 2026 14:43:45 -0700 Subject: [PATCH 15/67] reworking destructor --- public/tracy/TracyD3D12.hpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index 2ea24b21..003f7e04 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -283,13 +283,16 @@ namespace tracy // and the immediately wait for the signal, in order to avoid busy-waiting // (need to create an ID3D12Fence and associate an Event object to it) - // wait for all pending queries to be collected (busy-wait...) - // TODO: need to relax the while condition, since now m_previousCheckpoint - // advances in batches of the window size, but not the query counter... - while (m_previousCheckpoint.load() != m_queryCounter.load()) - { + // collect all pending queries up to this point + uint64_t latestQuery = m_queryCounter.load(); + while (m_window.rangeEnd != latestQuery) Collect(); - } + // TODO: ensure we collect a "partial" window (Signal/Wait should do it) + + // if the client is still pushing queries past the latest checkpoint above, + // assume there's a bug in the client, and ignore them (don't collect) + if (latestQuery != m_queryCounter.load()) + TracyD3D12Panic("client is still pushing queries."); #if TRACY_D3D12_PERSISTENT_TIMESTAMP_BUFFER D3D12_RANGE fullRange { 0, m_queryLimit * sizeof(UINT64) }; From f06989e17c34fb2ce982175b0aeb93206d802285 Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Wed, 1 Apr 2026 15:31:18 -0700 Subject: [PATCH 16/67] cleanup --- public/tracy/TracyD3D12.hpp | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index 003f7e04..9751ef45 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -101,9 +101,6 @@ namespace tracy UINT64 m_prevCalibrationTicksCPU = 0; - // Last absolute GPU counter passed to EmitGpuTime (authoritative for delta stream vs Tracy's refGpu). - UINT64 m_lastEmittedGpuTimestamp = 0; - void RecalibrateClocks() { UINT64 cpuTimestamp; @@ -247,7 +244,6 @@ namespace tracy { TracyD3D12Panic("Failed to get queue clock calibration.", return); } - m_lastEmittedGpuTimestamp = gpuTimestamp; // initialize CollectWindow UpdateLatestKnownGpuTimestamp(gpuTimestamp); @@ -446,9 +442,6 @@ namespace tracy MemWrite(&item->gpuTime.queryId, static_cast(queryId)); MemWrite(&item->gpuTime.context, GetId()); Profiler::QueueSerialFinish(); - // Tracy converts gpuTime to deltas by updating refGpu on each GpuTime event, - // so we must consistently track the same value here. - m_lastEmittedGpuTimestamp = gpuTimestamp; TracyD3D12Debug( TracyFreeN(reinterpret_cast(uintptr_t(queryId)), "TracyD3D12 Query"); ); @@ -516,7 +509,7 @@ namespace tracy const bool m_active; D3D12QueueCtx* m_ctx = nullptr; ID3D12GraphicsCommandList* m_cmdList = nullptr; - uint32_t m_queryId = 0; // Used for tracking in nested zones. + uint32_t m_queryId = 0; tracy_force_inline void WriteQueueItem(const SourceLocationData* srcLocation, int32_t callstackDepth, uint32_t sourceLine, const char* sourceFile, size_t sourceFileLen, const char* functionName, size_t functionNameLen, const char* zoneName, size_t zoneNameLen) { From 9649302da8c4be2ceaa28ee9f07d93e89270ecd3 Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Wed, 1 Apr 2026 15:38:31 -0700 Subject: [PATCH 17/67] Distance utility --- public/tracy/TracyD3D12.hpp | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index 9751ef45..f06adcf0 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -369,7 +369,8 @@ namespace tracy UINT64 gpuZoneBeginTimestamp = timestampBuffer[queryId]; UINT64 gpuZoneEndTimestamp = timestampBuffer[queryId+1]; - int64_t diff = static_cast(gpuZoneEndTimestamp - m_window.shadowBuffer[shadowIdx+1]); + UINT64 baselineTimestamp = m_window.shadowBuffer[shadowIdx+1]; + int64_t diff = Distance(baselineTimestamp, gpuZoneEndTimestamp); if (diff == 0) continue; if (diff < 0) @@ -415,13 +416,9 @@ namespace tracy } private: - // Update the window's latest known GPU timestamp only if ts is strictly "ahead" - // of the current value. Uses a signed-difference comparison to be robust against - // counter wrap-around (though UINT64 at any conceivable clock rate won't wrap - // within the lifetime of the process). tracy_force_inline void UpdateLatestKnownGpuTimestamp(UINT64 timestamp) { - int64_t diff = static_cast(timestamp - m_window.latestKnownGpuTimestamp); + int64_t diff = Distance(m_window.latestKnownGpuTimestamp, timestamp); if (diff > 0) m_window.latestKnownGpuTimestamp = timestamp; } @@ -457,9 +454,10 @@ namespace tracy return static_cast(logicalSlot % RingSize()); } - tracy_force_inline uint32_t RingCount(uint64_t begin, uint64_t end) const + tracy_force_inline static int64_t Distance(uint64_t begin, uint64_t end) { - return static_cast(end - begin); + // difference accounting for unsigned wrap-around + return static_cast(end - begin); } tracy_force_inline uint32_t NextQueryId() @@ -478,7 +476,7 @@ namespace tracy // immediately upon m_queryCounter being incremented, the late timestamp // query may be collected as if it was the timestamp of the new query... const uint64_t seqIdx = m_queryCounter.fetch_add(2, std::memory_order_relaxed); - if (RingCount(m_previousCheckpoint.load(), seqIdx) >= RingSize()) + if (Distance(m_previousCheckpoint, seqIdx) >= RingSize()) { ZoneScopedC(Color::Red4); ZoneValue(int64_t(m_contextId)); From 19e48a843ddd5ed158fde820f18470eaa5b915c8 Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Wed, 1 Apr 2026 15:44:14 -0700 Subject: [PATCH 18/67] conditional clarity --- public/tracy/TracyD3D12.hpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index f06adcf0..f497932b 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -359,6 +359,7 @@ namespace tracy auto timeout = std::chrono::duration(TRACY_D3D12_TIMESTAMP_COLLECT_TIMEOUT); auto windowAge = now - m_window.ageStart; + bool dropUnresolved = (windowAge >= timeout); // iterate over every slot (pair) in the window int unresolved = 0; @@ -376,15 +377,14 @@ namespace tracy if (diff < 0) { ++unresolved; - if (windowAge < timeout) + if (!dropUnresolved) continue; - // timed-out: drop it TracyD3D12Debug( ZoneScopedNC("tracy::D3D12QueueCtx::Collect::[drop]", Color::Red4); ZoneValue(int64_t(queryId)); ); - // must still emit a bogus GpuTime to avoid problems with - // the server/profiler internal tracking/matching logic + // emit a "bogus" GpuTime to avoid problems with the internal + // zone tracking and matching logic in the server/profiler gpuZoneBeginTimestamp = m_window.latestKnownGpuTimestamp; gpuZoneEndTimestamp = gpuZoneBeginTimestamp + 1; } @@ -397,15 +397,15 @@ namespace tracy UpdateLatestKnownGpuTimestamp(gpuZoneEndTimestamp); } - if (windowSize == m_window.capacity) + bool windowFull = (windowSize == m_window.capacity); + bool allResolved = (unresolved == 0); + bool allDone = windowFull && (allResolved || dropUnresolved); + if (allDone) { - if ((unresolved == 0) || (windowAge >= timeout)) - { - AdvanceCollectWindow(); - // Publish new checkpoint — NextQueryId can now reuse these slots. - m_previousCheckpoint.store(m_window.rangeBegin); - // TODO: start collecting the next window immediately... - } + AdvanceCollectWindow(); + // Publish new checkpoint — NextQueryId can now reuse these slots. + m_previousCheckpoint.store(m_window.rangeBegin); + // TODO: start collecting the next window immediately... } #if !TRACY_D3D12_PERSISTENT_TIMESTAMP_BUFFER From 0afe331d1ea284d2aae72d6f2226df1424d2264b Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Wed, 1 Apr 2026 16:09:14 -0700 Subject: [PATCH 19/67] consolidating map/unmap buffer logic --- public/tracy/TracyD3D12.hpp | 70 +++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 34 deletions(-) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index f497932b..cada8217 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -79,7 +79,7 @@ namespace tracy ID3D12Resource* m_readbackBuffer = nullptr; #if TRACY_D3D12_PERSISTENT_TIMESTAMP_BUFFER - UINT64* m_timestampBuffer = nullptr; + UINT64* m_persistentTimestampBuffer = nullptr; #endif using atomic_counter = std::atomic; @@ -203,25 +203,10 @@ namespace tracy TracyD3D12Panic("Failed to create query readback buffer.", return); } - { - D3D12_RANGE zeroRange{ 0, m_queryLimit * sizeof(UINT64) }; - void* buffer = nullptr; - if (FAILED(m_readbackBuffer->Map(0, &zeroRange, &buffer))) - { - TracyD3D12Panic("Failed to map readback buffer for initialization.", return); - } - UINT64* timestampBuffer = static_cast(buffer); - for (uint64_t i = 0; i < m_queryLimit; ++i) - { - timestampBuffer[i] = 0; - } - // TODO: any advantages to making this Map() persist? -#if TRACY_D3D12_PERSISTENT_TIMESTAMP_BUFFER - m_timestampBuffer = timestampBuffer; -#else - m_readbackBuffer->Unmap(0, &zeroRange); -#endif - } + UINT64* timestampBuffer = MapTimestampBuffer(); + for (uint64_t i = 0; i < m_queryLimit; ++i) + timestampBuffer[i] = 0; + UnmapTimestampBuffer(timestampBuffer); float period = [queue]() { @@ -293,6 +278,7 @@ namespace tracy #if TRACY_D3D12_PERSISTENT_TIMESTAMP_BUFFER D3D12_RANGE fullRange { 0, m_queryLimit * sizeof(UINT64) }; m_readbackBuffer->Unmap(0, &fullRange); + m_persistentTimestampBuffer = nullptr; #endif m_readbackBuffer->Release(); m_queryHeap->Release(); @@ -345,17 +331,7 @@ namespace tracy if (windowSize == 0) return; -#if TRACY_D3D12_PERSISTENT_TIMESTAMP_BUFFER - UINT64* timestampBuffer = m_timestampBuffer; -#else - D3D12_RANGE mapRange{ 0, m_queryLimit * sizeof(UINT64) }; - void* readbackBufferMapping = nullptr; - if (FAILED(m_readbackBuffer->Map(0, &mapRange, &readbackBufferMapping))) - { - TracyD3D12Panic("Collect: failed to map timestamp buffer.", return); - } - UINT64* timestampBuffer = static_cast(readbackBufferMapping); -#endif + UINT64* timestampBuffer = MapTimestampBuffer(); auto timeout = std::chrono::duration(TRACY_D3D12_TIMESTAMP_COLLECT_TIMEOUT); auto windowAge = now - m_window.ageStart; @@ -408,9 +384,7 @@ namespace tracy // TODO: start collecting the next window immediately... } -#if !TRACY_D3D12_PERSISTENT_TIMESTAMP_BUFFER - m_readbackBuffer->Unmap(0, &mapRange); -#endif + UnmapTimestampBuffer(timestampBuffer); RecalibrateClocks(); } @@ -496,6 +470,34 @@ namespace tracy return queryId; } + UINT64* MapTimestampBuffer() + { +#if TRACY_D3D12_PERSISTENT_TIMESTAMP_BUFFER + if (m_persistentTimestampBuffer != nullptr) + return m_persistentTimestampBuffer; +#endif + D3D12_RANGE fullRange { 0, m_queryLimit * sizeof(UINT64) }; + void* readbackBufferMapping = nullptr; + if (FAILED(m_readbackBuffer->Map(0, &fullRange, &readbackBufferMapping))) + { + TracyD3D12Panic("failed to map timestamp buffer.", return nullptr); + } + UINT64* timestampBuffer = static_cast(readbackBufferMapping); +#if TRACY_D3D12_PERSISTENT_TIMESTAMP_BUFFER + assert(m_persistentTimestampBuffer == nullptr); + m_persistentTimestampBuffer = timestampBuffer; +#endif + return timestampBuffer; + } + + void UnmapTimestampBuffer(UINT64*) + { +#if !TRACY_D3D12_PERSISTENT_TIMESTAMP_BUFFER + D3D12_RANGE fullRange { 0, m_queryLimit * sizeof(UINT64) }; + m_readbackBuffer->Unmap(0, &fullRange); +#endif + } + tracy_force_inline uint8_t GetId() const { return m_contextId; From 1d8022c3012fd81409c35ffa8238d7489d53a91c Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Wed, 1 Apr 2026 16:19:27 -0700 Subject: [PATCH 20/67] comments, debugging --- public/tracy/TracyD3D12.hpp | 52 +++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index cada8217..81fc5655 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -263,12 +263,16 @@ namespace tracy // TODO: could use queue->Signal() to inject a progress point in the queue // and the immediately wait for the signal, in order to avoid busy-waiting // (need to create an ID3D12Fence and associate an Event object to it) - - // collect all pending queries up to this point + // NOTE: even with Signal(), there are no guarantees the queries were sent + // to the GPU for execution, so the Signal() does not give us much "signal" + + // attempt to collect all pending queries up to the latest known query uint64_t latestQuery = m_queryCounter.load(); - while (m_window.rangeEnd != latestQuery) + while (Distance(m_window.rangeEnd, latestQuery) > 0) Collect(); - // TODO: ensure we collect a "partial" window (Signal/Wait should do it) + // TODO: even though the collect window caugt up to the latestQuery, + // the window could still be "partial", and the timeout policy does + // not apply for a partial window... How to ensure collection? // if the client is still pushing queries past the latest checkpoint above, // assume there's a bug in the client, and ignore them (don't collect) @@ -318,6 +322,9 @@ namespace tracy auto now = CollectWindow::AgeClock::now(); + // update the window range and age + // (the timeout policy only starts kicking when the window is full) + // (until then, there's no rush, just keep refreshing the window age) uint64_t windowSize = m_window.rangeEnd - m_window.rangeBegin; if (windowSize < m_window.capacity) { @@ -358,6 +365,9 @@ namespace tracy TracyD3D12Debug( ZoneScopedNC("tracy::D3D12QueueCtx::Collect::[drop]", Color::Red4); ZoneValue(int64_t(queryId)); + TracyPlot("TracyD3D12|timeout", float(0)); + TracyPlot("TracyD3D12|timeout", float(timeout.count())); + TracyPlot("TracyD3D12|timeout", float(0)); ); // emit a "bogus" GpuTime to avoid problems with the internal // zone tracking and matching logic in the server/profiler @@ -379,7 +389,8 @@ namespace tracy if (allDone) { AdvanceCollectWindow(); - // Publish new checkpoint — NextQueryId can now reuse these slots. + // publish the new checkpoint: + // NextQueryId() is now free to immediately start reusing these slots m_previousCheckpoint.store(m_window.rangeBegin); // TODO: start collecting the next window immediately... } @@ -414,7 +425,7 @@ namespace tracy MemWrite(&item->gpuTime.context, GetId()); Profiler::QueueSerialFinish(); TracyD3D12Debug( - TracyFreeN(reinterpret_cast(uintptr_t(queryId)), "TracyD3D12 Query"); + TracyFreeN(reinterpret_cast(uintptr_t(queryId)), "TracyD3D12|Query"); ); } @@ -436,19 +447,16 @@ namespace tracy tracy_force_inline uint32_t NextQueryId() { - // WARN: the moment m_queryCounter is incremented, Collect() will have - // instant visibility of the query pair and will attempt to collect it! - // Under normal circumstances, this is fine: the corresponding id slots - // would have been rset to the InvalidTimestamp sentinel value already, - // right after the timestamp slots being collected. - // However, if Collect() decides to timeout-drop a timestamp query that - // was indeed submitted to the GPU queue for execution, the dropped query - // will eventually be resolved by the GPU and will arrive "late" at its - // corrsponding query slot (the GPU will write asynchronously to it). - // The "new" query pair here will have matching slots with the late query - // pair of "before". Given that Collect() will "sense" the new query pair - // immediately upon m_queryCounter being incremented, the late timestamp - // query may be collected as if it was the timestamp of the new query... + // WARN: the moment m_queryCounter is incremented, Collect() will have instant + // visibility of the query id pair and may start attempting to collect it! + // Under most circumstances, this is fine. However, if Collect() decided to + // drop a timestamp query due to timeout, but the query was indeed submitted + // to the GPU queue for execution later on, the "late" query will eventually + // be resolved by the GPU (asynchronously writting to the corrsponding query + // "slot"). The newly produced query pair below could have matching ids/slots + // with the late query. Given that Collect() may attempt to inspect the new + // query pair immediately upon m_queryCounter being incremented, the "late" + // timestamp value may be collected as if it belonged to the the new query. const uint64_t seqIdx = m_queryCounter.fetch_add(2, std::memory_order_relaxed); if (Distance(m_previousCheckpoint, seqIdx) >= RingSize()) { @@ -461,12 +469,10 @@ namespace tracy } const uint32_t queryId = RingIndex(seqIdx); - TracyD3D12Debug( - TracyAllocN(reinterpret_cast(uintptr_t(queryId+0)), 1, "TracyD3D12 Query"); - TracyAllocN(reinterpret_cast(uintptr_t(queryId+1)), 1, "TracyD3D12 Query"); + TracyAllocN(reinterpret_cast(uintptr_t(queryId+0)), 1, "TracyD3D12|Query"); + TracyAllocN(reinterpret_cast(uintptr_t(queryId+1)), 1, "TracyD3D12|Query"); ); - return queryId; } From c39d1d6441cf86ce9de4cc72ba9cdf9ccd884dd8 Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Wed, 1 Apr 2026 16:27:08 -0700 Subject: [PATCH 21/67] fudge factors... --- public/tracy/TracyD3D12.hpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index 81fc5655..58ba49aa 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -372,14 +372,13 @@ namespace tracy // emit a "bogus" GpuTime to avoid problems with the internal // zone tracking and matching logic in the server/profiler gpuZoneBeginTimestamp = m_window.latestKnownGpuTimestamp; - gpuZoneEndTimestamp = gpuZoneBeginTimestamp + 1; + gpuZoneEndTimestamp = gpuZoneBeginTimestamp; // 0ns } EmitGpuTime(gpuZoneBeginTimestamp, queryId); EmitGpuTime(gpuZoneEndTimestamp, queryId+1); m_window.shadowBuffer[shadowIdx] = gpuZoneBeginTimestamp; m_window.shadowBuffer[shadowIdx+1] = gpuZoneEndTimestamp; - // TODO: maybe only update timestamp for "resolved" queries UpdateLatestKnownGpuTimestamp(gpuZoneEndTimestamp); } @@ -411,9 +410,12 @@ namespace tracy void AdvanceCollectWindow() { m_window.rangeBegin = m_window.rangeEnd; - m_window.ageStart = CollectWindow::AgeClock::time_point::max(); + m_window.ageStart = CollectWindow::AgeClock::now(); + // establish a new baseline to detect timestamp changes + // (subtract one to allow for "slow" GPU ticking) + UINT64 baselineTimestamp = m_window.latestKnownGpuTimestamp - 1; for (auto& shadow : m_window.shadowBuffer) - shadow = m_window.latestKnownGpuTimestamp; + shadow = baselineTimestamp; } tracy_force_inline void EmitGpuTime(UINT64 gpuTimestamp, uint32_t queryId) From 3bf371c4fbecaef4162d615da91a02bcb70acc61 Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Wed, 1 Apr 2026 16:41:30 -0700 Subject: [PATCH 22/67] refactoring of Collect core --- public/tracy/TracyD3D12.hpp | 149 ++++++++++++++++++------------------ 1 file changed, 75 insertions(+), 74 deletions(-) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index 58ba49aa..43c65cc9 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -320,81 +320,10 @@ namespace tracy return; std::unique_lock lock (m_collectionMutex, std::adopt_lock); - auto now = CollectWindow::AgeClock::now(); - - // update the window range and age - // (the timeout policy only starts kicking when the window is full) - // (until then, there's no rush, just keep refreshing the window age) - uint64_t windowSize = m_window.rangeEnd - m_window.rangeBegin; - if (windowSize < m_window.capacity) - { - uint64_t latestIssued = m_queryCounter.load(); - uint64_t trail = latestIssued - m_window.rangeBegin; - windowSize = std::min(trail, m_window.capacity); - m_window.rangeEnd = m_window.rangeBegin + windowSize; - m_window.ageStart = now; - } - - if (windowSize == 0) - return; - - UINT64* timestampBuffer = MapTimestampBuffer(); - - auto timeout = std::chrono::duration(TRACY_D3D12_TIMESTAMP_COLLECT_TIMEOUT); - auto windowAge = now - m_window.ageStart; - bool dropUnresolved = (windowAge >= timeout); - - // iterate over every slot (pair) in the window - int unresolved = 0; - for (uint64_t i = m_window.rangeBegin; i != m_window.rangeEnd; i += 2) - { - uint32_t queryId = RingIndex(i); - uint32_t shadowIdx = static_cast(i - m_window.rangeBegin); - - UINT64 gpuZoneBeginTimestamp = timestampBuffer[queryId]; - UINT64 gpuZoneEndTimestamp = timestampBuffer[queryId+1]; - UINT64 baselineTimestamp = m_window.shadowBuffer[shadowIdx+1]; - int64_t diff = Distance(baselineTimestamp, gpuZoneEndTimestamp); - if (diff == 0) - continue; - if (diff < 0) - { - ++unresolved; - if (!dropUnresolved) - continue; - TracyD3D12Debug( - ZoneScopedNC("tracy::D3D12QueueCtx::Collect::[drop]", Color::Red4); - ZoneValue(int64_t(queryId)); - TracyPlot("TracyD3D12|timeout", float(0)); - TracyPlot("TracyD3D12|timeout", float(timeout.count())); - TracyPlot("TracyD3D12|timeout", float(0)); - ); - // emit a "bogus" GpuTime to avoid problems with the internal - // zone tracking and matching logic in the server/profiler - gpuZoneBeginTimestamp = m_window.latestKnownGpuTimestamp; - gpuZoneEndTimestamp = gpuZoneBeginTimestamp; // 0ns - } - - EmitGpuTime(gpuZoneBeginTimestamp, queryId); - EmitGpuTime(gpuZoneEndTimestamp, queryId+1); - m_window.shadowBuffer[shadowIdx] = gpuZoneBeginTimestamp; - m_window.shadowBuffer[shadowIdx+1] = gpuZoneEndTimestamp; - UpdateLatestKnownGpuTimestamp(gpuZoneEndTimestamp); - } - - bool windowFull = (windowSize == m_window.capacity); - bool allResolved = (unresolved == 0); - bool allDone = windowFull && (allResolved || dropUnresolved); - if (allDone) - { + uint64_t latestQueryIssued = m_queryCounter; + if (ProcessCollectWindow(latestQueryIssued)) AdvanceCollectWindow(); - // publish the new checkpoint: - // NextQueryId() is now free to immediately start reusing these slots - m_previousCheckpoint.store(m_window.rangeBegin); - // TODO: start collecting the next window immediately... - } - - UnmapTimestampBuffer(timestampBuffer); + // TODO: start collecting the next window immediately... RecalibrateClocks(); } @@ -416,6 +345,9 @@ namespace tracy UINT64 baselineTimestamp = m_window.latestKnownGpuTimestamp - 1; for (auto& shadow : m_window.shadowBuffer) shadow = baselineTimestamp; + // publish the new checkpoint: + // NextQueryId() is now free to immediately start reusing these slots + m_previousCheckpoint.store(m_window.rangeBegin); } tracy_force_inline void EmitGpuTime(UINT64 gpuTimestamp, uint32_t queryId) @@ -478,6 +410,75 @@ namespace tracy return queryId; } + bool ProcessCollectWindow(uint64_t limit) + { + auto now = CollectWindow::AgeClock::now(); + + // update the window range and age + // (the timeout policy only starts kicking when the window is full) + // (until then, there's no rush, just keep refreshing the window age) + uint64_t windowSize = m_window.rangeEnd - m_window.rangeBegin; + if (windowSize < m_window.capacity) + { + uint64_t trail = limit - m_window.rangeBegin; + windowSize = std::min(trail, m_window.capacity); + m_window.rangeEnd = m_window.rangeBegin + windowSize; + m_window.ageStart = now; + } + + if (windowSize == 0) + return false; + + auto timeout = std::chrono::duration(TRACY_D3D12_TIMESTAMP_COLLECT_TIMEOUT); + auto windowAge = now - m_window.ageStart; + bool dropUnresolved = (windowAge >= timeout); + + int unresolved = 0; + UINT64* timestampBuffer = MapTimestampBuffer(); + // iterate over every slot (pair) in the window + for (uint64_t i = m_window.rangeBegin; i != m_window.rangeEnd; i += 2) + { + uint32_t queryId = RingIndex(i); + uint32_t shadowIdx = static_cast(i - m_window.rangeBegin); + + UINT64 gpuZoneBeginTimestamp = timestampBuffer[queryId]; + UINT64 gpuZoneEndTimestamp = timestampBuffer[queryId+1]; + UINT64 baselineTimestamp = m_window.shadowBuffer[shadowIdx+1]; + int64_t diff = Distance(baselineTimestamp, gpuZoneEndTimestamp); + if (diff == 0) + continue; + if (diff < 0) + { + ++unresolved; + if (!dropUnresolved) + continue; + TracyD3D12Debug( + ZoneScopedNC("tracy::D3D12QueueCtx::Collect::[drop]", Color::Red4); + ZoneValue(int64_t(queryId)); + TracyPlot("TracyD3D12|timeout", float(0)); + TracyPlot("TracyD3D12|timeout", float(timeout.count())); + TracyPlot("TracyD3D12|timeout", float(0)); + ); + // emit a "bogus" GpuTime to avoid problems with the internal + // zone tracking and matching logic in the server/profiler + gpuZoneBeginTimestamp = m_window.latestKnownGpuTimestamp; + gpuZoneEndTimestamp = gpuZoneBeginTimestamp; // 0ns + } + + EmitGpuTime(gpuZoneBeginTimestamp, queryId); + EmitGpuTime(gpuZoneEndTimestamp, queryId+1); + m_window.shadowBuffer[shadowIdx] = gpuZoneBeginTimestamp; + m_window.shadowBuffer[shadowIdx+1] = gpuZoneEndTimestamp; + UpdateLatestKnownGpuTimestamp(gpuZoneEndTimestamp); + } + UnmapTimestampBuffer(timestampBuffer); + + bool windowFull = (windowSize == m_window.capacity); + bool allResolved = (unresolved == 0); + bool allDone = windowFull && (allResolved || dropUnresolved); + return allDone; + } + UINT64* MapTimestampBuffer() { #if TRACY_D3D12_PERSISTENT_TIMESTAMP_BUFFER From bde92ffed2a45e52d59a9d203360e54e7f292909 Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Wed, 1 Apr 2026 16:43:49 -0700 Subject: [PATCH 23/67] timestamp relax (disabled) --- public/tracy/TracyD3D12.hpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index 43c65cc9..f65cfcf0 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -430,6 +430,9 @@ namespace tracy return false; auto timeout = std::chrono::duration(TRACY_D3D12_TIMESTAMP_COLLECT_TIMEOUT); + //double relax = double(RingCapacity()) / (1 + Distance(m_previousCheckpoint, m_queryCounter)); + //relax = std::min(relax, 50.0); + //timeout *= relax; auto windowAge = now - m_window.ageStart; bool dropUnresolved = (windowAge >= timeout); From 9b2f1266ea45e23245485ff2eefdbef64e96ab66 Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Wed, 1 Apr 2026 20:20:53 -0700 Subject: [PATCH 24/67] cosmetic changes --- public/tracy/TracyD3D12.hpp | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index f65cfcf0..a982ba20 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -69,7 +69,7 @@ namespace tracy { friend class D3D12ZoneScope; - uint8_t m_contextId = 255; // TODO: apparently, 255 means "invalid id"; is this documented somewhere? + uint8_t m_contextId = 255; // 255 represents "invalid id" std::mutex m_collectionMutex; @@ -93,8 +93,8 @@ namespace tracy using AgeClock = std::chrono::high_resolution_clock; static constexpr uint64_t capacity = 4 * 1024; uint64_t rangeBegin = 0; - uint64_t rangeEnd = 0; - AgeClock::time_point ageStart = AgeClock::time_point::max(); + uint64_t rangeEnd = 0; + AgeClock::time_point ageStart = AgeClock::time_point::max(); UINT64 latestKnownGpuTimestamp = 0; UINT64 shadowBuffer[capacity] = {}; } m_window; @@ -150,11 +150,9 @@ namespace tracy ZoneScopedC(Color::Red4); // Verify we support timestamp queries on this queue. - if (queue->GetDesc().Type == D3D12_COMMAND_LIST_TYPE_COPY) { D3D12_FEATURE_DATA_D3D12_OPTIONS3 featureData{}; - HRESULT hr = device->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS3, &featureData, sizeof(featureData)); if (FAILED(hr) || (featureData.CopyQueueTimestampQueriesSupported == FALSE)) { @@ -267,7 +265,7 @@ namespace tracy // to the GPU for execution, so the Signal() does not give us much "signal" // attempt to collect all pending queries up to the latest known query - uint64_t latestQuery = m_queryCounter.load(); + uint64_t latestQuery = m_queryCounter; while (Distance(m_window.rangeEnd, latestQuery) > 0) Collect(); // TODO: even though the collect window caugt up to the latestQuery, @@ -276,7 +274,7 @@ namespace tracy // if the client is still pushing queries past the latest checkpoint above, // assume there's a bug in the client, and ignore them (don't collect) - if (latestQuery != m_queryCounter.load()) + if (latestQuery != m_queryCounter) TracyD3D12Panic("client is still pushing queries."); #if TRACY_D3D12_PERSISTENT_TIMESTAMP_BUFFER From b638a3adff81e092157192887a808a010a7f0398 Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Wed, 1 Apr 2026 20:21:26 -0700 Subject: [PATCH 25/67] opportunistically collect next window whenever possible --- public/tracy/TracyD3D12.hpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index a982ba20..b51b6ef3 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -319,9 +319,8 @@ namespace tracy std::unique_lock lock (m_collectionMutex, std::adopt_lock); uint64_t latestQueryIssued = m_queryCounter; - if (ProcessCollectWindow(latestQueryIssued)) + while (ProcessCollectWindow(latestQueryIssued)) AdvanceCollectWindow(); - // TODO: start collecting the next window immediately... RecalibrateClocks(); } From b80fa52ba20e0ce35201e91af9ad922428db8ead Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Wed, 1 Apr 2026 21:46:46 -0700 Subject: [PATCH 26/67] switching to a circular buffer with individual timeouts --- public/tracy/TracyD3D12.hpp | 179 ++++++++++++++---------------------- 1 file changed, 67 insertions(+), 112 deletions(-) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index b51b6ef3..ef67d957 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -88,16 +88,10 @@ namespace tracy uint32_t m_queryLimit = 0; - struct CollectWindow - { - using AgeClock = std::chrono::high_resolution_clock; - static constexpr uint64_t capacity = 4 * 1024; - uint64_t rangeBegin = 0; - uint64_t rangeEnd = 0; - AgeClock::time_point ageStart = AgeClock::time_point::max(); - UINT64 latestKnownGpuTimestamp = 0; - UINT64 shadowBuffer[capacity] = {}; - } m_window; + using AgeTime = std::chrono::high_resolution_clock::time_point; + std::vector m_queryRequestTime; + std::vector m_shadowBuffer; + UINT64 m_latestKnownGpuTimestamp = 0; UINT64 m_prevCalibrationTicksCPU = 0; @@ -228,9 +222,9 @@ namespace tracy TracyD3D12Panic("Failed to get queue clock calibration.", return); } - // initialize CollectWindow UpdateLatestKnownGpuTimestamp(gpuTimestamp); - AdvanceCollectWindow(); + m_queryRequestTime.resize(m_queryLimit, AgeTime::clock::now()); + m_shadowBuffer.resize(m_queryLimit, m_latestKnownGpuTimestamp); // Save the device cpu timestamp, not the profiler's timestamp. m_prevCalibrationTicksCPU = cpuTimestamp; @@ -266,11 +260,8 @@ namespace tracy // attempt to collect all pending queries up to the latest known query uint64_t latestQuery = m_queryCounter; - while (Distance(m_window.rangeEnd, latestQuery) > 0) + while (Distance(m_previousCheckpoint, latestQuery) > 0) Collect(); - // TODO: even though the collect window caugt up to the latestQuery, - // the window could still be "partial", and the timeout policy does - // not apply for a partial window... How to ensure collection? // if the client is still pushing queries past the latest checkpoint above, // assume there's a bug in the client, and ignore them (don't collect) @@ -303,11 +294,7 @@ namespace tracy { #ifdef TRACY_ON_DEMAND if (!GetProfiler().IsConnected()) - { - // TODO: reset the collect window here - m_previousCheckpoint = m_queryCounter.load(); return; - } #endif ZoneScopedC(Color::Red4); ZoneValue(uint64_t(m_contextId)); @@ -318,9 +305,58 @@ namespace tracy return; std::unique_lock lock (m_collectionMutex, std::adopt_lock); - uint64_t latestQueryIssued = m_queryCounter; - while (ProcessCollectWindow(latestQueryIssued)) - AdvanceCollectWindow(); + AgeTime now = AgeTime::clock::now(); + + auto timeout = std::chrono::duration(TRACY_D3D12_TIMESTAMP_COLLECT_TIMEOUT); + //double relax = double(RingCapacity()) / (1 + Distance(m_previousCheckpoint, m_queryCounter)); + //relax = std::min(relax, 50.0); + //timeout *= relax; + + UINT64* timestampBuffer = MapTimestampBuffer(); + uint64_t rangeBegin = m_previousCheckpoint; + uint64_t rangeEnd = m_queryCounter; + for (uint64_t i = rangeBegin; i != rangeEnd; i += 2) + { + uint32_t queryId = RingIndex(i); + + UINT64 gpuZoneBeginTimestamp = timestampBuffer[queryId]; + UINT64 gpuZoneEndTimestamp = timestampBuffer[queryId+1]; + UINT64 baselineTimestamp = m_shadowBuffer[queryId+1]; + int64_t diff = Distance(baselineTimestamp, gpuZoneEndTimestamp); + if (diff == 0) + DebugBreak(); + if (diff <= 0) + { + // WARN: reads from m_queryRequestTime[] here may race with writes in NextQueryID() + AgeTime ini = m_queryRequestTime[queryId+1]; + if (now - ini < timeout) + break; + TracyD3D12Debug( + ZoneScopedNC("tracy::D3D12QueueCtx::Collect::[drop]", Color::Red4); + ZoneValue(int64_t(queryId)); + TracyPlot("TracyD3D12|timeout", float(0)); + TracyPlot("TracyD3D12|timeout", float(timeout.count())); + TracyPlot("TracyD3D12|timeout", float(0)); + ); + // emit a "bogus" GpuTime to avoid problems with the internal + // zone tracking and matching logic in the server/profiler + gpuZoneBeginTimestamp = m_latestKnownGpuTimestamp; + gpuZoneEndTimestamp = gpuZoneBeginTimestamp; // 0ns + } + + EmitGpuTime(gpuZoneBeginTimestamp, queryId); + EmitGpuTime(gpuZoneEndTimestamp, queryId+1); + UpdateLatestKnownGpuTimestamp(gpuZoneEndTimestamp); + + m_shadowBuffer[queryId+0] = gpuZoneEndTimestamp; + m_shadowBuffer[queryId+1] = gpuZoneEndTimestamp; + m_queryRequestTime[queryId+0] = now; + m_queryRequestTime[queryId+1] = now; + + // move the goalpost: NextQueryId() can now reuse the query pair + m_previousCheckpoint.store(i+2); + } + UnmapTimestampBuffer(timestampBuffer); RecalibrateClocks(); } @@ -328,23 +364,9 @@ namespace tracy private: tracy_force_inline void UpdateLatestKnownGpuTimestamp(UINT64 timestamp) { - int64_t diff = Distance(m_window.latestKnownGpuTimestamp, timestamp); + int64_t diff = Distance(m_latestKnownGpuTimestamp, timestamp); if (diff > 0) - m_window.latestKnownGpuTimestamp = timestamp; - } - - void AdvanceCollectWindow() - { - m_window.rangeBegin = m_window.rangeEnd; - m_window.ageStart = CollectWindow::AgeClock::now(); - // establish a new baseline to detect timestamp changes - // (subtract one to allow for "slow" GPU ticking) - UINT64 baselineTimestamp = m_window.latestKnownGpuTimestamp - 1; - for (auto& shadow : m_window.shadowBuffer) - shadow = baselineTimestamp; - // publish the new checkpoint: - // NextQueryId() is now free to immediately start reusing these slots - m_previousCheckpoint.store(m_window.rangeBegin); + m_latestKnownGpuTimestamp = timestamp; } tracy_force_inline void EmitGpuTime(UINT64 gpuTimestamp, uint32_t queryId) @@ -383,7 +405,7 @@ namespace tracy // Under most circumstances, this is fine. However, if Collect() decided to // drop a timestamp query due to timeout, but the query was indeed submitted // to the GPU queue for execution later on, the "late" query will eventually - // be resolved by the GPU (asynchronously writting to the corrsponding query + // be resolved by the GPU (asynchronously writting to the corresponding query // "slot"). The newly produced query pair below could have matching ids/slots // with the late query. Given that Collect() may attempt to inspect the new // query pair immediately upon m_queryCounter being incremented, the "late" @@ -400,6 +422,11 @@ namespace tracy } const uint32_t queryId = RingIndex(seqIdx); + // WARN: writes to m_queryRequestTime[] here race with reads in Collect() + AgeTime now = AgeTime::clock::now(); + m_queryRequestTime[queryId+0] = now; + m_queryRequestTime[queryId+1] = now; + // TODO: memory release fence here TracyD3D12Debug( TracyAllocN(reinterpret_cast(uintptr_t(queryId+0)), 1, "TracyD3D12|Query"); TracyAllocN(reinterpret_cast(uintptr_t(queryId+1)), 1, "TracyD3D12|Query"); @@ -407,78 +434,6 @@ namespace tracy return queryId; } - bool ProcessCollectWindow(uint64_t limit) - { - auto now = CollectWindow::AgeClock::now(); - - // update the window range and age - // (the timeout policy only starts kicking when the window is full) - // (until then, there's no rush, just keep refreshing the window age) - uint64_t windowSize = m_window.rangeEnd - m_window.rangeBegin; - if (windowSize < m_window.capacity) - { - uint64_t trail = limit - m_window.rangeBegin; - windowSize = std::min(trail, m_window.capacity); - m_window.rangeEnd = m_window.rangeBegin + windowSize; - m_window.ageStart = now; - } - - if (windowSize == 0) - return false; - - auto timeout = std::chrono::duration(TRACY_D3D12_TIMESTAMP_COLLECT_TIMEOUT); - //double relax = double(RingCapacity()) / (1 + Distance(m_previousCheckpoint, m_queryCounter)); - //relax = std::min(relax, 50.0); - //timeout *= relax; - auto windowAge = now - m_window.ageStart; - bool dropUnresolved = (windowAge >= timeout); - - int unresolved = 0; - UINT64* timestampBuffer = MapTimestampBuffer(); - // iterate over every slot (pair) in the window - for (uint64_t i = m_window.rangeBegin; i != m_window.rangeEnd; i += 2) - { - uint32_t queryId = RingIndex(i); - uint32_t shadowIdx = static_cast(i - m_window.rangeBegin); - - UINT64 gpuZoneBeginTimestamp = timestampBuffer[queryId]; - UINT64 gpuZoneEndTimestamp = timestampBuffer[queryId+1]; - UINT64 baselineTimestamp = m_window.shadowBuffer[shadowIdx+1]; - int64_t diff = Distance(baselineTimestamp, gpuZoneEndTimestamp); - if (diff == 0) - continue; - if (diff < 0) - { - ++unresolved; - if (!dropUnresolved) - continue; - TracyD3D12Debug( - ZoneScopedNC("tracy::D3D12QueueCtx::Collect::[drop]", Color::Red4); - ZoneValue(int64_t(queryId)); - TracyPlot("TracyD3D12|timeout", float(0)); - TracyPlot("TracyD3D12|timeout", float(timeout.count())); - TracyPlot("TracyD3D12|timeout", float(0)); - ); - // emit a "bogus" GpuTime to avoid problems with the internal - // zone tracking and matching logic in the server/profiler - gpuZoneBeginTimestamp = m_window.latestKnownGpuTimestamp; - gpuZoneEndTimestamp = gpuZoneBeginTimestamp; // 0ns - } - - EmitGpuTime(gpuZoneBeginTimestamp, queryId); - EmitGpuTime(gpuZoneEndTimestamp, queryId+1); - m_window.shadowBuffer[shadowIdx] = gpuZoneBeginTimestamp; - m_window.shadowBuffer[shadowIdx+1] = gpuZoneEndTimestamp; - UpdateLatestKnownGpuTimestamp(gpuZoneEndTimestamp); - } - UnmapTimestampBuffer(timestampBuffer); - - bool windowFull = (windowSize == m_window.capacity); - bool allResolved = (unresolved == 0); - bool allDone = windowFull && (allResolved || dropUnresolved); - return allDone; - } - UINT64* MapTimestampBuffer() { #if TRACY_D3D12_PERSISTENT_TIMESTAMP_BUFFER From 333e4bcb0555bdee6cbcbebabeba60d2e761ba53 Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Wed, 1 Apr 2026 22:05:25 -0700 Subject: [PATCH 27/67] comments and debugging --- public/tracy/TracyD3D12.hpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index ef67d957..908db106 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -315,6 +315,7 @@ namespace tracy UINT64* timestampBuffer = MapTimestampBuffer(); uint64_t rangeBegin = m_previousCheckpoint; uint64_t rangeEnd = m_queryCounter; + // iterate queries in pairs for (uint64_t i = rangeBegin; i != rangeEnd; i += 2) { uint32_t queryId = RingIndex(i); @@ -323,14 +324,18 @@ namespace tracy UINT64 gpuZoneEndTimestamp = timestampBuffer[queryId+1]; UINT64 baselineTimestamp = m_shadowBuffer[queryId+1]; int64_t diff = Distance(baselineTimestamp, gpuZoneEndTimestamp); - if (diff == 0) - DebugBreak(); + //if (diff == 0) + // DebugBreak(); if (diff <= 0) { // WARN: reads from m_queryRequestTime[] here may race with writes in NextQueryID() AgeTime ini = m_queryRequestTime[queryId+1]; - if (now - ini < timeout) + if (ini == AgeTime::max()) + DebugBreak(); + auto age = now - ini; + if (age < timeout) break; + // timeout reached: give up and drop it TracyD3D12Debug( ZoneScopedNC("tracy::D3D12QueueCtx::Collect::[drop]", Color::Red4); ZoneValue(int64_t(queryId)); @@ -350,8 +355,8 @@ namespace tracy m_shadowBuffer[queryId+0] = gpuZoneEndTimestamp; m_shadowBuffer[queryId+1] = gpuZoneEndTimestamp; - m_queryRequestTime[queryId+0] = now; - m_queryRequestTime[queryId+1] = now; + m_queryRequestTime[queryId+0] = AgeTime::max(); + m_queryRequestTime[queryId+1] = AgeTime::max(); // move the goalpost: NextQueryId() can now reuse the query pair m_previousCheckpoint.store(i+2); From 85789a8a138ea589625be8bf439d32dbee7df3db Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Thu, 2 Apr 2026 10:35:56 -0700 Subject: [PATCH 28/67] minor refactoring --- public/tracy/TracyD3D12.hpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index 908db106..7778f84c 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -293,8 +293,7 @@ namespace tracy void Collect() { #ifdef TRACY_ON_DEMAND - if (!GetProfiler().IsConnected()) - return; + if (!GetProfiler().IsConnected()) return; #endif ZoneScopedC(Color::Red4); ZoneValue(uint64_t(m_contextId)); @@ -323,13 +322,14 @@ namespace tracy UINT64 gpuZoneBeginTimestamp = timestampBuffer[queryId]; UINT64 gpuZoneEndTimestamp = timestampBuffer[queryId+1]; UINT64 baselineTimestamp = m_shadowBuffer[queryId+1]; + AgeTime ini = m_queryRequestTime[queryId+1]; int64_t diff = Distance(baselineTimestamp, gpuZoneEndTimestamp); - //if (diff == 0) - // DebugBreak(); + if (diff == 0) + DebugBreak(); if (diff <= 0) { // WARN: reads from m_queryRequestTime[] here may race with writes in NextQueryID() - AgeTime ini = m_queryRequestTime[queryId+1]; + //AgeTime ini = m_queryRequestTime[queryId+1]; if (ini == AgeTime::max()) DebugBreak(); auto age = now - ini; @@ -351,7 +351,6 @@ namespace tracy EmitGpuTime(gpuZoneBeginTimestamp, queryId); EmitGpuTime(gpuZoneEndTimestamp, queryId+1); - UpdateLatestKnownGpuTimestamp(gpuZoneEndTimestamp); m_shadowBuffer[queryId+0] = gpuZoneEndTimestamp; m_shadowBuffer[queryId+1] = gpuZoneEndTimestamp; @@ -382,6 +381,7 @@ namespace tracy MemWrite(&item->gpuTime.queryId, static_cast(queryId)); MemWrite(&item->gpuTime.context, GetId()); Profiler::QueueSerialFinish(); + UpdateLatestKnownGpuTimestamp(gpuTimestamp); TracyD3D12Debug( TracyFreeN(reinterpret_cast(uintptr_t(queryId)), "TracyD3D12|Query"); ); From b34966fe629e74d70c7d46fe78e2fc54748316b2 Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Thu, 2 Apr 2026 16:55:40 -0700 Subject: [PATCH 29/67] refactoring --- public/tracy/TracyD3D12.hpp | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index 7778f84c..01615435 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -222,7 +222,7 @@ namespace tracy TracyD3D12Panic("Failed to get queue clock calibration.", return); } - UpdateLatestKnownGpuTimestamp(gpuTimestamp); + m_latestKnownGpuTimestamp = gpuTimestamp; m_queryRequestTime.resize(m_queryLimit, AgeTime::clock::now()); m_shadowBuffer.resize(m_queryLimit, m_latestKnownGpuTimestamp); @@ -319,19 +319,19 @@ namespace tracy { uint32_t queryId = RingIndex(i); + // WARN: reads from m_queryRequestTime[] here may race with writes in NextQueryID() + std::atomic_thread_fence(std::memory_order_acquire); + AgeTime ini = m_queryRequestTime[queryId+1]; + if (ini == AgeTime::max()) + DebugBreak(); UINT64 gpuZoneBeginTimestamp = timestampBuffer[queryId]; UINT64 gpuZoneEndTimestamp = timestampBuffer[queryId+1]; UINT64 baselineTimestamp = m_shadowBuffer[queryId+1]; - AgeTime ini = m_queryRequestTime[queryId+1]; int64_t diff = Distance(baselineTimestamp, gpuZoneEndTimestamp); if (diff == 0) DebugBreak(); if (diff <= 0) { - // WARN: reads from m_queryRequestTime[] here may race with writes in NextQueryID() - //AgeTime ini = m_queryRequestTime[queryId+1]; - if (ini == AgeTime::max()) - DebugBreak(); auto age = now - ini; if (age < timeout) break; @@ -345,6 +345,7 @@ namespace tracy ); // emit a "bogus" GpuTime to avoid problems with the internal // zone tracking and matching logic in the server/profiler + //gpuZoneBeginTimestamp = baselineTimestamp; gpuZoneBeginTimestamp = m_latestKnownGpuTimestamp; gpuZoneEndTimestamp = gpuZoneBeginTimestamp; // 0ns } @@ -356,6 +357,8 @@ namespace tracy m_shadowBuffer[queryId+1] = gpuZoneEndTimestamp; m_queryRequestTime[queryId+0] = AgeTime::max(); m_queryRequestTime[queryId+1] = AgeTime::max(); + if (Distance(m_latestKnownGpuTimestamp, gpuZoneEndTimestamp) > 0) + m_latestKnownGpuTimestamp = gpuZoneEndTimestamp; // move the goalpost: NextQueryId() can now reuse the query pair m_previousCheckpoint.store(i+2); @@ -366,13 +369,6 @@ namespace tracy } private: - tracy_force_inline void UpdateLatestKnownGpuTimestamp(UINT64 timestamp) - { - int64_t diff = Distance(m_latestKnownGpuTimestamp, timestamp); - if (diff > 0) - m_latestKnownGpuTimestamp = timestamp; - } - tracy_force_inline void EmitGpuTime(UINT64 gpuTimestamp, uint32_t queryId) { auto* item = Profiler::QueueSerial(); @@ -381,7 +377,6 @@ namespace tracy MemWrite(&item->gpuTime.queryId, static_cast(queryId)); MemWrite(&item->gpuTime.context, GetId()); Profiler::QueueSerialFinish(); - UpdateLatestKnownGpuTimestamp(gpuTimestamp); TracyD3D12Debug( TracyFreeN(reinterpret_cast(uintptr_t(queryId)), "TracyD3D12|Query"); ); @@ -431,7 +426,7 @@ namespace tracy AgeTime now = AgeTime::clock::now(); m_queryRequestTime[queryId+0] = now; m_queryRequestTime[queryId+1] = now; - // TODO: memory release fence here + std::atomic_thread_fence(std::memory_order_release); TracyD3D12Debug( TracyAllocN(reinterpret_cast(uintptr_t(queryId+0)), 1, "TracyD3D12|Query"); TracyAllocN(reinterpret_cast(uintptr_t(queryId+1)), 1, "TracyD3D12|Query"); From 0a27c61313ce13233c87247b7102a794b296352f Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Sat, 4 Apr 2026 13:19:36 -0700 Subject: [PATCH 30/67] debugging --- public/tracy/TracyD3D12.hpp | 58 ++++++++++++++++++++++++++++++------- server/TracyWorker.cpp | 32 +++++++++++++++++--- 2 files changed, 76 insertions(+), 14 deletions(-) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index 01615435..94512821 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -65,6 +65,7 @@ namespace tracy { // Command queue context. +#pragma optimize ( "", off ) class D3D12QueueCtx { friend class D3D12ZoneScope; @@ -290,6 +291,27 @@ namespace tracy SubmitQueueItem(item); } + #define TracyD3D12Assert(expr) if (!(expr)) __debugbreak(); + + bool DebugGetTimeoutBit(UINT64 timestamp) { + constexpr UINT64 mask = 0x8000'0000'0000'0000llu; + bool bit = (timestamp & mask) != 0llu; + return bit; + } + + UINT64 DebugSetTimeoutBit(UINT64 timestamp) { + TracyD3D12Assert(!DebugGetTimeoutBit(timestamp)); + constexpr UINT64 mask = 0x8000'0000'0000'0000llu; + timestamp = timestamp | mask; + return timestamp; + } + + UINT64 DebugClearTimeoutBit(UINT64 timestamp) { + constexpr UINT64 mask = 0x8000'0000'0000'0000llu; + timestamp = timestamp & ~mask; + return timestamp; + } + void Collect() { #ifdef TRACY_ON_DEMAND @@ -322,15 +344,25 @@ namespace tracy // WARN: reads from m_queryRequestTime[] here may race with writes in NextQueryID() std::atomic_thread_fence(std::memory_order_acquire); AgeTime ini = m_queryRequestTime[queryId+1]; - if (ini == AgeTime::max()) - DebugBreak(); + TracyD3D12Assert(ini != AgeTime::max()); UINT64 gpuZoneBeginTimestamp = timestampBuffer[queryId]; UINT64 gpuZoneEndTimestamp = timestampBuffer[queryId+1]; + TracyD3D12Assert(!DebugGetTimeoutBit(gpuZoneBeginTimestamp)); + TracyD3D12Assert(!DebugGetTimeoutBit(gpuZoneEndTimestamp)); UINT64 baselineTimestamp = m_shadowBuffer[queryId+1]; - int64_t diff = Distance(baselineTimestamp, gpuZoneEndTimestamp); - if (diff == 0) - DebugBreak(); - if (diff <= 0) + bool sbtb = DebugGetTimeoutBit(baselineTimestamp); + baselineTimestamp = DebugClearTimeoutBit(baselineTimestamp); + int64_t baseline_diff = Distance(baselineTimestamp, gpuZoneEndTimestamp); + int64_t timeline_diff = Distance(m_latestKnownGpuTimestamp, gpuZoneEndTimestamp); + // attempt to detect "late" timestamps that got resolved after being dropped + // they should have a negative timeline_diff with absolute magnitude similar to the baseline difference + double drift_ratio = double(timeline_diff) / double(baseline_diff); + if (sbtb && (baseline_diff > 0) && (timeline_diff < 0)) { + TracyD3D12Assert(drift_ratio < -10.0); + } + bool anomaly = drift_ratio < -10.0; + bool dropped = false; + if ((baseline_diff <= 0) || anomaly /* || (timeline_diff < -1'000'000'000)*/) { auto age = now - ini; if (age < timeout) @@ -348,20 +380,24 @@ namespace tracy //gpuZoneBeginTimestamp = baselineTimestamp; gpuZoneBeginTimestamp = m_latestKnownGpuTimestamp; gpuZoneEndTimestamp = gpuZoneBeginTimestamp; // 0ns + dropped = true; } EmitGpuTime(gpuZoneBeginTimestamp, queryId); EmitGpuTime(gpuZoneEndTimestamp, queryId+1); + if (Distance(m_latestKnownGpuTimestamp, gpuZoneEndTimestamp) > 0) + m_latestKnownGpuTimestamp = gpuZoneEndTimestamp; + + if (dropped) + gpuZoneEndTimestamp = DebugSetTimeoutBit(gpuZoneEndTimestamp); m_shadowBuffer[queryId+0] = gpuZoneEndTimestamp; m_shadowBuffer[queryId+1] = gpuZoneEndTimestamp; m_queryRequestTime[queryId+0] = AgeTime::max(); m_queryRequestTime[queryId+1] = AgeTime::max(); - if (Distance(m_latestKnownGpuTimestamp, gpuZoneEndTimestamp) > 0) - m_latestKnownGpuTimestamp = gpuZoneEndTimestamp; // move the goalpost: NextQueryId() can now reuse the query pair - m_previousCheckpoint.store(i+2); + m_previousCheckpoint.store(i+2); // (implicit memory release) } UnmapTimestampBuffer(timestampBuffer); @@ -419,6 +455,7 @@ namespace tracy // TODO: decide what to do when "full" // (maybe a loop with a few iterations attempting to Collect() queries // and if it fails, return some "invalid query" id) + DebugBreak(); } const uint32_t queryId = RingIndex(seqIdx); @@ -467,6 +504,7 @@ namespace tracy return m_contextId; } }; +#pragma optimize ( "", on ) class D3D12ZoneScope { @@ -483,7 +521,7 @@ namespace tracy const bool transientZone = srcLocation == nullptr; uint64_t srcLocationAddr = reinterpret_cast( srcLocation ); - QueueItem* item; + QueueItem* item = nullptr; QueueType itemType; if( transientZone ) { diff --git a/server/TracyWorker.cpp b/server/TracyWorker.cpp index b7055d22..38dc5477 100644 --- a/server/TracyWorker.cpp +++ b/server/TracyWorker.cpp @@ -5956,21 +5956,45 @@ void Worker::ProcessGpuTime( const QueueGpuTime& ev ) //DebugDump(ev, m_refTimeGpu); - int64_t tgpu = RefTime( m_refTimeGpu, ev.gpuTime ); - if( tgpu < ctx->lastGpuTime - ( 1u << 31 ) ) + if (ev.gpuTime < -1'000'000'000) + __debugbreak(); + + int64_t tgpu0 = RefTime( m_refTimeGpu, ev.gpuTime ); + +#if 0 + int64_t tgpu = tgpu0; +#else + int64_t tgpu1 = tgpu0; + bool wraparound = false; + auto lastGpuTime0 = ctx->lastGpuTime; + auto overflow0 = ctx->overflow; + auto overflowMul0 = ctx->overflowMul; + // This overflow logic is for detecting backward time jumps due to asynchronous GPU scheduling. + // (e.g. when tasks are scheduled by the GPU, instead of being forced to execute in queue order) + // it gives a grace period of 2^31 ticks (about 2s when 1 tick = 1 ns) + if(tgpu1 < ctx->lastGpuTime - ( 1u << 31 ) ) { + wraparound = true; if( ctx->overflow == 0 ) { ctx->overflow = uint64_t( 1 ) << ( 64 - TracyLzcnt( ctx->lastGpuTime ) ); } ctx->overflowMul++; } - ctx->lastGpuTime = tgpu; + ctx->lastGpuTime = tgpu1; + auto lastGpuTime1 = ctx->lastGpuTime; + auto overflow1 = ctx->overflow; + auto overflowMul1 = ctx->overflowMul; + + int64_t tgpu2 = tgpu1; if( ctx->overflow != 0 ) { - tgpu += ctx->overflow * ctx->overflowMul; + tgpu2 += ctx->overflow * ctx->overflowMul; } + int64_t tgpu = tgpu2; +#endif + int64_t gpuTime; if( !ctx->hasPeriod ) { From d94c484adcda368053fa0c4164c9b3e9f4c7df72 Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Sat, 4 Apr 2026 13:54:10 -0700 Subject: [PATCH 31/67] adopting new producer-consumer "panic" strategy --- public/tracy/TracyD3D12.hpp | 144 ++++++++++++++++-------------------- 1 file changed, 63 insertions(+), 81 deletions(-) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index 94512821..a3afaac8 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -326,85 +326,75 @@ namespace tracy return; std::unique_lock lock (m_collectionMutex, std::adopt_lock); - AgeTime now = AgeTime::clock::now(); - - auto timeout = std::chrono::duration(TRACY_D3D12_TIMESTAMP_COLLECT_TIMEOUT); - //double relax = double(RingCapacity()) / (1 + Distance(m_previousCheckpoint, m_queryCounter)); - //relax = std::min(relax, 50.0); - //timeout *= relax; + uint64_t earliestTicket = m_previousCheckpoint; + uint64_t latestTicket = m_queryCounter; + if (Distance(earliestTicket, latestTicket) < 0) + return; UINT64* timestampBuffer = MapTimestampBuffer(); - uint64_t rangeBegin = m_previousCheckpoint; - uint64_t rangeEnd = m_queryCounter; - // iterate queries in pairs - for (uint64_t i = rangeBegin; i != rangeEnd; i += 2) + + while (Distance(earliestTicket, latestTicket) > RingSize()) { - uint32_t queryId = RingIndex(i); - - // WARN: reads from m_queryRequestTime[] here may race with writes in NextQueryID() - std::atomic_thread_fence(std::memory_order_acquire); - AgeTime ini = m_queryRequestTime[queryId+1]; - TracyD3D12Assert(ini != AgeTime::max()); - UINT64 gpuZoneBeginTimestamp = timestampBuffer[queryId]; - UINT64 gpuZoneEndTimestamp = timestampBuffer[queryId+1]; - TracyD3D12Assert(!DebugGetTimeoutBit(gpuZoneBeginTimestamp)); - TracyD3D12Assert(!DebugGetTimeoutBit(gpuZoneEndTimestamp)); - UINT64 baselineTimestamp = m_shadowBuffer[queryId+1]; - bool sbtb = DebugGetTimeoutBit(baselineTimestamp); - baselineTimestamp = DebugClearTimeoutBit(baselineTimestamp); - int64_t baseline_diff = Distance(baselineTimestamp, gpuZoneEndTimestamp); - int64_t timeline_diff = Distance(m_latestKnownGpuTimestamp, gpuZoneEndTimestamp); - // attempt to detect "late" timestamps that got resolved after being dropped - // they should have a negative timeline_diff with absolute magnitude similar to the baseline difference - double drift_ratio = double(timeline_diff) / double(baseline_diff); - if (sbtb && (baseline_diff > 0) && (timeline_diff < 0)) { - TracyD3D12Assert(drift_ratio < -10.0); - } - bool anomaly = drift_ratio < -10.0; - bool dropped = false; - if ((baseline_diff <= 0) || anomaly /* || (timeline_diff < -1'000'000'000)*/) - { - auto age = now - ini; - if (age < timeout) - break; - // timeout reached: give up and drop it - TracyD3D12Debug( - ZoneScopedNC("tracy::D3D12QueueCtx::Collect::[drop]", Color::Red4); - ZoneValue(int64_t(queryId)); - TracyPlot("TracyD3D12|timeout", float(0)); - TracyPlot("TracyD3D12|timeout", float(timeout.count())); - TracyPlot("TracyD3D12|timeout", float(0)); - ); - // emit a "bogus" GpuTime to avoid problems with the internal - // zone tracking and matching logic in the server/profiler - //gpuZoneBeginTimestamp = baselineTimestamp; - gpuZoneBeginTimestamp = m_latestKnownGpuTimestamp; - gpuZoneEndTimestamp = gpuZoneBeginTimestamp; // 0ns - dropped = true; - } - - EmitGpuTime(gpuZoneBeginTimestamp, queryId); - EmitGpuTime(gpuZoneEndTimestamp, queryId+1); - - if (Distance(m_latestKnownGpuTimestamp, gpuZoneEndTimestamp) > 0) - m_latestKnownGpuTimestamp = gpuZoneEndTimestamp; - - if (dropped) - gpuZoneEndTimestamp = DebugSetTimeoutBit(gpuZoneEndTimestamp); - m_shadowBuffer[queryId+0] = gpuZoneEndTimestamp; - m_shadowBuffer[queryId+1] = gpuZoneEndTimestamp; - m_queryRequestTime[queryId+0] = AgeTime::max(); - m_queryRequestTime[queryId+1] = AgeTime::max(); - - // move the goalpost: NextQueryId() can now reuse the query pair - m_previousCheckpoint.store(i+2); // (implicit memory release) + uint64_t ticket = earliestTicket; + DropTimestamp(earliestTicket, timestampBuffer); + earliestTicket += 2; + m_previousCheckpoint.store(earliestTicket); } + + for (uint64_t ticket = earliestTicket; ticket != latestTicket; ticket += 2) + { + if (!ResolveTimestamp(ticket, timestampBuffer)) + break; + m_previousCheckpoint.store(ticket + 2); + } + UnmapTimestampBuffer(timestampBuffer); RecalibrateClocks(); } private: + bool ResolveTimestamp(uint64_t ticket, UINT64* timestampBuffer) + { + uint32_t queryId = RingIndex(ticket); + UINT64 gpuZoneBeginTimestamp = timestampBuffer[queryId]; + UINT64 gpuZoneEndTimestamp = timestampBuffer[queryId+1]; + UINT64 baselineTimestamp = m_shadowBuffer[queryId+1]; + int64_t baseline_diff = Distance(baselineTimestamp, gpuZoneEndTimestamp); + if (baseline_diff <= 0) + return false; + EmitGpuTime(gpuZoneBeginTimestamp, queryId); + EmitGpuTime(gpuZoneEndTimestamp, queryId+1); + m_shadowBuffer[queryId+0] = gpuZoneEndTimestamp; + m_shadowBuffer[queryId+1] = gpuZoneEndTimestamp; + if (Distance(m_latestKnownGpuTimestamp, gpuZoneEndTimestamp) > 0) + m_latestKnownGpuTimestamp = gpuZoneEndTimestamp; + return true; + } + void DropTimestamp(uint64_t ticket, UINT64* timestampBuffer) + { + if (ResolveTimestamp(ticket, timestampBuffer)) + return; + // emit a "bogus" GpuTime to avoid problems with the internal + // zone tracking and matching logic in the server/profiler + uint32_t queryId = RingIndex(ticket); + uint64_t latestCpuTimestamp; + uint64_t latestGpuTimestamp; + if (FAILED(m_queue->GetClockCalibration(&latestGpuTimestamp, &latestCpuTimestamp))) + TracyD3D12Panic("Failed to get queue clock calibration.", return); + EmitGpuTime(latestGpuTimestamp, queryId); + EmitGpuTime(latestGpuTimestamp, queryId+1); + m_shadowBuffer[queryId+0] = latestGpuTimestamp; + m_shadowBuffer[queryId+1] = latestGpuTimestamp; + TracyD3D12Debug( + ZoneScopedNC("tracy::D3D12QueueCtx::Collect::[drop]", Color::Red4); + ZoneValue(int64_t(queryId)); + //TracyPlot("TracyD3D12|timeout", float(0)); + //TracyPlot("TracyD3D12|timeout", float(timeout.count())); + //TracyPlot("TracyD3D12|timeout", float(0)); + ); + } + tracy_force_inline void EmitGpuTime(UINT64 gpuTimestamp, uint32_t queryId) { auto* item = Profiler::QueueSerial(); @@ -446,24 +436,16 @@ namespace tracy // with the late query. Given that Collect() may attempt to inspect the new // query pair immediately upon m_queryCounter being incremented, the "late" // timestamp value may be collected as if it belonged to the the new query. - const uint64_t seqIdx = m_queryCounter.fetch_add(2, std::memory_order_relaxed); - if (Distance(m_previousCheckpoint, seqIdx) >= RingSize()) + const uint64_t ticket = m_queryCounter.fetch_add(2, std::memory_order_relaxed); + while (Distance(m_previousCheckpoint, ticket) >= RingSize()) { ZoneScopedC(Color::Red4); ZoneValue(int64_t(m_contextId)); TracyD3D12Panic("Submitted too many GPU queries!"); - // TODO: decide what to do when "full" - // (maybe a loop with a few iterations attempting to Collect() queries - // and if it fails, return some "invalid query" id) - DebugBreak(); + Collect(); } - const uint32_t queryId = RingIndex(seqIdx); - // WARN: writes to m_queryRequestTime[] here race with reads in Collect() - AgeTime now = AgeTime::clock::now(); - m_queryRequestTime[queryId+0] = now; - m_queryRequestTime[queryId+1] = now; - std::atomic_thread_fence(std::memory_order_release); + const uint32_t queryId = RingIndex(ticket); TracyD3D12Debug( TracyAllocN(reinterpret_cast(uintptr_t(queryId+0)), 1, "TracyD3D12|Query"); TracyAllocN(reinterpret_cast(uintptr_t(queryId+1)), 1, "TracyD3D12|Query"); From 7e70154e4ed5d73b20c975bd573b3ccd76b548fb Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Sat, 4 Apr 2026 14:02:25 -0700 Subject: [PATCH 32/67] cleanup --- public/tracy/TracyD3D12.hpp | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index a3afaac8..ca0fa27a 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -89,10 +89,7 @@ namespace tracy uint32_t m_queryLimit = 0; - using AgeTime = std::chrono::high_resolution_clock::time_point; - std::vector m_queryRequestTime; std::vector m_shadowBuffer; - UINT64 m_latestKnownGpuTimestamp = 0; UINT64 m_prevCalibrationTicksCPU = 0; @@ -223,9 +220,7 @@ namespace tracy TracyD3D12Panic("Failed to get queue clock calibration.", return); } - m_latestKnownGpuTimestamp = gpuTimestamp; - m_queryRequestTime.resize(m_queryLimit, AgeTime::clock::now()); - m_shadowBuffer.resize(m_queryLimit, m_latestKnownGpuTimestamp); + m_shadowBuffer.resize(m_queryLimit, gpuTimestamp); // Save the device cpu timestamp, not the profiler's timestamp. m_prevCalibrationTicksCPU = cpuTimestamp; @@ -333,7 +328,7 @@ namespace tracy UINT64* timestampBuffer = MapTimestampBuffer(); - while (Distance(earliestTicket, latestTicket) > RingSize()) + while (Distance(earliestTicket, latestTicket) > RingCapacity()) { uint64_t ticket = earliestTicket; DropTimestamp(earliestTicket, timestampBuffer); @@ -344,6 +339,7 @@ namespace tracy for (uint64_t ticket = earliestTicket; ticket != latestTicket; ticket += 2) { if (!ResolveTimestamp(ticket, timestampBuffer)) + // TODO: preemptive timeout policy break; m_previousCheckpoint.store(ticket + 2); } @@ -367,8 +363,6 @@ namespace tracy EmitGpuTime(gpuZoneEndTimestamp, queryId+1); m_shadowBuffer[queryId+0] = gpuZoneEndTimestamp; m_shadowBuffer[queryId+1] = gpuZoneEndTimestamp; - if (Distance(m_latestKnownGpuTimestamp, gpuZoneEndTimestamp) > 0) - m_latestKnownGpuTimestamp = gpuZoneEndTimestamp; return true; } void DropTimestamp(uint64_t ticket, UINT64* timestampBuffer) @@ -408,14 +402,14 @@ namespace tracy ); } - tracy_force_inline uint32_t RingSize() const + tracy_force_inline uint32_t RingCapacity() const { return m_queryLimit; } tracy_force_inline uint32_t RingIndex(uint64_t logicalSlot) const { - return static_cast(logicalSlot % RingSize()); + return static_cast(logicalSlot % RingCapacity()); } tracy_force_inline static int64_t Distance(uint64_t begin, uint64_t end) @@ -437,7 +431,7 @@ namespace tracy // query pair immediately upon m_queryCounter being incremented, the "late" // timestamp value may be collected as if it belonged to the the new query. const uint64_t ticket = m_queryCounter.fetch_add(2, std::memory_order_relaxed); - while (Distance(m_previousCheckpoint, ticket) >= RingSize()) + while (Distance(m_previousCheckpoint, ticket) >= RingCapacity()) { ZoneScopedC(Color::Red4); ZoneValue(int64_t(m_contextId)); From 60935f2a253d98866a60a48bd7d6b18caf91fab9 Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Sat, 4 Apr 2026 14:18:29 -0700 Subject: [PATCH 33/67] refactoring --- public/tracy/TracyD3D12.hpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index ca0fa27a..2b30ef6e 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -332,8 +332,7 @@ namespace tracy { uint64_t ticket = earliestTicket; DropTimestamp(earliestTicket, timestampBuffer); - earliestTicket += 2; - m_previousCheckpoint.store(earliestTicket); + earliestTicket = RetireTicket(earliestTicket); } for (uint64_t ticket = earliestTicket; ticket != latestTicket; ticket += 2) @@ -341,7 +340,7 @@ namespace tracy if (!ResolveTimestamp(ticket, timestampBuffer)) // TODO: preemptive timeout policy break; - m_previousCheckpoint.store(ticket + 2); + RetireTicket(ticket); } UnmapTimestampBuffer(timestampBuffer); @@ -418,6 +417,14 @@ namespace tracy return static_cast(end - begin); } + uint64_t RetireTicket(uint64_t ticket) + { + assert(m_previousCheckpoint == ticket); + ticket += 2; + m_previousCheckpoint.store(ticket); + return ticket; + } + tracy_force_inline uint32_t NextQueryId() { // WARN: the moment m_queryCounter is incremented, Collect() will have instant From 4f9de46d3dc4c01810042ca11a6f4f7b5affa986 Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Sun, 5 Apr 2026 13:02:28 -0700 Subject: [PATCH 34/67] cleanup --- public/tracy/TracyD3D12.hpp | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index 2b30ef6e..7d2da80c 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -286,27 +286,6 @@ namespace tracy SubmitQueueItem(item); } - #define TracyD3D12Assert(expr) if (!(expr)) __debugbreak(); - - bool DebugGetTimeoutBit(UINT64 timestamp) { - constexpr UINT64 mask = 0x8000'0000'0000'0000llu; - bool bit = (timestamp & mask) != 0llu; - return bit; - } - - UINT64 DebugSetTimeoutBit(UINT64 timestamp) { - TracyD3D12Assert(!DebugGetTimeoutBit(timestamp)); - constexpr UINT64 mask = 0x8000'0000'0000'0000llu; - timestamp = timestamp | mask; - return timestamp; - } - - UINT64 DebugClearTimeoutBit(UINT64 timestamp) { - constexpr UINT64 mask = 0x8000'0000'0000'0000llu; - timestamp = timestamp & ~mask; - return timestamp; - } - void Collect() { #ifdef TRACY_ON_DEMAND From ea7fb726ac760f25e9f3c5b769239f807811e084 Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Mon, 6 Apr 2026 07:19:59 -0700 Subject: [PATCH 35/67] refactoring Collect --- public/tracy/TracyD3D12.hpp | 57 ++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index 7d2da80c..0fb3cf85 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -291,18 +291,24 @@ namespace tracy #ifdef TRACY_ON_DEMAND if (!GetProfiler().IsConnected()) return; #endif + // Only one thread is allowed to collect timestamps at any given time + // but there's no need to block contending threads + if (!m_collectionMutex.try_lock()) return; + std::unique_lock lock (m_collectionMutex, std::adopt_lock); + Collect(lock); + } + + private: + void Collect(std::unique_lock& lock) + { ZoneScopedC(Color::Red4); ZoneValue(uint64_t(m_contextId)); - // Only one thread is allowed to collect timestamps at any given time - // but there's no need to block contending threads - if (!m_collectionMutex.try_lock()) - return; - std::unique_lock lock (m_collectionMutex, std::adopt_lock); - uint64_t earliestTicket = m_previousCheckpoint; uint64_t latestTicket = m_queryCounter; - if (Distance(earliestTicket, latestTicket) < 0) + ZoneValue(uint64_t(earliestTicket)); + ZoneValue(uint64_t(latestTicket)); + if (Distance(earliestTicket, latestTicket) <= 0) return; UINT64* timestampBuffer = MapTimestampBuffer(); @@ -314,20 +320,22 @@ namespace tracy earliestTicket = RetireTicket(earliestTicket); } - for (uint64_t ticket = earliestTicket; ticket != latestTicket; ticket += 2) + for (earliestTicket; earliestTicket != latestTicket; earliestTicket += 2) { - if (!ResolveTimestamp(ticket, timestampBuffer)) + if (!ResolveTimestamp(earliestTicket, timestampBuffer)) // TODO: preemptive timeout policy break; - RetireTicket(ticket); + RetireTicket(earliestTicket); } + // TODO: consider retiring the whole batch of tickets at once + // m_previousCheckpoint.store(earliestTicket); + UnmapTimestampBuffer(timestampBuffer); RecalibrateClocks(); } - private: bool ResolveTimestamp(uint64_t ticket, UINT64* timestampBuffer) { uint32_t queryId = RingIndex(ticket); @@ -337,12 +345,15 @@ namespace tracy int64_t baseline_diff = Distance(baselineTimestamp, gpuZoneEndTimestamp); if (baseline_diff <= 0) return false; + TracyD3D12Debug( ZoneScoped ); + TracyD3D12Debug( ZoneValue(int64_t(queryId)) ); EmitGpuTime(gpuZoneBeginTimestamp, queryId); EmitGpuTime(gpuZoneEndTimestamp, queryId+1); m_shadowBuffer[queryId+0] = gpuZoneEndTimestamp; m_shadowBuffer[queryId+1] = gpuZoneEndTimestamp; return true; } + void DropTimestamp(uint64_t ticket, UINT64* timestampBuffer) { if (ResolveTimestamp(ticket, timestampBuffer)) @@ -354,17 +365,14 @@ namespace tracy uint64_t latestGpuTimestamp; if (FAILED(m_queue->GetClockCalibration(&latestGpuTimestamp, &latestCpuTimestamp))) TracyD3D12Panic("Failed to get queue clock calibration.", return); + TracyD3D12Debug( ZoneScopedC(Color::Red4) ); + TracyD3D12Debug( ZoneValue(int64_t(queryId)) ); + TracyD3D12Debug( TracyPlot("TracyD3D12|timeout", float(0)) ); + TracyD3D12Debug( TracyPlot("TracyD3D12|timeout", float(1)) ); EmitGpuTime(latestGpuTimestamp, queryId); EmitGpuTime(latestGpuTimestamp, queryId+1); - m_shadowBuffer[queryId+0] = latestGpuTimestamp; - m_shadowBuffer[queryId+1] = latestGpuTimestamp; - TracyD3D12Debug( - ZoneScopedNC("tracy::D3D12QueueCtx::Collect::[drop]", Color::Red4); - ZoneValue(int64_t(queryId)); - //TracyPlot("TracyD3D12|timeout", float(0)); - //TracyPlot("TracyD3D12|timeout", float(timeout.count())); - //TracyPlot("TracyD3D12|timeout", float(0)); - ); + TracyD3D12Debug( TracyPlot("TracyD3D12|timeout", float(1)) ); + TracyD3D12Debug( TracyPlot("TracyD3D12|timeout", float(0)) ); } tracy_force_inline void EmitGpuTime(UINT64 gpuTimestamp, uint32_t queryId) @@ -375,6 +383,7 @@ namespace tracy MemWrite(&item->gpuTime.queryId, static_cast(queryId)); MemWrite(&item->gpuTime.context, GetId()); Profiler::QueueSerialFinish(); + m_shadowBuffer[queryId] = gpuTimestamp; TracyD3D12Debug( TracyFreeN(reinterpret_cast(uintptr_t(queryId)), "TracyD3D12|Query"); ); @@ -417,12 +426,14 @@ namespace tracy // query pair immediately upon m_queryCounter being incremented, the "late" // timestamp value may be collected as if it belonged to the the new query. const uint64_t ticket = m_queryCounter.fetch_add(2, std::memory_order_relaxed); - while (Distance(m_previousCheckpoint, ticket) >= RingCapacity()) + if (Distance(m_previousCheckpoint, ticket) >= RingCapacity()) { ZoneScopedC(Color::Red4); ZoneValue(int64_t(m_contextId)); - TracyD3D12Panic("Submitted too many GPU queries!"); - Collect(); + ZoneValue(int64_t(RingIndex(ticket))); + TracyD3D12Panic("Too many pending GPU queries: stalling!"); + while (Distance(m_previousCheckpoint, ticket) >= RingCapacity()) + Collect(); } const uint32_t queryId = RingIndex(ticket); From 601c55bad2715b2b4df36df4b2aadc3c07f01d2f Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Mon, 6 Apr 2026 07:21:51 -0700 Subject: [PATCH 36/67] track latest known GPU timestamp (to avoid "time-travel" later on in the server) --- public/tracy/TracyD3D12.hpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index 0fb3cf85..6c218dda 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -90,6 +90,7 @@ namespace tracy uint32_t m_queryLimit = 0; std::vector m_shadowBuffer; + UINT64 m_latestKnownGpuTimestamp = 0; UINT64 m_prevCalibrationTicksCPU = 0; @@ -221,6 +222,7 @@ namespace tracy } m_shadowBuffer.resize(m_queryLimit, gpuTimestamp); + m_latestKnownGpuTimestamp = gpuTimestamp; // Save the device cpu timestamp, not the profiler's timestamp. m_prevCalibrationTicksCPU = cpuTimestamp; @@ -349,8 +351,8 @@ namespace tracy TracyD3D12Debug( ZoneValue(int64_t(queryId)) ); EmitGpuTime(gpuZoneBeginTimestamp, queryId); EmitGpuTime(gpuZoneEndTimestamp, queryId+1); - m_shadowBuffer[queryId+0] = gpuZoneEndTimestamp; - m_shadowBuffer[queryId+1] = gpuZoneEndTimestamp; + if (Distance(m_latestKnownGpuTimestamp, gpuZoneEndTimestamp) > 0) + m_latestKnownGpuTimestamp = gpuZoneEndTimestamp; return true; } @@ -361,10 +363,7 @@ namespace tracy // emit a "bogus" GpuTime to avoid problems with the internal // zone tracking and matching logic in the server/profiler uint32_t queryId = RingIndex(ticket); - uint64_t latestCpuTimestamp; - uint64_t latestGpuTimestamp; - if (FAILED(m_queue->GetClockCalibration(&latestGpuTimestamp, &latestCpuTimestamp))) - TracyD3D12Panic("Failed to get queue clock calibration.", return); + uint64_t latestGpuTimestamp = m_latestKnownGpuTimestamp; TracyD3D12Debug( ZoneScopedC(Color::Red4) ); TracyD3D12Debug( ZoneValue(int64_t(queryId)) ); TracyD3D12Debug( TracyPlot("TracyD3D12|timeout", float(0)) ); From 952da01f33f4bc55d3a80180fd3b94783e5bdf89 Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Mon, 6 Apr 2026 07:22:40 -0700 Subject: [PATCH 37/67] comments about ResolveQueryData --- public/tracy/TracyD3D12.hpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index 6c218dda..ef215de3 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -578,7 +578,6 @@ namespace tracy if (!m_active) return; const auto queryId = m_queryId + 1; // Our end query slot is immediately after the begin slot. - m_cmdList->EndQuery(m_ctx->m_queryHeap, D3D12_QUERY_TYPE_TIMESTAMP, queryId); auto* item = Profiler::QueueSerial(); MemWrite(&item->hdr.type, QueueType::GpuZoneEndSerial); @@ -588,7 +587,11 @@ namespace tracy MemWrite(&item->gpuZoneEnd.context, m_ctx->GetId()); Profiler::QueueSerialFinish(); - // TODO: maybe move this to Collect()? + m_cmdList->EndQuery(m_ctx->m_queryHeap, D3D12_QUERY_TYPE_TIMESTAMP, queryId); + // NOTE: can't quite move this ResolveQueryData() call to Collect()... + // If a command is instrumented, but the command list is never submitted + // for execution, we should not ask the GPU to resolve the corresponding + // queries (we'll get stale/garbage data if we do so) m_cmdList->ResolveQueryData(m_ctx->m_queryHeap, D3D12_QUERY_TYPE_TIMESTAMP, m_queryId, 2, m_ctx->m_readbackBuffer, m_queryId * sizeof(uint64_t)); } }; From 303680257f253af97ad3994b7c15483c9ff2977b Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Mon, 6 Apr 2026 07:22:49 -0700 Subject: [PATCH 38/67] debugging --- public/tracy/TracyD3D12.hpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index ef215de3..2c55da68 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -532,6 +532,15 @@ namespace tracy Profiler::QueueSerialFinish(); } + tracy_force_inline bool ShouldSkipQuery(uint32_t queryId) { + static constexpr uint32_t ignoreList[] = { 6000 }; + constexpr auto begin = std::begin(ignoreList); + constexpr auto end = std::end(ignoreList); + auto it = std::find(begin, end, queryId); + bool contains = (it != end); + return contains; + } + tracy_force_inline D3D12ZoneScope(D3D12QueueCtx* ctx, ID3D12GraphicsCommandList* cmdList, bool active) #ifdef TRACY_ON_DEMAND : m_active(active&& GetProfiler().IsConnected()) @@ -545,6 +554,7 @@ namespace tracy m_cmdList = cmdList; m_queryId = m_ctx->NextQueryId(); + if (ShouldSkipQuery(m_queryId)) return; m_cmdList->EndQuery(m_ctx->m_queryHeap, D3D12_QUERY_TYPE_TIMESTAMP, m_queryId); } @@ -587,6 +597,7 @@ namespace tracy MemWrite(&item->gpuZoneEnd.context, m_ctx->GetId()); Profiler::QueueSerialFinish(); + if (ShouldSkipQuery(m_queryId)) return; m_cmdList->EndQuery(m_ctx->m_queryHeap, D3D12_QUERY_TYPE_TIMESTAMP, queryId); // NOTE: can't quite move this ResolveQueryData() call to Collect()... // If a command is instrumented, but the command list is never submitted From 760eeb40e8f1a2a119a827b851f0e90b4228771d Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Mon, 6 Apr 2026 16:49:09 -0700 Subject: [PATCH 39/67] cleanup includes --- public/tracy/TracyD3D12.hpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index 2c55da68..965340d4 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -33,11 +33,8 @@ using TracyD3D12Ctx = void*; #else #include "Tracy.hpp" -#include "../client/TracyProfiler.hpp" -#include "../client/TracyCallstack.hpp" #include -#include #include #include #include From 2b4e5250ed40979b812ac8d57d7545baa00e63ab Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Mon, 6 Apr 2026 17:01:00 -0700 Subject: [PATCH 40/67] refactoring debug macros --- public/tracy/TracyD3D12.hpp | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index 965340d4..33b78abf 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -47,17 +47,22 @@ using TracyD3D12Ctx = void*; #define TRACY_D3D12_TIMESTAMP_COLLECT_TIMEOUT 0.005f #endif -#define TRACY_D3D12_DEBUG (1) +#define TRACY_D3D12_DEBUG_LEVEL (1) #define TRACY_D3D12_PERSISTENT_TIMESTAMP_BUFFER (1) -#define TracyD3D12Panic(msg, ...) do { assert(false && "TracyD3D12: " msg); tracy::Profiler::LogString( tracy::MessageSourceType::Tracy, tracy::MessageSeverity::Error, tracy::Color::Red4, 0, msg ); __VA_ARGS__; } while(false); - -#if TRACY_D3D12_DEBUG -#define TracyD3D12Debug(...) __VA_ARGS__ +#if TRACY_D3D12_DEBUG_LEVEL +#define TracyD3D12Debug(...) __VA_ARGS__; +#define TracyD3D12Break() __debugbreak(); // MSVC +#define TracyD3D12Assert(predicate, ...) if (predicate) {} else { __VA_ARGS__; TracyD3D12Break(); } #else #define TracyD3D12Debug(...) +#define TracyD3D12Break() +#define TracyD3D12Assert(predicate, ...) assert(predicate); #endif +#define TracyD3D12Log(severity, msg) tracy::Profiler::LogString( tracy::MessageSourceType::Tracy, tracy::MessageSeverity::severity, tracy::Color::Red4, 0, msg ); +#define TracyD3D12Panic(msg, ...) do { TracyD3D12Log(Error, msg); TracyD3D12Assert(false && "TracyD3D12: " msg); __VA_ARGS__; } while(false); + namespace tracy { @@ -301,7 +306,8 @@ namespace tracy void Collect(std::unique_lock& lock) { ZoneScopedC(Color::Red4); - ZoneValue(uint64_t(m_contextId)); + TracyD3D12Assert( lock.owns_lock() ); + TracyD3D12Debug( ZoneValue(uint64_t(m_contextId)) ); uint64_t earliestTicket = m_previousCheckpoint; uint64_t latestTicket = m_queryCounter; @@ -403,7 +409,7 @@ namespace tracy uint64_t RetireTicket(uint64_t ticket) { - assert(m_previousCheckpoint == ticket); + TracyD3D12Assert( m_previousCheckpoint == ticket ); ticket += 2; m_previousCheckpoint.store(ticket); return ticket; @@ -454,7 +460,7 @@ namespace tracy } UINT64* timestampBuffer = static_cast(readbackBufferMapping); #if TRACY_D3D12_PERSISTENT_TIMESTAMP_BUFFER - assert(m_persistentTimestampBuffer == nullptr); + TracyD3D12Assert( m_persistentTimestampBuffer == nullptr ); m_persistentTimestampBuffer = timestampBuffer; #endif return timestampBuffer; @@ -624,6 +630,10 @@ namespace tracy #undef TRACY_D3D12_PERSISTENT_TIMESTAMP_BUFFER #undef TRACY_D3D12_TIMESTAMP_COLLECT_TIMEOUT #undef TracyD3D12Panic +#undef TracyD3D12Log +#undef TracyD3D12Assert +#undef TracyD3D12Break +#undef TracyD3D12Debug using TracyD3D12Ctx = tracy::D3D12QueueCtx*; From 0fdc70a75f6f1d08d5a998abc3fe79321d91c438 Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Mon, 6 Apr 2026 17:02:55 -0700 Subject: [PATCH 41/67] implementing Drain/Wait --- public/tracy/TracyD3D12.hpp | 61 +++++++++++++++++++++++++++++-------- 1 file changed, 49 insertions(+), 12 deletions(-) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index 33b78abf..a071195b 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -258,14 +258,13 @@ namespace tracy // NOTE: even with Signal(), there are no guarantees the queries were sent // to the GPU for execution, so the Signal() does not give us much "signal" - // attempt to collect all pending queries up to the latest known query + // collect all pending queries up to the latest known query uint64_t latestQuery = m_queryCounter; - while (Distance(m_previousCheckpoint, latestQuery) > 0) - Collect(); + Drain(latestQuery, 200); // if the client is still pushing queries past the latest checkpoint above, // assume there's a bug in the client, and ignore them (don't collect) - if (latestQuery != m_queryCounter) + if (Distance(latestQuery, m_queryCounter) > 0) TracyD3D12Panic("client is still pushing queries."); #if TRACY_D3D12_PERSISTENT_TIMESTAMP_BUFFER @@ -299,11 +298,11 @@ namespace tracy // but there's no need to block contending threads if (!m_collectionMutex.try_lock()) return; std::unique_lock lock (m_collectionMutex, std::adopt_lock); - Collect(lock); + Collect(lock, m_previousCheckpoint); } private: - void Collect(std::unique_lock& lock) + void Collect(std::unique_lock& lock, uint64_t targetQueryTicket) { ZoneScopedC(Color::Red4); TracyD3D12Assert( lock.owns_lock() ); @@ -320,7 +319,11 @@ namespace tracy while (Distance(earliestTicket, latestTicket) > RingCapacity()) { - uint64_t ticket = earliestTicket; + + // Now ensure that the target query ticket gets processed + TracyD3D12Assert( Distance(targetQueryTicket, endTicket) > 0 ); + while (Distance(earliestTicket, targetQueryTicket) >= 0) + { DropTimestamp(earliestTicket, timestampBuffer); earliestTicket = RetireTicket(earliestTicket); } @@ -341,7 +344,44 @@ namespace tracy RecalibrateClocks(); } - bool ResolveTimestamp(uint64_t ticket, UINT64* timestampBuffer) + bool Wait(uint64_t queryTicket, uint64_t timeout_ms) + { + ZoneScopedC(Color::Red4); + TracyD3D12Debug( + ZoneValue(uint64_t(m_contextId)); + ZoneValue(queryTicket); + ZoneValue(int64_t(RingIndex(queryTicket))); + ); + if (Distance(m_previousCheckpoint, queryTicket) < 0) + return true; + auto Now = GetTickCount64; + auto ini = Now(); + while (Distance(m_previousCheckpoint, queryTicket) >= 0) + { + Collect(); + auto now = Now(); + if ((now - ini) >= timeout_ms) + break; + } + return (Distance(m_previousCheckpoint, queryTicket) < 0); + } + + void Drain(uint64_t queryTicket, uint64_t gracePeriod_ms) + { + ZoneScopedC(Color::Red4); + TracyD3D12Debug( + ZoneValue(uint64_t(m_contextId)); + ZoneValue(queryTicket); + ZoneValue(int64_t(RingIndex(queryTicket))); + ); + if (Wait(queryTicket, gracePeriod_ms) == false) + { + std::unique_lock lock (m_collectionMutex); + Collect(lock, queryTicket); + } + TracyD3D12Assert( Distance(m_previousCheckpoint, queryTicket) < 0 ); + } + { uint32_t queryId = RingIndex(ticket); UINT64 gpuZoneBeginTimestamp = timestampBuffer[queryId]; @@ -431,11 +471,8 @@ namespace tracy if (Distance(m_previousCheckpoint, ticket) >= RingCapacity()) { ZoneScopedC(Color::Red4); - ZoneValue(int64_t(m_contextId)); - ZoneValue(int64_t(RingIndex(ticket))); TracyD3D12Panic("Too many pending GPU queries: stalling!"); - while (Distance(m_previousCheckpoint, ticket) >= RingCapacity()) - Collect(); + Drain(ticket, 0); } const uint32_t queryId = RingIndex(ticket); From 77be1c8616b5d732b321b706fd094daa3ccbe9e7 Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Mon, 6 Apr 2026 17:03:12 -0700 Subject: [PATCH 42/67] minor refactoring --- public/tracy/TracyD3D12.hpp | 41 +++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index a071195b..4f63821f 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -309,16 +309,20 @@ namespace tracy TracyD3D12Debug( ZoneValue(uint64_t(m_contextId)) ); uint64_t earliestTicket = m_previousCheckpoint; - uint64_t latestTicket = m_queryCounter; - ZoneValue(uint64_t(earliestTicket)); - ZoneValue(uint64_t(latestTicket)); - if (Distance(earliestTicket, latestTicket) <= 0) + uint64_t endTicket = m_queryCounter; + TracyD3D12Debug( ZoneValue(earliestTicket) ); + TracyD3D12Debug( ZoneValue(endTicket) ); + if (Distance(earliestTicket, endTicket) <= 0) return; UINT64* timestampBuffer = MapTimestampBuffer(); - while (Distance(earliestTicket, latestTicket) > RingCapacity()) + // Panic! Too many queries: start dropping aggressively! + while (Distance(earliestTicket, endTicket) > RingCapacity()) { + DropTimestamp(earliestTicket, timestampBuffer); + earliestTicket = RetireTicket(earliestTicket); + } // Now ensure that the target query ticket gets processed TracyD3D12Assert( Distance(targetQueryTicket, endTicket) > 0 ); @@ -328,17 +332,15 @@ namespace tracy earliestTicket = RetireTicket(earliestTicket); } - for (earliestTicket; earliestTicket != latestTicket; earliestTicket += 2) + // Finally, scan progress on remaining query tickets + for (earliestTicket; earliestTicket != endTicket; earliestTicket += 2) { if (!ResolveTimestamp(earliestTicket, timestampBuffer)) - // TODO: preemptive timeout policy + // TODO: implement preemptive timeout policy break; RetireTicket(earliestTicket); } - // TODO: consider retiring the whole batch of tickets at once - // m_previousCheckpoint.store(earliestTicket); - UnmapTimestampBuffer(timestampBuffer); RecalibrateClocks(); @@ -382,8 +384,9 @@ namespace tracy TracyD3D12Assert( Distance(m_previousCheckpoint, queryTicket) < 0 ); } + bool ResolveTimestamp(uint64_t queryTicket, UINT64* timestampBuffer) { - uint32_t queryId = RingIndex(ticket); + uint32_t queryId = RingIndex(queryTicket); UINT64 gpuZoneBeginTimestamp = timestampBuffer[queryId]; UINT64 gpuZoneEndTimestamp = timestampBuffer[queryId+1]; UINT64 baselineTimestamp = m_shadowBuffer[queryId+1]; @@ -391,6 +394,7 @@ namespace tracy if (baseline_diff <= 0) return false; TracyD3D12Debug( ZoneScoped ); + TracyD3D12Debug( ZoneValue(queryTicket) ); TracyD3D12Debug( ZoneValue(int64_t(queryId)) ); EmitGpuTime(gpuZoneBeginTimestamp, queryId); EmitGpuTime(gpuZoneEndTimestamp, queryId+1); @@ -399,22 +403,23 @@ namespace tracy return true; } - void DropTimestamp(uint64_t ticket, UINT64* timestampBuffer) + void DropTimestamp(uint64_t queryTicket, UINT64* timestampBuffer) { - if (ResolveTimestamp(ticket, timestampBuffer)) + if (ResolveTimestamp(queryTicket, timestampBuffer)) return; // emit a "bogus" GpuTime to avoid problems with the internal // zone tracking and matching logic in the server/profiler - uint32_t queryId = RingIndex(ticket); + uint32_t queryId = RingIndex(queryTicket); uint64_t latestGpuTimestamp = m_latestKnownGpuTimestamp; TracyD3D12Debug( ZoneScopedC(Color::Red4) ); + TracyD3D12Debug( ZoneValue(queryTicket) ); TracyD3D12Debug( ZoneValue(int64_t(queryId)) ); - TracyD3D12Debug( TracyPlot("TracyD3D12|timeout", float(0)) ); - TracyD3D12Debug( TracyPlot("TracyD3D12|timeout", float(1)) ); + TracyD3D12Debug( TracyPlot("TracyD3D12|drop", float(0)) ); + TracyD3D12Debug( TracyPlot("TracyD3D12|drop", float(1)) ); EmitGpuTime(latestGpuTimestamp, queryId); EmitGpuTime(latestGpuTimestamp, queryId+1); - TracyD3D12Debug( TracyPlot("TracyD3D12|timeout", float(1)) ); - TracyD3D12Debug( TracyPlot("TracyD3D12|timeout", float(0)) ); + TracyD3D12Debug( TracyPlot("TracyD3D12|drop", float(1)) ); + TracyD3D12Debug( TracyPlot("TracyD3D12|drop", float(0)) ); } tracy_force_inline void EmitGpuTime(UINT64 gpuTimestamp, uint32_t queryId) From c35acce915135b35947ccb5938674ed7544c203b Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Tue, 7 Apr 2026 12:51:29 -0700 Subject: [PATCH 43/67] debugger check --- public/tracy/TracyD3D12.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index 4f63821f..4282425c 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -52,7 +52,7 @@ using TracyD3D12Ctx = void*; #if TRACY_D3D12_DEBUG_LEVEL #define TracyD3D12Debug(...) __VA_ARGS__; -#define TracyD3D12Break() __debugbreak(); // MSVC +#define TracyD3D12Break() if (IsDebuggerPresent()) __debugbreak() // MSVC #define TracyD3D12Assert(predicate, ...) if (predicate) {} else { __VA_ARGS__; TracyD3D12Break(); } #else #define TracyD3D12Debug(...) From f538ac49c6422acde77e7023b533e8ec1ea65e67 Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Tue, 7 Apr 2026 12:54:11 -0700 Subject: [PATCH 44/67] nomenclature --- public/tracy/TracyD3D12.hpp | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index 4282425c..fc20462b 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -259,12 +259,13 @@ namespace tracy // to the GPU for execution, so the Signal() does not give us much "signal" // collect all pending queries up to the latest known query - uint64_t latestQuery = m_queryCounter; - Drain(latestQuery, 200); + uint64_t endTicket = m_queryCounter; + uint64_t lastIssuedTicket = endTicket - 2; + Drain(lastIssuedTicket, 200); // if the client is still pushing queries past the latest checkpoint above, // assume there's a bug in the client, and ignore them (don't collect) - if (Distance(latestQuery, m_queryCounter) > 0) + if (Distance(endTicket, m_queryCounter) > 0) TracyD3D12Panic("client is still pushing queries."); #if TRACY_D3D12_PERSISTENT_TIMESTAMP_BUFFER @@ -298,7 +299,8 @@ namespace tracy // but there's no need to block contending threads if (!m_collectionMutex.try_lock()) return; std::unique_lock lock (m_collectionMutex, std::adopt_lock); - Collect(lock, m_previousCheckpoint); + uint64_t lastCollectedTicket = m_previousCheckpoint - 2; + Collect(lock, lastCollectedTicket); } private: @@ -413,9 +415,7 @@ namespace tracy uint64_t latestGpuTimestamp = m_latestKnownGpuTimestamp; TracyD3D12Debug( ZoneScopedC(Color::Red4) ); TracyD3D12Debug( ZoneValue(queryTicket) ); - TracyD3D12Debug( ZoneValue(int64_t(queryId)) ); - TracyD3D12Debug( TracyPlot("TracyD3D12|drop", float(0)) ); - TracyD3D12Debug( TracyPlot("TracyD3D12|drop", float(1)) ); + uint64_t latestGpuTimestamp = m_latestKnownGpuTimestamp; EmitGpuTime(latestGpuTimestamp, queryId); EmitGpuTime(latestGpuTimestamp, queryId+1); TracyD3D12Debug( TracyPlot("TracyD3D12|drop", float(1)) ); @@ -476,8 +476,9 @@ namespace tracy if (Distance(m_previousCheckpoint, ticket) >= RingCapacity()) { ZoneScopedC(Color::Red4); - TracyD3D12Panic("Too many pending GPU queries: stalling!"); - Drain(ticket, 0); + TracyD3D12Log(Warning, "Too many pending GPU queries: stalling!"); + uint64_t oldTicket = ticket - RingCapacity(); + Drain(oldTicket, 0); } const uint32_t queryId = RingIndex(ticket); From 8fc8b42221412fdc348213f03d98fb2759e82ee3 Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Tue, 7 Apr 2026 12:56:07 -0700 Subject: [PATCH 45/67] simplify debug ZoneValue --- public/tracy/TracyD3D12.hpp | 37 ++++++++++++++++--------------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index fc20462b..40f0d211 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -233,7 +233,7 @@ namespace tracy // All setup/init checks completed: ready to create the context. m_contextId = GetGpuCtxCounter().fetch_add(1); - ZoneValue(int64_t(m_contextId)); + ZoneValue(m_contextId); auto* item = Profiler::QueueSerial(); MemWrite(&item->hdr.type, QueueType::GpuNewContext); @@ -250,7 +250,7 @@ namespace tracy ~D3D12QueueCtx() { ZoneScopedC(Color::Red4); - ZoneValue(int64_t(m_contextId)); + ZoneValue(m_contextId); // TODO: could use queue->Signal() to inject a progress point in the queue // and the immediately wait for the signal, in order to avoid busy-waiting @@ -308,7 +308,7 @@ namespace tracy { ZoneScopedC(Color::Red4); TracyD3D12Assert( lock.owns_lock() ); - TracyD3D12Debug( ZoneValue(uint64_t(m_contextId)) ); + TracyD3D12Debug( ZoneValue(m_contextId) ); uint64_t earliestTicket = m_previousCheckpoint; uint64_t endTicket = m_queryCounter; @@ -351,11 +351,9 @@ namespace tracy bool Wait(uint64_t queryTicket, uint64_t timeout_ms) { ZoneScopedC(Color::Red4); - TracyD3D12Debug( - ZoneValue(uint64_t(m_contextId)); - ZoneValue(queryTicket); - ZoneValue(int64_t(RingIndex(queryTicket))); - ); + TracyD3D12Debug( ZoneValue(m_contextId) ); + TracyD3D12Debug( ZoneValue(queryTicket) ); + TracyD3D12Debug( ZoneValue(RingIndex(queryTicket)) ); if (Distance(m_previousCheckpoint, queryTicket) < 0) return true; auto Now = GetTickCount64; @@ -373,12 +371,10 @@ namespace tracy void Drain(uint64_t queryTicket, uint64_t gracePeriod_ms) { ZoneScopedC(Color::Red4); - TracyD3D12Debug( - ZoneValue(uint64_t(m_contextId)); - ZoneValue(queryTicket); - ZoneValue(int64_t(RingIndex(queryTicket))); - ); - if (Wait(queryTicket, gracePeriod_ms) == false) + TracyD3D12Debug( ZoneValue(m_contextId) ); + TracyD3D12Debug( ZoneValue(queryTicket) ); + TracyD3D12Debug( ZoneValue(RingIndex(queryTicket)) ); + if (!Wait(queryTicket, gracePeriod_ms)) { std::unique_lock lock (m_collectionMutex); Collect(lock, queryTicket); @@ -395,9 +391,6 @@ namespace tracy int64_t baseline_diff = Distance(baselineTimestamp, gpuZoneEndTimestamp); if (baseline_diff <= 0) return false; - TracyD3D12Debug( ZoneScoped ); - TracyD3D12Debug( ZoneValue(queryTicket) ); - TracyD3D12Debug( ZoneValue(int64_t(queryId)) ); EmitGpuTime(gpuZoneBeginTimestamp, queryId); EmitGpuTime(gpuZoneEndTimestamp, queryId+1); if (Distance(m_latestKnownGpuTimestamp, gpuZoneEndTimestamp) > 0) @@ -412,17 +405,19 @@ namespace tracy // emit a "bogus" GpuTime to avoid problems with the internal // zone tracking and matching logic in the server/profiler uint32_t queryId = RingIndex(queryTicket); - uint64_t latestGpuTimestamp = m_latestKnownGpuTimestamp; TracyD3D12Debug( ZoneScopedC(Color::Red4) ); TracyD3D12Debug( ZoneValue(queryTicket) ); + TracyD3D12Debug( ZoneValue(queryId) ); + TracyD3D12Debug( TracyPlot("TracyD3D12|drop", int64_t(0)) ); + TracyD3D12Debug( TracyPlot("TracyD3D12|drop", int64_t(1)) ); uint64_t latestGpuTimestamp = m_latestKnownGpuTimestamp; EmitGpuTime(latestGpuTimestamp, queryId); EmitGpuTime(latestGpuTimestamp, queryId+1); - TracyD3D12Debug( TracyPlot("TracyD3D12|drop", float(1)) ); - TracyD3D12Debug( TracyPlot("TracyD3D12|drop", float(0)) ); + TracyD3D12Debug( TracyPlot("TracyD3D12|drop", int64_t(1)) ); + TracyD3D12Debug( TracyPlot("TracyD3D12|drop", int64_t(0)) ); } - tracy_force_inline void EmitGpuTime(UINT64 gpuTimestamp, uint32_t queryId) + void EmitGpuTime(UINT64 gpuTimestamp, uint32_t queryId) { auto* item = Profiler::QueueSerial(); MemWrite(&item->hdr.type, QueueType::GpuTime); From 2a7e2215e068cc0a4dff40c021cde867a50e2728 Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Tue, 7 Apr 2026 12:56:35 -0700 Subject: [PATCH 46/67] handling special wait cases, and misc comments --- public/tracy/TracyD3D12.hpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index 40f0d211..d74915b0 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -354,8 +354,18 @@ namespace tracy TracyD3D12Debug( ZoneValue(m_contextId) ); TracyD3D12Debug( ZoneValue(queryTicket) ); TracyD3D12Debug( ZoneValue(RingIndex(queryTicket)) ); + // queryTicket is at or ahead of the end-ticket: + if (Distance(m_queryCounter, queryTicket) >= 0) + { + TracyD3D12Assert(false); + uint64_t lastIssuedTicket = m_queryCounter - 2; + Wait(lastIssuedTicket, timeout_ms); + return false; + } + // queryTicket is already behind the checkpoint: if (Distance(m_previousCheckpoint, queryTicket) < 0) return true; + // queryTicket is between [checkpoint, end): auto Now = GetTickCount64; auto ini = Now(); while (Distance(m_previousCheckpoint, queryTicket) >= 0) @@ -364,6 +374,7 @@ namespace tracy auto now = Now(); if ((now - ini) >= timeout_ms) break; + _mm_pause(); // TracyYield(); } return (Distance(m_previousCheckpoint, queryTicket) < 0); } @@ -376,6 +387,7 @@ namespace tracy TracyD3D12Debug( ZoneValue(RingIndex(queryTicket)) ); if (!Wait(queryTicket, gracePeriod_ms)) { + // force the collection of queryTicket std::unique_lock lock (m_collectionMutex); Collect(lock, queryTicket); } @@ -400,6 +412,7 @@ namespace tracy void DropTimestamp(uint64_t queryTicket, UINT64* timestampBuffer) { + // might as well attempt to resolve it before dropping it if (ResolveTimestamp(queryTicket, timestampBuffer)) return; // emit a "bogus" GpuTime to avoid problems with the internal From 099af6dd7439e1c99902086fd9eff6c2f33cd487 Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Tue, 7 Apr 2026 14:14:58 -0700 Subject: [PATCH 47/67] simplified private API --- public/tracy/TracyD3D12.hpp | 92 ++++++++++++++----------------------- 1 file changed, 34 insertions(+), 58 deletions(-) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index d74915b0..3a9aeffd 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -299,12 +299,11 @@ namespace tracy // but there's no need to block contending threads if (!m_collectionMutex.try_lock()) return; std::unique_lock lock (m_collectionMutex, std::adopt_lock); - uint64_t lastCollectedTicket = m_previousCheckpoint - 2; - Collect(lock, lastCollectedTicket); + Collect(lock); } private: - void Collect(std::unique_lock& lock, uint64_t targetQueryTicket) + void Collect(std::unique_lock& lock) { ZoneScopedC(Color::Red4); TracyD3D12Assert( lock.owns_lock() ); @@ -314,84 +313,59 @@ namespace tracy uint64_t endTicket = m_queryCounter; TracyD3D12Debug( ZoneValue(earliestTicket) ); TracyD3D12Debug( ZoneValue(endTicket) ); + TracyD3D12Debug( ZoneValue(endTicket - earliestTicket) ); if (Distance(earliestTicket, endTicket) <= 0) return; UINT64* timestampBuffer = MapTimestampBuffer(); - - // Panic! Too many queries: start dropping aggressively! - while (Distance(earliestTicket, endTicket) > RingCapacity()) - { - DropTimestamp(earliestTicket, timestampBuffer); - earliestTicket = RetireTicket(earliestTicket); - } - // Now ensure that the target query ticket gets processed - TracyD3D12Assert( Distance(targetQueryTicket, endTicket) > 0 ); - while (Distance(earliestTicket, targetQueryTicket) >= 0) + uint64_t ticket = earliestTicket; + for (ticket = earliestTicket; ticket != endTicket; ticket += 2) { - DropTimestamp(earliestTicket, timestampBuffer); - earliestTicket = RetireTicket(earliestTicket); - } - - // Finally, scan progress on remaining query tickets - for (earliestTicket; earliestTicket != endTicket; earliestTicket += 2) - { - if (!ResolveTimestamp(earliestTicket, timestampBuffer)) + if (!ResolveTimestamp(ticket, timestampBuffer)) // TODO: implement preemptive timeout policy break; - RetireTicket(earliestTicket); } + TracyD3D12Debug( ZoneValue(ticket - earliestTicket) ); UnmapTimestampBuffer(timestampBuffer); RecalibrateClocks(); } - bool Wait(uint64_t queryTicket, uint64_t timeout_ms) - { - ZoneScopedC(Color::Red4); - TracyD3D12Debug( ZoneValue(m_contextId) ); - TracyD3D12Debug( ZoneValue(queryTicket) ); - TracyD3D12Debug( ZoneValue(RingIndex(queryTicket)) ); - // queryTicket is at or ahead of the end-ticket: - if (Distance(m_queryCounter, queryTicket) >= 0) - { - TracyD3D12Assert(false); - uint64_t lastIssuedTicket = m_queryCounter - 2; - Wait(lastIssuedTicket, timeout_ms); - return false; - } - // queryTicket is already behind the checkpoint: - if (Distance(m_previousCheckpoint, queryTicket) < 0) - return true; - // queryTicket is between [checkpoint, end): - auto Now = GetTickCount64; - auto ini = Now(); - while (Distance(m_previousCheckpoint, queryTicket) >= 0) - { - Collect(); - auto now = Now(); - if ((now - ini) >= timeout_ms) - break; - _mm_pause(); // TracyYield(); - } - return (Distance(m_previousCheckpoint, queryTicket) < 0); - } - void Drain(uint64_t queryTicket, uint64_t gracePeriod_ms) { ZoneScopedC(Color::Red4); TracyD3D12Debug( ZoneValue(m_contextId) ); TracyD3D12Debug( ZoneValue(queryTicket) ); TracyD3D12Debug( ZoneValue(RingIndex(queryTicket)) ); - if (!Wait(queryTicket, gracePeriod_ms)) + auto Now = GetTickCount64; + auto ini = Now(); + // polite wait during the grace period + while (Distance(m_previousCheckpoint, queryTicket) >= 0) { - // force the collection of queryTicket - std::unique_lock lock (m_collectionMutex); - Collect(lock, queryTicket); + Collect(); + auto now = Now(); + if ((now - ini) >= gracePeriod_ms) + break; + _mm_pause(); // TracyYield(); + } + // can't wait anymore, start dropping: + while (Distance(m_previousCheckpoint, queryTicket) >= 0) + { + if (!m_collectionMutex.try_lock()) + { + _mm_pause(); // TracyYield(); + continue; + }; + std::unique_lock lock (m_collectionMutex, std::adopt_lock); + UINT64* timestampBuffer = MapTimestampBuffer(); + while (Distance(m_previousCheckpoint, queryTicket) >= 0) + DropTimestamp(m_previousCheckpoint, timestampBuffer); + while (Distance(m_previousCheckpoint, m_queryCounter) > RingCapacity()) + DropTimestamp(m_previousCheckpoint, timestampBuffer); + UnmapTimestampBuffer(timestampBuffer); } - TracyD3D12Assert( Distance(m_previousCheckpoint, queryTicket) < 0 ); } bool ResolveTimestamp(uint64_t queryTicket, UINT64* timestampBuffer) @@ -405,6 +379,7 @@ namespace tracy return false; EmitGpuTime(gpuZoneBeginTimestamp, queryId); EmitGpuTime(gpuZoneEndTimestamp, queryId+1); + RetireTicket(queryTicket); if (Distance(m_latestKnownGpuTimestamp, gpuZoneEndTimestamp) > 0) m_latestKnownGpuTimestamp = gpuZoneEndTimestamp; return true; @@ -426,6 +401,7 @@ namespace tracy uint64_t latestGpuTimestamp = m_latestKnownGpuTimestamp; EmitGpuTime(latestGpuTimestamp, queryId); EmitGpuTime(latestGpuTimestamp, queryId+1); + RetireTicket(queryTicket); TracyD3D12Debug( TracyPlot("TracyD3D12|drop", int64_t(1)) ); TracyD3D12Debug( TracyPlot("TracyD3D12|drop", int64_t(0)) ); } From 22428110048bfe87149b2c1c42f28fb0528c1fb6 Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Tue, 7 Apr 2026 17:34:39 -0700 Subject: [PATCH 48/67] refactoring --- public/tracy/TracyD3D12.hpp | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index 3a9aeffd..e056687f 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -63,6 +63,8 @@ using TracyD3D12Ctx = void*; #define TracyD3D12Log(severity, msg) tracy::Profiler::LogString( tracy::MessageSourceType::Tracy, tracy::MessageSeverity::severity, tracy::Color::Red4, 0, msg ); #define TracyD3D12Panic(msg, ...) do { TracyD3D12Log(Error, msg); TracyD3D12Assert(false && "TracyD3D12: " msg); __VA_ARGS__; } while(false); +#define TracyD3D12Relax() _mm_pause() // TracyYield() + namespace tracy { @@ -339,6 +341,7 @@ namespace tracy TracyD3D12Debug( ZoneValue(m_contextId) ); TracyD3D12Debug( ZoneValue(queryTicket) ); TracyD3D12Debug( ZoneValue(RingIndex(queryTicket)) ); + TracyD3D12Assert(Distance(queryTicket, m_queryCounter) > 0); auto Now = GetTickCount64; auto ini = Now(); // polite wait during the grace period @@ -348,23 +351,19 @@ namespace tracy auto now = Now(); if ((now - ini) >= gracePeriod_ms) break; - _mm_pause(); // TracyYield(); + TracyD3D12Relax(); } // can't wait anymore, start dropping: while (Distance(m_previousCheckpoint, queryTicket) >= 0) { - if (!m_collectionMutex.try_lock()) - { - _mm_pause(); // TracyYield(); - continue; - }; - std::unique_lock lock (m_collectionMutex, std::adopt_lock); + std::unique_lock lock (m_collectionMutex); UINT64* timestampBuffer = MapTimestampBuffer(); while (Distance(m_previousCheckpoint, queryTicket) >= 0) DropTimestamp(m_previousCheckpoint, timestampBuffer); while (Distance(m_previousCheckpoint, m_queryCounter) > RingCapacity()) DropTimestamp(m_previousCheckpoint, timestampBuffer); UnmapTimestampBuffer(timestampBuffer); + Collect(lock); } } @@ -447,15 +446,16 @@ namespace tracy tracy_force_inline uint32_t NextQueryId() { // WARN: the moment m_queryCounter is incremented, Collect() will have instant - // visibility of the query id pair and may start attempting to collect it! - // Under most circumstances, this is fine. However, if Collect() decided to - // drop a timestamp query due to timeout, but the query was indeed submitted - // to the GPU queue for execution later on, the "late" query will eventually - // be resolved by the GPU (asynchronously writting to the corresponding query - // "slot"). The newly produced query pair below could have matching ids/slots - // with the late query. Given that Collect() may attempt to inspect the new - // query pair immediately upon m_queryCounter being incremented, the "late" - // timestamp value may be collected as if it belonged to the the new query. + // visibility of the change and may therefore start attempting to collect them. + // Under most circumstances, this is fine. However, suppose Collect() ends up + // dropping a query due to timeout, but said query is indeed submitted to the + // GPU queue for execution later on. This "late" query will eventually become + // resolved by the GPU asynchronously writting to the corresponding query slot. + // The newly produced query below could have matching ids/slot with said query. + // From there, there's no bullet-proof way for Collect() to distinguish between + // the timestamp written to the query slot belonging to the old/late query, or + // to the query that has just been generated. The value of the "late" query may + // may end up being collected as if it belonged to the the new query. const uint64_t ticket = m_queryCounter.fetch_add(2, std::memory_order_relaxed); if (Distance(m_previousCheckpoint, ticket) >= RingCapacity()) { @@ -563,7 +563,7 @@ namespace tracy } tracy_force_inline bool ShouldSkipQuery(uint32_t queryId) { - static constexpr uint32_t ignoreList[] = { 6000 }; + static constexpr uint32_t ignoreList[] = { 7949, 9970, 12306, 13712, 14001, 14510, 16192, 16243, 16911, 18016, 18223, 20278, 21079, 21333, 25097, 26966, 29634, 33158, 35220, 35745, 36202, 39545, 49108, 50510, 53098, 54975, 55569, 55730, 56867, 62322 }; constexpr auto begin = std::begin(ignoreList); constexpr auto end = std::end(ignoreList); auto it = std::find(begin, end, queryId); From 035a2ba30d65687b12f651f03d64146c28660019 Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Wed, 8 Apr 2026 07:16:09 -0700 Subject: [PATCH 49/67] debug cleanup --- public/client/TracyProfiler.cpp | 13 ---------- server/TracyEvent.hpp | 13 +++++----- server/TracyWorker.cpp | 43 ++++++--------------------------- 3 files changed, 14 insertions(+), 55 deletions(-) diff --git a/public/client/TracyProfiler.cpp b/public/client/TracyProfiler.cpp index 0ba118da..a44e255e 100644 --- a/public/client/TracyProfiler.cpp +++ b/public/client/TracyProfiler.cpp @@ -2856,18 +2856,6 @@ Profiler::DequeueStatus Profiler::DequeueContextSwitches( tracy::moodycamel::Con default: assert( false ); break; \ } -static void DebugDump(QueueGpuTime& item, int64_t dt, int64_t refGpu) -{ - static FILE* hFileDump = fopen("gpu-time-client.dump", "wb"); - fwrite(&item, sizeof(item), 1, hFileDump); - fwrite(&dt, sizeof(dt), 1, hFileDump); - fwrite(&refGpu, sizeof(refGpu), 1, hFileDump); - fflush(hFileDump); - TracyPlot("Tracy refGpu", int64_t(0)); - TracyPlot("Tracy refGpu", dt); - TracyPlot("Tracy refGpu", int64_t(0)); -} - Profiler::DequeueStatus Profiler::DequeueSerial() { { @@ -3016,7 +3004,6 @@ Profiler::DequeueStatus Profiler::DequeueSerial() { int64_t t = MemRead( &item->gpuTime.gpuTime ); int64_t dt = t - refGpu; - //DebugDump(item->gpuTime, dt, refGpu), refGpu = t; MemWrite( &item->gpuTime.gpuTime, dt ); break; diff --git a/server/TracyEvent.hpp b/server/TracyEvent.hpp index 6c8bb3d0..ee63cd75 100644 --- a/server/TracyEvent.hpp +++ b/server/TracyEvent.hpp @@ -380,17 +380,16 @@ struct GpuEvent tracy_force_inline int64_t CpuEnd() const { return int64_t( _cpuEnd_thread ) >> 16; } tracy_force_inline void SetCpuEnd( int64_t cpuEnd ) { assert( cpuEnd < (int64_t)( 1ull << 47 ) ); memcpy( ((char*)&_cpuEnd_thread)+2, &cpuEnd, 4 ); memcpy( ((char*)&_cpuEnd_thread)+6, ((char*)&cpuEnd)+4, 2 ); } tracy_force_inline int64_t GpuStart() const { return int64_t( _gpuStart_child1 ) >> 16; } - tracy_force_inline void SetGpuStart( int64_t gpuStart ) { - // https://github.com/wolfpld/tracy/commit/c99dc5c43141e3359b10fb7e25c1e2e9ab91b82f - /*assert( gpuStart < (int64_t)( 1ull << 47 ) );*/ + tracy_force_inline void SetGpuStart( int64_t gpuStart ) + { + // Assert omitted for v0.6 trace compatibility + // assert( gpuStart < (int64_t)( 1ull << 47 ) ); memcpy( ((char*)&_gpuStart_child1)+2, &gpuStart, 4 ); memcpy( ((char*)&_gpuStart_child1)+6, ((char*)&gpuStart)+4, 2 ); } tracy_force_inline int64_t GpuEnd() const { return int64_t( _gpuEnd_child2 ) >> 16; } - tracy_force_inline void SetGpuEnd( int64_t gpuEnd ) { - if (gpuEnd >= (int64_t)(1ull << 47)) { - fprintf(stderr, "ERROR: unexpected GpuEnd timestamp: %llu\n", gpuEnd); - } + tracy_force_inline void SetGpuEnd( int64_t gpuEnd ) + { assert( gpuEnd < (int64_t)( 1ull << 47 ) ); memcpy( ((char*)&_gpuEnd_child2)+2, &gpuEnd, 4 ); memcpy( ((char*)&_gpuEnd_child2)+6, ((char*)&gpuEnd)+4, 2 ); diff --git a/server/TracyWorker.cpp b/server/TracyWorker.cpp index 38dc5477..7821899c 100644 --- a/server/TracyWorker.cpp +++ b/server/TracyWorker.cpp @@ -5924,7 +5924,7 @@ void Worker::ProcessGpuZoneEnd( const QueueGpuZoneEnd& ev, bool serial ) assert( !td->second.stack.empty() ); auto zone = td->second.stack.back_and_pop(); - assert( !ctx->query[ev.queryId] ); + assert( !ctx->query[ev.queryId] ); ctx->query[ev.queryId] = zone; int64_t cpuTime; @@ -5941,60 +5941,33 @@ void Worker::ProcessGpuZoneEnd( const QueueGpuZoneEnd& ev, bool serial ) if( m_data.lastTime < time ) m_data.lastTime = time; } -static void DebugDump(const QueueGpuTime& item, const int64_t refGpu) -{ - static FILE* hFileDump = fopen("gpu-time-server.dump", "wb"); - fwrite(&item, sizeof(item), 1, hFileDump); - fwrite(&refGpu, sizeof(refGpu), 1, hFileDump); - fflush(hFileDump); -} - void Worker::ProcessGpuTime( const QueueGpuTime& ev ) { auto ctx = m_gpuCtxMap[ev.context]; assert( ctx ); - //DebugDump(ev, m_refTimeGpu); - if (ev.gpuTime < -1'000'000'000) __debugbreak(); - int64_t tgpu0 = RefTime( m_refTimeGpu, ev.gpuTime ); + int64_t tgpu = RefTime( m_refTimeGpu, ev.gpuTime ); -#if 0 - int64_t tgpu = tgpu0; -#else - int64_t tgpu1 = tgpu0; - bool wraparound = false; - auto lastGpuTime0 = ctx->lastGpuTime; - auto overflow0 = ctx->overflow; - auto overflowMul0 = ctx->overflowMul; - // This overflow logic is for detecting backward time jumps due to asynchronous GPU scheduling. - // (e.g. when tasks are scheduled by the GPU, instead of being forced to execute in queue order) - // it gives a grace period of 2^31 ticks (about 2s when 1 tick = 1 ns) - if(tgpu1 < ctx->lastGpuTime - ( 1u << 31 ) ) + // This overflow logic is for detecting "backward time jumps" due to asynchronous + // GPU scheduling (commands being rescheduled internally by the command processor, + // instead of being forced to execute in submit queue order) + constexpr int64_t gracePeriod = ( 1u << 31 ); // 2^31 ticks (about 2s, when 1 tick = 1ns) + if( tgpu < ctx->lastGpuTime - gracePeriod ) { - wraparound = true; if( ctx->overflow == 0 ) { ctx->overflow = uint64_t( 1 ) << ( 64 - TracyLzcnt( ctx->lastGpuTime ) ); } ctx->overflowMul++; } - ctx->lastGpuTime = tgpu1; - auto lastGpuTime1 = ctx->lastGpuTime; - auto overflow1 = ctx->overflow; - auto overflowMul1 = ctx->overflowMul; - - int64_t tgpu2 = tgpu1; if( ctx->overflow != 0 ) { - tgpu2 += ctx->overflow * ctx->overflowMul; + tgpu += ctx->overflow * ctx->overflowMul; } - int64_t tgpu = tgpu2; -#endif - int64_t gpuTime; if( !ctx->hasPeriod ) { From 0ec1b29a66b8d9ce535b4a8d77dea8d9d52dab3d Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Wed, 8 Apr 2026 07:17:34 -0700 Subject: [PATCH 50/67] debug cleanup --- public/tracy/TracyD3D12.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index e056687f..0db21180 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -562,7 +562,7 @@ namespace tracy Profiler::QueueSerialFinish(); } - tracy_force_inline bool ShouldSkipQuery(uint32_t queryId) { + tracy_force_inline bool DebugShouldSkipQuery(uint32_t queryId) { static constexpr uint32_t ignoreList[] = { 7949, 9970, 12306, 13712, 14001, 14510, 16192, 16243, 16911, 18016, 18223, 20278, 21079, 21333, 25097, 26966, 29634, 33158, 35220, 35745, 36202, 39545, 49108, 50510, 53098, 54975, 55569, 55730, 56867, 62322 }; constexpr auto begin = std::begin(ignoreList); constexpr auto end = std::end(ignoreList); @@ -584,7 +584,7 @@ namespace tracy m_cmdList = cmdList; m_queryId = m_ctx->NextQueryId(); - if (ShouldSkipQuery(m_queryId)) return; + //if (DebugShouldSkipQuery(m_queryId)) return; m_cmdList->EndQuery(m_ctx->m_queryHeap, D3D12_QUERY_TYPE_TIMESTAMP, m_queryId); } @@ -627,7 +627,7 @@ namespace tracy MemWrite(&item->gpuZoneEnd.context, m_ctx->GetId()); Profiler::QueueSerialFinish(); - if (ShouldSkipQuery(m_queryId)) return; + //if (DebugShouldSkipQuery(m_queryId)) return; m_cmdList->EndQuery(m_ctx->m_queryHeap, D3D12_QUERY_TYPE_TIMESTAMP, queryId); // NOTE: can't quite move this ResolveQueryData() call to Collect()... // If a command is instrumented, but the command list is never submitted From bf1222c4b16c12c9ac5624f0a60b7af1c2012f4c Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Wed, 8 Apr 2026 11:47:03 -0700 Subject: [PATCH 51/67] re-enabling assert --- server/TracyEvent.hpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/server/TracyEvent.hpp b/server/TracyEvent.hpp index ee63cd75..d8dbad88 100644 --- a/server/TracyEvent.hpp +++ b/server/TracyEvent.hpp @@ -382,8 +382,7 @@ struct GpuEvent tracy_force_inline int64_t GpuStart() const { return int64_t( _gpuStart_child1 ) >> 16; } tracy_force_inline void SetGpuStart( int64_t gpuStart ) { - // Assert omitted for v0.6 trace compatibility - // assert( gpuStart < (int64_t)( 1ull << 47 ) ); + assert( gpuStart < (int64_t)( 1ull << 47 ) ); memcpy( ((char*)&_gpuStart_child1)+2, &gpuStart, 4 ); memcpy( ((char*)&_gpuStart_child1)+6, ((char*)&gpuStart)+4, 2 ); } From c4d18c8291932fee52694414942999cda6d4a6a0 Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Wed, 8 Apr 2026 11:47:38 -0700 Subject: [PATCH 52/67] refactoring Drain/Collect --- public/tracy/TracyD3D12.hpp | 87 ++++++++++++++++++++++++------------- 1 file changed, 56 insertions(+), 31 deletions(-) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index 0db21180..466b3191 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -47,7 +47,7 @@ using TracyD3D12Ctx = void*; #define TRACY_D3D12_TIMESTAMP_COLLECT_TIMEOUT 0.005f #endif -#define TRACY_D3D12_DEBUG_LEVEL (1) +#define TRACY_D3D12_DEBUG_LEVEL (0) #define TRACY_D3D12_PERSISTENT_TIMESTAMP_BUFFER (1) #if TRACY_D3D12_DEBUG_LEVEL @@ -301,11 +301,12 @@ namespace tracy // but there's no need to block contending threads if (!m_collectionMutex.try_lock()) return; std::unique_lock lock (m_collectionMutex, std::adopt_lock); - Collect(lock); + uint64_t targetTicket = m_queryCounter; + Collect(lock, m_queryCounter, false); } private: - void Collect(std::unique_lock& lock) + void Collect(std::unique_lock& lock, uint64_t targetTicket, bool urgent) { ZoneScopedC(Color::Red4); TracyD3D12Assert( lock.owns_lock() ); @@ -315,12 +316,15 @@ namespace tracy uint64_t endTicket = m_queryCounter; TracyD3D12Debug( ZoneValue(earliestTicket) ); TracyD3D12Debug( ZoneValue(endTicket) ); - TracyD3D12Debug( ZoneValue(endTicket - earliestTicket) ); if (Distance(earliestTicket, endTicket) <= 0) return; + // TODO: check device lost + // check the queries, but do not emit them just yet + UINT64* timestampBuffer = MapTimestampBuffer(); + // Attempt to collect as many resolved queries as possible uint64_t ticket = earliestTicket; for (ticket = earliestTicket; ticket != endTicket; ticket += 2) { @@ -328,43 +332,64 @@ namespace tracy // TODO: implement preemptive timeout policy break; } - TracyD3D12Debug( ZoneValue(ticket - earliestTicket) ); + + // Urgent request: ensure 'targetTicket' is collected before returning + if (urgent) + { + TracyD3D12Assert( Distance(targetTicket, endTicket) > 0 ); + while (Distance(ticket, targetTicket) >= 0) + { + DropTimestamp(ticket, timestampBuffer); + ticket += 2; + } + } + + // Panic: overflow, start dropping queries to normalize the situation + while (Distance(ticket, endTicket) > RingCapacity()) + { + DropTimestamp(ticket, timestampBuffer); + ticket += 2; + } UnmapTimestampBuffer(timestampBuffer); + // TODO: check device lost, again + // only emit resolved queries if device has not been lost + // (reading from readback heaps can be wonky in such cases) + RecalibrateClocks(); } + bool Wait(uint64_t queryTicket, uint64_t timeout_ms) + { + ZoneScopedC(Color::Red4); + TracyD3D12Debug( ZoneValue(m_contextId) ); + TracyD3D12Debug( ZoneValue(queryTicket) ); + TracyD3D12Debug( ZoneValue(RingIndex(queryTicket)) ); + auto Now = GetTickCount64; + auto ini = Now(); + auto elapsed = 0; + // TODO: could use condition variable to avoid spurious lock + collect iterations + while ((Distance(m_previousCheckpoint, queryTicket) >= 0) && (elapsed < timeout_ms)) + { + std::unique_lock lock (m_collectionMutex); + Collect(lock, queryTicket, false); + elapsed = Now() - ini; + } + return Distance(m_previousCheckpoint, queryTicket) < 0; + } + void Drain(uint64_t queryTicket, uint64_t gracePeriod_ms) { ZoneScopedC(Color::Red4); TracyD3D12Debug( ZoneValue(m_contextId) ); TracyD3D12Debug( ZoneValue(queryTicket) ); TracyD3D12Debug( ZoneValue(RingIndex(queryTicket)) ); - TracyD3D12Assert(Distance(queryTicket, m_queryCounter) > 0); - auto Now = GetTickCount64; - auto ini = Now(); - // polite wait during the grace period - while (Distance(m_previousCheckpoint, queryTicket) >= 0) - { - Collect(); - auto now = Now(); - if ((now - ini) >= gracePeriod_ms) - break; - TracyD3D12Relax(); - } - // can't wait anymore, start dropping: - while (Distance(m_previousCheckpoint, queryTicket) >= 0) - { - std::unique_lock lock (m_collectionMutex); - UINT64* timestampBuffer = MapTimestampBuffer(); - while (Distance(m_previousCheckpoint, queryTicket) >= 0) - DropTimestamp(m_previousCheckpoint, timestampBuffer); - while (Distance(m_previousCheckpoint, m_queryCounter) > RingCapacity()) - DropTimestamp(m_previousCheckpoint, timestampBuffer); - UnmapTimestampBuffer(timestampBuffer); - Collect(lock); - } + if (Wait(queryTicket, gracePeriod_ms)) + return; + // can't wait anymore: urgent collect request + std::unique_lock lock (m_collectionMutex); + Collect(lock, queryTicket, true); } bool ResolveTimestamp(uint64_t queryTicket, UINT64* timestampBuffer) @@ -584,7 +609,7 @@ namespace tracy m_cmdList = cmdList; m_queryId = m_ctx->NextQueryId(); - //if (DebugShouldSkipQuery(m_queryId)) return; + TracyD3D12Debug( if (DebugShouldSkipQuery(m_queryId)) return; ); m_cmdList->EndQuery(m_ctx->m_queryHeap, D3D12_QUERY_TYPE_TIMESTAMP, m_queryId); } @@ -627,7 +652,7 @@ namespace tracy MemWrite(&item->gpuZoneEnd.context, m_ctx->GetId()); Profiler::QueueSerialFinish(); - //if (DebugShouldSkipQuery(m_queryId)) return; + TracyD3D12Debug( if (DebugShouldSkipQuery(m_queryId)) return; ); m_cmdList->EndQuery(m_ctx->m_queryHeap, D3D12_QUERY_TYPE_TIMESTAMP, queryId); // NOTE: can't quite move this ResolveQueryData() call to Collect()... // If a command is instrumented, but the command list is never submitted From 80e6087ff3ab360d34ea259a8f3ab6dc213892a2 Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Wed, 8 Apr 2026 12:53:48 -0700 Subject: [PATCH 53/67] claim shared ownership of the queue to keep the queue (and device) alive --- public/tracy/TracyD3D12.hpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index 466b3191..f3df4cc7 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -146,6 +146,8 @@ namespace tracy { ZoneScopedC(Color::Red4); + m_queue->AddRef(); + // Verify we support timestamp queries on this queue. if (queue->GetDesc().Type == D3D12_COMMAND_LIST_TYPE_COPY) { @@ -277,6 +279,7 @@ namespace tracy #endif m_readbackBuffer->Release(); m_queryHeap->Release(); + m_queue->Release(); } void Name( const char* name, uint16_t len ) From 474b3dbdfb020d0bae071aa6fa405a7f0a0ece04 Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Wed, 8 Apr 2026 13:10:11 -0700 Subject: [PATCH 54/67] comments --- public/tracy/TracyD3D12.hpp | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index f3df4cc7..969362dd 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -322,9 +322,6 @@ namespace tracy if (Distance(earliestTicket, endTicket) <= 0) return; - // TODO: check device lost - // check the queries, but do not emit them just yet - UINT64* timestampBuffer = MapTimestampBuffer(); // Attempt to collect as many resolved queries as possible @@ -356,9 +353,12 @@ namespace tracy UnmapTimestampBuffer(timestampBuffer); - // TODO: check device lost, again - // only emit resolved queries if device has not been lost - // (reading from readback heaps can be wonky in such cases) + // TODO: check for device status (device lost). + // Technically speaking, values read from the timestamp buffer (readback heap) + // should only be trusted while the device is "fine". Ideally, queries should + // be "peeked" first, and those deemed "resolved" should only be "emitted" if + // the device is fine, or dropped otherwise. All that said, the collect code, + // as is, should not cause catastrophic issues. RecalibrateClocks(); } @@ -529,6 +529,12 @@ namespace tracy #endif } + bool DeviceLost() + { + HRESULT status = m_device->GetDeviceRemovedReason(); + return (status != S_OK); + } + tracy_force_inline uint8_t GetId() const { return m_contextId; From bb4c6bc5a9964d202f6c8ed6c01d6aa868c77038 Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Wed, 8 Apr 2026 13:12:03 -0700 Subject: [PATCH 55/67] removing debug pragma --- public/tracy/TracyD3D12.hpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index 969362dd..6c03f261 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -69,7 +69,6 @@ namespace tracy { // Command queue context. -#pragma optimize ( "", off ) class D3D12QueueCtx { friend class D3D12ZoneScope; @@ -540,7 +539,6 @@ namespace tracy return m_contextId; } }; -#pragma optimize ( "", on ) class D3D12ZoneScope { From b452e84e5332650a526ea2ac1eaf3de1ba3d1501 Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Wed, 8 Apr 2026 14:45:45 -0700 Subject: [PATCH 56/67] removing debug code --- profiler/src/profiler/TracyView_ZoneInfo.cpp | 6 ------ public/client/TracyProfiler.cpp | 2 -- server/TracyEvent.hpp | 14 ++------------ server/TracyWorker.cpp | 13 +++---------- 4 files changed, 5 insertions(+), 30 deletions(-) diff --git a/profiler/src/profiler/TracyView_ZoneInfo.cpp b/profiler/src/profiler/TracyView_ZoneInfo.cpp index a466bf2c..0f5ff3c1 100644 --- a/profiler/src/profiler/TracyView_ZoneInfo.cpp +++ b/profiler/src/profiler/TracyView_ZoneInfo.cpp @@ -2025,12 +2025,6 @@ void View::ZoneTooltip( const GpuEvent& ev ) } } - ImGui::Separator(); - TextDisabledUnformatted( "GPU zone debug" ); - TextFocused( "GPU begin (Tracy ns):", RealToString( ev.GpuStart() ) ); - TextFocused( "GPU end (Tracy ns):", RealToString( end ) ); - TextFocused( "Begin query id:", RealToString( ev.query_id ) ); - ImGui::EndTooltip(); } diff --git a/public/client/TracyProfiler.cpp b/public/client/TracyProfiler.cpp index a44e255e..7d527517 100644 --- a/public/client/TracyProfiler.cpp +++ b/public/client/TracyProfiler.cpp @@ -84,8 +84,6 @@ #include "TracySysTrace.hpp" #include "../tracy/TracyC.h" -#include "../tracy/Tracy.hpp" - #if defined TRACY_MANUAL_LIFETIME && !defined(TRACY_DELAYED_INIT) # error "TRACY_MANUAL_LIFETIME requires enabled TRACY_DELAYED_INIT" #endif diff --git a/server/TracyEvent.hpp b/server/TracyEvent.hpp index d8dbad88..075479a5 100644 --- a/server/TracyEvent.hpp +++ b/server/TracyEvent.hpp @@ -380,19 +380,9 @@ struct GpuEvent tracy_force_inline int64_t CpuEnd() const { return int64_t( _cpuEnd_thread ) >> 16; } tracy_force_inline void SetCpuEnd( int64_t cpuEnd ) { assert( cpuEnd < (int64_t)( 1ull << 47 ) ); memcpy( ((char*)&_cpuEnd_thread)+2, &cpuEnd, 4 ); memcpy( ((char*)&_cpuEnd_thread)+6, ((char*)&cpuEnd)+4, 2 ); } tracy_force_inline int64_t GpuStart() const { return int64_t( _gpuStart_child1 ) >> 16; } - tracy_force_inline void SetGpuStart( int64_t gpuStart ) - { - assert( gpuStart < (int64_t)( 1ull << 47 ) ); - memcpy( ((char*)&_gpuStart_child1)+2, &gpuStart, 4 ); - memcpy( ((char*)&_gpuStart_child1)+6, ((char*)&gpuStart)+4, 2 ); - } + tracy_force_inline void SetGpuStart( int64_t gpuStart ) { /*assert( gpuStart < (int64_t)( 1ull << 47 ) );*/ memcpy( ((char*)&_gpuStart_child1)+2, &gpuStart, 4 ); memcpy( ((char*)&_gpuStart_child1)+6, ((char*)&gpuStart)+4, 2 ); } tracy_force_inline int64_t GpuEnd() const { return int64_t( _gpuEnd_child2 ) >> 16; } - tracy_force_inline void SetGpuEnd( int64_t gpuEnd ) - { - assert( gpuEnd < (int64_t)( 1ull << 47 ) ); - memcpy( ((char*)&_gpuEnd_child2)+2, &gpuEnd, 4 ); - memcpy( ((char*)&_gpuEnd_child2)+6, ((char*)&gpuEnd)+4, 2 ); - } + tracy_force_inline void SetGpuEnd( int64_t gpuEnd ) { assert( gpuEnd < (int64_t)( 1ull << 47 ) ); memcpy( ((char*)&_gpuEnd_child2)+2, &gpuEnd, 4 ); memcpy( ((char*)&_gpuEnd_child2)+6, ((char*)&gpuEnd)+4, 2 ); } tracy_force_inline int16_t SrcLoc() const { return int16_t( _cpuStart_srcloc & 0xFFFF ); } tracy_force_inline void SetSrcLoc( int16_t srcloc ) { memcpy( &_cpuStart_srcloc, &srcloc, 2 ); } tracy_force_inline uint16_t Thread() const { return uint16_t( _cpuEnd_thread & 0xFFFF ); } diff --git a/server/TracyWorker.cpp b/server/TracyWorker.cpp index 7821899c..21bd21ba 100644 --- a/server/TracyWorker.cpp +++ b/server/TracyWorker.cpp @@ -5924,7 +5924,7 @@ void Worker::ProcessGpuZoneEnd( const QueueGpuZoneEnd& ev, bool serial ) assert( !td->second.stack.empty() ); auto zone = td->second.stack.back_and_pop(); - assert( !ctx->query[ev.queryId] ); + assert( !ctx->query[ev.queryId] ); ctx->query[ev.queryId] = zone; int64_t cpuTime; @@ -5946,16 +5946,8 @@ void Worker::ProcessGpuTime( const QueueGpuTime& ev ) auto ctx = m_gpuCtxMap[ev.context]; assert( ctx ); - if (ev.gpuTime < -1'000'000'000) - __debugbreak(); - int64_t tgpu = RefTime( m_refTimeGpu, ev.gpuTime ); - - // This overflow logic is for detecting "backward time jumps" due to asynchronous - // GPU scheduling (commands being rescheduled internally by the command processor, - // instead of being forced to execute in submit queue order) - constexpr int64_t gracePeriod = ( 1u << 31 ); // 2^31 ticks (about 2s, when 1 tick = 1ns) - if( tgpu < ctx->lastGpuTime - gracePeriod ) + if( tgpu < ctx->lastGpuTime - ( 1u << 31 ) ) { if( ctx->overflow == 0 ) { @@ -5963,6 +5955,7 @@ void Worker::ProcessGpuTime( const QueueGpuTime& ev ) } ctx->overflowMul++; } + ctx->lastGpuTime = tgpu; if( ctx->overflow != 0 ) { tgpu += ctx->overflow * ctx->overflowMul; From 1e5926b9c0e667f8d4622672ffcd25e1ad22ef6d Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Wed, 8 Apr 2026 15:12:25 -0700 Subject: [PATCH 57/67] debug cleanup --- public/tracy/TracyD3D12.hpp | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index 6c03f261..89575c97 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -43,12 +43,13 @@ using TracyD3D12Ctx = void*; #include #include -#ifndef TRACY_D3D12_TIMESTAMP_COLLECT_TIMEOUT -#define TRACY_D3D12_TIMESTAMP_COLLECT_TIMEOUT 0.005f -#endif - +#ifndef TRACY_D3D12_DEBUG_LEVEL #define TRACY_D3D12_DEBUG_LEVEL (0) +#endif//TRACY_D3D12_DEBUG_LEVEL + +#ifndef TRACY_D3D12_PERSISTENT_TIMESTAMP_BUFFER #define TRACY_D3D12_PERSISTENT_TIMESTAMP_BUFFER (1) +#endif//TRACY_D3D12_PERSISTENT_TIMESTAMP_BUFFER #if TRACY_D3D12_DEBUG_LEVEL #define TracyD3D12Debug(...) __VA_ARGS__; @@ -594,15 +595,6 @@ namespace tracy Profiler::QueueSerialFinish(); } - tracy_force_inline bool DebugShouldSkipQuery(uint32_t queryId) { - static constexpr uint32_t ignoreList[] = { 7949, 9970, 12306, 13712, 14001, 14510, 16192, 16243, 16911, 18016, 18223, 20278, 21079, 21333, 25097, 26966, 29634, 33158, 35220, 35745, 36202, 39545, 49108, 50510, 53098, 54975, 55569, 55730, 56867, 62322 }; - constexpr auto begin = std::begin(ignoreList); - constexpr auto end = std::end(ignoreList); - auto it = std::find(begin, end, queryId); - bool contains = (it != end); - return contains; - } - tracy_force_inline D3D12ZoneScope(D3D12QueueCtx* ctx, ID3D12GraphicsCommandList* cmdList, bool active) #ifdef TRACY_ON_DEMAND : m_active(active&& GetProfiler().IsConnected()) @@ -616,7 +608,6 @@ namespace tracy m_cmdList = cmdList; m_queryId = m_ctx->NextQueryId(); - TracyD3D12Debug( if (DebugShouldSkipQuery(m_queryId)) return; ); m_cmdList->EndQuery(m_ctx->m_queryHeap, D3D12_QUERY_TYPE_TIMESTAMP, m_queryId); } @@ -659,7 +650,6 @@ namespace tracy MemWrite(&item->gpuZoneEnd.context, m_ctx->GetId()); Profiler::QueueSerialFinish(); - TracyD3D12Debug( if (DebugShouldSkipQuery(m_queryId)) return; ); m_cmdList->EndQuery(m_ctx->m_queryHeap, D3D12_QUERY_TYPE_TIMESTAMP, queryId); // NOTE: can't quite move this ResolveQueryData() call to Collect()... // If a command is instrumented, but the command list is never submitted @@ -685,14 +675,13 @@ namespace tracy } -#undef TRACY_D3D12_DEBUG -#undef TRACY_D3D12_PERSISTENT_TIMESTAMP_BUFFER -#undef TRACY_D3D12_TIMESTAMP_COLLECT_TIMEOUT #undef TracyD3D12Panic #undef TracyD3D12Log #undef TracyD3D12Assert #undef TracyD3D12Break #undef TracyD3D12Debug +#undef TRACY_D3D12_PERSISTENT_TIMESTAMP_BUFFER +#undef TRACY_D3D12_DEBUG using TracyD3D12Ctx = tracy::D3D12QueueCtx*; From 33b540403352a48db20acec2474ea67b32adccb9 Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Wed, 29 Apr 2026 11:24:22 -0700 Subject: [PATCH 58/67] early exit when MapTimestampBuffer fails --- public/tracy/TracyD3D12.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index 89575c97..a24d8b6a 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -201,6 +201,7 @@ namespace tracy } UINT64* timestampBuffer = MapTimestampBuffer(); + if (timestampBuffer == nullptr) return; for (uint64_t i = 0; i < m_queryLimit; ++i) timestampBuffer[i] = 0; UnmapTimestampBuffer(timestampBuffer); @@ -323,6 +324,7 @@ namespace tracy return; UINT64* timestampBuffer = MapTimestampBuffer(); + if (timestampBuffer == nullptr) return; // Attempt to collect as many resolved queries as possible uint64_t ticket = earliestTicket; From 8b9c25f19c8358aead0ab0949f9d582f21d9c183 Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Wed, 29 Apr 2026 11:25:10 -0700 Subject: [PATCH 59/67] handle case where constructor (context initializaion) may fail --- public/tracy/TracyD3D12.hpp | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index a24d8b6a..235b2235 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -274,15 +274,23 @@ namespace tracy TracyD3D12Panic("client is still pushing queries."); #if TRACY_D3D12_PERSISTENT_TIMESTAMP_BUFFER - D3D12_RANGE fullRange { 0, m_queryLimit * sizeof(UINT64) }; - m_readbackBuffer->Unmap(0, &fullRange); - m_persistentTimestampBuffer = nullptr; + if (m_readbackBuffer) + { + D3D12_RANGE fullRange { 0, m_queryLimit * sizeof(UINT64) }; + m_readbackBuffer->Unmap(0, &fullRange); + m_persistentTimestampBuffer = nullptr; + } #endif - m_readbackBuffer->Release(); - m_queryHeap->Release(); + if (m_readbackBuffer) m_readbackBuffer->Release(); + if (m_queryHeap) m_queryHeap->Release(); m_queue->Release(); } + tracy_force_inline uint8_t GetId() const + { + return m_contextId; + } + void Name( const char* name, uint16_t len ) { auto ptr = (char*)tracy_malloc( len ); @@ -536,11 +544,6 @@ namespace tracy HRESULT status = m_device->GetDeviceRemovedReason(); return (status != S_OK); } - - tracy_force_inline uint8_t GetId() const - { - return m_contextId; - } }; class D3D12ZoneScope @@ -665,7 +668,12 @@ namespace tracy { auto* ctx = static_cast(tracy_malloc(sizeof(D3D12QueueCtx))); new (ctx) D3D12QueueCtx{ device, queue }; - + // constructor may have failed: + if (ctx->GetId() == 255) + { + DestroyD3D12Context(ctx); + return nullptr; + } return ctx; } From dbb17e50e32cc228a559ceafe143a4dcd7ed2218 Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Wed, 29 Apr 2026 11:25:47 -0700 Subject: [PATCH 60/67] relaxing atomics in hot multi-threaded path --- public/tracy/TracyD3D12.hpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index 235b2235..7c25e10e 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -473,12 +473,11 @@ namespace tracy return static_cast(end - begin); } - uint64_t RetireTicket(uint64_t ticket) + void RetireTicket(uint64_t ticket) { TracyD3D12Assert( m_previousCheckpoint == ticket ); - ticket += 2; - m_previousCheckpoint.store(ticket); - return ticket; + uint64_t nextTicket = ticket + 2; + m_previousCheckpoint.store(nextTicket, std::memory_order_release); } tracy_force_inline uint32_t NextQueryId() @@ -495,7 +494,8 @@ namespace tracy // to the query that has just been generated. The value of the "late" query may // may end up being collected as if it belonged to the the new query. const uint64_t ticket = m_queryCounter.fetch_add(2, std::memory_order_relaxed); - if (Distance(m_previousCheckpoint, ticket) >= RingCapacity()) + const uint64_t checkpoint = m_previousCheckpoint.load(std::memory_order_relaxed); + if (Distance(checkpoint, ticket) >= RingCapacity()) { ZoneScopedC(Color::Red4); TracyD3D12Log(Warning, "Too many pending GPU queries: stalling!"); From 3d6129f8b0916193bf883dd21d723d93136f7be6 Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Wed, 29 Apr 2026 11:28:08 -0700 Subject: [PATCH 61/67] dependent function ordering --- public/tracy/TracyD3D12.hpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index 7c25e10e..c21c9244 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -664,6 +664,13 @@ namespace tracy } }; + static inline void DestroyD3D12Context(D3D12QueueCtx* ctx) + { + TracyD3D12Assert(ctx); + ctx->~D3D12QueueCtx(); + tracy_free(ctx); + } + static inline D3D12QueueCtx* CreateD3D12Context(ID3D12Device* device, ID3D12CommandQueue* queue) { auto* ctx = static_cast(tracy_malloc(sizeof(D3D12QueueCtx))); @@ -677,12 +684,6 @@ namespace tracy return ctx; } - static inline void DestroyD3D12Context(D3D12QueueCtx* ctx) - { - ctx->~D3D12QueueCtx(); - tracy_free(ctx); - } - } #undef TracyD3D12Panic From ee3120010aade09e273812047b9c00173539f681 Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Wed, 29 Apr 2026 11:33:00 -0700 Subject: [PATCH 62/67] cleanup --- public/tracy/TracyD3D12.hpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index c21c9244..a105a24f 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -379,15 +379,14 @@ namespace tracy TracyD3D12Debug( ZoneValue(m_contextId) ); TracyD3D12Debug( ZoneValue(queryTicket) ); TracyD3D12Debug( ZoneValue(RingIndex(queryTicket)) ); - auto Now = GetTickCount64; - auto ini = Now(); + auto ini = GetTickCount64(); auto elapsed = 0; // TODO: could use condition variable to avoid spurious lock + collect iterations while ((Distance(m_previousCheckpoint, queryTicket) >= 0) && (elapsed < timeout_ms)) { std::unique_lock lock (m_collectionMutex); Collect(lock, queryTicket, false); - elapsed = Now() - ini; + elapsed = GetTickCount64() - ini; } return Distance(m_previousCheckpoint, queryTicket) < 0; } @@ -692,7 +691,7 @@ namespace tracy #undef TracyD3D12Break #undef TracyD3D12Debug #undef TRACY_D3D12_PERSISTENT_TIMESTAMP_BUFFER -#undef TRACY_D3D12_DEBUG +#undef TRACY_D3D12_DEBUG_LEVEL using TracyD3D12Ctx = tracy::D3D12QueueCtx*; From 8692f2a650b9d1bfeb04903bc4b2e31e2370ce2d Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Mon, 4 May 2026 16:17:47 -0700 Subject: [PATCH 63/67] updating manual --- manual/tracy.tex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manual/tracy.tex b/manual/tracy.tex index b4add84f..8f7910bc 100644 --- a/manual/tracy.tex +++ b/manual/tracy.tex @@ -1710,7 +1710,7 @@ The queue must have been created through the specified device, however, a comman Using GPU zones is the same as the Vulkan implementation, where the \texttt{TracyD3D12Zone(ctx, cmdList, name)} macro is used, with \texttt{name} as a string literal. \texttt{TracyD3D12ZoneC(ctx, cmdList, name, color)} can be used to create a custom-colored zone. The given command list must be in an open state. -The macro \texttt{TracyD3D12NewFrame(ctx)} is used to mark a new frame, and should appear before or after recording command lists, similar to \texttt{FrameMark}. This macro is a key component that enables automatic query data synchronization, so the user doesn't have to worry about synchronizing GPU execution before invoking a collection. Event data can then be collected and sent to the profiler using the \texttt{TracyD3D12Collect(ctx)} macro. +You must periodically collect the GPU events by calling \texttt{TracyD3D12Collect(ctx)}. Good places for collection are after synchronous GPU waits (e.g., \texttt{ID3D12CommandQueue::Wait}) and after swap chain present calls (i.e., \texttt{IDXGISwapChain::Present}). Note that GPU profiling may be slightly inaccurate due to artifacts from dynamic frequency scaling. To counter this, \texttt{ID3D12Device::SetStablePowerState()} can be used to enable accurate profiling, at the expense of some performance. If the machine is not in developer mode, the operating system will remove the device upon calling. Do not use this in the shipping code. From 027a37409bd18a5ac174cb8e344f60ede2607f1e Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Fri, 8 May 2026 14:30:24 -0700 Subject: [PATCH 64/67] addressing code review comments --- public/tracy/TracyD3D12.hpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index a105a24f..a56efbde 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -324,7 +324,7 @@ namespace tracy TracyD3D12Assert( lock.owns_lock() ); TracyD3D12Debug( ZoneValue(m_contextId) ); - uint64_t earliestTicket = m_previousCheckpoint; + uint64_t earliestTicket = m_previousCheckpoint.load(std::memory_order::relaxed); uint64_t endTicket = m_queryCounter; TracyD3D12Debug( ZoneValue(earliestTicket) ); TracyD3D12Debug( ZoneValue(endTicket) ); @@ -373,6 +373,12 @@ namespace tracy RecalibrateClocks(); } + bool IsTicketPending(uint64_t queryTicket) + { + auto checkpoint = m_previousCheckpoint.load(std::memory_order::acquire); + return (Distance(checkpoint, queryTicket) >= 0); + } + bool Wait(uint64_t queryTicket, uint64_t timeout_ms) { ZoneScopedC(Color::Red4); @@ -382,13 +388,15 @@ namespace tracy auto ini = GetTickCount64(); auto elapsed = 0; // TODO: could use condition variable to avoid spurious lock + collect iterations - while ((Distance(m_previousCheckpoint, queryTicket) >= 0) && (elapsed < timeout_ms)) + while (IsTicketPending(queryTicket)) { + if (elapsed >= timeout_ms) + return false; std::unique_lock lock (m_collectionMutex); Collect(lock, queryTicket, false); elapsed = GetTickCount64() - ini; } - return Distance(m_previousCheckpoint, queryTicket) < 0; + return true; } void Drain(uint64_t queryTicket, uint64_t gracePeriod_ms) From d5bce07a906a20cdb0f58a3fc1046c23050644fa Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Fri, 8 May 2026 15:33:12 -0700 Subject: [PATCH 65/67] oopsie --- public/tracy/TracyD3D12.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index a56efbde..ed35f25e 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -324,7 +324,7 @@ namespace tracy TracyD3D12Assert( lock.owns_lock() ); TracyD3D12Debug( ZoneValue(m_contextId) ); - uint64_t earliestTicket = m_previousCheckpoint.load(std::memory_order::relaxed); + uint64_t earliestTicket = m_previousCheckpoint.load(std::memory_order_relaxed); uint64_t endTicket = m_queryCounter; TracyD3D12Debug( ZoneValue(earliestTicket) ); TracyD3D12Debug( ZoneValue(endTicket) ); @@ -373,9 +373,9 @@ namespace tracy RecalibrateClocks(); } - bool IsTicketPending(uint64_t queryTicket) + tracy_force_inline bool IsTicketPending(uint64_t queryTicket) { - auto checkpoint = m_previousCheckpoint.load(std::memory_order::acquire); + auto checkpoint = m_previousCheckpoint.load(std::memory_order_acquire); return (Distance(checkpoint, queryTicket) >= 0); } From ad9c6f2f18d3b7d4260b72e92521f05d63e4e36c Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Thu, 14 May 2026 07:18:30 -0700 Subject: [PATCH 66/67] addressing code review --- public/tracy/TracyD3D12.hpp | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index ed35f25e..37a0fe46 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -33,10 +33,10 @@ using TracyD3D12Ctx = void*; #else #include "Tracy.hpp" +#include "../client/TracyFastVector.hpp" #include #include -#include #include #include #include @@ -64,8 +64,6 @@ using TracyD3D12Ctx = void*; #define TracyD3D12Log(severity, msg) tracy::Profiler::LogString( tracy::MessageSourceType::Tracy, tracy::MessageSeverity::severity, tracy::Color::Red4, 0, msg ); #define TracyD3D12Panic(msg, ...) do { TracyD3D12Log(Error, msg); TracyD3D12Assert(false && "TracyD3D12: " msg); __VA_ARGS__; } while(false); -#define TracyD3D12Relax() _mm_pause() // TracyYield() - namespace tracy { @@ -91,9 +89,9 @@ namespace tracy atomic_counter m_queryCounter = 0; atomic_counter m_previousCheckpoint = 0; - uint32_t m_queryLimit = 0; + uint32_t m_queryLimit = 64 * 1024; // Must be even: each scope is a (begin, end) pair of queries - std::vector m_shadowBuffer; + FastVector m_shadowBuffer; UINT64 m_latestKnownGpuTimestamp = 0; UINT64 m_prevCalibrationTicksCPU = 0; @@ -143,6 +141,7 @@ namespace tracy D3D12QueueCtx(ID3D12Device* device, ID3D12CommandQueue* queue) : m_device(device) , m_queue(queue) + , m_shadowBuffer(m_queryLimit) { ZoneScopedC(Color::Red4); @@ -159,9 +158,6 @@ namespace tracy } } - static constexpr uint32_t MaxQueries = 64 * 1024; // Must be even, because queries are (begin, end) pairs - m_queryLimit = MaxQueries; - D3D12_QUERY_HEAP_DESC heapDesc{}; heapDesc.Type = queue->GetDesc().Type == D3D12_COMMAND_LIST_TYPE_COPY ? D3D12_QUERY_HEAP_TYPE_COPY_QUEUE_TIMESTAMP : D3D12_QUERY_HEAP_TYPE_TIMESTAMP; heapDesc.Count = m_queryLimit; @@ -228,7 +224,9 @@ namespace tracy TracyD3D12Panic("Failed to get queue clock calibration.", return); } - m_shadowBuffer.resize(m_queryLimit, gpuTimestamp); + // FastVector: clean/resize/init + for (size_t i = 0; i < m_queryLimit; ++i) *m_shadowBuffer.push_next() = gpuTimestamp; + m_latestKnownGpuTimestamp = gpuTimestamp; // Save the device cpu timestamp, not the profiler's timestamp. From af9802e3f2c38a62afbf98c141d2f955b41c9f19 Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Thu, 14 May 2026 08:24:54 -0700 Subject: [PATCH 67/67] debugbreak only for msvc --- public/tracy/TracyD3D12.hpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/public/tracy/TracyD3D12.hpp b/public/tracy/TracyD3D12.hpp index 37a0fe46..9e3d1ba5 100644 --- a/public/tracy/TracyD3D12.hpp +++ b/public/tracy/TracyD3D12.hpp @@ -52,13 +52,17 @@ using TracyD3D12Ctx = void*; #endif//TRACY_D3D12_PERSISTENT_TIMESTAMP_BUFFER #if TRACY_D3D12_DEBUG_LEVEL -#define TracyD3D12Debug(...) __VA_ARGS__; -#define TracyD3D12Break() if (IsDebuggerPresent()) __debugbreak() // MSVC -#define TracyD3D12Assert(predicate, ...) if (predicate) {} else { __VA_ARGS__; TracyD3D12Break(); } +# define TracyD3D12Debug(...) __VA_ARGS__; +# ifdef _MSC_VER +# define TracyD3D12Break() if (IsDebuggerPresent()) __debugbreak() +# else +# define TracyD3D12Break() /* TODO */ +# endif +# define TracyD3D12Assert(predicate, ...) if (predicate) {} else { __VA_ARGS__; TracyD3D12Break(); } #else -#define TracyD3D12Debug(...) -#define TracyD3D12Break() -#define TracyD3D12Assert(predicate, ...) assert(predicate); +# define TracyD3D12Debug(...) +# define TracyD3D12Break() +# define TracyD3D12Assert(predicate, ...) assert(predicate); #endif #define TracyD3D12Log(severity, msg) tracy::Profiler::LogString( tracy::MessageSourceType::Tracy, tracy::MessageSeverity::severity, tracy::Color::Red4, 0, msg );