This contributor runbook covers local rendering checks. GitHub Actions compile and format the repository, while visual and GPU validation remains local because it requires a Vulkan-capable device and produces artifacts for inspection.
Install the exact clang-format release recorded in .clang-format-version, then build the tests and editor. The format
script selects the pinned Python package and rejects a mismatched executable:
python -m pip install clang-format==22.1.8
python Scripts\format_cpp.py --check --root EvoEngine_SDK --root EvoEngine_App --root EvoEngine_Tests --root PythonBinding
cmake --build out\build\vs2026-x64\EvoEngine_Tests --config RelWithDebInfo --target EvoEngine_Tests --parallel 4
cmake --build out\build\vs2026-x64 --config RelWithDebInfo --target EvoEngineEditor --parallel 4Run the focused suite appropriate to the changed subsystem. A broad rendering contract pass is:
out\build\vs2026-x64\EvoEngine_Tests\RelWithDebInfo\EvoEngine_Tests.exe --gtest_filter="CameraRenderTechnique.*:GltfRasterMaterial.*:GltfRayTracingMaterial.*:EnvironmentalLightingContract.*:DdgiVolume.*:DirectionalShadowCascadeFit.*"GPU shader and numerical tests require a device with their Vulkan prerequisites. Report an unavailable prerequisite as missing coverage rather than treating a CPU/source test as equivalent.
Inspect validation output as well as test exit status: a Vulkan callback error does not automatically fail a GoogleTest
assertion. The geometry compaction readback test requires the vertex buffer to have TRANSFER_SRC usage, in addition to
its upload, storage and vertex usages; check that VUID-vkCmdCopyBuffer-srcBuffer-00118 is absent.
Vulkan validation is disabled by default, including Debug and RelWithDebInfo builds. Enable it explicitly when running graphics API correctness checks:
cmake -S . -B out\build\vs2026-x64 -DEVOENGINE_ENABLE_GRAPHICS_VALIDATION=ONThe option only changes Debug and RelWithDebInfo configurations; Release builds remain validation-free.
Install applications before process or visual testing:
python Scripts\install_apps.py --preset vs2026-x64 --config RelWithDebInfo --incremental --no-open --jobs 8Use out\install\vs2026-x64\bin\EvoEngineEditor.exe for installed-runtime checks.
The capture script EvoEngine_Tests/Rendering/render_demo_capture.py accepts --scene bistro with
--view default (exterior) or --view doorway (looking into the bar). Prepare Bistro assets first.
RenderingDemo.BistroDefaultGoldenImage and RenderingDemo.BistroDoorwayGoldenImage capture 1280x720 raster
views with DDGI occlusion after 600 warmup frames; they skip when Bistro is unavailable. Each has its own baseline.
The script also accepts Sponza (the default scene), GI settings, and all three camera render modes.
Capture fixtures wait for scene setup before selecting GI. ConfigureIndirectLightingDebugForCapture(view, pause_ddgi_updates)
selects beauty (0), diffuse (1), unoccluded specular (2), specular visibility (3), occluded specular (4), or DDGI blend loss (5).
RenderingDemo.SdfgiGoldenImage selects Automatic SDFGI explicitly with RT pipeline, ray query and acceleration
structures disabled. It captures the same 2560x1440 scene after 1800 warmup frames, checks that SDFGI is effective and
has updated its field, and compares against its own RenderingDemo.SdfgiGoldenImage.2560x1440.png baseline.
The DDGI texture-lifecycle and SDFGI golden tests retain PSNR >= 30 dB and SSIM >= 0.95. Refresh only these two approved
targets with the following command, then rerun without --accept-render-baseline to verify fresh captures:
python Scripts/test.py --render-only --accept-render-baseline --ctest-arg=-R --ctest-arg="RenderingDemo.(TextureLifecycleStressThenCanonicalRasterGolden|SdfgiGoldenImage)$"For SDFGI alone, use ^RenderingDemo.SdfgiGoldenImage$. The accepted image uses fixed-grid probes with occlusion.
The pillar/ground seam also occurs with HDDAGI and remains unresolved; baseline acceptance does not fix it.
SDFGI captures pin seed zero and save .before.yaml / .after.yaml snapshots and .json status.
--sdfgi-debug-views exports frozen diffuse, specular and coverage views after beauty.
Compare sampling/update state across failures; equal seeds do not establish repeatability.
The raster gate compares all meshlet/indirect combinations against one 2560x1440 golden (PSNR >= 29 dB, SSIM >= 0.94):
| Cell | Meshlet | Indirect |
|---|---|---|
| 1 | enabled | enabled |
| 2 | enabled | disabled |
| 3 | disabled | enabled |
| 4 | disabled | disabled |
python Scripts\test.py --render-only --ctest-arg=-R --ctest-arg=RenderingDemo.RasterPathMatrixGoldenImageMeshlet cells require mesh shaders. Refresh from the meshlet-enabled, indirect-enabled cell:
python Scripts\test.py --render-only --accept-render-baseline --ctest-arg=-R --ctest-arg=RenderingDemo.RasterPathMatrixGoldenImageOnly that cell writes the baseline. Retain all four PNGs when diagnosing failures.
Use an installed RelWithDebInfo editor, a fixed camera, and the same shader cache for every comparison. The raster profile report records CPU frame median/p95 and every named GPU timestamp median/p95 after scene and shader readiness:
out\install\vs2026-x64\bin\EvoEngineEditor.exe --demo bistro --editor --capture-demo-preview out\raster-visibility\bistro.png --preview-render-mode rasterization --preview-raster-profile-report out\raster-visibility\bistro.json --preview-warmup-frames 240 --preview-width 1920 --preview-height 1080 --preview-deterministicRaster profile schema 4 uses a fixed-window frame-budget model. Requesting
--preview-raster-profile-report automatically enables CPU and GPU profiler capture only for the first requested
--preview-warmup-frames window; interactive profiler capture remains disabled by default. The report labels this
policy as first-requested-frames and includes both the full window and a final, at-most-240-frame interpretation.
It records the stable Application Loop hierarchy with inclusive/self average, median, p95, maximum, observation counts,
missing-frame zeros, main-thread wall time, and separate worker CPU work. GPU data is grouped by render-pass taxonomy
with span, summed contributing work, pass duty cycle, and frequency-weighted time. Raster resolution and DDGI
active/update/trace sample counts make DDGI activity explicit; the capture does not wait
for DDGI steady state.
For instrumentation-overhead A/B runs, append --preview-gpu-timestamps disabled; profile reports otherwise enable GPU
timestamps automatically. The report records the effective capture.gpu_timestamps state.
The report includes capture metadata, CPU/GPU timing, draw accounting, DDGI activity, and render_path.
Camera and shadow visibility use spatially indexed, parallel command compaction.
Camera indexed commands and mesh-task commands use two persistent arenas. Every camera segment starts at
minStorageBufferOffsetAlignment; deferred draws add its byte offset, so shader command indices remain local.
Directional-cascade, point-face, and spot-view
commands share one persistent shadow arena for the active indexed or mesh-task path. This removes per-view buffer
allocation and synchronous upload waits while preserving stable camera/view order and dynamic reflection-probe cameras.
Run the command at least three times and retain the median run. Record the executable path, commit, GPU and driver from
the JSON, camera overrides, resolution, enabled renderer settings, image hash, CPU frame median/p95, and GPU timestamp
median/p95. The Deferred Geometry, Depth Pyramid, Directional Shadow, Point Shadow, Spot Shadow, and
Transparent Geometry scopes isolate the raster work changed by visibility optimizations. Use the editor Rendering
Diagnostics panel to record direct/indirect draw calls, indirect commands, and primitive counts for the same view.
For image correctness, pair the Bistro measurement with the rendering-regression and strand fixtures described below. Compare captures against a known-good image from the same fixed render path.
The depth hierarchy is a separate R32F image derived from the regular camera depth after deferred geometry and motion coverage. Mip 0 is an exact copy of the camera's D32 depth and every coarser mip stores the maximum depth over its complete source footprint, including odd image edges. Under the conventional depth convention (clear 1, nearer values smaller), that maximum reduction is required to prove that every covered sample is in front of an occlusion candidate. Per-mip compute barriers make each level available to the next dispatch. AO and DDGI continue to sample exact camera depth rather than the coarse hierarchy. Reflection-probe face cameras do not generate the hierarchy. The former depth prepass and same-frame object Hi-Z occlusion pass have been removed.
Camera, shadow, meshlet-frustum, and eligible rigid normal-cone culling are permanent parts of the optimized raster path. Invalid bounds and unsupported deformation or transform cases remain conservatively visible.
Use the same scene, resolution, sample count, deterministic settings, and shader cache when comparing camera techniques:
out\install\vs2026-x64\bin\EvoEngineEditor.exe --demo rendering-regression --editor --capture-demo-preview out\render-validation\raytracing.hdr --preview-render-mode raytracing --preview-warmup-frames 64 --preview-sample-size 4 --preview-width 1280 --preview-height 720 --preview-deterministic
out\install\vs2026-x64\bin\EvoEngineEditor.exe --demo rendering-regression --editor --capture-demo-preview out\render-validation\rayquery.hdr --preview-render-mode rayquery --preview-warmup-frames 64 --preview-sample-size 4 --preview-width 1280 --preview-height 720 --preview-deterministicUse PNG for presented output and HDR for linear-radiance comparison. Confirm the log reports the requested technique; unsupported ray modes may otherwise produce a valid raster fallback image.
Ray debug views use --preview-ray-debug <name>. Optional outputs use --preview-ray-outputs with albedo, normal,
ray-count, path-length, time, debug, or all. Validate every emitted image and sidecar for expected dimensions,
type, and finite values.
out\install\vs2026-x64\bin\EvoEngineEditor.exe --demo rendering-regression --editor --capture-demo-preview out\render-validation\strand-raytracing.hdr --preview-strand-fixture --preview-render-mode raytracing --preview-warmup-frames 64 --preview-sample-size 4 --preview-width 1280 --preview-height 720 --preview-deterministic
out\install\vs2026-x64\bin\EvoEngineEditor.exe --demo rendering-regression --editor --capture-demo-preview out\render-validation\strand-rayquery.hdr --preview-strand-fixture --preview-render-mode rayquery --preview-warmup-frames 64 --preview-sample-size 4 --preview-width 1280 --preview-height 720 --preview-deterministicOn a capable NVIDIA device, require the startup log to report linear swept-sphere support and inspect framing, silhouette, taper, materials, transforms, and shadows in both images. On an unsupported device, both cameras must render without the strands and without Vulkan feature, descriptor, or acceleration-structure errors.
The installed-editor validation scripts write their captures, logs, and reports below out/:
| Area | Command |
|---|---|
| DDGI application smoke | python Scripts\run_ddgi_app_validation.py --config RelWithDebInfo |
| Automatic GI cascades and provider edits | python Scripts/run_automatic_gi_validation.py --provider ddgi --resources Resources/.generated/demos (installed RT-enabled 1440p Sponza); use --provider sdfgi for RT-disabled SDFGI |
| Emissive sampling | python Scripts\run_ddgi_emissive_validation.py --config RelWithDebInfo --width 1920 --height 1080 |
| Environment controls | python Scripts\run_environment_lighting_validation.py --config RelWithDebInfo --output-dir out\environment-lighting-validation |
| Reflection probes | python Scripts\run_reflection_probe_validation.py --config RelWithDebInfo --output-dir out\reflection-probe-validation |
DDGI report schema 9 records periodic rolling-history windows instead of convergence/variability or hysteresis boosts.
Existing image baselines need fresh temporal-policy baselines. Python CaptureCurrentScene rejects empty or nonfinite
float output before PNG conversion.
Inspect DDGI reports for readiness, completed history windows, update reasons, finite atlas metadata, correct overlap selection, and expected response to light, material, geometry, and environment changes. Reflection-probe checks should cover explicit bake publication, priority and boundary selection, global fallback, box/sphere projection, dynamic updates, and the absence of recursive local-probe capture.
Directional shadows use four cascades. Stable Sphere is the default fit and should remain stable under small camera motion. Tight Light-Space AABB is an unsnapped comparison mode.
Capture both modes with otherwise identical settings:
out\install\vs2026-x64\bin\EvoEngineEditor.exe --demo bistro --editor --capture-demo-preview out\render-validation\csm-stable.png --preview-render-mode rasterization --preview-shadow-fit stable-sphere --preview-warmup-frames 32 --preview-width 1280 --preview-height 720 --preview-deterministic
out\install\vs2026-x64\bin\EvoEngineEditor.exe --demo bistro --editor --capture-demo-preview out\render-validation\csm-tight.png --preview-render-mode rasterization --preview-shadow-fit tight-aabb --preview-warmup-frames 32 --preview-width 1280 --preview-height 720 --preview-deterministicUse --preview-shadow-debug with cascade-index, light-uv, light-depth, atlas-uv, or texel-density to isolate
cascade selection and sampling. Directional, point, and spot shadows use eight PCF samples fixed in shader code.
Optional capture overrides can change resolution, split lambda, transition width, and distance fade without changing
runtime defaults.
Verify all four cascades receive shadows, transitions have no gaps or double-darkening, stable fitting does not shimmer, off-camera casters remain represented, and bias/filtering stay consistent across resolution and fit changes.
BufferUploadBatch is the reusable SDK upload-plan API. Added payloads borrow their CPU memory: callers must keep every
source range alive and unchanged until SubmitImmediate or Record returns. Uploads default to RequireCapacity;
GrowIfNeeded is explicit and is valid only when no recorded or submitted work references the destination. Batches
reject overlapping destination ranges and provide uniform, storage, vertex, index, and indirect consumer usages plus a
raw Vulkan stage/access override. Cross-queue consumers remain responsible for semaphore and ownership synchronization.
RenderInstanceStorage records its batch through a per-frame-slot BufferUploadArena. Arena blocks are append-only
while the slot submission is pending, stay persistently mapped, flush written ranges for non-coherent memory, retain
overflow blocks, and reset only after the existing frame fence resolves. Copies and barriers are recorded before later
main-queue consumers; there is no render-instance immediate submission or upload fence. Unchanged prepared payloads are
omitted without skipping camera or shadow visibility classification, and meshlet statistics are reset with a recorded
buffer fill.
The matched Bistro validation uses three installed-editor runs at 1920x1080 with 240 measured frames. The original baseline recorded about 11 immediate submissions per frame, a 6.07-6.73 ms upload median, and a 16.16-17.19 ms CPU median. Immediate batching reduced that to about one submission per frame, a 0.39-0.60 ms upload median, and a 12.73-13.20 ms CPU median. Frame-stream recording then left only one non-render-instance immediate submission in each entire 240-frame capture; final upload medians were 0.43-0.44 ms, CPU medians 12.92-13.09 ms, and GPU medians 13.09-13.27 ms. The recycled-frame fence absorbs the remaining GPU wait, so further CPU upload work is lower priority than the current GPU workload.
python Scripts\compare_reference_render.py --self-test
python Scripts\compare_reference_render.py reference.hdr candidate.hdr --out out\render-validation\comparison.jsonUse exact comparison only for deterministic byte-equivalent contracts. Stochastic camera captures require metric review plus visual inspection.
- The requested renderer and optional features are active rather than silently falling back.
- The process exits normally without Vulkan validation errors, device loss, fatal logs, or shader failures.
- Output dimensions and formats are correct; HDR values are finite and preserve expected highlights.
- Materials, normal maps, alpha masking, transmission, emission, direct lights, and shadows remain intact.
- Camera and scene changes invalidate the appropriate temporal history; unchanged views continue accumulating.
- Rasterization, ray tracing, and ray query preserve the same scene and authored material meaning.
- Validation notes record the installed executable, command line, GPU, and inspected artifact paths.