mirror of
https://github.com/wolfpld/tracy.git
synced 2026-09-02 00:18:23 +00:00
eliminate NewFrame, and account for "abandoned" and "out-of-order" timestamp queries
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user