graphics: attribute native-pass GPU time to per-stage profile buckets

This commit is contained in:
mchughalex 2026-07-21 12:31:10 +01:00
parent 95d2792585
commit 17caf72bdd
4 changed files with 151 additions and 0 deletions

View file

@ -398,6 +398,26 @@ struct Rect {
int32_t bottom = 0;
};
// GPU-time attribution stages for ProfileRegion: the renderer marks the start
// of each of its passes and the command processor's timestamp-bucket profiler
// (d3d12_gpu_timestamp_buckets / vulkan_gpu_timestamp_buckets) reports the
// per-stage GPU spans. kTail marks the end of the last stage; on Vulkan the
// span from kTail to frame end is reported as its own bucket, on D3D12 it
// stays in the profiler's untimed remainder.
enum class ProfileStage : uint8_t {
kCommit, // content-store commits, decode uploads, eviction
kShadow, // dynamic caster shadow atlas + blur
kStaticSun, // static sun-shadow cascade renders
kMain, // main scene pass (opaque + transparent)
kResolve, // MSAA resolve + outline composite
kAmbientOcclusion,
kSsr,
kVolumetrics,
kBloom, // bloom pyramid + tonemap
k2d, // photo grab, blur chains, 2D overlay replay
kTail, // after the last stage: backend/present tail
};
// ---------------------------------------------------------------------------
// Command recording. Wraps the command processor's deferred command list for
// the current frame; valid only inside the render callback, render thread
@ -469,6 +489,11 @@ class Cmd {
ResourceState after) = 0;
virtual void FlushBarriers() = 0;
// Marks the start of a renderer pass for GPU-time attribution (closing the
// previously marked stage). No-op while the backend's timestamp-bucket
// profiler is disabled; see ProfileStage for the reporting semantics.
virtual void ProfileRegion(ProfileStage stage) = 0;
protected:
virtual ~Cmd() = default;
};

View file

@ -63,6 +63,20 @@ enum class DrawBucket : uint8_t {
// The native guest-output renderer's whole GPU pass (scene + 2D replay +
// resolve), recorded inside IssueSwap. Runs until frame end.
kNativeScene,
// Native-pass stages (nrhi::Cmd::ProfileRegion): successive marks partition
// the native pass into its renderer-defined stages; when the renderer marks
// them, they replace kNativeScene in the frame's attribution.
kNativeCommit, // content-store commits, decode uploads, eviction
kNativeShadow, // dynamic caster shadow atlas + blur
kNativeSun, // static sun-shadow cascade renders
kNativeMain, // main scene pass (opaque + transparent + SSR G-buffer)
kNativeResolve, // MSAA resolve + outline composite
kNativeAo, // screen-space ambient occlusion
kNativeSsr, // screen-space reflection march + composite
kNativeVol, // volumetric sun shafts + haze
kNativeBloom, // bloom pyramid + tonemap
kNative2d, // photo grab, blur chains, 2D overlay replay
kNativeTail, // after the last stage: backend/present tail
kCount,
};
@ -103,6 +117,28 @@ inline const char* DrawBucketName(DrawBucket bucket) {
return "TexUpCopy";
case DrawBucket::kNativeScene:
return "Native";
case DrawBucket::kNativeCommit:
return "NatCommit";
case DrawBucket::kNativeShadow:
return "NatShadow";
case DrawBucket::kNativeSun:
return "NatSun";
case DrawBucket::kNativeMain:
return "NatMain";
case DrawBucket::kNativeResolve:
return "NatResolve";
case DrawBucket::kNativeAo:
return "NatAO";
case DrawBucket::kNativeSsr:
return "NatSSR";
case DrawBucket::kNativeVol:
return "NatVol";
case DrawBucket::kNativeBloom:
return "NatBloom";
case DrawBucket::kNative2d:
return "Nat2D";
case DrawBucket::kNativeTail:
return "NatTail";
default:
return "Unknown";
}

View file

@ -398,6 +398,7 @@ class NrCmdD3D12 : public nrhi::Cmd {
void Barrier(nrhi::Texture* texture, ResourceState before,
ResourceState after) override;
void FlushBarriers() override;
void ProfileRegion(nrhi::ProfileStage stage) override;
// Called at frame begin and on root-signature changes: the fresh deferred
// command list / new root signature carries no bindings, so the latched
@ -407,9 +408,20 @@ class NrCmdD3D12 : public nrhi::Cmd {
std::memset(last_table_counts_, 0, sizeof(last_table_counts_));
}
// Frame begin only (never on root-signature changes): each frame gets a
// fresh timestamp-query range, so an open profile region must not carry an
// end query across frames.
void ResetProfileRegion() { profile_region_query_ = UINT32_MAX; }
NrDeviceD3D12* device = nullptr;
private:
// Start query of the open ProfileRegion span, UINT32_MAX when none. Unlike
// Vulkan's auto-closing regions, D3D12 buckets are explicit begin/end query
// pairs; an unended pair would read stale readback memory, so every opened
// region is closed by the next mark (kTail closes without opening).
uint32_t profile_region_query_ = UINT32_MAX;
void BindTextureTable(uint32_t param, NrTextureViewD3D12* const* views, uint32_t count);
// Last tuple bound per root param: consecutive draws mostly rebind the
@ -936,6 +948,7 @@ class NrDeviceD3D12 : public nrhi::Device {
uint32_t height, nrhi::Texture** guest_output_out) {
guest_output_state_ = guest_output_internal_state;
cmd_.ResetFrameState();
cmd_.ResetProfileRegion();
const auto maint_t0 = std::chrono::steady_clock::now();
const size_t dissolved_count = dissolved_views_.size();
FlushDissolvedViews();
@ -1417,6 +1430,47 @@ void NrCmdD3D12::Barrier(nrhi::Texture* texture, ResourceState before, ResourceS
void NrCmdD3D12::FlushBarriers() { device->cp()->SubmitBarriers(); }
rex::perf::DrawBucket ProfileStageBucket(nrhi::ProfileStage stage) {
switch (stage) {
case nrhi::ProfileStage::kCommit:
return rex::perf::DrawBucket::kNativeCommit;
case nrhi::ProfileStage::kShadow:
return rex::perf::DrawBucket::kNativeShadow;
case nrhi::ProfileStage::kStaticSun:
return rex::perf::DrawBucket::kNativeSun;
case nrhi::ProfileStage::kMain:
return rex::perf::DrawBucket::kNativeMain;
case nrhi::ProfileStage::kResolve:
return rex::perf::DrawBucket::kNativeResolve;
case nrhi::ProfileStage::kAmbientOcclusion:
return rex::perf::DrawBucket::kNativeAo;
case nrhi::ProfileStage::kSsr:
return rex::perf::DrawBucket::kNativeSsr;
case nrhi::ProfileStage::kVolumetrics:
return rex::perf::DrawBucket::kNativeVol;
case nrhi::ProfileStage::kBloom:
return rex::perf::DrawBucket::kNativeBloom;
case nrhi::ProfileStage::k2d:
return rex::perf::DrawBucket::kNative2d;
case nrhi::ProfileStage::kTail:
return rex::perf::DrawBucket::kNativeTail;
}
return rex::perf::DrawBucket::kNativeScene;
}
void NrCmdD3D12::ProfileRegion(nrhi::ProfileStage stage) {
if (profile_region_query_ != UINT32_MAX) {
device->cp()->EndGpuTimestampedDraw(profile_region_query_);
profile_region_query_ = UINT32_MAX;
}
if (stage == nrhi::ProfileStage::kTail) {
// No frame-end query to pair with on D3D12; the tail lands in the
// profiler's untimed remainder.
return;
}
profile_region_query_ = device->cp()->BeginGpuTimestampedDraw(ProfileStageBucket(stage));
}
} // namespace
nrhi::Device* CreateNativeRhiDevice(D3D12CommandProcessor* command_processor) {

View file

@ -440,6 +440,7 @@ class NrCmdVulkan : public nrhi::Cmd {
uint32_t height) override;
void Barrier(nrhi::Texture* texture, ResourceState before, ResourceState after) override;
void FlushBarriers() override;
void ProfileRegion(nrhi::ProfileStage stage) override;
// --- frame handling (driven by the device) ---
void ResetFrameState();
@ -2693,6 +2694,41 @@ void NrCmdVulkan::FlushBarriers() {
device->cp()->SubmitBarriers(true);
}
rex::perf::DrawBucket ProfileStageBucket(nrhi::ProfileStage stage) {
switch (stage) {
case nrhi::ProfileStage::kCommit:
return rex::perf::DrawBucket::kNativeCommit;
case nrhi::ProfileStage::kShadow:
return rex::perf::DrawBucket::kNativeShadow;
case nrhi::ProfileStage::kStaticSun:
return rex::perf::DrawBucket::kNativeSun;
case nrhi::ProfileStage::kMain:
return rex::perf::DrawBucket::kNativeMain;
case nrhi::ProfileStage::kResolve:
return rex::perf::DrawBucket::kNativeResolve;
case nrhi::ProfileStage::kAmbientOcclusion:
return rex::perf::DrawBucket::kNativeAo;
case nrhi::ProfileStage::kSsr:
return rex::perf::DrawBucket::kNativeSsr;
case nrhi::ProfileStage::kVolumetrics:
return rex::perf::DrawBucket::kNativeVol;
case nrhi::ProfileStage::kBloom:
return rex::perf::DrawBucket::kNativeBloom;
case nrhi::ProfileStage::k2d:
return rex::perf::DrawBucket::kNative2d;
case nrhi::ProfileStage::kTail:
return rex::perf::DrawBucket::kNativeTail;
}
return rex::perf::DrawBucket::kNativeScene;
}
void NrCmdVulkan::ProfileRegion(nrhi::ProfileStage stage) {
// Region semantics: beginning a bucket closes the active one, and the CP
// closes the last bucket at frame end - so the kTail mark records the
// present/submission tail as its own span.
device->cp()->BeginGpuTimestampedRegion(ProfileStageBucket(stage));
}
} // namespace
// ---------------------------------------------------------------------------