Merge commit '0c96ce5cdfde8503e1ffc3fd65fdc9d7f3793d35' into feat/p1-p3-integration

# Conflicts:
#	PLAN.md
#	docs/IMPLEMENTATION.md
#	docs/manual/editor/diagnostics.md
#	docs/manual/editor/lighting.md
#	docs/manual/editor/profiling.md
#	docs/validation/README.md
#	docs/validation/p3-lighting-2026-09-24/README.md
This commit is contained in:
Emil
2026-09-24 04:08:01 +03:00
43 changed files with 3963 additions and 139 deletions
+35
View File
@@ -520,6 +520,41 @@ real-diagnostic-navigation assertion still awaits its Windows CI result.
Windows had no active Khronos validation layer, and no physical Windows GPU
performance result is claimed.
## P3 lighting checkpoint — authored lights and bounded shadow views
At source revision `b191ae0`, the versioned `faset.light` schema and SceneView
extract directional, point, and spot lights. Any authored Light, even disabled,
suppresses the compatibility sun; scenes without a Light keep their previous
appearance. The renderer validates all local records, then selects at most 128
by priority, projected influence, and stable ID. A single typed lighting
descriptor ABI serves Direct and P2 GPU graphics: materials remain set 0,
lighting is set 1, GPU scene graphics data moves to set 2, and existing push
constant sizes remain unchanged. Both paths shade the same sun/local PBR lights
before tone mapping.
A pure CPU shadow planner builds up to four texel-snapped sun cascades from an
explicit camera frustum, ending at at most 80 world units; a low-level Snapshot
without the frustum keeps one shadow view. Shadow caster bounds come from the
source LOD-0 draw and are tested against the light view, independently of
camera/P2 culling. The Vulkan backend renders the sun to its own D32 atlas and
point/spot shadows to a separate 4×4 D32 atlas. A point light claims six faces
atomically, a spot one. Both atlases try 2048² and then 1024² if required by
capabilities or allocation. The combined frame budget is 4096 caster draws;
scheduled tiles are cleared and redrawn each frame. Overflow, disabled shadow,
or unavailable atlas leaves a submitted light illuminating without shadow.
There is no hidden sun raster when the sun is absent, its shadow is disabled, or
the scene only has sprites. Atlas ownership, dropout, submitted light counts,
actual raster work and GPU timings are exposed in `FrameStats`, Player profiles
and the optional Editor diagnostics overlay.
The implementation's Linux Debug checkpoint at `a5fb216` built all targets and
ran 60 CTests with no failures; the existing native window lifecycle test
skipped under the compositor. The optional ImGui overlay passed its dedicated
test in an enabled build. After benchmark integration at `b191ae0`, six focused
tests passed, including the real Vulkan benchmark smoke. These are bounded
checks, not a final P3 acceptance run. The [lighting validation record](validation/p3-lighting-2026-09-24/README.md)
lists cases, exact revision, and remaining Windows/Release evidence.
The fixed-scene Release reference-GPU sweep uses 1920×1080, 0/4/16/32/64/128
lights, Direct/GPU frustum/GPU occlusion, shadows on/off, three independent
repeats, ten warm-up and thirty measured frames per configuration. It reached
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

+6
View File
@@ -35,6 +35,12 @@ On Windows, use `windows-debug` for both presets and `build/windows-debug/faset_
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. **Effective path** names the algorithm that actually ran. A **Fallback from** line appears when device or target capabilities prevent the selected mode; for example, GPU occlusion may use GPU frustum if HZB is unavailable.
Use the **Temporal** selector for **Off**, **TAA**, or **Upscale**. Upscale shows a
50–99% render-scale slider; output UI remains sharp. The requested/effective
mode, fallback reason, internal extent, history reset reason and temporal GPU
pass times are shown separately from visibility and HZB history. This selector
only changes the live Editor viewport. See [Temporal rendering](temporal.md).
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. 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.
+34 -6
View File
@@ -147,6 +147,32 @@ and reads back the full image, so `cpu_ms` is wall time including waits, not CPU
utilization. An open scene can run slower with HZB; visibility correctness and
full-frame speed are separate findings.
## Compare temporal modes
Use one scene, output resolution, camera sequence, visibility path, binary and GPU
for Off, TAA and Upscale. Run enough frames to include both the first-frame reset
and steady-state accumulation. Keep the raw captures as well as timing samples:
```sh
./faset_player --headless --frames 240 --profile off.json --temporal off
./faset_player --headless --frames 240 --profile taa.json --temporal taa
./faset_player --headless --frames 240 --profile upscale.json \
--temporal upscale --render-scale 0.67
```
The profile records requested and effective temporal modes, fallback and history
reset reason, internal/output extent, jitter, and valid previous-transform count
per completed frame. `gpu_temporal_resolve_ms`, `gpu_temporal_composite_ms`, and
`gpu_ui_ms` are separate submitted GPU pass times when timestamp queries work;
otherwise they are `null`. `gpu_allocated_bytes` includes live temporal targets
and histories, subject to the allocation limits described above. Compare full
frame GPU and renderer wall time too: scene raster savings can be offset by
resolve, memory and synchronous readback. A valid frame-level history flag says
the previous frame may be sampled, not that every pixel accepted it. For image
quality, inspect a still thin edge, a slow pan and a newly uncovered surface, and
compare the same frame against Off. See [Temporal rendering](temporal.md) for
mode controls and native C++ configuration.
## Measure P3 lighting and shadows
A Player `--profile` sample includes `effective_lighting_path`, local lights
@@ -216,9 +242,11 @@ raw frames, shader hashes, and the decision.
The accepted MVP path uses direct draws and CPU culling; P2 adds optional GPU
visibility for opaque static meshes, with prepared LODs supplied by the project.
Both paths currently use one graphics queue and synchronous full-image
capture/readback. Use measurements to find the next bottleneck before introducing
parallel jobs or expanding GPU-driven rendering. Neither an offscreen capture
benchmark nor a tiny demo is a promise of a production frame budget. Observed
measurements and follow-up targets belong in the implementation acceptance report
with their source revision and method.
P3 adds local lights and bounded sun/local shadow atlases. The benchmark's
`lighting_path` and a Player profile's `effective_lighting_path` identify the
algorithm actually used. Both paths currently use one graphics queue and
synchronous full-image capture/readback. Use measurements to find the next
bottleneck before introducing parallel jobs or expanding GPU-driven rendering.
Neither an offscreen capture benchmark nor a tiny demo is a promise of a
production frame budget. Observed measurements and follow-up targets belong in
the implementation acceptance report with their source revision and method.
+54
View File
@@ -0,0 +1,54 @@
# Temporal rendering
Faset renders the scene with **Off** by default. In the optional Editor diagnostics
panel (**F12**), choose **TAA** to accumulate a full-resolution scene over successive
frames, or **Upscale** to render the scene at a lower resolution and reconstruct it
at the output resolution. The Upscale slider accepts 50–99%; 67% is a useful
starting point for visual comparison. UI text and controls always render at output
resolution after the scene resolve. Shadow maps keep their own unjittered views.
The diagnostic selector affects only the current Editor viewport. It does not edit
the scene, gameplay code, or an exported Player. The panel's **Requested** and
**Effective** fields identify a device fallback. It also shows internal and output
extent, whether the previous completed frame's color history was eligible, the
reason it reset, and separate GPU times for resolve, composite and UI where
timestamp queries are available. A reset on the first frame, camera cut, changed
view, resize, scale switch or compatible shader reload is expected. A valid history
does not imply every pixel reused it: newly visible surfaces can still reject
their individual history samples.
For a Player or exported game, select the mode at launch:
```sh
./faset_player --headless --frames 120 --temporal taa --profile taa.json
./faset_player --headless --frames 120 --temporal upscale \
--render-scale 0.67 --profile upscale.json
```
`--temporal` accepts `off`, `taa`, or `upscale`. Off and TAA use scale `1`; Upscale
requires a scale from `0.5` inclusive to `1` exclusive. An invalid mode or scale
stops startup with an error. If Vulkan compute or the required image formats are
unavailable, the renderer falls back to Off and records its effective mode and
reason in the profile. Direct, GPU frustum and GPU occlusion visibility can be
combined with either temporal mode. See [Profiling](profiling.md) for how to compare
their timings fairly.
Native renderer users can make the same choice without modifying gameplay scripts:
```cpp
faset::render::RendererConfig config;
config.temporal_mode = faset::render::TemporalMode::Upscale;
config.render_scale = 0.67f;
faset::render::Renderer renderer(config);
// A live viewport switch recreates scene targets and resets color history.
renderer.set_temporal_mode(faset::render::TemporalMode::TAA);
```
Provide a stable `DrawItem::instance_key` for moving opaque objects so the renderer
can find their previous model transform. Camera cuts must be marked in the
`Snapshot`; cuts, teleports and incompatible projection changes reject old history.
World transparency and sprites use the scene depth/order and reject stale color on
their reactive pixels. TAA and Upscale are optional image-quality paths; compare
them against Off on the actual game scene, especially thin geometry, slow pans,
newly revealed surfaces and moving transparent content.
@@ -0,0 +1,12 @@
Test project /home/emil/Desktop/.worktrees/Faset_Engine-p3-lighting/build/linux-debug
Start 9: render_lighting_policy
1/2 Test #9: render_lighting_policy ............. Passed 0.04 sec
Start 17: render_lighting_benchmark_schema
2/2 Test #17: render_lighting_benchmark_schema ... Passed 4.95 sec
100% tests passed, 0 tests failed out of 2
Label Time Summary:
p3 = 4.99 sec*proc (2 tests)
Total Test time (real) = 5.00 sec
@@ -0,0 +1,8 @@
Test project /home/emil/Desktop/.worktrees/Faset_Engine-p3-lighting/build/linux-debug
Test #7: render_lighting_sun
Test #8: render_lighting_local
Test #9: render_lighting_policy
Test #17: render_lighting_benchmark_schema
Test #18: render_lighting_benchmark_smoke
Total Tests: 5
@@ -0,0 +1,20 @@
warning: An executable named `mkdocs` is not provided by package `mkdocs-material` but is available via the dependency `mkdocs`. Consider using `uvx --from mkdocs mkdocs` instead.
 │ ⚠ Warning from the Material for MkDocs team
 │
 │ MkDocs 2.0, the underlying framework of Material for MkDocs,
 │ will introduce backward-incompatible changes, including:
 │
 │ × All plugins will stop working – the plugin system has been removed
 │ × All theme overrides will break – the theming system has been rewritten
 │ × No migration path exists – existing projects cannot be upgraded
 │ × Closed contribution model – community members can't report bugs
 │ × Currently unlicensed – unsuitable for production use
 │
 │ Our full analysis:
 │
 │ https://squidfunk.github.io/mkdocs-material/blog/2026/02/18/mkdocs-2.0/

INFO - Cleaning site directory
INFO - Building documentation to directory: /home/emil/Desktop/.worktrees/Faset_Engine-p3-lighting/build/manual
INFO - Documentation built in 0.84 seconds