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>
This commit is contained in:
parent
4cc38805b5
commit
d16d8cd4e5
14 changed files with 3535 additions and 19 deletions
120
src/AcDream.App/Rendering/Wb/TextureAtlasManager.cs
Normal file
120
src/AcDream.App/Rendering/Wb/TextureAtlasManager.cs
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
using Chorizite.Core.Render;
|
||||
using Chorizite.Core.Render.Enums;
|
||||
using DatReaderWriter.Enums;
|
||||
using Silk.NET.OpenGL;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using PixelFormat = Silk.NET.OpenGL.PixelFormat;
|
||||
|
||||
namespace AcDream.App.Rendering.Wb {
|
||||
/// <summary>
|
||||
/// Manages texture arrays grouped by (Width, Height, Format).
|
||||
/// Deduplicates textures by a TextureKey and supports reference counting.
|
||||
/// </summary>
|
||||
public class TextureAtlasManager : IDisposable {
|
||||
private static uint _nextSlot = 1;
|
||||
private readonly OpenGLGraphicsDevice _graphicsDevice;
|
||||
private readonly int _textureWidth;
|
||||
private readonly int _textureHeight;
|
||||
private readonly TextureFormat _format;
|
||||
private readonly Dictionary<TextureKey, int> _textureIndices = new();
|
||||
private readonly Dictionary<int, int> _refCounts = new();
|
||||
private readonly Stack<int> _freeSlots = new();
|
||||
private int _nextIndex = 0;
|
||||
private const int InitialCapacity = 32;
|
||||
|
||||
public uint Slot { get; }
|
||||
public ManagedGLTextureArray TextureArray { get; private set; } = null!;
|
||||
public int UsedSlots => _textureIndices.Count;
|
||||
public int TotalSlots => TextureArray?.Size ?? InitialCapacity;
|
||||
public int FreeSlots => TotalSlots - UsedSlots;
|
||||
|
||||
public TextureAtlasManager(OpenGLGraphicsDevice graphicsDevice, int width, int height, TextureFormat format = TextureFormat.RGBA8) {
|
||||
Slot = _nextSlot++;
|
||||
_graphicsDevice = graphicsDevice;
|
||||
_textureWidth = width;
|
||||
_textureHeight = height;
|
||||
_format = format;
|
||||
TextureArray = (ManagedGLTextureArray)graphicsDevice.CreateTextureArrayInternal(format, width, height, InitialCapacity, TextureParameters.ClampToEdge);
|
||||
}
|
||||
|
||||
public int AddTexture(TextureKey key, byte[] data, PixelFormat? uploadPixelFormat = null, PixelType? uploadPixelType = null) {
|
||||
if (_textureIndices.TryGetValue(key, out var existingIndex)) {
|
||||
_refCounts[existingIndex]++;
|
||||
return existingIndex;
|
||||
}
|
||||
|
||||
int index;
|
||||
if (_freeSlots.Count > 0) {
|
||||
index = _freeSlots.Pop();
|
||||
}
|
||||
else {
|
||||
index = _nextIndex++;
|
||||
if (index >= TextureArray.Size) {
|
||||
throw new Exception($"Texture atlas is full! {TextureArray.Size} / {_nextIndex} used.");
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
TextureArray.UpdateLayer(index, data, uploadPixelFormat, uploadPixelType);
|
||||
_textureIndices[key] = index;
|
||||
_refCounts[index] = 1;
|
||||
return index;
|
||||
}
|
||||
catch (Exception) {
|
||||
if (!_textureIndices.ContainsKey(key)) {
|
||||
_freeSlots.Push(index);
|
||||
}
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public void ReleaseTexture(TextureKey key) {
|
||||
if (!_textureIndices.TryGetValue(key, out var index)) return;
|
||||
|
||||
if (!_refCounts.ContainsKey(index)) return;
|
||||
|
||||
_refCounts[index]--;
|
||||
if (_refCounts[index] <= 0) {
|
||||
_textureIndices.Remove(key);
|
||||
_refCounts.Remove(index);
|
||||
_freeSlots.Push(index);
|
||||
TextureArray?.RemoveLayer(index);
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasTexture(TextureKey key) => _textureIndices.ContainsKey(key);
|
||||
|
||||
public int GetTextureIndex(TextureKey key) =>
|
||||
_textureIndices.TryGetValue(key, out var index) ? index : -1;
|
||||
|
||||
public void Dispose() {
|
||||
TextureArray?.Dispose();
|
||||
_textureIndices.Clear();
|
||||
_refCounts.Clear();
|
||||
_freeSlots.Clear();
|
||||
}
|
||||
|
||||
public struct TextureKey : IEquatable<TextureKey> {
|
||||
public uint SurfaceId;
|
||||
public uint PaletteId;
|
||||
public StipplingType Stippling;
|
||||
public bool IsSolid;
|
||||
|
||||
public bool Equals(TextureKey other) {
|
||||
return SurfaceId == other.SurfaceId &&
|
||||
PaletteId == other.PaletteId &&
|
||||
Stippling == other.Stippling &&
|
||||
IsSolid == other.IsSolid;
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj) {
|
||||
return obj is TextureKey other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode() {
|
||||
return HashCode.Combine(SurfaceId, PaletteId, Stippling, IsSolid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue