acdream/src/AcDream.App/Rendering/Wb/GlobalMeshBuffer.cs
Erik d16d8cd4e5 feat(O-T4): extract ObjectMeshManager + mesh pipeline closure into AcDream.App.Rendering.Wb
Phase O Task 4: extract the WB mesh pipeline (ObjectMeshManager + 7 support files)
from references/WorldBuilder into src/AcDream.App/Rendering/Wb/ and bridge dat I/O
through our DatCollection via a thin DatCollectionAdapter.

O-D7 adapter path taken: ObjectMeshManager has 26 _dats.X call sites (threshold 20),
so a DatCollectionAdapter : IDatReaderWriter is introduced rather than refactoring
ObjectMeshManager's internal dat access directly.

Files added (verbatim copies, namespace-only changes):
- ObjectMeshManager.cs — mesh pipeline hub; IDatReaderWriter field satisfied by adapter
- GlobalMeshBuffer.cs — single global VAO/VBO/IBO manager
- EdgeLineBuilder.cs — wireframe edge geometry from CellStruct polygons
- ModernRenderData.cs — ModernBatchData + LandblockMdiCommand structs
- TextureAtlasManager.cs — texture array grouping by (Width, Height, Format)
- ParticleBatcher.cs — GPU particle batching; T4 interim uses BaseObjectRenderManager
  static fields from Chorizite.OpenGLSDLBackend.Lib (stays until T7)
- ParticleEmitterRenderer.cs — per-emitter particle lifecycle + rendering
- ActiveParticleEmitter.cs — wrapper holding renderer + part index + local offset
- DatCollectionAdapter.cs — NEW: bridges DatCollection → IDatReaderWriter; implements
  ResolveId() via DatDatabase.TypeFromId + Tree.TryGetFile in HighRes→Portal→Language→Cell
  order matching DefaultDatReaderWriter; DatDatabaseWrapper wraps DatDatabase as IDatDatabase

WbMeshAdapter.cs changes (T4 Step 6):
- _graphicsDevice switched from Chorizite.OpenGLSDLBackend.OpenGLGraphicsDevice to
  extracted AcDream.App.Rendering.Wb.OpenGLGraphicsDevice
- ParticleBatcher = new ParticleBatcher(_graphicsDevice) restored (T3 had null! placeholder)
- ObjectMeshManager now constructed with new DatCollectionAdapter(dats) instead of _wbDats
- _wbDats field + its construction + disposal + [indoor-upload] NULL_RESULT diagnostic block
  left intact — T7 cleanup removes these once WorldBuilder project ref is dropped

EmbeddedResourceReader.cs: replaced assembly manifest lookup (wrong prefix for our assembly)
with disk-based lookup mapping "Shaders.Particle.vert" → Rendering/Shaders/wb_particle.vert;
consistent with all other acdream shaders.

wb_particle.vert / wb_particle.frag: WB particle shaders copied verbatim with wb_ prefix
to distinguish from acdream's own particle.vert.

OpenGLGraphicsDevice.cs: ParticleBatcher property type updated to extracted ParticleBatcher;
setter changed from private to internal so WbMeshAdapter (same assembly) can assign post-ctor.

Build: green (0 errors, 0 warnings in AcDream.App).
Tests: 1147+8 baseline maintained (8 pre-existing failures unchanged).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 16:37:55 +02:00

127 lines
5.3 KiB
C#

using Chorizite.Core.Render.Enums;
using Silk.NET.OpenGL;
using System;
namespace AcDream.App.Rendering.Wb {
public class GlobalMeshBuffer : IDisposable {
private readonly GL _gl;
public uint VAO { get; private set; }
public uint VBO { get; private set; }
public uint IBO { get; private set; }
private int _vboCapacity = 1024 * 1024; // 1M vertices (~32MB)
private int _iboCapacity = 3 * 1024 * 1024; // 3M indices (~6MB)
private int _vboOffset = 0;
private int _iboOffset = 0;
public GlobalMeshBuffer(GL gl) {
_gl = gl;
InitBuffers();
}
private unsafe void InitBuffers() {
_gl.GenVertexArrays(1, out uint vao);
VAO = vao;
_gl.BindVertexArray(VAO);
_gl.GenBuffers(1, out uint vbo);
VBO = vbo;
_gl.BindBuffer(GLEnum.ArrayBuffer, VBO);
_gl.BufferData(GLEnum.ArrayBuffer, (nuint)(_vboCapacity * VertexPositionNormalTexture.Size), null, GLEnum.StaticDraw);
int stride = VertexPositionNormalTexture.Size;
_gl.EnableVertexAttribArray(0);
_gl.VertexAttribPointer(0, 3, GLEnum.Float, false, (uint)stride, (void*)0);
_gl.EnableVertexAttribArray(1);
_gl.VertexAttribPointer(1, 3, GLEnum.Float, false, (uint)stride, (void*)(3 * sizeof(float)));
_gl.EnableVertexAttribArray(2);
_gl.VertexAttribPointer(2, 2, GLEnum.Float, false, (uint)stride, (void*)(6 * sizeof(float)));
_gl.GenBuffers(1, out uint ibo);
IBO = ibo;
_gl.BindBuffer(GLEnum.ElementArrayBuffer, IBO);
_gl.BufferData(GLEnum.ElementArrayBuffer, (nuint)(_iboCapacity * sizeof(ushort)), null, GLEnum.StaticDraw);
_gl.BindVertexArray(0);
}
public unsafe (int baseVertex, int firstIndex) Append(VertexPositionNormalTexture[] vertices, ushort[] indices) {
if (vertices.Length == 0 || indices.Length == 0) return (0, 0);
// Check capacity
if (_vboOffset + vertices.Length > _vboCapacity) {
ResizeVBO(Math.Max(_vboCapacity * 2, _vboCapacity + vertices.Length));
}
if (_iboOffset + indices.Length > _iboCapacity) {
ResizeIBO(Math.Max(_iboCapacity * 2, _iboCapacity + indices.Length));
}
int baseVertex = _vboOffset;
int firstIndex = _iboOffset;
_gl.BindBuffer(GLEnum.ArrayBuffer, VBO);
fixed (VertexPositionNormalTexture* ptr = vertices) {
_gl.BufferSubData(GLEnum.ArrayBuffer, (nint)(baseVertex * VertexPositionNormalTexture.Size), (nuint)(vertices.Length * VertexPositionNormalTexture.Size), ptr);
}
_gl.BindBuffer(GLEnum.ElementArrayBuffer, IBO);
fixed (ushort* ptr = indices) {
_gl.BufferSubData(GLEnum.ElementArrayBuffer, (nint)(firstIndex * sizeof(ushort)), (nuint)(indices.Length * sizeof(ushort)), ptr);
}
_vboOffset += vertices.Length;
_iboOffset += indices.Length;
return (baseVertex, firstIndex);
}
private unsafe void ResizeVBO(int newCapacity) {
_gl.GenBuffers(1, out uint newVbo);
_gl.BindBuffer(GLEnum.ArrayBuffer, newVbo);
_gl.BufferData(GLEnum.ArrayBuffer, (nuint)(newCapacity * VertexPositionNormalTexture.Size), null, GLEnum.StaticDraw);
_gl.BindBuffer(GLEnum.CopyReadBuffer, VBO);
_gl.BindBuffer(GLEnum.CopyWriteBuffer, newVbo);
_gl.CopyBufferSubData(GLEnum.CopyReadBuffer, GLEnum.CopyWriteBuffer, 0, 0, (nuint)(_vboOffset * VertexPositionNormalTexture.Size));
_gl.DeleteBuffer(VBO);
VBO = newVbo;
_vboCapacity = newCapacity;
// Re-bind to VAO
_gl.BindVertexArray(VAO);
_gl.BindBuffer(GLEnum.ArrayBuffer, VBO);
int stride = VertexPositionNormalTexture.Size;
_gl.VertexAttribPointer(0, 3, GLEnum.Float, false, (uint)stride, (void*)0);
_gl.VertexAttribPointer(1, 3, GLEnum.Float, false, (uint)stride, (void*)(3 * sizeof(float)));
_gl.VertexAttribPointer(2, 2, GLEnum.Float, false, (uint)stride, (void*)(6 * sizeof(float)));
_gl.BindVertexArray(0);
}
private unsafe void ResizeIBO(int newCapacity) {
_gl.GenBuffers(1, out uint newIbo);
_gl.BindBuffer(GLEnum.ElementArrayBuffer, newIbo);
_gl.BufferData(GLEnum.ElementArrayBuffer, (nuint)(newCapacity * sizeof(ushort)), null, GLEnum.StaticDraw);
_gl.BindBuffer(GLEnum.CopyReadBuffer, IBO);
_gl.BindBuffer(GLEnum.CopyWriteBuffer, newIbo);
_gl.CopyBufferSubData(GLEnum.CopyReadBuffer, GLEnum.CopyWriteBuffer, 0, 0, (nuint)(_iboOffset * sizeof(ushort)));
_gl.DeleteBuffer(IBO);
IBO = newIbo;
_iboCapacity = newCapacity;
// Re-bind to VAO
_gl.BindVertexArray(VAO);
_gl.BindBuffer(GLEnum.ElementArrayBuffer, IBO);
_gl.BindVertexArray(0);
}
public void Dispose() {
if (VAO != 0) _gl.DeleteVertexArray(VAO);
if (VBO != 0) _gl.DeleteBuffer(VBO);
if (IBO != 0) _gl.DeleteBuffer(IBO);
VAO = VBO = IBO = 0;
}
}
}