ImGui frame profiler overlay, and Vulkan per-draw descriptor set churn fix#7652
Open
The-E wants to merge 10 commits into
Open
ImGui frame profiler overlay, and Vulkan per-draw descriptor set churn fix#7652The-E wants to merge 10 commits into
The-E wants to merge 10 commits into
Conversation
The-E
force-pushed
the
imgui-profiler
branch
2 times, most recently
from
July 25, 2026 06:33
8687175 to
4020b5e
Compare
Contributor
|
This just renders a black screen for me on Mac. The one exception is that text renders normally, though only if the font is TTF, otherwise it too is black/missing. No apparent errors or log info of note that I can see compared to a build from master that works fine. I should also note that the lab appears to render normally. Until you load a model, at which point everything disappears except for the text. |
Adds ImPlot as a build dependency, to be used for the upcoming ImGui frame profiler overlay's frametime graph and pie chart.
Replaces the old text-dump frame profile display (-profile_frame_time) with a runtime-toggleable ImGui/ImPlot overlay showing average/median frametime, a scrolling frametime graph, and a pie chart of the top contributors to frame time. Toggled via a new "Frame Profiler Overlay" option, which also seeds from -profile_frame_time for backward compatibility and now drives frame profiling collection at runtime instead of only at startup.
- Integrated per-backend diagnostic counters into the ImGui frame profiler overlay, triggered by `-gr_debug`. - Consolidated and cleaned up Vulkan rendering state and descriptor management for compatibility with ImGui's Vulkan pipeline. - Enabled uniform buffer and draw statistics reporting for enhanced performance insights during debugging.
- Adjust copyright notices for extended license coverage (2025-2026). - Modernize constants with `constexpr` replacements, improving type safety and clarity. - Enforce `nullptr` usage over `NULL` throughout, aligning with modern C++ practices. - Add various enhancements to ImPlot legends, markers, and axes behavior. - Introduce additional helper methods for time handling and timestamp calculations. - Address UI and typo improvements in comments and documentation, refining accuracy.
- Introduced `accumulate_self_times` for single-pass exclusive time computation, improving performance and accuracy. - Updated `FrameProfiler` and overlay snapshot logic to leverage `Category`'s stable `getId` and `getCount`. - Replaced pie chart with a stacked bar for frame budget visualization, optimizing rendering cost. - Cached `get_tid` and `get_pid` with thread-local storage to minimize syscall overhead. - Added support for unit testing frame profiling functionality and included new test cases. - Improved code comments and clarified documentation.
…Vulkan
The Material and PerDraw descriptor sets were reallocated and fully rewritten on
essentially every draw call. model_draw_list::render_buffer rebinds ModelData with
a fresh uniform buffer offset for each queued draw, and that offset was part of the
Material set's memoization key, so the cache in VulkanDrawManager::applyMaterial
missed every time - taking a frame-pool allocation, a full template write, the
16-entry material texture resolution and a vkUpdateDescriptorSets with it.
GenericData/Matrices did the same to the PerDraw set. renderShadowDraw was worse
still: all three sets allocated and rewritten per shadow draw, with no memoization
at all.
A large asteroid field (Solaris m21) measured 2566 descriptor sets and 19107
descriptor writes per frame at 1606 model draws, and grew an extra descriptor pool
chunk every frame.
Declare the bindings whose offset moves per draw - MaterialBinding::ModelData,
MaterialBinding::ShadowMapData, PerDrawBinding::GenericData and
PerDrawBinding::Matrices - as eUniformBufferDynamic. DescriptorWriter::setBuffer
now splits a dynamic binding's offset out of the descriptor, writing
{buffer, 0, range} and stashing the offset for vkCmdBindDescriptorSets'
pDynamicOffsets, so an offset-only change leaves the set's contents identical and
the memoization caches start hitting. Shader sources are unchanged.
VulkanStateTracker::bindDescriptorSet now compares the dynamic offsets as well as
the set handle. This is required, not an optimization: the common case is now the
same set rebound with a new offset, and the old handle-only redundancy check would
have skipped that bind and fed every draw the previous draw's uniforms.
renderShadowDraw memoizes its three sets the way applyMaterial does. It does not
invalidate applyMaterial's caches - those record what a set contains, the shadow
pass allocates its own sets rather than overwriting theirs, and it binds through
the state tracker, so the next material cache hit still rebinds correctly.
Also switch allocateFrameSet to the non-allocating allocateDescriptorSets overload;
the vector-returning one heap-allocated once per set, thousands of times a frame.
MAX_SETS_PER_POOL stays at 1024 - the per-frame pool growth it used to log was a
symptom of the churn, and one chunk now covers the scene outright.
Measured on that mission in a Debug build, A/B on the same tree with and without
this change:
descriptor sets/frame @ ~1370 draws 2566 -> 250-345
descriptor writes/frame 19107 -> ~1700-2300
median frametime 92.9/83.6 ms -> 72.0/66.5 ms
Render Buffer (profiler overlay) 24.68 ms (30.8%) -> 12.73 ms (18.4%)
Build Shadow Map 5.44 ms -> off the top-5 entirely
Release builds already inlined most of this away and are unchanged, as expected.
Verified with the validation layers (sync validation enabled): no new VUIDs versus
the same build without the change.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Consolidated dynamic descriptor offset logic into `DescriptorWriter::bindSets`, replacing repetitive `vkCmdBindDescriptorSets` patterns across Vulkan modules. - Eliminated redundant descriptor binding operations for Material and PerDraw sets by centralizing logic in `bindSets`. - Simplified descriptors by directly splitting offset changes from descriptor contents, avoiding unnecessary updates. - Updated comments and documentation to align with the new approach while maintaining reference consistency. - Applied optimizations across Vulkan modules (`VulkanPostProcessing`, `VulkanDescriptorManager`, etc.) to reduce per-draw overhead and improve readability.
- Added `topologySupportsPrimitiveRestart` to determine restart eligibility for each topology. - Updated `primitiveRestartEnable` to be conditionally set based on topology, addressing compatibility issues with Metal/MoltenVK. - Prevented crash scenarios on macOS by ensuring pipelines are valid for strip/fan topologies requiring primitive restart.
…dings - Introduced `DYNAMIC_SLOT_INVALID` sentinel to replace the throw in `dynamic_offset_slot` for MSVC compatibility. - Added `static_assert` checks to validate binding constants, ensuring compile-time validation for dynamic offsets. - Updated comments to clarify design rationale and avoid potential misuse.
Contributor
|
It's working for me now, though I had to clear the cached shaders first. I'd tried that previously without success. The |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This branch contains two related pieces of work: an in-game ImGui frame profiler
overlay, and a Vulkan descriptor-set optimisation that the overlay was used to
find and verify.
1. ImGui frame profiler overlay
Vendors ImPlot (
lib/implot/, updated to the v1.1 WIP) and adds a runtime frameprofiler overlay on top of the existing
tracinginfrastructure:code/tracing/ProfilerOverlay.{h,cpp}— the overlay itself: a frametime historyplot plus a breakdown of the biggest per-category contributors to the frame.
FrameProfilergainsaccumulate_self_times(), computing per-categoryexclusive (self) time for a frame's trace events in a single pass over the
event stream, rather than building a tree and comparing strings. Split out so it
can be unit-tested directly — see
test/src/tracing/test_frame_profiler.cpp.Game.ProfilerOverlayoption;-profile_frame_timestill works and now seeds the same toggle.
pipeline count) to the overlay.
2. Vulkan: stop per-draw descriptor set churn
The Material and PerDraw descriptor sets were being reallocated and fully
rewritten on essentially every draw call.
model_draw_list::render_bufferrebindsModelDatawith a fresh uniform buffer offset for each queued draw, and thatoffset was part of the Material set's memoization key, so the cache in
VulkanDrawManager::applyMaterialmissed every time — taking a frame-poolallocation, a full template write, the 16-entry material texture resolution and a
vkUpdateDescriptorSetswith it.GenericData/Matricesdid the same to thePerDraw set.
renderShadowDrawwas worse still: all three sets allocated andrewritten per shadow draw, with no memoization at all.
The fix declares the bindings whose offset moves per draw
(
MaterialBinding::ModelData,MaterialBinding::ShadowMapData,PerDrawBinding::GenericData,PerDrawBinding::Matrices) aseUniformBufferDynamic.DescriptorWriter::setBuffersplits a dynamic binding'soffset out of the descriptor — writing
{buffer, 0, range}and stashing the offsetfor
vkCmdBindDescriptorSets'pDynamicOffsets— so an offset-only change leavesthe set's contents identical and the memoization caches start hitting.
Shader sources are unchanged.
VulkanStateTracker::bindDescriptorSetnow compares the dynamic offsets as well asthe set handle. That part is required rather than an optimisation: the common case
is now the same set rebound with a new offset, and the old handle-only redundancy
check would have skipped that bind and fed every draw the previous draw's uniforms.
Also switches
allocateFrameSetto the non-allocatingallocateDescriptorSetsoverload (the vector-returning one heap-allocated once per set, thousands of times
a frame).
MAX_SETS_PER_POOLis deliberately left at 1024 — the per-frame poolgrowth it used to log was a symptom of the churn, and one chunk now covers a dense
scene outright.
Measured
Solaris mission 21 (large asteroid field, ~1370 model draws/frame), RTX 4080 SUPER
at 1080p. Debug build, A/B on the same tree with and without the change:
Render Buffer(overlay)Build Shadow Map(overlay)frame pool exhaustedlog linesRelease builds already inlined most of this per-draw work away and are unchanged
within run-to-run noise, which is the expected result —
Render Buffercosts only~1.4 µs/draw there. Worth noting: neither Release run happened to reach the heavy
Testing
HUD, shadows) — no wrong-uniform corruption.
-gr_sync_validation -gr_debug: the set of reported VUIDs is identicalto the same build without the change. No descriptor or dynamic-offset VUIDs.
unittests233/233 pass.-DFSO_FATAL_WARNINGS=ON(GCC).