#1440

Feature Request: Expose CPU time in C API calibration function

OpenRogueLogixopened 3mo ago

Currently, ___tracy_emit_gpu_calibration immediately does a GetTime call to fill in the CPU time. When using VK_EXT_calibrated_timestamps from a language that is bound via the C API (Java in this case) I am unable to pass through the CPU time that is returned by Vulkan like TracyVulkan.hpp does.

Activity

1
wolfpld2mo ago

@Pilzschaf is there a specific reason for doing CPU time retrieval directly in ___tracy_emit_gpu_calibration?

1. The wire message

QueueGpuCalibration (public/common/TracyQueue.hpp:587-593) — 24 payload bytes, 3 of 4 fields are timestamps:

struct QueueGpuCalibration
{
    int64_t gpuTime;    // raw GPU device timestamp (device ticks)
    int64_t cpuTime;    // CPU timestamp at the moment of gpuTime (client GetTime() domain)
    int64_t cpuDelta;   // CPU nanoseconds elapsed since previous calibration event
    uint8_t context;    // GPU context id
};

The server turns each event into an affine GPU→CPU mapping (server/TracyWorker.cpp:6215-6236, used at :6166):

ctx->calibrationMod    = double( ev.cpuDelta ) / ( gpuTime - ctx->calibratedGpuTime );  // slope: CPU ns per GPU ns
ctx->calibratedGpuTime = gpuTime;        // anchor, GPU side
ctx->calibratedCpuTime = TscTime( ev.cpuTime );   // anchor, CPU side  <-- the field in question

// later, for every GPU timestamp:
cpu_ns = ( tgpu_ns - calibratedGpuTime ) * calibrationMod + calibratedCpuTime;

So gpuTime + cpuTime must be a simultaneous pair — that's the anchor; cpuDelta (with the GPU-side delta) is the slope.

2. What the C++ side does (TracyVulkan.hpp:316-333)

int64_t tgpu, tcpu = m_prevCalibration;
Calibrate( tcpu, tgpu, 10 );                  // -> vkGetCalibratedTimestampsEXT: atomic (device, host) pair
const auto refCpu = Profiler::GetTime();      // fresh CPU read, in-process
const auto delta = tcpu - m_prevCalibration;  // ns difference of the Vulkan *host* timestamps
if( delta > 0 )
{
    m_prevCalibration = tcpu;
    MemWrite( &item->gpuCalibration.gpuTime,   tgpu );    // Vulkan device ts
    MemWrite( &item->gpuCalibration.cpuTime,   refCpu );  // fresh GetTime()
    MemWrite( &item->gpuCalibration.cpuDelta,  delta );   // Vulkan host-ts delta, in ns
    MemWrite( &item->gpuCalibration.context,   m_context );
}

Where tcpu comes from GetCalibratedTimestamps() (:371-387): one vkGetCalibratedTimestampsEXT call requesting both VK_TIME_DOMAIN_DEVICE_EXT and the host domain, host result converted to ns (:359-369: QPC→ns on Windows, pass-through on Linux). The C++ code has direct access to every field of the message — it queues the wire struct itself.

3. What the C API does (TracyProfiler.cpp:5094-5105)

struct ___tracy_gpu_calibration_data {          // TracyC.h:209-213
    int64_t gpuTime;
    int64_t cpuDelta;
    uint8_t context;                            // <- no cpuTime member
};

TRACY_API void ___tracy_emit_gpu_calibration( const struct ___tracy_gpu_calibration_data data )
{
    TracyLfqPrepareC( tracy::QueueType::GpuCalibration );
    tracy::MemWrite( &item->gpuCalibration.cpuTime,  tracy::Profiler::GetTime() );  // :5100  hardcoded
    tracy::MemWrite( &item->gpuCalibration.gpuTime,  data.gpuTime );
    tracy::MemWrite( &item->gpuCalibration.cpuDelta, data.cpuDelta );
    tracy::MemWrite( &item->gpuCalibration.context,  data.context );
    TracyLfqCommitC;
}