eliminate NewFrame, and account for "abandoned" and "out-of-order" timestamp queries

This commit is contained in:
Marcos Slomp
2026-03-23 16:58:41 -07:00
parent 5bbafeabd7
commit b50876e77c

View File

@@ -36,42 +36,48 @@ using TracyD3D12Ctx = void*;
#include "../client/TracyProfiler.hpp"
#include "../client/TracyCallstack.hpp"
#include <atomic>
#include <chrono>
#include <mutex>
#include <vector>
#include <cstdlib>
#include <cstring>
#include <cassert>
#include <d3d12.h>
#include <dxgi.h>
#include <queue>
#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<uint32_t> m_queryCounter = 0;
uint32_t m_previousQueryCounter = 0;
using atomic_counter = std::atomic<uint64_t>;
atomic_counter m_queryCounter = 0;
atomic_counter m_previousCheckpoint = 0;
uint32_t m_activePayload = 0;
ID3D12Fence* m_payloadFence = nullptr;
std::queue<D3D12QueryPayload> m_payloadQueue;
uint32_t m_queryLimit = 0;
std::vector<std::chrono::high_resolution_clock::time_point> 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<UINT64*>(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<int64_t>(cpuTimestamp));
MemWrite(&item->gpuNewContext.gpuTime, static_cast<int64_t>(gpuTimestamp));
MemWrite(&item->gpuNewContext.thread, static_cast<uint32_t>(0)); // zero means the context is not associated with a specific thread
MemWrite(&item->gpuNewContext.period, static_cast<float>(period));
MemWrite(&item->gpuNewContext.context, static_cast<uint8_t>(GetId()));
MemWrite(&item->gpuNewContext.flags, static_cast<uint8_t>(GpuContextCalibration));
MemWrite(&item->gpuNewContext.type, static_cast<uint8_t>(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<UINT64*>(readbackBufferMapping);
auto* timestampData = static_cast<uint64_t*>(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<uint16_t>(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<float>{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<int64_t>(gpuTimestamp));
MemWrite(&item->gpuTime.queryId, static_cast<uint16_t>(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<uint32_t>(logicalSlot % RingSize());
}
tracy_force_inline uint32_t RingCount(uint64_t begin, uint64_t end) const
{
return static_cast<uint32_t>(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)