Files
Faset_Engine/docs/superpowers/plans/2026-09-23-p2-gpu-visibility.md
T
Emil d18c5d77b3
Native and manual checks / native (ubuntu-24.04) (push) Failing after 31s
Native and manual checks / manual (push) Successful in 29s
Windows editor and software Vulkan / windows-graphics (push) Canceled after 0s
Native and manual checks / native (windows-2025) (push) Canceled after 0s
Validate and document P2 GPU visibility milestone
2026-09-24 00:48:54 +03:00

125 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# P2 GPU Visibility and Mesh LOD Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Deliver all of PLAN P2: stable GPU instance IDs, GPU frustum culling, fixed indirect bins, a visible HZB debug mode, conservative two-pass occlusion, prepared mesh LOD with hysteresis, and measured comparisons to the direct renderer.
**Architecture:** Keep the current direct path as a selectable reference. Add an opaque instanced path with local mesh geometry, instance/visible-ID storage buffers and compute-authored indirect instance counts. Split opaque rasterization around an ordinary-Z max-depth HZB; repair previous-frame occlusion guesses in a current-frame post pass. Keep shadows and ordered sprites/UI independent.
**Tech Stack:** C++20, Vulkan 1.3, Slang/SPIR-V, CMake/Ninja, SDL3, CPU contract tests and GPU validation/image tests.
**Spec:** `docs/superpowers/specs/2026-09-23-p2-gpu-visibility-design.md`
## Global Constraints
- Preserve the existing direct renderer and a runtime switch that selects it.
- Player/export contain cooked SPIR-V and metadata; no Slang compiler is required at runtime.
- MCP remains editor-only and never exposes the live Player world.
- Forward Z uses max-depth HZB, depth clear 1, and `LESS_OR_EQUAL`.
- No camera-frustum decision may remove a shadow caster; sprites and UI remain ordered.
- No required ray tracing, mesh shader, multi-draw-indirect, `drawIndirectFirstInstance`, or synchronous GPU visibility readback.
- Check actual Vulkan feature/format/limit support and retain a direct fallback if the P2 profile is unavailable.
## Review Focus
- Empty scene and exact bin capacity: indirect counts reset to zero and cannot address outside reserved ID ranges; Task 3 pins this.
- Door opens or disappears: a previously hidden object appears in the same final frame through Post; Task 5 pins this.
- Odd and resized viewports: HZB padding is far depth and stale history cannot be sampled; Tasks 4–5 pin this.
- Offscreen shadow caster: camera visibility leaves shadow generation intact; Task 3 pins this.
- Mesh/key reuse and LOD changes: previous transforms and occlusion are invalidated; Tasks 1 and 6 pin this.
---
**Implementation note, 23 September 2026:** this checklist records the proposed execution sequence. A checked item has direct code/test/commit evidence; an unchecked item may be an unrecorded test-first step or a narrower validation/documentation gap, even where the corresponding P2 feature works. The [acceptance protocol](../../studies/19-p2-gpu-visibility-acceptance.md), [initial benchmark](../../studies/20-p2-gpu-visibility-benchmark-2026-09-23.md), [optimized repeat](../../studies/21-p2-gpu-visibility-optimization-2026-09-23.md) and [SwiftShader compatibility record](../../validation/p2-swiftshader-2026-09-23/README.md) report their tested configurations. Real-game performance and physical Windows GPUs require separate evidence.
### Task 1: Stable instances, conservative bounds, and LOD policy
**Files:** Modify `include/faset/render/renderer.hpp`, `src/player/SceneView.cpp`, `cmake/Renderer.cmake`; create `include/faset/render/visibility.hpp`, `src/render/visibility.cpp`, `tests/render_visibility_policy_tests.cpp`.
**Interfaces:** Produce `InstanceTracker::update(key, meshIdentity, model, bounds, viewId)` returning slot, generation, previous bounds and `previousValid`; `select_lod(projectedPixels, previousLevel, thresholds, hysteresis)`; `transformed_bounds(mesh, model)`. Add optional `DrawItem::instance_key`, `DrawItem::lod_meshes`, `Snapshot::view_id`, `Snapshot::camera_cut` without changing existing aggregate initialization order.
- [x] **Step 1: Write failing CPU tests.** Assert transformed bounds for rotated, negative/nonuniform scaled meshes; stable slot through reorder; generation change after removal/reuse or mesh replacement; no previous state on camera cut; LOD hysteresis on both sides of a threshold; fallback when a level is absent. Include this core assertion:
```cpp
auto first = tracker.update("object/primitive", meshA, identity, box, "game");
auto second = tracker.update("object/primitive", meshA, moved, box, "game");
require(first.slot == second.slot && second.previous_valid);
tracker.finish_frame();
require(!tracker.update("object/primitive", meshB, moved, box, "game").previous_valid);
```
- [ ] **Step 2: Run the focused test target and record the expected missing-interface failure.** `cmake --build --preset linux-debug --target faset_render_visibility_policy_tests -j 6` must fail before implementation because the new interfaces/target do not exist.
- [x] **Step 3: Implement the policy and extraction keys.** Derive keys from persistent scene object and imported primitive identity; anonymous draws remain renderable without temporal state. Use all eight local AABB corners and finite-value checks. Define an explicit LOD threshold/hysteresis contract in the header.
- [x] **Step 4: Rebuild/run the focused CPU test, then the existing scene-view tests.** `ctest --test-dir build/linux-debug --output-on-failure -R 'visibility_policy|player_scene_contracts'` must pass.
- [x] **Step 5: Commit** `Introduce stable render instances and prepared LOD policy`.
### Task 2: P2 Slang bundle and checked shader metadata
**Files:** Create `shaders/gpu_scene.slang`; modify `tools/compile_shader.py`, `cmake/Renderer.cmake`, `src/render/shader_contract.cpp`, `src/render/shader_contract.hpp`, `src/editor/build_service.cpp`, relevant shader/build tests.
**Interfaces:** Compile and validate `gpuVertexMain`, `gpuShadowMain`, `gpuCullMain`, `gpuHzbMain`, `gpuPostCullMain` with documented set/binding layouts. Baseline `vertexMain`, `fragmentMain`, `shadowMain` and their hot-reload fingerprint contract remain valid. Export includes each new `.spv` and `.reflection.json` pair.
- [x] **Step 1: Write failing reflection/package tests.** A storage-buffer/storage-image Slang reflection fixture must normalize to a typed descriptor; missing or tampered P2 SPIR-V/metadata must fail validation; a packaged Player must contain all required P2 shaders.
- [ ] **Step 2: Run focused tests and verify the expected rejection or missing-artifact failure.** `ctest --test-dir build/linux-debug --output-on-failure -R 'render_shader_reload|build_schema_publication'` plus the new reflection test target.
- [x] **Step 3: Extend the compiler's descriptor normalization and add the P2 shader entries.** Vertex resolves `visibleIds[binBase + SV_VulkanInstanceID]`; compute writes bounded per-bin IDs/counts; HZB computes max of valid children and far depth for padding. Keep C++/Slang record strides explicit and checked.
- [x] **Step 4: Validate generated reflection and package.** Rebuild `faset_shaders`, run the focused tests and inspect each generated metadata stage/binding/fingerprint.
- [x] **Step 5: Commit** `Add checked Slang shaders for GPU visibility and HZB`.
### Task 3: GPU frustum culling and fixed indirect bins
**Files:** Modify `src/render/renderer.cpp`, `include/faset/render/renderer.hpp`, `tests/render_tests.cpp`; create `tests/render_gpu_visibility_tests.cpp`.
**Interfaces:** `RendererConfig::visibility_mode` selects direct/auto/GPU; `FrameStats` exposes submitted bin and frustum-visible counts. Renderer-owned buffers hold local vertices, instance records, candidate/bin tables, visible IDs and `VkDrawIndirectCommand` templates. One draw per bin uses `firstInstance=0`.
- [ ] **Step 1: Write GPU integration tests.** Direct/GPU images must agree on the same opaque cube/plane scene; empty and one-instance scenes have zero/one visible instances; over-capacity growth and all-six-plane rejects remain validation-clean; an offscreen caster continues to affect a visible receiver.
- [ ] **Step 2: Run the new GPU test and verify that GPU mode is absent/fails for the intended reason.** Use `ctest --test-dir build/linux-debug --output-on-failure -R '^render_gpu_visibility$'`.
- [x] **Step 3: Add checked device capability selection and distinct GPU scene resources.** Build mesh+texture bins, upload local vertices once per unique mesh each frame, reserve one ID range per bin, create/update descriptors, dispatch frustum cull, barrier compute writes to indirect and vertex-storage reads, and issue fixed indirect draws. Keep the direct path byte-for-byte selectable.
- [x] **Step 4: Run the focused GPU test with Khronos validation and the existing offscreen renderer tests.** No Vulkan errors, no visible image holes, and no shadow regression.
- [x] **Step 5: Commit** `Render opaque meshes through GPU culling and fixed indirect bins`.
### Task 4: Current HZB and diagnostic view
**Files:** Modify `src/render/renderer.cpp`, `include/faset/render/renderer.hpp`, `shaders/gpu_scene.slang`, `tests/render_gpu_visibility_tests.cpp`; add a focused HZB CPU oracle test if the reduction/projection contract needs isolation.
**Interfaces:** `RendererConfig` or `Snapshot` selects HZB visualization; `FrameStats` records HZB extent, levels and build time. The current depth attachment is stored and sampled; a per-mip `R32_SFLOAT` image stores max-depth reduction.
- [ ] **Step 1: Write failing tests.** Assert odd 319×241 extent, a far-depth hole, mip chain dimensions/padding, resized target recreation and an HZB debug image with non-uniform depth.
- [ ] **Step 2: Run the tests and confirm the missing HZB/debug capability is the failure.** `ctest --test-dir build/linux-debug --output-on-failure -R '^render_gpu_visibility$'`.
- [x] **Step 3: Split Main opaque raster from sprites/UI, store depth, allocate sampled/storage HZB mip views, dispatch each reduction with explicit depth→compute and mip→mip barriers, and draw a selectable debug visualization.** Preserve existing forward-Z convention.
- [x] **Step 4: Run focused GPU tests and baseline image tests under validation; compare HZB-off output with direct mode.**
- [x] **Step 5: Commit** `Build and visualize current-frame max-depth HZB`.
### Task 5: Previous-HZB main pass and same-frame post repair
**Files:** Modify `src/render/renderer.cpp`, `src/render/visibility.cpp`, `shaders/gpu_scene.slang`, `tests/render_gpu_visibility_tests.cpp`.
**Interfaces:** View history contains view identity, extent/viewport/projection, frame epoch, previous VP and HZB handle. MainCull may defer only a candidate with valid previous instance/view history; PostCull retests deferred candidates against current HZB and writes separate post args/IDs. PostRaster loads existing color and depth.
- [ ] **Step 1: Write failing frame-sequence tests.** A wall hides an object in frame N; opening/deleting/teleporting it in N+1 reveals that object in the final N+1 image. Camera cut, projection change, view ID change and resize force history invalid; near-plane crossing fails open. Compare every frame against HZB-disabled output.
- [ ] **Step 2: Run the tests and observe the missing deferral/post behavior.** `ctest --test-dir build/linux-debug --output-on-failure -R '^render_gpu_visibility$'`.
- [x] **Step 3: Implement previous/current projection tests, bounded deferred/post buffers, history ping-pong and invalidation, explicit compute→indirect/vertex barriers and Main/CurrentHZB/Post pass order.** Use ordinary-Z max-depth comparison with precision bias and full projected rectangle.
- [x] **Step 4: Run the sequence and full GPU render suites with validation; confirm no same-frame holes.**
- [x] **Step 5: Commit** `Repair temporal occlusion with current-frame post pass`.
### Task 6: Prepared mesh LOD, editor controls and profiling
**Files:** Modify `src/player/SceneView.cpp`, `src/editor/editor_ui.cpp`, `include/faset/render/renderer.hpp`, `src/render/renderer.cpp`, `tests/render_gpu_visibility_tests.cpp`; add a prepared-LOD example asset/scene and manual page under `docs/manual/editor/`.
**Interfaces:** Imported or C++-supplied prepared LOD meshes are selected by projected size and Task 1 hysteresis. Changing level changes the bin but keeps the logical instance key and invalidates previous occlusion. Editor can switch direct/GPU/HZB modes and inspect culling/LOD counters without adding MCP access to the Player.
- [x] **Step 1: Write failing tests.** Jitter around both thresholds must retain the previous LOD; moving well across a threshold selects a different mesh/bin; missing levels fall back; both modes keep a valid image through level changes.
- [ ] **Step 2: Run focused tests and verify the missing selection/control behavior.**
- [ ] **Step 3: Hook prepared LODs into extraction and GPU binning, expose compact editor controls/statistics and a documented C++/asset authoring path.** Keep source scene IDs unchanged.
- [x] **Step 4: Run CPU/GPU/editor UI suites and capture a representative debug screenshot.**
- [x] **Step 5: Commit** `Expose prepared mesh LOD and GPU visibility diagnostics`.
### Task 7: Adversarial validation, baselines and publication
**Files:** Add `docs/validation/p2-gpu-visibility/README.md` and capture artifacts; update `PLAN.md`, `docs/manual/editor/profiling.md`, `docs/ARCHITECTURE.md`, CI GPU test registration as appropriate.
**Interfaces:** Close P2 only with exact revision, compiler/driver/GPU/OS, scene/camera paths, commands, full-frame direct/GPU timings, pass counters, image comparison and stated coverage limits.
- [ ] **Step 1: Add adversarial fixtures for mass deletion/reuse, near-plane, camera inside bounds, odd/offset viewport, door/wall, open scene and offscreen shadow caster; confirm at least one fails before its corresponding fix.**
- [x] **Step 2: Run Debug and Release build/test suites, GPU validation, shader reload, two sample-game export/relaunch checks, and available Windows CI.** Record exact outputs; a platform without executed GPU coverage remains explicitly unverified.
- [x] **Step 3: Profile the same closed and open scenes in direct and GPU modes.** Record CPU extraction/upload/submission, GPU pass and full-frame time, readback conditions, memory and culling counters. Do not turn a scene-specific result into a universal performance claim.
- [x] **Step 4: Review all PLAN P2 criteria against evidence, update docs, run `graphify update .`, request independent code review and fix load-bearing findings.**
- [x] **Step 5: Commit** `Validate and document P2 GPU visibility milestone`; publish only after all checks are green.