perf(wgpu): wire vsync config, add frame phase timing instrumentation - #662
Conversation
- Wire existing GameConfig.vsync flag to wgpu PresentMode (was hardcoded to AutoVsync). Use AutoNoVsync when vsync is disabled. - Reduce desired_maximum_frame_latency from 2 to 1 for lower input latency. - Add per-phase frame timing instrumentation in begin_frame/end_frame (surface_acquire, uniform_upload, render_pass, gpu_submit, readback_stall, surface_present) and Renderer3D::render() (shadow_build, render3d_scene). - Store timings in thread-local cache (libs/graphics/frame_timing.rs) accessible without debugger. - Expose FfiFramePhaseTimings struct and goud_renderer_get_frame_phase_timings FFI function. Codegen updated for all SDKs. - Upgrade character_sandbox profiling to Throne parity: duration-based sampling, per-frame metrics, markdown/JSON reports, shadow config, Makefile with profile/profile-stress targets. Profiling confirms vsync fix eliminates ~16ms overhead per frame (one 60Hz period). With shadows disabled, 280 entities achieve 42 FPS. Fixes #660 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Claude finished @aram-devdocs's task in 2m 18s —— View job PR #662 Review: perf(wgpu): wire vsync config, add frame phase timing instrumentationVerdict: APPROVED with warnings Phase 1–3: Architecture & Anti-Pattern Scan
BLOCKERSNone. WARNINGS (non-blocking)1. String-key dispatch in match field {
"surface_acquire" => t.surface_acquire_us = value,
// ...
_ => {} // silent drop
}A typo at any call site (e.g. 2. Thread-local without a context guard —
3. Missing
4. The value was silently reduced from 2 → 1. This is a meaningful behavioral change: lower frame latency but higher risk of GPU pipeline stalls on slower hardware. The PR description doesn't mention it. Worth a brief comment at the call site explaining the trade-off (e.g. "reduced to 1 to cut acquisition latency at the cost of one pipeline slot; revisit if stutter appears on integrated GPUs"). 5. Xbox vsync path has no coverage — The Positive Callouts
|
- Move TextureOps and ShaderOps impls to frame_trait_impls.rs to keep frame.rs under the 500-line CI limit (520 -> 403 lines). - Add render3d_scene timing around the full Renderer3D::render() call. - Add unit tests for frame_timing module (record, reset, unknown field, defaults). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Incorporate PR #661 (SDL windowing/Switch PoC) changes. No conflicts. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Apply the same vsync parameter pattern to new_from_sdl_handle() and new_from_switch_handle() introduced by PR #661, ensuring vsync config is respected across all platform backends. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the software triangle rasterizer in shadow.rs with a GPU depth-only render pass. This is the primary fix for the 14 FPS frame pacing issue — shadow generation drops from ~50ms to <2ms. Changes: - Add shadow_pass.rs: GPU shadow resource management, depth-only pipeline building, and shadow render pass execution - Extend end_frame() with two-pass rendering: shadow pass to offscreen Depth32Float texture, then main pass sampling it via comparison sampler - Add shadow sampling to ALL WGSL shaders (previously only GLSL had it), including PCF 3x3 with hardware depth comparison - Add depth-only WGSL shader for shadow geometry pass - Expand pipeline layout to 4 bind groups (uniforms/texture/storage/shadow) - Extract compute_light_space_matrix() from CPU rasterizer for reuse - Gate frame readback: only prepare when post-processing/FXAA requests it - Keep CPU rasterizer as fallback for OpenGL legacy backend Profiling results at 280 entities with shadows + 2048 shadow map: - Before: 13.2 FPS (71ms/frame, 65ms render) - After: 62.0 FPS (15ms/frame, 1.8ms render) — 36x render speedup At 500 entities with shadows: 50.6 FPS (18ms/frame) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CI Fixes: - Add 12 missing shadow/readback fields to SDL, Switch, Xbox init paths - Extract shared shadow resource helpers to avoid code quadruplication - Split render/mod.rs (537→474 lines) by extracting shadow_render.rs Frame Pacing: - Restore desired_maximum_frame_latency from 1 to 2 (wgpu default) for proper double-buffering on high-refresh displays Critical Quality: - Fix shadow_pass timing silently dropped (missing field in match) - Fix depth_only_shader GPU resource leak on Renderer3D drop - Fix Xbox vsync hardcoded to true instead of window_config.vsync - Dedup light-space matrix computation (build_directional_shadow_map now calls compute_light_space_matrix internally) - Remove dead _has_storage variable in pipeline.rs Dead Code Cleanup: - Delete orphan sdk/game/instance/runtime.rs (not in any mod.rs) - Remove stale "demonstrate the API" comment - Replace magic 10000 with MAX_HEADLESS_FRAMES constant - Update MAX_SHADOW_VERTICES comment for CPU fallback context - Fix stale doc on depth_only_vertex_layout - Remove redundant draw_commands.clear() in end_frame Robustness: - Add shadow_strength to ShadowConfig (replaces hardcoded 0.65) - Compute shadow camera distance from scene bounds (was hardcoded 20.0) - Derive shadow ortho padding from scene radius (was hardcoded 10.0) - Add record_phase() helper unifying frame_timing + debugger calls - Log warning and skip shadow pass on shader bind failure Profiling: 63.9 FPS at 280 entities with shadows (no vsync), 48-55 FPS with vsync on 120Hz ProMotion, 54 FPS at 500 entities. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Overview
Type: perf
Summary:
Wire the existing vsync config flag to wgpu PresentMode (was hardcoded to AutoVsync), add per-phase frame timing instrumentation across the render pipeline, and upgrade the character_sandbox profiling to match Throne's pattern. Profiling confirms the vsync fix eliminates ~16ms of overhead per frame.
Related Issues: Fixes #660
Changes Made
Engine Core (
goud_engine/src/)libs/graphics/frame_timing.rs— thread-local per-frame phase timing cache with 8 measured phaseslibs/graphics/backend/wgpu_backend/init.rs— acceptvsync: bool, useAutoNoVsyncwhen disabled, reducedesired_maximum_frame_latencyfrom 2 to 1libs/graphics/backend/wgpu_backend/xbox_init.rs— same vsync parameter for Xbox GDK pathlibs/graphics/backend/wgpu_backend/frame.rs— instrumented begin_frame (surface_acquire) and end_frame (uniform_upload, render_pass, gpu_submit, readback_stall, surface_present)libs/graphics/renderer3d/render/mod.rs— instrumented shadow_build and render3d_scene phaseslibs/platform/native_runtime.rs— passwindow_config.vsyncto WgpuBackendFFI Layer (
goud_engine/src/ffi/)FfiFramePhaseTimingsstruct toffi/types.rsgoud_renderer_get_frame_phase_timings()function toffi/renderer/metrics.rsC# SDK (
sdks/csharp/)FfiFramePhaseTimingsstruct and P/Invoke declarationPython SDK (
sdks/python/)FfiFramePhaseTimingsctypes struct and FFI bindingTypeScript SDK (
sdks/typescript/)No changes
Codegen Pipeline (
codegen/)FramePhaseTimingstypeProc Macros (
goud_engine_macros/)No changes
Tools (
tools/)No changes
WASM (
goud_engine/src/wasm/)No changes
Examples (
examples/)character_sandboxprofiling: duration-based sampling, per-frame metrics, markdown/JSON reports, shadow config, VSync toggleMakefilewithprofile(200 NPCs + 80 animals) andprofile-stress(400+ entities) targetsDocumentation
No changes
Architectural Compliance
#[no_mangle] extern "C"and#[repr(C)]frame_timing.rsat libs/graphics level, accessible from both wgpu_backend and renderer3d// SAFETY:comment on FFI functionTesting
cargo testpasses (pre-existingnative_main_threadtest failure unrelated)cargo clippy -- -D warningsis cleancargo fmt --all -- --checkpassesframe_timingmodule (record, reset, unknown field, defaults)Code Quality
todo!()orunimplemented!()in production codeResult, notunwrap()/expect()in library codeBreaking Changes
None
Version Bump
Bump type: patch
Justification: Performance improvement, no API breaking changes
Security
unsafeblock has a// SAFETY:comment (FFI metrics function)Performance
Profiling results with 280 entities (200 NPCs + 80 animals):
VSync fix eliminates ~16ms overhead (one 60Hz vsync period).
Remaining render bottleneck is CPU shadow rasterization (out of scope).
Reviewer Notes
goud_renderer_get_frame_phase_timingsFFI function does not take acontext_idparameter (reads from thread-local cache). A typedGoudGame.GetFramePhaseTimings()SDK wrapper is deferred until the function signature is finalized with context support.native_main_threadintegration test failure is pre-existing onmain.