From 9c5991061f652bc96236eeff3782c7961796242e Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 27 May 2026 16:06:07 +0200 Subject: [PATCH] =?UTF-8?q?fix(render):=20Phase=20A8=20=E2=80=94=20Step=20?= =?UTF-8?q?5=20gate-off-by-default=20+=20restore=20GL=20state?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First user-driven visual gate (1,595 indoor frames, 109 other-buildings) reported textures flickering / can barely move / client crashed indoors. Root causes: 1. Step 5 (cross-building visibility) iterates EVERY loaded other- building per frame with NO frustum culling. At Holtburg that's 109 other-buildings × 5 GL draws each = ~545 extra draws/frame on top of the 4 setup steps. Each Render() within Step 5c also re-uploads the instance SSBO via glBufferData(orphan) + glBufferSubData and a glMemoryBarrier. Combined with rapid 109-iteration back-to-back state churn, the driver hits TDR and crashes. GATE: Step 5 is now OFF BY DEFAULT. Set ACDREAM_A8_STEP5=1 to opt in once we add per-building frustum culling on otherBuildings. Cross-building visibility is a polish feature; M1.5 indoor walking doesn't require it. 2. WB's RenderInsideOut cleanup at line 234-238 exits with ColorMask(t,t,t,FALSE) — alpha-bit OFF — matching WB's editor pipeline expectations. acdream's subsequent rendering (particles, anything writing alpha) needs alpha-bit ON. The flicker symptom is consistent with subsequent passes mis-writing alpha. FIX: cleanup now restores ColorMask(t,t,t,t), DepthMask(true), DepthFunc.Less, Enable(CullFace) — all to acdream defaults so the outer render frame sees a clean slate. Step 5's loop also leaves DepthMask/CullFace in non-default states; defensive restore makes this safe whether Step 5 ran or not. Build green. Tests unchanged. Expectation for next relaunch: indoor frames hit only Steps 1+2+3+4 (camera-building stencil + cell render + stencil-gated outdoor scenery). Cross-building visibility (Step 5) is intentionally inert. Flicker should be resolved by the ColorMask alpha restore. Perf should be closer to pre-A8 outdoor (one extra full-screen pass + a small stencil mask). Co-Authored-By: Claude Opus 4.7 (1M context) --- src/AcDream.App/Rendering/GameWindow.cs | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs index b323bed..4df8455 100644 --- a/src/AcDream.App/Rendering/GameWindow.cs +++ b/src/AcDream.App/Rendering/GameWindow.cs @@ -10727,7 +10727,16 @@ public sealed class GameWindow : IDisposable // Step 5: per-other-building 3-bit stencil pipeline. // WB VisibilityManager.cs:157-232 - if (didInsideStencil && otherBuildings.Count > 0) + // + // FIX 2026-05-28 (post-first-visual-gate): Step 5 is GATED OFF BY DEFAULT. + // First visual gate showed perf collapse + texture flicker indoors because + // Step 5 iterates EVERY loaded other-building per frame (109 at Holtburg), + // each doing 5 GL draws (mark/end-query/punch/render-opaque/render-trans/reset) + // = ~545 extra draws/frame with no frustum culling. The driver hit TDR + // limits. Set ACDREAM_A8_STEP5=1 to re-enable for cross-building visibility + // testing once we add per-building frustum culling. + bool step5Enabled = string.Equals(Environment.GetEnvironmentVariable("ACDREAM_A8_STEP5"), "1", StringComparison.Ordinal); + if (step5Enabled && didInsideStencil && otherBuildings.Count > 0) { gl.Enable(EnableCap.StencilTest); gl.ColorMask(false, false, false, false); @@ -10791,7 +10800,18 @@ public sealed class GameWindow : IDisposable { gl.Disable(EnableCap.StencilTest); gl.StencilMask(0xFFu); - gl.ColorMask(true, true, true, false); + // FIX 2026-05-28: WB exits with ColorMask(t,t,t,FALSE) — alpha-bit + // off. That breaks subsequent acdream rendering (particles + + // anything that relies on alpha-to-coverage writing alpha). Restore + // full ColorMask before returning to the outer render frame. + // + // Step 5's iteration loop (when ACDREAM_A8_STEP5=1) leaves + // DepthMask=false / CullFace=disabled / ColorMask=(f,f,f,f) on + // its last iteration. Restore to acdream-default before returning. + gl.ColorMask(true, true, true, true); + gl.DepthMask(true); + gl.DepthFunc(DepthFunction.Less); + gl.Enable(EnableCap.CullFace); } }