Add P2 visibility controls to editor diagnostics
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 8.6 KiB |
@@ -0,0 +1,11 @@
|
|||||||
|
# P2 visibility diagnostics visual reference
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Generated with the built-in image generation tool before implementing the developer overlay. This is a visual prototype, not a Faset screenshot or a feature specification. The greybox scene, values, metrics, and viewport controls in the generated image are illustrative. In particular, Faset does not report the prototype's triangle-visibility, HZB texture-size, or history-length numbers.
|
||||||
|
|
||||||
|
The implementation uses the prototype's compact three-mode visibility selector, near-black surfaces, restrained lavender selection, simple dividers, and grouping. It only reports actual `Renderer::FrameStats` fields and retains the existing ImGui diagnostics behavior. The runtime renderer and source code define functionality.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
The second image is the actual 640 × 420 test capture with a deliberately empty scene. It verifies the selected GPU frustum button and the real counters, with the panel scrolling on a small viewport.
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 1.4 MiB |
@@ -10,7 +10,9 @@ build/linux-debug/faset_editor --project examples/projects/collect-3d --gui
|
|||||||
|
|
||||||
On Windows, use `windows-debug` for both presets and `build/windows-debug/faset_editor.exe`. Press **F12** to show or hide the panel. Drag its title bar to move it; **Freeze counters** holds a completed-frame sample for inspection. Closing the panel does not stop rendering. Pointer gestures inside the overlay are kept out of the authoring UI.
|
On Windows, use `windows-debug` for both presets and `build/windows-debug/faset_editor.exe`. Press **F12** to show or hide the panel. Drag its title bar to move it; **Freeze counters** holds a completed-frame sample for inspection. Closing the panel does not stop rendering. Pointer gestures inside the overlay are kept out of the authoring UI.
|
||||||
|
|
||||||
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. 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.
|
Use the **Visibility** selector to compare **Direct**, **GPU frustum**, and **GPU occlusion** on the same open scene. This is a live renderer setting for the Editor viewport; it does not change the scene or exported game. The selected mode is independent of **Freeze counters**. The counters describe the previous completed frame, so render one more frame after changing modes before reading them. **Path: active** under GPU visibility confirms that the GPU path actually ran; a selected GPU mode by itself is not evidence that it ran.
|
||||||
|
|
||||||
|
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, and counts per prepared LOD level. A zero may mean either that there were no candidates or that the selected path did not run; check the active-path indicator. **HZB history: unavailable / invalid** is expected after a camera cut or resize until compatible depth history is available. 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.
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ class DebugOverlay {
|
|||||||
void set_visible(bool);
|
void set_visible(bool);
|
||||||
// F12 toggles the overlay; events captured by its widgets are removed for this frame.
|
// F12 toggles the overlay; events captured by its widgets are removed for this frame.
|
||||||
std::vector<render::Event> process_events(std::span<const render::Event>);
|
std::vector<render::Event> process_events(std::span<const render::Event>);
|
||||||
void append(render::Snapshot&, const render::Renderer&, float delta_seconds);
|
void append(render::Snapshot&, render::Renderer&, float delta_seconds);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct Impl;
|
struct Impl;
|
||||||
|
|||||||
@@ -168,7 +168,7 @@ std::vector<render::Event> DebugOverlay::process_events(std::span<const render::
|
|||||||
}
|
}
|
||||||
return forwarded;
|
return forwarded;
|
||||||
}
|
}
|
||||||
void DebugOverlay::append(render::Snapshot& output, const render::Renderer& renderer, float delta) {
|
void DebugOverlay::append(render::Snapshot& output, render::Renderer& renderer, float delta) {
|
||||||
CurrentContext current(impl_->context);
|
CurrentContext current(impl_->context);
|
||||||
auto& state = *impl_;
|
auto& state = *impl_;
|
||||||
auto& io = ImGui::GetIO();
|
auto& io = ImGui::GetIO();
|
||||||
@@ -180,17 +180,36 @@ void DebugOverlay::append(render::Snapshot& output, const render::Renderer& rend
|
|||||||
auto& style = ImGui::GetStyle();
|
auto& style = ImGui::GetStyle();
|
||||||
style = ImGuiStyle{};
|
style = ImGuiStyle{};
|
||||||
ImGui::StyleColorsDark();
|
ImGui::StyleColorsDark();
|
||||||
|
// Developer tooling follows the editor's neutral, low-contrast dark palette.
|
||||||
|
style.Colors[ImGuiCol_WindowBg] = {0.11f, 0.11f, 0.115f, 0.98f};
|
||||||
|
style.Colors[ImGuiCol_TitleBg] = {0.085f, 0.085f, 0.087f, 1.f};
|
||||||
|
style.Colors[ImGuiCol_TitleBgActive] = {0.11f, 0.11f, 0.115f, 1.f};
|
||||||
|
style.Colors[ImGuiCol_FrameBg] = {0.15f, 0.15f, 0.16f, 1.f};
|
||||||
|
style.Colors[ImGuiCol_FrameBgHovered] = {0.19f, 0.19f, 0.20f, 1.f};
|
||||||
|
style.Colors[ImGuiCol_Button] = {0.15f, 0.15f, 0.16f, 1.f};
|
||||||
|
style.Colors[ImGuiCol_ButtonHovered] = {0.21f, 0.21f, 0.22f, 1.f};
|
||||||
|
style.Colors[ImGuiCol_ButtonActive] = {0.24f, 0.23f, 0.28f, 1.f};
|
||||||
|
style.Colors[ImGuiCol_Border] = {0.23f, 0.23f, 0.24f, 1.f};
|
||||||
|
style.Colors[ImGuiCol_Separator] = style.Colors[ImGuiCol_Border];
|
||||||
|
style.Colors[ImGuiCol_Text] = {0.88f, 0.88f, 0.89f, 1.f};
|
||||||
|
style.Colors[ImGuiCol_TextDisabled] = {0.60f, 0.60f, 0.62f, 1.f};
|
||||||
|
style.Colors[ImGuiCol_CheckMark] = {0.66f, 0.63f, 0.76f, 1.f};
|
||||||
|
style.WindowRounding = 5.f;
|
||||||
|
style.FrameRounding = 4.f;
|
||||||
|
style.WindowBorderSize = 1.f;
|
||||||
style.ScaleAllSizes(scale);
|
style.ScaleAllSizes(scale);
|
||||||
style.FontScaleMain = scale;
|
style.FontScaleMain = scale;
|
||||||
if (state.window_rect[2] == 0)
|
if (state.window_rect[2] == 0)
|
||||||
state.window_rect = {16 * scale, 44 * scale, 390 * scale, 310 * scale};
|
state.window_rect = {16 * scale, 44 * scale, 420 * scale, 455 * scale};
|
||||||
state.scale = scale;
|
state.scale = scale;
|
||||||
}
|
}
|
||||||
// Complete an ImGui frame while hidden too, so queued input cannot accumulate.
|
// Complete an ImGui frame while hidden too, so queued input cannot accumulate.
|
||||||
ImGui::NewFrame();
|
ImGui::NewFrame();
|
||||||
if (state.visible) {
|
if (state.visible) {
|
||||||
ImGui::SetNextWindowPos({16 * scale, 44 * scale}, ImGuiCond_FirstUseEver);
|
ImGui::SetNextWindowPos({16 * scale, 44 * scale}, ImGuiCond_FirstUseEver);
|
||||||
ImGui::SetNextWindowSize({390 * scale, 310 * scale}, ImGuiCond_FirstUseEver);
|
ImGui::SetNextWindowSize({420 * scale,
|
||||||
|
std::min(455 * scale, std::max(220.f, io.DisplaySize.y - 60.f * scale))},
|
||||||
|
ImGuiCond_FirstUseEver);
|
||||||
if (ImGui::Begin("Faset diagnostics (F12)", &state.visible,
|
if (ImGui::Begin("Faset diagnostics (F12)", &state.visible,
|
||||||
ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoCollapse)) {
|
ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoCollapse)) {
|
||||||
const auto position = ImGui::GetWindowPos(), size = ImGui::GetWindowSize();
|
const auto position = ImGui::GetWindowPos(), size = ImGui::GetWindowSize();
|
||||||
@@ -198,10 +217,33 @@ void DebugOverlay::append(render::Snapshot& output, const render::Renderer& rend
|
|||||||
if (!state.freeze)
|
if (!state.freeze)
|
||||||
state.displayed = renderer.stats();
|
state.displayed = renderer.stats();
|
||||||
const auto& stats = state.displayed;
|
const auto& stats = state.displayed;
|
||||||
ImGui::TextUnformatted("Previous completed frame");
|
ImGui::TextUnformatted("Visibility");
|
||||||
ImGui::TextWrapped("%s", stats.device.c_str());
|
const auto current_mode = renderer.visibility_mode();
|
||||||
ImGui::Checkbox("Freeze counters", &state.freeze);
|
const struct {
|
||||||
|
const char* label;
|
||||||
|
render::VisibilityMode value;
|
||||||
|
} modes[] = {{"Direct", render::VisibilityMode::Direct},
|
||||||
|
{"GPU frustum", render::VisibilityMode::GpuFrustum},
|
||||||
|
{"GPU occlusion", render::VisibilityMode::GpuOcclusion}};
|
||||||
|
const float button_width =
|
||||||
|
(ImGui::GetContentRegionAvail().x - 2.f * ImGui::GetStyle().ItemSpacing.x) / 3.f;
|
||||||
|
for (int i = 0; i < 3; ++i) {
|
||||||
|
if (i)
|
||||||
|
ImGui::SameLine();
|
||||||
|
const bool selected = current_mode == modes[i].value;
|
||||||
|
if (selected)
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4{.35f, .33f, .43f, 1.f});
|
||||||
|
if (ImGui::Button(modes[i].label, {button_width, 0}))
|
||||||
|
renderer.set_visibility_mode(modes[i].value);
|
||||||
|
if (selected)
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
}
|
||||||
|
ImGui::TextDisabled("Renderer mode; no scene or export changes");
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
|
ImGui::TextUnformatted("Previous completed frame");
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::Checkbox("Freeze counters", &state.freeze);
|
||||||
|
ImGui::TextWrapped("%s", stats.device.c_str());
|
||||||
ImGui::Text("Frame: %llu", static_cast<unsigned long long>(stats.frame));
|
ImGui::Text("Frame: %llu", static_cast<unsigned long long>(stats.frame));
|
||||||
ImGui::Text("Render call (wall): %.3f ms", stats.cpu_ms);
|
ImGui::Text("Render call (wall): %.3f ms", stats.cpu_ms);
|
||||||
if (stats.gpu_ms > 0)
|
if (stats.gpu_ms > 0)
|
||||||
@@ -212,6 +254,18 @@ void DebugOverlay::append(render::Snapshot& output, const render::Renderer& rend
|
|||||||
ImGui::Text("Draws: %u Packed vertices: %u", stats.draw_calls, stats.vertices);
|
ImGui::Text("Draws: %u Packed vertices: %u", stats.draw_calls, stats.vertices);
|
||||||
ImGui::Text("Culled meshes: %u Textures: %u", stats.culled_meshes,
|
ImGui::Text("Culled meshes: %u Textures: %u", stats.culled_meshes,
|
||||||
stats.texture_count);
|
stats.texture_count);
|
||||||
|
ImGui::Separator();
|
||||||
|
ImGui::TextUnformatted("GPU visibility");
|
||||||
|
ImGui::Text("Path: %s", stats.gpu_visibility_active ? "active" : "inactive");
|
||||||
|
ImGui::Text("Indirect bins: %u Visible: %u", stats.gpu_bins,
|
||||||
|
stats.gpu_visible_instances);
|
||||||
|
ImGui::Text("Frustum rejected: %u", stats.gpu_frustum_rejected);
|
||||||
|
ImGui::Text("HZB history: %s", stats.hzb_valid ? "valid" : "unavailable / invalid");
|
||||||
|
ImGui::Text("Deferred: %u Post visible: %u", stats.gpu_occlusion_deferred,
|
||||||
|
stats.gpu_post_visible);
|
||||||
|
ImGui::Text("Prepared LOD: %u / %u / %u / %u+", stats.lod_counts[0],
|
||||||
|
stats.lod_counts[1], stats.lod_counts[2], stats.lod_counts[3]);
|
||||||
|
ImGui::Separator();
|
||||||
ImGui::Text("Vulkan allocations: %.2f MiB",
|
ImGui::Text("Vulkan allocations: %.2f MiB",
|
||||||
double(stats.gpu_allocated_bytes) / 1048576.0);
|
double(stats.gpu_allocated_bytes) / 1048576.0);
|
||||||
ImGui::Text("Validation: %s Errors: %u",
|
ImGui::Text("Validation: %s Errors: %u",
|
||||||
|
|||||||
@@ -25,6 +25,26 @@ int main(int argc, char** argv) {
|
|||||||
overlay.append(scene, renderer, 1.f / 60.f);
|
overlay.append(scene, renderer, 1.f / 60.f);
|
||||||
require(!scene.ui_triangles.empty() && scene.ui_triangles.front().texture,
|
require(!scene.ui_triangles.empty() && scene.ui_triangles.front().texture,
|
||||||
"ImGui draw data reaches Faset's textured UI triangle API");
|
"ImGui draw data reaches Faset's textured UI triangle API");
|
||||||
|
require(renderer.visibility_mode() == render::VisibilityMode::Direct,
|
||||||
|
"Diagnostic visibility mode starts with the renderer default");
|
||||||
|
render::Event mode_click;
|
||||||
|
mode_click.type = render::Event::Type::MouseDown;
|
||||||
|
mode_click.button = 1;
|
||||||
|
mode_click.x = 205;
|
||||||
|
mode_click.y = 99;
|
||||||
|
require(overlay.process_events(std::span(&mode_click, 1)).empty(),
|
||||||
|
"Visibility mode button captures pointer down");
|
||||||
|
scene.ui_triangles.clear();
|
||||||
|
overlay.append(scene, renderer, 1.f / 60.f);
|
||||||
|
mode_click.type = render::Event::Type::MouseUp;
|
||||||
|
require(overlay.process_events(std::span(&mode_click, 1)).empty(),
|
||||||
|
"Visibility mode button captures pointer up");
|
||||||
|
scene.ui_triangles.clear();
|
||||||
|
overlay.append(scene, renderer, 1.f / 60.f);
|
||||||
|
require(renderer.visibility_mode() == render::VisibilityMode::GpuFrustum,
|
||||||
|
"Clicking GPU frustum switches the live renderer");
|
||||||
|
scene.ui_triangles.clear();
|
||||||
|
overlay.append(scene, renderer, 1.f / 60.f);
|
||||||
renderer.render(scene);
|
renderer.render(scene);
|
||||||
auto shown = renderer.pixels();
|
auto shown = renderer.pixels();
|
||||||
std::size_t changed{};
|
std::size_t changed{};
|
||||||
|
|||||||
Reference in New Issue
Block a user