Shadow lod diagnostics - #123
Merged
Merged
Conversation
A caster's shadow LOD is selected from the **camera** view and the single resulting draw command is replayed into every shadow view (`object.cpp`, `selectLod` on `cameraPosition` / `proj[1,1]` / `viewportHeight`). SH-03 fixes that; nothing today can *measure* it — one `ProfilePass::Shadow` total can't say whether cost moved between cascades, punctual lights, or self-shadowing, and no counter reports which geometry level a given shadow view rasterised. This adds the evidence layer first, deliberately, so the fix can be judged rather than asserted.
assets/physics_demos/generate.py owned both the demo scenes and the machinery to build them. The shadow-LOD arc needs the same machinery, so the geometry primitives, quaternion helpers and self-contained-glTF assembler move to tools/assetgen/ and the script keeps only its scenes. The move is output-preserving: the committed .gltf regenerate byte-for-byte from any working directory (Scene now takes its `generator` string instead of hard-coding one script's name). On top of that, a correctness fix that is NOT output-preserving. Three primitives wound their triangles against their authored normals: subdivided_box (4 of 6 faces), cylinder (both caps), and sphere — the last pre-existing, and shipped by the physics demos, which is why RestitutionDemo and StaticMeshDemo change here. The defect is invisible in the forward pass, which shades from the authored normal, and corrupts only the shadow pass, which culls by winding: it produced self-shadow acne, visible triangle edges, and holes punched out of ground shadows. Scene.mesh() now asserts per-triangle winding against the vertex normals for every mesh from every generator, so no future primitive can repeat it.
Adds what SH-01 needs before any selection policy changes:
- makeForwardPushConstants — one DrawCommand→push-constant packer shared
by the forward and transmission recorders. They had drifted:
transmission never set lodLevel, so every transmissive draw reported LOD
0 to the debug tint. Push-constant offsets are now static_asserted.
- shadowLodLevel through to DebugView::ShadowLod, tinting each mesh by
the level its shadow draw picked, with a kNoShadowLod sentinel so a
non-caster reads neutral grey instead of "full detail chosen".
- A Shadows overlay panel: per view family and physical slot, raster
passes and drawn/candidate draws + triangles kept visually separate from
the LOD histograms (work vs selection samples), timings from
shadowProfilePass(group).
- extras.Shadow = {"Casts": false} authors a receive-only surface. A
wide flat caster writes its own depth into every cascade and greys its
whole area out; the built-in -f floor has always set this in code and an
authored scene had no way to say it. The block is closed — any other
key, including Receives, is rejected rather than ignored.
Shadow eligibility lives on Object::GeometryBindings, not Geometry: a
Geometry is shared by every instance that draws it, so a flag there
could not describe two nodes instancing one mesh as caster and receiver.
Two generated scenes: ShadowLodDemo (fully static — the measurement baseline, so overlay numbers and captures are comparable run to run) and ShadowLodMotionDemo (adds motion for a qualitative no-chatter loop; an animated frame has no reproducible timestamp, so it gets no reference image). validate() checks the coverage claims the runbook makes before either is written — named nodes, all three light types, skin and morph data, a MASK material with a real texture and UVs, front-lit sun, every LOD exposure above the engine's 512-triangle threshold and connected. --capture <path.png> writes a frame and exits, so the references are regenerated by command rather than grabbed from a window. It copies the final swapchain image (post-process + overlay, immediately before present), requests TRANSFER_SRC only when asked for and only after checking the surface supports it, and rejects any swapchain format that isn't 8-bit BGRA/RGBA rather than guessing the channel order. --capture-frame counts frames, never seconds. --no-lod gives the full-detail half of the A/B and governs shadow selection too. The PNG encodes deterministically, but Vulkan output is not guaranteed byte-identical across GPUs or drivers, so these are documented baselines, not a hash gate — the tolerance arrives with SH-02's shadow-texel metric.
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.
No description provided.