From 26b2871b10b1721ab4b6926f073d9d3c210aa593 Mon Sep 17 00:00:00 2001 From: Erik Date: Sun, 10 May 2026 08:25:59 +0200 Subject: [PATCH] feat(A.5 T20): MSAA 4x + alpha-to-coverage on foliage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per Phase A.5 spec §4.9.2: ClipMap foliage uses binary alpha-cutoff. At N₂=12 horizon distance the pixel-stepped silhouettes are visible. A2C with MSAA 4x produces smooth retail-faithful tree edges. GL context now requests Samples=4. WbDrawDispatcher's opaque pass toggles GL_SAMPLE_ALPHA_TO_COVERAGE on/off around the multi-draw indirect call. mesh_modern.frag's opaque pass now discards only truly-empty (α<0.05) so the GPU derives sample mask from coverage; transparent pass boundary logic is unchanged. MSAA audit: no custom FBOs found — all rendering uses default framebuffer. Sky/particles/ImGui are all MSAA-compatible. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/AcDream.App/Rendering/GameWindow.cs | 1 + src/AcDream.App/Rendering/Shaders/mesh_modern.frag | 5 ++++- src/AcDream.App/Rendering/Wb/WbDrawDispatcher.cs | 5 +++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs index f788b83..c879195 100644 --- a/src/AcDream.App/Rendering/GameWindow.cs +++ b/src/AcDream.App/Rendering/GameWindow.cs @@ -830,6 +830,7 @@ public sealed class GameWindow : IDisposable ContextFlags.ForwardCompatible, new APIVersion(4, 3)), VSync = false, // off during development so the perf overlay shows true framerate + Samples = 4, // A.5 T20: MSAA 4x for A2C foliage smoothing }; _window = Window.Create(options); diff --git a/src/AcDream.App/Rendering/Shaders/mesh_modern.frag b/src/AcDream.App/Rendering/Shaders/mesh_modern.frag index c5d9a02..1145dc7 100644 --- a/src/AcDream.App/Rendering/Shaders/mesh_modern.frag +++ b/src/AcDream.App/Rendering/Shaders/mesh_modern.frag @@ -80,8 +80,11 @@ void main() { vec4 color = texture(tex, vec3(vTexCoord, float(vTextureLayer))); // Two-pass alpha-test (N.5 Decision 2). + // A.5 T20: opaque pass writes alpha as-sampled so GL_SAMPLE_ALPHA_TO_COVERAGE + // derives the MSAA sample mask from it — ClipMap foliage edges become smooth. + // Discard only fully-transparent (α < 0.05); the GPU handles coverage masking. if (uRenderPass == 0) { - if (color.a < 0.95) discard; // opaque pass + if (color.a < 0.05) discard; // opaque pass — kill truly empty only (A2C) } else { if (color.a >= 0.95) discard; // transparent pass if (color.a < 0.05) discard; // skip totally-empty diff --git a/src/AcDream.App/Rendering/Wb/WbDrawDispatcher.cs b/src/AcDream.App/Rendering/Wb/WbDrawDispatcher.cs index fcb9e66..5d35f68 100644 --- a/src/AcDream.App/Rendering/Wb/WbDrawDispatcher.cs +++ b/src/AcDream.App/Rendering/Wb/WbDrawDispatcher.cs @@ -488,6 +488,10 @@ public sealed unsafe class WbDrawDispatcher : IDisposable { _gl.Disable(EnableCap.Blend); _gl.DepthMask(true); + // A.5 T20: enable A2C for ClipMap foliage — GPU derives sample mask + // from the alpha written by mesh_modern.frag so foliage edges are + // smooth under MSAA 4x. A no-op for fully-opaque (α=1) batches. + _gl.Enable(EnableCap.SampleAlphaToCoverage); _shader.SetInt("uRenderPass", 0); _gl.BindBuffer(BufferTargetARB.DrawIndirectBuffer, _indirectBuffer); if (diag && _gpuQueriesInitialized) _gl.BeginQuery(QueryTarget.TimeElapsed, _gpuQueryOpaque); @@ -498,6 +502,7 @@ public sealed unsafe class WbDrawDispatcher : IDisposable (uint)_opaqueDrawCount, (uint)DrawCommandStride); if (diag && _gpuQueriesInitialized) _gl.EndQuery(QueryTarget.TimeElapsed); + _gl.Disable(EnableCap.SampleAlphaToCoverage); } // ── Phase 8: transparent pass ────────────────────────────────────────