diff --git a/docs/tickets.md b/docs/tickets.md index e227841..6f7ef1d 100644 --- a/docs/tickets.md +++ b/docs/tickets.md @@ -689,3 +689,27 @@ symbolized stack; fix then. (profiler overlay/history strings are the heaviest string traffic in the crashing runs), or an engine-side heap read overrun in a small shared helper. Audit report: `shooter/docs/audit-round2.md` finding F1. + +## EN-023 — GI software path: colored bounce is unreachable 🟡 + +**Why.** Round-2 audit (F4): on adapters without EXPERIMENTAL_RAY_QUERY +(the dev box's Radeon 760M reports none — see the new boot log), the +probe trace runs the SDF path, where the mesh-card lookup compares +world-space hits against OBJECT-space AABBs (ssgi.rs broad phase) — every +transformed instance falls through to the flat gray 0.55 analytic — and +the software WSRC bake is analytic sun+sky with no geometry at all. +Measured in-game: SSGI on/off is ≤2.5% achromatic luma, hue ratio +unchanged to 4 decimals. The ~267 gi_only proxies feed a pipeline whose +colored output cannot reach the screen on SW adapters, at ~1.6 ms GPU in +combat. + +**Sketch.** (1) Transform SDF hits into instance object space (or +instance AABBs into world space) in the card broad-phase so textured/ +tinted cards resolve. (2) Make the SW WSRC bake sample cards (or at +least sun-shadowed ground/albedo bounce) instead of pure analytic. +(3) Re-run the audit's GI A/B: acceptance = G/R hue shift at the +building base and ≥8-10% luma in shaded receivers. + +**Interim option** (decision D2-B in the shooter audit): boot-time +`setSsgiEnabled(false)` on SW adapters banks the ~1.6 ms until this +lands; needs a backend query FFI or the game reading the new boot log. diff --git a/native/shared/src/renderer/scene_pass.rs b/native/shared/src/renderer/scene_pass.rs index 29fabf7..b24c59e 100644 --- a/native/shared/src/renderer/scene_pass.rs +++ b/native/shared/src/renderer/scene_pass.rs @@ -464,6 +464,15 @@ impl Renderer { if let Some(tid) = scene_color_tid { self.transient_pool.release(tid); } + // Round-2 audit: the depth snapshot was acquired every frame but + // NEVER released — the pool (which has no auto-reclaim by design) + // allocated a brand-new render-res Depth32Float every frame with + // refractive water on screen. ~8 MB/frame of texture creation plus + // an ever-growing slot scan; measured as translucent_pass CPU + // climbing 0.4 ms → 6.7 ms over ~50 s of play, in every session. + if let Some(tid) = scene_depth_tid { + self.transient_pool.release(tid); + } profiler.end("translucent_pass"); } } diff --git a/native/shared/src/renderer/transient.rs b/native/shared/src/renderer/transient.rs index c5e6a73..dd7b8fb 100644 --- a/native/shared/src/renderer/transient.rs +++ b/native/shared/src/renderer/transient.rs @@ -187,6 +187,21 @@ impl TransientPool { } // Nothing matched — allocate a new slot. + // + // Leak watchdog: a steady-state frame needs a handful of slots. + // A count that keeps growing means some caller acquires without + // releasing (the pool has no auto-reclaim by design) — exactly + // the depth-snapshot leak the round-2 audit spent a day tracing + // from the profiler side. Warn at every power of two past 64 so + // the log stays quiet but the failure mode is named. + let n = self.slots.len(); + if n >= 64 && n.is_power_of_two() { + eprintln!( + "bloom transient pool: {} slots allocated and growing — \ + an acquire without a matching release is leaking textures", + n + ); + } let texture = device.create_texture(&wgpu::TextureDescriptor { label: Some("transient"), size: wgpu::Extent3d {