From 1477cda60a0041a472554ac5b48e30361901b199 Mon Sep 17 00:00:00 2001 From: Erik Date: Sun, 5 Jul 2026 20:27:38 +0200 Subject: [PATCH] refactor(pipeline): MP1a - Content-owned upload enums; drop Silk.NET from Content MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Coordinator-directed follow-up: AcDream.Content must stay Silk.NET-free (the MP1b bake tool must not ship GL binaries). The Silk.NET.OpenGL PackageReference added for the PixelFormat?/PixelType? upload hints is replaced by Content-owned UploadPixelFormat/UploadPixelType enums (UploadFormats.cs) whose underlying values are the GL ABI constants (Rgba = 0x1908, UnsignedByte = 0x1401), verified numerically identical to the Silk.NET.OpenGL members against 2.23.0. This is the one sanctioned edit to the verbatim-moved Prepare* bodies: enum literal for enum literal, numeric value identical, behavior unchanged. App casts at the single upload boundary (AddTexture call in UploadGfxObjMeshData) via lifted nullable enum conversion — value- and null-preserving. Also hardens the MP1a _sideStaged hand-off seam: List -> ConcurrentQueue. One MeshExtractor is shared by up to MaxParallelLoads (4) decode workers; the original code enqueued to the thread-safe _stagedMeshData directly, so the hand-off buffer must be thread-safe too. Drain ordering verified: side-staged entries enqueue BEFORE the top-level result, preserving the original mid-Prepare FIFO order. Verified: grep -i silk on the csproj -> no matches; deps.json has zero Silk entries; dotnet build 0 errors; full test suite green (4059 passed). Co-Authored-By: Claude Fable 5 --- .../Rendering/Wb/ObjectMeshManager.cs | 8 +- src/AcDream.Content/AcDream.Content.csproj | 14 ++-- src/AcDream.Content/MeshExtractor.cs | 73 ++++++++++--------- src/AcDream.Content/ObjectMeshData.cs | 10 +-- src/AcDream.Content/UploadFormats.cs | 34 +++++++++ 5 files changed, 89 insertions(+), 50 deletions(-) create mode 100644 src/AcDream.Content/UploadFormats.cs diff --git a/src/AcDream.App/Rendering/Wb/ObjectMeshManager.cs b/src/AcDream.App/Rendering/Wb/ObjectMeshManager.cs index be98a1c2..ec92af48 100644 --- a/src/AcDream.App/Rendering/Wb/ObjectMeshManager.cs +++ b/src/AcDream.App/Rendering/Wb/ObjectMeshManager.cs @@ -807,7 +807,13 @@ namespace AcDream.App.Rendering.Wb { atlasList.Add(atlasManager); } - textureIndex = atlasManager.AddTexture(batch.Key, batch.TextureData, batch.UploadPixelFormat, batch.UploadPixelType); + // MP1a: AcDream.Content is Silk.NET-free — the extraction records + // carry Content-owned UploadPixelFormat/UploadPixelType enums whose + // underlying values are the GL ABI constants (numerically identical + // to Silk.NET.OpenGL.PixelFormat/PixelType), so this lifted nullable + // cast is value- and null-preserving. + textureIndex = atlasManager.AddTexture(batch.Key, batch.TextureData, + (PixelFormat?)batch.UploadPixelFormat, (PixelType?)batch.UploadPixelType); if (_useModernRendering) { ibo = GlobalBuffer!.IBO; diff --git a/src/AcDream.Content/AcDream.Content.csproj b/src/AcDream.Content/AcDream.Content.csproj index 1abed36e..e54e275e 100644 --- a/src/AcDream.Content/AcDream.Content.csproj +++ b/src/AcDream.Content/AcDream.Content.csproj @@ -12,14 +12,12 @@ exact versions (mirror AcDream.App.csproj — keep in lockstep). --> - - + diff --git a/src/AcDream.Content/MeshExtractor.cs b/src/AcDream.Content/MeshExtractor.cs index 66b4d373..fa5f1d46 100644 --- a/src/AcDream.Content/MeshExtractor.cs +++ b/src/AcDream.Content/MeshExtractor.cs @@ -15,8 +15,6 @@ using System.Linq; using System.Numerics; using System.Threading; using CullMode = DatReaderWriter.Enums.CullMode; -using PixelFormat = Silk.NET.OpenGL.PixelFormat; -using PixelType = Silk.NET.OpenGL.PixelType; using BoundingBox = Chorizite.Core.Lib.BoundingBox; namespace AcDream.Content; @@ -50,8 +48,12 @@ public sealed class MeshExtractor { /// immediately after each Prepare* call and /// enqueues the results itself. No behavior change — same objects reach /// the same queue, just via an explicit hand-off instead of a shared field. + /// ConcurrentQueue because one MeshExtractor is shared by up to + /// MaxParallelLoads (4) decode workers — the original enqueued to the + /// thread-safe _stagedMeshData, so the hand-off buffer must be + /// thread-safe too. /// - private readonly List _sideStaged = new(); + private readonly ConcurrentQueue _sideStaged = new(); public MeshExtractor(IDatReaderWriter dats, ILogger logger) { _dats = dats; @@ -64,9 +66,10 @@ public sealed class MeshExtractor { /// . /// public List DrainSideStaged() { - if (_sideStaged.Count == 0) return new List(); - var result = new List(_sideStaged); - _sideStaged.Clear(); + var result = new List(); + while (_sideStaged.TryDequeue(out var item)) { + result.Add(item); + } return result; } @@ -173,13 +176,13 @@ public sealed class MeshExtractor { if (emitter.HwGfxObjId.DataId != 0) { var meshData = PrepareMeshData(emitter.HwGfxObjId.DataId, false, ct); if (meshData != null) { - _sideStaged.Add(meshData); + _sideStaged.Enqueue(meshData); } } if (emitter.GfxObjId.DataId != 0 && emitter.GfxObjId.DataId != emitter.HwGfxObjId.DataId) { var meshData = PrepareMeshData(emitter.GfxObjId.DataId, false, ct); if (meshData != null) { - _sideStaged.Add(meshData); + _sideStaged.Enqueue(meshData); } } } @@ -368,8 +371,8 @@ public sealed class MeshExtractor { int texWidth, texHeight; byte[] textureData; TextureFormat textureFormat; - PixelFormat? uploadPixelFormat = null; - PixelType? uploadPixelType = null; + UploadPixelFormat? uploadPixelFormat = null; + UploadPixelType? uploadPixelType = null; bool isSolid = poly.Stippling.HasFlag(StipplingType.NoPos) || surface.Type.HasFlag(SurfaceType.Base1Solid); bool isClipMap = surface.Type.HasFlag(SurfaceType.Base1ClipMap); uint paletteId = 0; @@ -382,7 +385,7 @@ public sealed class MeshExtractor { texWidth = texHeight = 32; textureData = TextureHelpers.CreateSolidColorTexture(surface.ColorValue, texWidth, texHeight); textureFormat = TextureFormat.RGBA8; - uploadPixelFormat = PixelFormat.Rgba; + uploadPixelFormat = UploadPixelFormat.Rgba; } else if (_dats.Portal.TryGet(surface.OrigTextureId, out var surfaceTexture)) { var renderSurfaceId = surfaceTexture.Textures.First(); @@ -403,7 +406,7 @@ public sealed class MeshExtractor { if (TextureHelpers.IsCompressedFormat(renderSurface.Format)) { isDxt3or5 = renderSurface.Format == DatReaderWriter.Enums.PixelFormat.PFID_DXT3 || renderSurface.Format == DatReaderWriter.Enums.PixelFormat.PFID_DXT5; textureFormat = TextureFormat.RGBA8; - uploadPixelFormat = PixelFormat.Rgba; + uploadPixelFormat = UploadPixelFormat.Rgba; if (_decodedTextureCache.TryGetValue(renderSurfaceId, out textureData!)) { // use cached data @@ -446,36 +449,36 @@ public sealed class MeshExtractor { case DatReaderWriter.Enums.PixelFormat.PFID_A8R8G8B8: textureData = new byte[texWidth * texHeight * 4]; TextureHelpers.FillA8R8G8B8(renderSurface.SourceData, textureData.AsSpan(), texWidth, texHeight); - uploadPixelFormat = PixelFormat.Rgba; + uploadPixelFormat = UploadPixelFormat.Rgba; break; case DatReaderWriter.Enums.PixelFormat.PFID_R8G8B8: textureData = new byte[texWidth * texHeight * 4]; TextureHelpers.FillR8G8B8(renderSurface.SourceData, textureData.AsSpan(), texWidth, texHeight); - uploadPixelFormat = PixelFormat.Rgba; + uploadPixelFormat = UploadPixelFormat.Rgba; break; case DatReaderWriter.Enums.PixelFormat.PFID_INDEX16: if (!_dats.Portal.TryGet(renderSurface.DefaultPaletteId, out var paletteData)) throw new Exception($"Unable to load Palette: 0x{renderSurface.DefaultPaletteId:X8}"); textureData = new byte[texWidth * texHeight * 4]; TextureHelpers.FillIndex16(renderSurface.SourceData, paletteData, textureData.AsSpan(), texWidth, texHeight, isClipMap); - uploadPixelFormat = PixelFormat.Rgba; + uploadPixelFormat = UploadPixelFormat.Rgba; break; case DatReaderWriter.Enums.PixelFormat.PFID_P8: if (!_dats.Portal.TryGet(renderSurface.DefaultPaletteId, out var p8PaletteData)) throw new Exception($"Unable to load Palette: 0x{renderSurface.DefaultPaletteId:X8}"); textureData = new byte[texWidth * texHeight * 4]; TextureHelpers.FillP8(renderSurface.SourceData, p8PaletteData, textureData.AsSpan(), texWidth, texHeight, isClipMap); - uploadPixelFormat = PixelFormat.Rgba; + uploadPixelFormat = UploadPixelFormat.Rgba; break; case DatReaderWriter.Enums.PixelFormat.PFID_R5G6B5: textureData = new byte[texWidth * texHeight * 4]; TextureHelpers.FillR5G6B5(renderSurface.SourceData, textureData.AsSpan(), texWidth, texHeight); - uploadPixelFormat = PixelFormat.Rgba; + uploadPixelFormat = UploadPixelFormat.Rgba; break; case DatReaderWriter.Enums.PixelFormat.PFID_A4R4G4B4: textureData = new byte[texWidth * texHeight * 4]; TextureHelpers.FillA4R4G4B4(renderSurface.SourceData, textureData.AsSpan(), texWidth, texHeight); - uploadPixelFormat = PixelFormat.Rgba; + uploadPixelFormat = UploadPixelFormat.Rgba; break; case DatReaderWriter.Enums.PixelFormat.PFID_A8: case DatReaderWriter.Enums.PixelFormat.PFID_CUSTOM_LSCAPE_ALPHA: @@ -486,7 +489,7 @@ public sealed class MeshExtractor { else { TextureHelpers.FillA8(renderSurface.SourceData, textureData.AsSpan(), texWidth, texHeight); } - uploadPixelFormat = PixelFormat.Rgba; + uploadPixelFormat = UploadPixelFormat.Rgba; break; default: throw new NotSupportedException($"Unsupported surface format: {renderSurface.Format}"); @@ -722,8 +725,8 @@ public sealed class MeshExtractor { int texWidth, texHeight; byte[] textureData; TextureFormat textureFormat; - PixelFormat? uploadPixelFormat = null; - PixelType? uploadPixelType = null; + UploadPixelFormat? uploadPixelFormat = null; + UploadPixelType? uploadPixelType = null; bool isSolid = poly.Stippling.HasFlag(StipplingType.NoPos) || surface.Type.HasFlag(SurfaceType.Base1Solid); bool isClipMap = surface.Type.HasFlag(SurfaceType.Base1ClipMap); uint paletteId = 0; @@ -736,7 +739,7 @@ public sealed class MeshExtractor { texWidth = texHeight = 32; textureData = TextureHelpers.CreateSolidColorTexture(surface.ColorValue, texWidth, texHeight); textureFormat = TextureFormat.RGBA8; - uploadPixelFormat = PixelFormat.Rgba; + uploadPixelFormat = UploadPixelFormat.Rgba; } else if (_dats.Portal.TryGet(surface.OrigTextureId, out var surfaceTexture)) { var renderSurfaceId = surfaceTexture.Textures.First(); @@ -757,13 +760,13 @@ public sealed class MeshExtractor { if (_decodedTextureCache.TryGetValue(renderSurfaceId, out var cachedData)) { textureData = cachedData; textureFormat = TextureFormat.RGBA8; - uploadPixelFormat = PixelFormat.Rgba; + uploadPixelFormat = UploadPixelFormat.Rgba; } else { if (TextureHelpers.IsCompressedFormat(renderSurface.Format)) { isDxt3or5 = renderSurface.Format == DatReaderWriter.Enums.PixelFormat.PFID_DXT3 || renderSurface.Format == DatReaderWriter.Enums.PixelFormat.PFID_DXT5; textureFormat = TextureFormat.RGBA8; - uploadPixelFormat = PixelFormat.Rgba; + uploadPixelFormat = UploadPixelFormat.Rgba; textureData = new byte[texWidth * texHeight * 4]; CompressionFormat compressionFormat = renderSurface.Format switch { @@ -784,34 +787,34 @@ public sealed class MeshExtractor { case DatReaderWriter.Enums.PixelFormat.PFID_A8R8G8B8: textureData = new byte[texWidth * texHeight * 4]; TextureHelpers.FillA8R8G8B8(renderSurface.SourceData, textureData.AsSpan(), texWidth, texHeight); - uploadPixelFormat = PixelFormat.Rgba; + uploadPixelFormat = UploadPixelFormat.Rgba; break; case DatReaderWriter.Enums.PixelFormat.PFID_R8G8B8: textureData = new byte[texWidth * texHeight * 4]; TextureHelpers.FillR8G8B8(renderSurface.SourceData, textureData.AsSpan(), texWidth, texHeight); - uploadPixelFormat = PixelFormat.Rgba; + uploadPixelFormat = UploadPixelFormat.Rgba; break; case DatReaderWriter.Enums.PixelFormat.PFID_INDEX16: if (!_dats.Portal.TryGet(renderSurface.DefaultPaletteId, out var paletteData)) return; textureData = new byte[texWidth * texHeight * 4]; TextureHelpers.FillIndex16(renderSurface.SourceData, paletteData, textureData.AsSpan(), texWidth, texHeight, isClipMap); - uploadPixelFormat = PixelFormat.Rgba; + uploadPixelFormat = UploadPixelFormat.Rgba; break; case DatReaderWriter.Enums.PixelFormat.PFID_P8: if (!_dats.Portal.TryGet(renderSurface.DefaultPaletteId, out var p8PaletteData)) return; textureData = new byte[texWidth * texHeight * 4]; TextureHelpers.FillP8(renderSurface.SourceData, p8PaletteData, textureData.AsSpan(), texWidth, texHeight, isClipMap); - uploadPixelFormat = PixelFormat.Rgba; + uploadPixelFormat = UploadPixelFormat.Rgba; break; case DatReaderWriter.Enums.PixelFormat.PFID_R5G6B5: textureData = new byte[texWidth * texHeight * 4]; TextureHelpers.FillR5G6B5(renderSurface.SourceData, textureData.AsSpan(), texWidth, texHeight); - uploadPixelFormat = PixelFormat.Rgba; + uploadPixelFormat = UploadPixelFormat.Rgba; break; case DatReaderWriter.Enums.PixelFormat.PFID_A4R4G4B4: textureData = new byte[texWidth * texHeight * 4]; TextureHelpers.FillA4R4G4B4(renderSurface.SourceData, textureData.AsSpan(), texWidth, texHeight); - uploadPixelFormat = PixelFormat.Rgba; + uploadPixelFormat = UploadPixelFormat.Rgba; break; case DatReaderWriter.Enums.PixelFormat.PFID_A8: case DatReaderWriter.Enums.PixelFormat.PFID_CUSTOM_LSCAPE_ALPHA: @@ -822,7 +825,7 @@ public sealed class MeshExtractor { else { TextureHelpers.FillA8(renderSurface.SourceData, textureData.AsSpan(), texWidth, texHeight); } - uploadPixelFormat = PixelFormat.Rgba; + uploadPixelFormat = UploadPixelFormat.Rgba; break; default: return; } @@ -1144,8 +1147,8 @@ public sealed class MeshExtractor { }, TextureIndex = 0, TextureData = transparentTexture, - UploadPixelFormat = PixelFormat.Rgba, - UploadPixelType = PixelType.UnsignedByte, + UploadPixelFormat = UploadPixelFormat.Rgba, + UploadPixelType = UploadPixelType.UnsignedByte, CullMode = CullMode.None } }, @@ -1161,8 +1164,8 @@ public sealed class MeshExtractor { IsSolid = true }, TextureData = transparentTexture, - UploadPixelFormat = PixelFormat.Rgba, - UploadPixelType = PixelType.UnsignedByte, + UploadPixelFormat = UploadPixelFormat.Rgba, + UploadPixelType = UploadPixelType.UnsignedByte, CullMode = CullMode.None, IsTransparent = false // Render in opaque pass but transparent } diff --git a/src/AcDream.Content/ObjectMeshData.cs b/src/AcDream.Content/ObjectMeshData.cs index 8bba1e0c..49089475 100644 --- a/src/AcDream.Content/ObjectMeshData.cs +++ b/src/AcDream.Content/ObjectMeshData.cs @@ -7,8 +7,6 @@ using System.Collections.Generic; using System.Numerics; using System.Runtime.InteropServices; using BoundingBox = Chorizite.Core.Lib.BoundingBox; -using PixelFormat = Silk.NET.OpenGL.PixelFormat; -using PixelType = Silk.NET.OpenGL.PixelType; namespace AcDream.Content; @@ -104,8 +102,8 @@ public class MeshBatchData { public TextureKey TextureKey { get; set; } public int TextureIndex { get; set; } public byte[] TextureData { get; set; } = Array.Empty(); - public PixelFormat? UploadPixelFormat { get; set; } - public PixelType? UploadPixelType { get; set; } + public UploadPixelFormat? UploadPixelFormat { get; set; } + public UploadPixelType? UploadPixelType { get; set; } public DatReaderWriter.Enums.CullMode CullMode { get; set; } } @@ -115,8 +113,8 @@ public class MeshBatchData { public class TextureBatchData { public TextureKey Key { get; set; } public byte[] TextureData { get; set; } = Array.Empty(); - public PixelFormat? UploadPixelFormat { get; set; } - public PixelType? UploadPixelType { get; set; } + public UploadPixelFormat? UploadPixelFormat { get; set; } + public UploadPixelType? UploadPixelType { get; set; } public List Indices { get; set; } = new(); public DatReaderWriter.Enums.CullMode CullMode { get; set; } public bool IsTransparent { get; set; } diff --git a/src/AcDream.Content/UploadFormats.cs b/src/AcDream.Content/UploadFormats.cs new file mode 100644 index 00000000..b0f63612 --- /dev/null +++ b/src/AcDream.Content/UploadFormats.cs @@ -0,0 +1,34 @@ +namespace AcDream.Content; + +// MP1a follow-up (2026-07-05): Content-owned upload-format hint enums. +// AcDream.Content must stay Silk.NET-free (the MP1b bake tool must not ship +// GL binaries), so the moved MeshBatchData/TextureBatchData records carry +// these instead of Silk.NET.OpenGL.PixelFormat/PixelType. The underlying +// values are the OpenGL ABI constants, kept numerically identical to the +// corresponding Silk.NET.OpenGL members (verified against Silk.NET.OpenGL +// 2.23.0) so the App-boundary cast +// `(Silk.NET.OpenGL.PixelFormat?)batch.UploadPixelFormat` is +// value-preserving. GL enum constants are a stable specification ABI — +// they cannot drift between Silk.NET versions. +// +// Members are ONLY the values the extraction code actually assigns +// (see MeshExtractor.cs). Add new members with their GL constant if a +// future decode path needs them — never renumber. + +/// +/// GL pixel-format upload hint computed at extraction time. +/// Numeric values = OpenGL GL_* constants (= Silk.NET.OpenGL.PixelFormat). +/// +public enum UploadPixelFormat { + /// GL_RGBA. + Rgba = 0x1908, +} + +/// +/// GL pixel-type upload hint computed at extraction time. +/// Numeric values = OpenGL GL_* constants (= Silk.NET.OpenGL.PixelType). +/// +public enum UploadPixelType { + /// GL_UNSIGNED_BYTE. + UnsignedByte = 0x1401, +}