rexglue-skate3/src/ui/overlay/debug_overlay.cpp
mchughalex b845372efa
Some checks failed
Build rexglue-sdk linux-arm64 / build (push) Failing after 46s
Build rexglue-sdk linux-amd64 / build (push) Failing after 2s
Build rexglue-sdk win-amd64 / build (push) Failing after 2s
Lint & Format Check / Format Check (push) Has been cancelled
add product version metadata
2026-06-04 19:56:26 +02:00

224 lines
11 KiB
C++

/**
* @file ui/overlay/debug_overlay.cpp
*
* @brief Debug overlay implementation. See debug_overlay.h for details.
*
* @copyright Copyright (c) 2026 Tom Clay <tomc@tctechstuff.com>
* All rights reserved.
*
* @license BSD 3-Clause License
* See LICENSE file in the project root for full license text.
*/
#include <rex/ui/overlay/debug_overlay.h>
#include <rex/version.h>
#include <imgui.h>
#ifdef REXGLUE_ENABLE_PERF_COUNTERS
#include <rex/perf/counter.h>
#include <cinttypes>
#endif
namespace rex::ui {
#ifdef REXGLUE_ENABLE_PERF_COUNTERS
namespace {
int64_t Counter(rex::perf::CounterId id) {
return rex::perf::GetSnapshotCounter(id);
}
void DrawBucketRow(const char* label, rex::perf::CounterId calls, rex::perf::CounterId vertices,
rex::perf::CounterId primitives) {
ImGui::Text("%-6s %6" PRId64 " %9" PRId64 " %9" PRId64, label, Counter(calls),
Counter(vertices), Counter(primitives));
}
float CounterMs(rex::perf::CounterId id) {
return static_cast<float>(Counter(id)) / 1000.0f;
}
} // namespace
#endif
DebugOverlayDialog::DebugOverlayDialog(ImGuiDrawer* imgui_drawer, FrameStatsProvider stats_provider,
std::string_view build_stamp)
: ImGuiDialog(imgui_drawer),
stats_provider_(std::move(stats_provider)),
build_stamp_(build_stamp.empty() ? REXGLUE_BUILD_STAMP : std::string(build_stamp)) {}
DebugOverlayDialog::~DebugOverlayDialog() {}
void DebugOverlayDialog::OnDraw(ImGuiIO& io) {
ImGui::SetNextWindowPos(ImVec2(10, 10), ImGuiCond_FirstUseEver);
#ifdef REXGLUE_ENABLE_PERF_COUNTERS
ImGui::SetNextWindowSize(ImVec2(620, 760), ImGuiCond_FirstUseEver);
#else
ImGui::SetNextWindowSize(ImVec2(360, 80), ImGuiCond_FirstUseEver);
#endif
ImGui::SetNextWindowBgAlpha(0.5f);
ImGui::PushFont(nullptr, 17.0f);
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(8.0f, 8.0f));
if (ImGui::Begin("Debug##overlay", nullptr, ImGuiWindowFlags_NoCollapse)) {
if (stats_provider_) {
auto stats = stats_provider_();
if (stats.frame_count > 0) {
ImGui::Text("Guest: %.1f FPS (%.2f ms)", stats.fps, stats.frame_time_ms);
}
}
#ifdef REXGLUE_ENABLE_PERF_COUNTERS
ImGui::Separator();
// Frame time graph
auto ft_us = Counter(rex::perf::CounterId::kFrameTimeUs);
float ft_ms = static_cast<float>(ft_us) / 1000.0f;
frame_time_history_[frame_history_idx_] = ft_ms;
frame_history_idx_ = (frame_history_idx_ + 1) % kFrameHistorySize;
ImGui::PlotLines("##ft", frame_time_history_.data(), kFrameHistorySize,
static_cast<int>(frame_history_idx_), "Frame (ms)", 0.0f, 50.0f,
ImVec2(340, 48));
// GPU
ImGui::Text("PreDraw: %" PRId64 " Submit: %" PRId64 " Stalls: %" PRId64,
Counter(rex::perf::CounterId::kGuestDrawPackets),
Counter(rex::perf::CounterId::kDrawCalls),
Counter(rex::perf::CounterId::kCommandBufferStalls));
ImGui::Text("Verts: %" PRId64 " Prims: %" PRId64,
Counter(rex::perf::CounterId::kVerticesProcessed),
Counter(rex::perf::CounterId::kPrimitivesProcessed));
ImGui::SeparatorText("Draw Buckets");
ImGui::TextUnformatted("Bucket Draws Verts Prims");
DrawBucketRow("Main", rex::perf::CounterId::kDrawMainCalls,
rex::perf::CounterId::kDrawMainVertices,
rex::perf::CounterId::kDrawMainPrimitives);
DrawBucketRow("Depth", rex::perf::CounterId::kDrawDepthCalls,
rex::perf::CounterId::kDrawDepthVertices,
rex::perf::CounterId::kDrawDepthPrimitives);
DrawBucketRow("Copy", rex::perf::CounterId::kDrawCopyCalls,
rex::perf::CounterId::kDrawCopyVertices,
rex::perf::CounterId::kDrawCopyPrimitives);
DrawBucketRow("MemExp", rex::perf::CounterId::kDrawMemexportCalls,
rex::perf::CounterId::kDrawMemexportVertices,
rex::perf::CounterId::kDrawMemexportPrimitives);
DrawBucketRow("NoPS", rex::perf::CounterId::kDrawNoPixelShaderCalls,
rex::perf::CounterId::kDrawNoPixelShaderVertices,
rex::perf::CounterId::kDrawNoPixelShaderPrimitives);
ImGui::SeparatorText("D3D12 Draw CPU ms");
ImGui::Text("Total: %.3f Prim: %.3f RT: %.3f",
CounterMs(rex::perf::CounterId::kDrawStageTotalUs),
CounterMs(rex::perf::CounterId::kDrawStagePrimitiveUs),
CounterMs(rex::perf::CounterId::kDrawStageRenderTargetUs));
ImGui::Text("Pipe: %.3f Tex: %.3f Fixed: %.3f",
CounterMs(rex::perf::CounterId::kDrawStagePipelineUs),
CounterMs(rex::perf::CounterId::kDrawStageTextureUs),
CounterMs(rex::perf::CounterId::kDrawStageFixedFunctionUs));
ImGui::Text("Bind: %.3f VB: %.3f Bar: %.3f Sub: %.3f",
CounterMs(rex::perf::CounterId::kDrawStageBindingsUs),
CounterMs(rex::perf::CounterId::kDrawStageVertexBuffersUs),
CounterMs(rex::perf::CounterId::kDrawStageBarriersUs),
CounterMs(rex::perf::CounterId::kDrawStageSubmitUs));
ImGui::SeparatorText("D3D12 GPU ms");
ImGui::Text("Main: %.3f Depth: %.3f NoPS: %.3f",
CounterMs(rex::perf::CounterId::kGpuMainUs),
CounterMs(rex::perf::CounterId::kGpuDepthUs),
CounterMs(rex::perf::CounterId::kGpuNoPixelShaderUs));
ImGui::Text("Copy: %.3f MemExp: %.3f Timed: %" PRId64,
CounterMs(rex::perf::CounterId::kGpuCopyUs),
CounterMs(rex::perf::CounterId::kGpuMemexportUs),
Counter(rex::perf::CounterId::kGpuTimestampedDraws));
ImGui::Text("Frame: %.3f Frames: %" PRId64,
CounterMs(rex::perf::CounterId::kGpuCommandProcessorFrameUs),
Counter(rex::perf::CounterId::kGpuTimestampedFrames));
ImGui::SeparatorText("CPU Phase ms");
ImGui::Text("PM4 Primary: %.3f Indirect: %.3f",
CounterMs(rex::perf::CounterId::kCpuPrimaryBufferUs),
CounterMs(rex::perf::CounterId::kCpuIndirectBufferUs));
ImGui::Text("BeginSub: %.3f EndSub: %.3f Deferred: %.3f Exec: %.3f",
CounterMs(rex::perf::CounterId::kCpuD3D12BeginSubmissionUs),
CounterMs(rex::perf::CounterId::kCpuD3D12EndSubmissionUs),
CounterMs(rex::perf::CounterId::kCpuD3D12DeferredExecuteUs),
CounterMs(rex::perf::CounterId::kCpuD3D12ExecuteCommandListsUs));
ImGui::Text("Paint: %.3f Consume: %.3f Record: %.3f UI: %.3f",
CounterMs(rex::perf::CounterId::kCpuD3D12PaintTotalUs),
CounterMs(rex::perf::CounterId::kCpuD3D12PaintConsumeUs),
CounterMs(rex::perf::CounterId::kCpuD3D12PaintRecordUs),
CounterMs(rex::perf::CounterId::kCpuD3D12PaintUiUs));
ImGui::Text("PresentWait: %.3f Present: %.3f",
CounterMs(rex::perf::CounterId::kCpuD3D12PresentWaitUs),
CounterMs(rex::perf::CounterId::kCpuD3D12PresentUs));
ImGui::SeparatorText("Texture Streaming");
ImGui::Text("Req: %" PRId64 " Fetches: %" PRId64 " Changed: %" PRId64 " CPU: %.3f",
Counter(rex::perf::CounterId::kTextureRequestCalls),
Counter(rex::perf::CounterId::kTextureFetchesRequested),
Counter(rex::perf::CounterId::kTextureBindingsChanged),
CounterMs(rex::perf::CounterId::kTextureRequestUs));
ImGui::Text("Loads: %" PRId64 " Ranges: %" PRId64 " Bytes: %.2f MB",
Counter(rex::perf::CounterId::kTexturePendingLoads),
Counter(rex::perf::CounterId::kTexturePendingRanges),
static_cast<double>(Counter(rex::perf::CounterId::kTexturePendingBytes)) /
(1024.0 * 1024.0));
ImGui::Text("SMem: %.3f Commit: %.3f Backend: %.3f Scaled: %.3f",
CounterMs(rex::perf::CounterId::kTextureSharedMemoryRequestUs),
CounterMs(rex::perf::CounterId::kTextureCommitLoadUs),
CounterMs(rex::perf::CounterId::kTextureLoadBackendUs),
CounterMs(rex::perf::CounterId::kTextureScaledResolveCommitUs));
ImGui::Text("Committed: %" PRId64 " LoadBytes: %.2f MB",
Counter(rex::perf::CounterId::kTextureLoadsCommitted),
static_cast<double>(Counter(rex::perf::CounterId::kTextureLoadBytes)) /
(1024.0 * 1024.0));
// Audio
ImGui::Text("XMA: %" PRId64 " Lat: %.1fms BufQ: %" PRId64,
Counter(rex::perf::CounterId::kXmaFramesDecoded),
static_cast<float>(Counter(rex::perf::CounterId::kAudioFrameLatencyUs)) / 1000.0f,
Counter(rex::perf::CounterId::kBufferQueueDepth));
// Dispatch
ImGui::Text("Dispatch: %" PRId64 " IRQ: %" PRId64,
Counter(rex::perf::CounterId::kFunctionsDispatched),
Counter(rex::perf::CounterId::kInterruptDispatches));
// Threading
ImGui::Text("Threads: %" PRId64 " APC: %" PRId64 " Contention: %" PRId64,
Counter(rex::perf::CounterId::kActiveThreads),
Counter(rex::perf::CounterId::kApcQueueDepth),
Counter(rex::perf::CounterId::kCriticalRegionContentions));
// Caches
auto tex_h = Counter(rex::perf::CounterId::kTextureCacheHits);
auto tex_m = Counter(rex::perf::CounterId::kTextureCacheMisses);
auto pip_h = Counter(rex::perf::CounterId::kPipelineCacheHits);
auto pip_m = Counter(rex::perf::CounterId::kPipelineCacheMisses);
ImGui::Text("TexCache: %" PRId64 "/%" PRId64 " PipeCache: %" PRId64 "/%" PRId64, tex_h,
tex_h + tex_m, pip_h, pip_h + pip_m);
#else
ImGui::TextUnformatted("Perf counters are disabled in this build.");
#endif
}
ImGui::End();
ImGui::PopStyleVar();
ImGui::PopFont();
// Build stamp watermark -- centered near bottom of screen
const char* build_stamp = build_stamp_.c_str();
auto text_size = ImGui::CalcTextSize(build_stamp);
float padding = ImGui::GetStyle().WindowPadding.x * 2.0f;
float bottom_offset = io.DisplaySize.y * 0.03f;
ImGui::SetNextWindowPos(ImVec2((io.DisplaySize.x - text_size.x - padding) * 0.5f,
io.DisplaySize.y - text_size.y - bottom_offset));
ImGui::SetNextWindowSize(ImVec2(0, 0));
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f, 0.5f));
if (ImGui::Begin("##watermark", nullptr,
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoBackground |
ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoNav |
ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::TextUnformatted(build_stamp);
}
ImGui::End();
ImGui::PopStyleColor();
}
} // namespace rex::ui