From 86c471d2d18badd8b2f76a95ea1770c0efb2b78c Mon Sep 17 00:00:00 2001 From: Erik Date: Fri, 8 May 2026 20:25:29 +0200 Subject: [PATCH] phase(N.5) Task 7: dispatcher SSBO + indirect buffer infrastructure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds DrawElementsIndirectCommand struct (20-byte layout for glMultiDrawElementsIndirect). Replaces _instanceVbo field on WbDrawDispatcher with three buffers: _instanceSsbo (mat4[]), _batchSsbo (BatchData[]), _indirectBuffer (DEIC[]). Adds BindlessSupport constructor parameter — non-null required since the dispatcher is only constructed when WB foundation is on (which implies bindless is present per Task 6 capability detection). Existing Draw() method substitutes _instanceVbo -> _instanceSsbo for compile. Behavior is temporarily wrong (SSBO bound as ArrayBuffer for per-vertex attribs); Tasks 9-10 fully rewrite the draw loop and the per-frame uploads to use BindBufferBase + glMultiDrawElementsIndirect. GameWindow construction site updated to add _bindlessSupport guard and pass it as the new last argument to the constructor. Dispatcher is only constructed when bindless is guaranteed present. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/AcDream.App/Rendering/GameWindow.cs | 5 +- .../Wb/DrawElementsIndirectCommand.cs | 17 +++++++ .../Rendering/Wb/WbDrawDispatcher.cs | 49 ++++++++++++++++--- 3 files changed, 63 insertions(+), 8 deletions(-) create mode 100644 src/AcDream.App/Rendering/Wb/DrawElementsIndirectCommand.cs diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs index 6c06a97..d6321c9 100644 --- a/src/AcDream.App/Rendering/GameWindow.cs +++ b/src/AcDream.App/Rendering/GameWindow.cs @@ -1524,10 +1524,11 @@ public sealed class GameWindow : IDisposable _staticMesh = new InstancedMeshRenderer(_gl, _meshShader, _textureCache, _wbMeshAdapter); if (AcDream.App.Rendering.Wb.WbFoundationFlag.IsEnabled - && _wbMeshAdapter is not null && _wbEntitySpawnAdapter is not null) + && _wbMeshAdapter is not null && _wbEntitySpawnAdapter is not null + && _bindlessSupport is not null) { _wbDrawDispatcher = new AcDream.App.Rendering.Wb.WbDrawDispatcher( - _gl, _meshShader, _textureCache, _wbMeshAdapter, _wbEntitySpawnAdapter); + _gl, _meshShader, _textureCache, _wbMeshAdapter, _wbEntitySpawnAdapter, _bindlessSupport); } // Phase G.1 sky renderer — its own shader (sky.vert / sky.frag) diff --git a/src/AcDream.App/Rendering/Wb/DrawElementsIndirectCommand.cs b/src/AcDream.App/Rendering/Wb/DrawElementsIndirectCommand.cs new file mode 100644 index 0000000..80d1119 --- /dev/null +++ b/src/AcDream.App/Rendering/Wb/DrawElementsIndirectCommand.cs @@ -0,0 +1,17 @@ +using System.Runtime.InteropServices; + +namespace AcDream.App.Rendering.Wb; + +/// +/// Layout matches what glMultiDrawElementsIndirect expects. +/// Total size 20 bytes; arrays are typically uploaded with stride = sizeof(this). +/// +[StructLayout(LayoutKind.Sequential, Pack = 4)] +public struct DrawElementsIndirectCommand +{ + public uint Count; // index count for this draw + public uint InstanceCount; // number of instances + public uint FirstIndex; // offset into IBO, in indices + public int BaseVertex; // vertex offset into VBO + public uint BaseInstance; // first instance ID (offsets per-instance attribs / SSBO read) +} diff --git a/src/AcDream.App/Rendering/Wb/WbDrawDispatcher.cs b/src/AcDream.App/Rendering/Wb/WbDrawDispatcher.cs index 4644f71..07d33d1 100644 --- a/src/AcDream.App/Rendering/Wb/WbDrawDispatcher.cs +++ b/src/AcDream.App/Rendering/Wb/WbDrawDispatcher.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Numerics; +using System.Runtime.InteropServices; using AcDream.Core.Meshing; using AcDream.Core.Terrain; using AcDream.Core.World; @@ -61,7 +62,32 @@ public sealed unsafe class WbDrawDispatcher : IDisposable private readonly WbMeshAdapter _meshAdapter; private readonly EntitySpawnAdapter _entitySpawnAdapter; - private readonly uint _instanceVbo; + private readonly BindlessSupport _bindless; + + // SSBO buffer ids + private uint _instanceSsbo; + private uint _batchSsbo; + private uint _indirectBuffer; + + // Per-frame scratch arrays — Tasks 9-10 fully wire these. + private float[] _instanceData = new float[256 * 16]; // mat4 floats per instance + private BatchData[] _batchData = new BatchData[256]; + private DrawElementsIndirectCommand[] _indirectCommands = new DrawElementsIndirectCommand[256]; + +#pragma warning disable CS0169 // Tasks 9-10 wire these counters + private int _opaqueDrawCount; + private int _transparentDrawCount; + private int _transparentByteOffset; +#pragma warning restore CS0169 + + [StructLayout(LayoutKind.Sequential, Pack = 4)] + private struct BatchData + { + public ulong TextureHandle; // bindless handle (uvec2 in GLSL) + public uint TextureLayer; + public uint Flags; + } + private readonly HashSet _patchedVaos = new(); // Per-frame scratch — reused across frames to avoid per-frame allocation. @@ -89,7 +115,8 @@ public sealed unsafe class WbDrawDispatcher : IDisposable Shader shader, TextureCache textures, WbMeshAdapter meshAdapter, - EntitySpawnAdapter entitySpawnAdapter) + EntitySpawnAdapter entitySpawnAdapter, + BindlessSupport bindless) { ArgumentNullException.ThrowIfNull(gl); ArgumentNullException.ThrowIfNull(shader); @@ -103,7 +130,10 @@ public sealed unsafe class WbDrawDispatcher : IDisposable _meshAdapter = meshAdapter; _entitySpawnAdapter = entitySpawnAdapter; - _instanceVbo = _gl.GenBuffer(); + _bindless = bindless ?? throw new ArgumentNullException(nameof(bindless)); + _instanceSsbo = _gl.GenBuffer(); + _batchSsbo = _gl.GenBuffer(); + _indirectBuffer = _gl.GenBuffer(); } public static Matrix4x4 ComposePartWorldMatrix( @@ -291,7 +321,10 @@ public sealed unsafe class WbDrawDispatcher : IDisposable _opaqueDraws.Sort(static (a, b) => a.SortDistance.CompareTo(b.SortDistance)); // ── Phase 3: one upload of all matrices ───────────────────────────── - _gl.BindBuffer(BufferTargetARB.ArrayBuffer, _instanceVbo); + // NOTE: _instanceSsbo is temporarily bound as ArrayBuffer for compile + // compatibility. Tasks 9-10 rewrite this to BindBufferBase(SSBO) + + // glMultiDrawElementsIndirect. + _gl.BindBuffer(BufferTargetARB.ArrayBuffer, _instanceSsbo); fixed (float* p = _instanceBuffer) _gl.BufferData(BufferTargetARB.ArrayBuffer, (nuint)(totalInstances * 16 * sizeof(float)), p, BufferUsageARB.DynamicDraw); @@ -472,7 +505,9 @@ public sealed unsafe class WbDrawDispatcher : IDisposable if (!_patchedVaos.Add(vao)) return; _gl.BindVertexArray(vao); - _gl.BindBuffer(BufferTargetARB.ArrayBuffer, _instanceVbo); + // NOTE: temporarily binding _instanceSsbo as ArrayBuffer for compile + // compatibility. Tasks 9-10 replace with BindBufferBase(SSBO). + _gl.BindBuffer(BufferTargetARB.ArrayBuffer, _instanceSsbo); for (uint row = 0; row < 4; row++) { uint loc = 3 + row; @@ -494,7 +529,9 @@ public sealed unsafe class WbDrawDispatcher : IDisposable { if (_disposed) return; _disposed = true; - _gl.DeleteBuffer(_instanceVbo); + _gl.DeleteBuffer(_instanceSsbo); + _gl.DeleteBuffer(_batchSsbo); + _gl.DeleteBuffer(_indirectBuffer); } private readonly record struct GroupKey(