Release HZB diagnostic texture when preview closes

This commit is contained in:
Emil
2026-09-23 23:09:24 +03:00
parent bb46e0091e
commit 868f19aaaa
3 changed files with 44 additions and 5 deletions
+1 -1
View File
@@ -14,7 +14,7 @@ Use the **Visibility** selector to compare **Direct**, **GPU frustum**, and **GP
The panel reports the previous completed frame: renderer wall time, GPU timestamp time where available, synchronous readback time, draw calls, packed vertices, culled meshes, textures, explicit Vulkan allocation sizes, actual validation availability/errors, and GPU pass-label count. It also shows whether GPU visibility ran, submitted indirect bins, visible instances, frustum rejects, deferred and post-pass visible instances, HZB history validity, counts per prepared LOD level, and GPU pass timings where available. GPU counts are explicitly marked unavailable until the first frame rendered with diagnostics open; only a displayed zero is a measured zero. **Previous HZB history: invalid** is expected after a camera cut or resize until compatible depth history is available. A current HZB preview can still exist after that first frame because it was built from the current depth. Renderer wall time includes waiting for GPU work; it is not thread CPU usage. Memory excludes driver-internal allocations. The overlay itself adds drawing work, so hide it for a baseline performance measurement.
In **GPU occlusion** mode, enable **Show HZB** to inspect the current grayscale depth pyramid. The **Mip** slider selects a pyramid level; the preview starts at mip 3 to keep its readback small. A larger mip number shows coarser depth. The preview reads the HZB only while the panel and toggle are open, and only once per completed frame or mip change. Opening diagnostics also enables readback of GPU visibility counters, which is disabled again when the panel closes. Disable the HZB preview for performance comparisons: its diagnostic copy and texture upload add GPU and CPU work. **Freeze counters** does not freeze the HZB image.
In **GPU occlusion** mode, enable **Show HZB** to inspect the current grayscale depth pyramid. The **Mip** slider selects a pyramid level; the preview starts at mip 3 to keep its readback small. A larger mip number shows coarser depth. The preview reads the HZB only while the panel and toggle are open, and only once per completed frame or mip change. Switching it off or closing the panel releases the preview; its GPU texture retires when the next frame begins. Opening diagnostics also enables readback of GPU visibility counters, which is disabled again when the panel closes. Disable the HZB preview for performance comparisons: its diagnostic copy and texture upload add GPU and CPU work. **Freeze counters** does not freeze the HZB image.
The Vulkan backend emits `VK_EXT_debug_utils` labels for `ShadowMap`, `ForwardAndUI`, `Readback`, and, when presenting, `Presentation`. A graphics capture tool that supports this extension can identify those command-buffer regions. Labels remain available without the Khronos validation layer when the extension is exposed; unsupported systems continue rendering and report labels unavailable. A submitted-label count confirms calls were emitted, not that an external capture tool was tested.
+20 -3
View File
@@ -83,6 +83,10 @@ bool DebugOverlay::visible() const {
}
void DebugOverlay::set_visible(bool value) {
impl_->visible = value;
if (!value) {
impl_->hzb_preview.reset();
impl_->hzb_available = impl_->hzb_sampled = false;
}
}
std::vector<render::Event> DebugOverlay::process_events(std::span<const render::Event> events) {
CurrentContext current(impl_->context);
@@ -104,8 +108,13 @@ std::vector<render::Event> DebugOverlay::process_events(std::span<const render::
for (const auto& event : events) {
using Type = render::Event::Type;
if ((event.type == Type::KeyDown || event.type == Type::KeyUp) && event.key == "F12") {
if (event.type == Type::KeyDown && !event.repeat)
if (event.type == Type::KeyDown && !event.repeat) {
impl_->visible = !impl_->visible;
if (!impl_->visible) {
impl_->hzb_preview.reset();
impl_->hzb_available = impl_->hzb_sampled = false;
}
}
continue;
}
io.AddKeyEvent(ImGuiMod_Ctrl, event.control);
@@ -244,6 +253,7 @@ void DebugOverlay::append(render::Snapshot& output, render::Renderer& renderer,
if (selected)
ImGui::PopStyleColor();
}
const auto selected_mode = renderer.visibility_mode();
ImGui::TextDisabled("Renderer mode; no scene or export changes");
ImGui::Separator();
ImGui::TextUnformatted("Previous completed frame");
@@ -295,9 +305,11 @@ void DebugOverlay::append(render::Snapshot& output, render::Renderer& renderer,
ImGui::SetWindowSize({ImGui::GetWindowSize().x, expanded});
} else {
state.hzb_available = false;
state.hzb_preview.reset();
}
}
if (state.show_hzb && state.visible && current_mode == render::VisibilityMode::GpuOcclusion) {
if (state.show_hzb && state.visible &&
selected_mode == render::VisibilityMode::GpuOcclusion) {
const auto max_extent = std::max(renderer.width(), renderer.height());
const int max_mip = static_cast<int>(std::bit_width(std::bit_ceil(max_extent))) - 1;
state.hzb_mip = std::clamp(state.hzb_mip, 0, max_mip);
@@ -320,9 +332,11 @@ void DebugOverlay::append(render::Snapshot& output, render::Renderer& renderer,
state.hzb_available = true;
} else {
state.hzb_available = false;
state.hzb_preview.reset();
}
} catch (const std::exception& error) {
state.hzb_available = false;
state.hzb_preview.reset();
state.hzb_error = error.what();
state.show_hzb = false;
}
@@ -344,6 +358,7 @@ void DebugOverlay::append(render::Snapshot& output, render::Renderer& renderer,
}
} else if (state.show_hzb) {
state.hzb_available = false;
state.hzb_preview.reset();
state.hzb_sampled = false;
ImGui::TextDisabled("Switch to GPU occlusion to view the HZB");
}
@@ -365,8 +380,10 @@ void DebugOverlay::append(render::Snapshot& output, render::Renderer& renderer,
current_size.x, current_size.y};
}
ImGui::End();
} else {
}
if (!state.visible) {
state.hzb_available = false;
state.hzb_preview.reset();
state.hzb_sampled = false;
}
// GPU counters are a diagnostics readback, never a normal renderer dependency.
+23 -1
View File
@@ -149,12 +149,15 @@ int main(int argc, char** argv) {
require(preview != hzb_scene.ui_triangles.end() && preview->texture->width == 128 &&
preview->texture->height == 128 && preview->texture->revision > 0,
"Enabled HZB preview emits the selected mip as a revised texture");
const std::weak_ptr<const render::Texture> first_preview = preview->texture;
click_preview();
hzb_scene.ui_triangles.clear();
hzb_overlay.append(hzb_scene, hzb_renderer, 1.f / 60.f);
require(std::all_of(hzb_scene.ui_triangles.begin(), hzb_scene.ui_triangles.end(),
[&](const auto& batch) { return batch.texture == font_texture; }),
"Disabling HZB preview removes its texture");
require(first_preview.expired(),
"Disabling HZB preview releases its CPU texture without rendering");
click_preview();
hzb_scene.ui_triangles.clear();
hzb_overlay.append(hzb_scene, hzb_renderer, 1.f / 60.f);
@@ -164,16 +167,35 @@ int main(int argc, char** argv) {
"HZB preview renders cleanly and open diagnostics enable GPU counters");
hzb_scene.ui_triangles.clear();
hzb_overlay.append(hzb_scene, hzb_renderer, 1.f / 60.f);
const auto active_preview = std::find_if(hzb_scene.ui_triangles.begin(),
hzb_scene.ui_triangles.end(),
[&](const auto& batch) {
return batch.texture != font_texture;
});
require(active_preview != hzb_scene.ui_triangles.end(),
"Re-enabling HZB preview creates a live texture");
const std::weak_ptr<const render::Texture> second_preview = active_preview->texture;
hzb_renderer.render(hzb_scene);
const auto with_preview_textures = hzb_renderer.stats().texture_count;
require(with_preview_textures >= 3,
"HZB preview uploads an additional GPU texture");
if (argc > 2)
hzb_renderer.capture(argv[2]);
hzb_overlay.set_visible(false);
render::Event close_hzb;
close_hzb.type = render::Event::Type::KeyDown;
close_hzb.key = "F12";
require(hzb_overlay.process_events(std::span(&close_hzb, 1)).empty() &&
!hzb_overlay.visible(),
"F12 closes an active HZB preview");
hzb_scene.ui_triangles.clear();
hzb_overlay.append(hzb_scene, hzb_renderer, 1.f / 60.f);
require(hzb_scene.ui_triangles.empty(), "Hidden panel emits no HZB preview");
hzb_renderer.render(hzb_scene);
require(!hzb_renderer.stats().visibility_counters_valid,
"Closing diagnostics disables GPU visibility readback");
require(second_preview.expired() &&
hzb_renderer.stats().texture_count + 1 == with_preview_textures,
"Closing diagnostics retires its HZB GPU texture on the next frame");
std::cout << "ImGui diagnostics, F12, font atlas, clipping and event isolation passed\n";
return 0;
} catch (const std::exception& error) {