refactor(pipeline): MP1a - Content-owned upload enums; drop Silk.NET from Content

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 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-07-05 20:27:38 +02:00
parent 30cc1e282e
commit 1477cda60a
5 changed files with 89 additions and 50 deletions

View file

@ -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 {
/// <see cref="DrainSideStaged"/> 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.
/// </summary>
private readonly List<ObjectMeshData> _sideStaged = new();
private readonly ConcurrentQueue<ObjectMeshData> _sideStaged = new();
public MeshExtractor(IDatReaderWriter dats, ILogger logger) {
_dats = dats;
@ -64,9 +66,10 @@ public sealed class MeshExtractor {
/// <see cref="_sideStaged"/>.
/// </summary>
public List<ObjectMeshData> DrainSideStaged() {
if (_sideStaged.Count == 0) return new List<ObjectMeshData>();
var result = new List<ObjectMeshData>(_sideStaged);
_sideStaged.Clear();
var result = new List<ObjectMeshData>();
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<SurfaceTexture>(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<Palette>(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<Palette>(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<SurfaceTexture>(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<Palette>(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<Palette>(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
}