Xray.cpp was the only suite that deleted Stylize after create(). The
pointer stays in ModuleManager, so the next case got a dangling
singleton and crashed on the second delete (xray.occludedSilhouette
after xray.shaderAndFlags). Other stylize tests never delete it.
Co-authored-by: 西风逍遥游 <sunxfancy@gmail.com>
Show only the part of a marked character that is hidden behind a building,
as a filled highlight silhouette (Overwatch-style). Visible parts render
normally.
- Shader: xray_mesh.frag samples G-buffer hardware depth (binding 7) and
compares it against its own fragment depth: occluded (behind) pixels emit
the highlight, visible pixels discard. Drawn as a second pass over the
scene with depth test/write off + alpha blend.
- RenderSystem3D: MeshRenderer gains xrayShader + xrayHighlight. X-ray
targets are skipped in the G-buffer (their pixels record occluder depth),
then redrawn with the X-ray shader; scene depth is bound via
Graphics::setMesh3DSceneDepth.
- Graphics: new setMesh3DSceneDepth + Shader::setXray. Vulkan adds binding 7
to the mesh3d set layout and a dedicated X-ray pipeline variant.
- WebGPU: new newMeshShaderFromWgsl (WGSL custom mesh shaders), scene-depth
binding 9, X-ray pipeline variant, xray params packed into the Frame UBO,
and the G-buffer hardware depth is exposed/sampleable (getHwDepthTexture).
AO (SPIR-V) is skipped on WebGPU via supportsGBufferPost.
- stylize: 'xray' style registered (createMeshShader dispatches SPIR-V on
Vulkan / WGSL on WebGPU) with push-constant params.
- Tests: test/Xray.cpp (shader/flags + occluded-silhouette render smoke).