refactor(N.3): thread isAdditive + substitute 5 decode methods with WB TextureHelpers
Task 2 — isAdditive threading: SurfaceDecoder.DecodeRenderSurface now accepts isAdditive parameter. A8/CUSTOM_LSCAPE_ALPHA format splits: - isAdditive=true: R=G=B=A=val (terrain alpha, additive entity textures) - isAdditive=false: R=G=B=255, A=val (non-additive entity textures) TextureCache passes surface.Type.HasFlag(SurfaceType.Additive). TerrainAtlas passes isAdditive:true (alpha masks always replicate). Aligns with WB ObjectMeshManager dispatch logic. Task 3 — WB body substitution + new formats: INDEX16, P8, A8R8G8B8, R8G8B8, A8 now delegate to TextureHelpers.FillIndex16/FillP8/FillA8R8G8B8/FillR8G8B8/ FillA8/FillA8Additive. Validation + DecodedTexture wrapping stays ours. X8R8G8B8, DXT1/3/5, SolidColor remain our implementations (no WB equiv). Bonus: R5G6B5 + A4R4G4B4 formats now handled (previously fell to magenta). 9 conformance tests pass. Build 0 errors. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
2a491c6f92
commit
0a67254c5e
3 changed files with 50 additions and 94 deletions
|
|
@ -316,10 +316,10 @@ public sealed unsafe class TerrainAtlas : IDisposable
|
|||
return false;
|
||||
|
||||
// Alpha maps ship as PFID_CUSTOM_LSCAPE_ALPHA (AC's landscape-alpha
|
||||
// format) or the more generic PFID_A8; SurfaceDecoder routes both
|
||||
// through the same "replicate single byte to RGBA" path. Palette is
|
||||
// not used.
|
||||
var d = SurfaceDecoder.DecodeRenderSurface(rs, palette: null);
|
||||
// format) or the more generic PFID_A8; terrain blending alpha masks
|
||||
// MUST use isAdditive=true so R=G=B=A=val — the terrain fragment shader
|
||||
// reads .r for the blend weight. Palette is not used.
|
||||
var d = SurfaceDecoder.DecodeRenderSurface(rs, palette: null, isClipMap: false, isAdditive: true);
|
||||
if (ReferenceEquals(d, DecodedTexture.Magenta))
|
||||
return false;
|
||||
|
||||
|
|
|
|||
|
|
@ -199,8 +199,9 @@ public sealed unsafe class TextureCache : IDisposable
|
|||
|
||||
// Clipmap surfaces use palette indices 0..7 as transparent sentinels.
|
||||
bool isClipMap = surface.Type.HasFlag(SurfaceType.Base1ClipMap);
|
||||
bool isAdditive = surface.Type.HasFlag(SurfaceType.Additive);
|
||||
|
||||
return SurfaceDecoder.DecodeRenderSurface(rs, effectivePalette, isClipMap);
|
||||
return SurfaceDecoder.DecodeRenderSurface(rs, effectivePalette, isClipMap, isAdditive);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using BCnEncoder.Decoder;
|
||||
using BCnEncoder.Shared;
|
||||
using Chorizite.OpenGLSDLBackend.Lib;
|
||||
using DatReaderWriter.DBObjs;
|
||||
using DatReaderWriter.Enums;
|
||||
|
||||
|
|
@ -16,7 +17,7 @@ public static class SurfaceDecoder
|
|||
/// when a palette is available.
|
||||
/// </summary>
|
||||
public static DecodedTexture DecodeRenderSurface(RenderSurface rs)
|
||||
=> DecodeRenderSurface(rs, palette: null);
|
||||
=> DecodeRenderSurface(rs, palette: null, isClipMap: false, isAdditive: false);
|
||||
|
||||
/// <summary>
|
||||
/// Decode a RenderSurface's pixel bytes into RGBA8 with optional palette support.
|
||||
|
|
@ -24,8 +25,11 @@ public static class SurfaceDecoder
|
|||
/// 16-bit value in SourceData is treated as an index into <see cref="Palette.Colors"/>.
|
||||
/// When <paramref name="isClipMap"/> is true on an indexed surface, palette indices
|
||||
/// below 8 are forced to fully-transparent (AC's clipmap alpha-key convention).
|
||||
/// When <paramref name="isAdditive"/> is true, A8/CUSTOM_LSCAPE_ALPHA surfaces
|
||||
/// replicate the byte into all four channels (R=G=B=A=val, for terrain alpha masks
|
||||
/// and additive surfaces). When false, R=G=B=255, A=val (WB FillA8 semantics).
|
||||
/// </summary>
|
||||
public static DecodedTexture DecodeRenderSurface(RenderSurface rs, Palette? palette, bool isClipMap = false)
|
||||
public static DecodedTexture DecodeRenderSurface(RenderSurface rs, Palette? palette, bool isClipMap = false, bool isAdditive = false)
|
||||
{
|
||||
if (rs.SourceData is null || rs.Width <= 0 || rs.Height <= 0)
|
||||
return DecodedTexture.Magenta;
|
||||
|
|
@ -40,9 +44,11 @@ public static class SurfaceDecoder
|
|||
PixelFormat.PFID_DXT1 => DecodeBc(rs, CompressionFormat.Bc1, isClipMap),
|
||||
PixelFormat.PFID_DXT3 => DecodeBc(rs, CompressionFormat.Bc2, isClipMap),
|
||||
PixelFormat.PFID_DXT5 => DecodeBc(rs, CompressionFormat.Bc3, isClipMap),
|
||||
PixelFormat.PFID_A8 or PixelFormat.PFID_CUSTOM_LSCAPE_ALPHA => DecodeA8(rs),
|
||||
PixelFormat.PFID_A8 or PixelFormat.PFID_CUSTOM_LSCAPE_ALPHA => DecodeA8(rs, isAdditive),
|
||||
PixelFormat.PFID_P8 when palette is not null => DecodeP8(rs, palette, isClipMap),
|
||||
PixelFormat.PFID_INDEX16 when palette is not null => DecodeIndex16(rs, palette, isClipMap),
|
||||
PixelFormat.PFID_R5G6B5 => DecodeR5G6B5(rs),
|
||||
PixelFormat.PFID_A4R4G4B4 => DecodeA4R4G4B4(rs),
|
||||
_ => DecodedTexture.Magenta,
|
||||
};
|
||||
}
|
||||
|
|
@ -59,33 +65,7 @@ public static class SurfaceDecoder
|
|||
return DecodedTexture.Magenta;
|
||||
|
||||
var rgba = new byte[rs.Width * rs.Height * 4];
|
||||
int paletteMax = palette.Colors.Count - 1;
|
||||
for (int i = 0; i < rs.Width * rs.Height; i++)
|
||||
{
|
||||
// Read each 16-bit value little-endian as a palette index
|
||||
int src = i * 2;
|
||||
ushort idx = (ushort)(rs.SourceData[src] | (rs.SourceData[src + 1] << 8));
|
||||
if (idx > paletteMax) idx = 0;
|
||||
var c = palette.Colors[idx];
|
||||
|
||||
int dst = i * 4;
|
||||
// Clipmap alpha-key convention (ACViewer: if (isClipMap && color < 8) r=g=b=a=0):
|
||||
// palette indices 0..7 on clipmap surfaces represent transparent pixels.
|
||||
if (isClipMap && idx < 8)
|
||||
{
|
||||
rgba[dst + 0] = 0;
|
||||
rgba[dst + 1] = 0;
|
||||
rgba[dst + 2] = 0;
|
||||
rgba[dst + 3] = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
rgba[dst + 0] = c.Red;
|
||||
rgba[dst + 1] = c.Green;
|
||||
rgba[dst + 2] = c.Blue;
|
||||
rgba[dst + 3] = c.Alpha;
|
||||
}
|
||||
}
|
||||
TextureHelpers.FillIndex16(rs.SourceData, palette, rgba.AsSpan(), rs.Width, rs.Height, isClipMap);
|
||||
return new DecodedTexture(rgba, rs.Width, rs.Height);
|
||||
}
|
||||
|
||||
|
|
@ -109,30 +89,22 @@ public static class SurfaceDecoder
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decode single-byte-per-pixel alpha (PFID_A8 / PFID_CUSTOM_LSCAPE_ALPHA)
|
||||
/// into RGBA8 by replicating each alpha byte into all four channels. AC's
|
||||
/// terrain blending alpha masks are stored as PFID_CUSTOM_LSCAPE_ALPHA and
|
||||
/// other generic 8-bit alpha surfaces use PFID_A8; the bit layout is
|
||||
/// identical so one decoder handles both. Replicating into all four
|
||||
/// channels lets the fragment shader pull "the blend amount" from either
|
||||
/// .a or .r without special-casing.
|
||||
/// Decode single-byte-per-pixel alpha (PFID_A8 / PFID_CUSTOM_LSCAPE_ALPHA) into RGBA8.
|
||||
/// When <paramref name="isAdditive"/> is true: R=G=B=A=val (terrain alpha masks and
|
||||
/// additive entity textures — the shader reads .r for the blend weight). When false:
|
||||
/// R=G=B=255, A=val (WB FillA8 semantics for non-additive entity textures).
|
||||
/// </summary>
|
||||
private static DecodedTexture DecodeA8(RenderSurface rs)
|
||||
private static DecodedTexture DecodeA8(RenderSurface rs, bool isAdditive)
|
||||
{
|
||||
int expected = rs.Width * rs.Height;
|
||||
if (rs.SourceData.Length < expected)
|
||||
return DecodedTexture.Magenta;
|
||||
|
||||
var rgba = new byte[expected * 4];
|
||||
for (int i = 0; i < expected; i++)
|
||||
{
|
||||
byte a = rs.SourceData[i];
|
||||
int d = i * 4;
|
||||
rgba[d + 0] = a;
|
||||
rgba[d + 1] = a;
|
||||
rgba[d + 2] = a;
|
||||
rgba[d + 3] = a;
|
||||
}
|
||||
if (isAdditive)
|
||||
TextureHelpers.FillA8Additive(rs.SourceData, rgba.AsSpan(), rs.Width, rs.Height);
|
||||
else
|
||||
TextureHelpers.FillA8(rs.SourceData, rgba.AsSpan(), rs.Width, rs.Height);
|
||||
return new DecodedTexture(rgba, rs.Width, rs.Height);
|
||||
}
|
||||
|
||||
|
|
@ -143,15 +115,7 @@ public static class SurfaceDecoder
|
|||
return DecodedTexture.Magenta;
|
||||
|
||||
var rgba = new byte[expected];
|
||||
// Source layout per pixel: B, G, R, A → swap to R, G, B, A
|
||||
for (int i = 0; i < rs.Width * rs.Height; i++)
|
||||
{
|
||||
int s = i * 4;
|
||||
rgba[s + 0] = rs.SourceData[s + 2]; // R <- R
|
||||
rgba[s + 1] = rs.SourceData[s + 1]; // G <- G
|
||||
rgba[s + 2] = rs.SourceData[s + 0]; // B <- B
|
||||
rgba[s + 3] = rs.SourceData[s + 3]; // A <- A
|
||||
}
|
||||
TextureHelpers.FillA8R8G8B8(rs.SourceData, rgba.AsSpan(), rs.Width, rs.Height);
|
||||
return new DecodedTexture(rgba, rs.Width, rs.Height);
|
||||
}
|
||||
|
||||
|
|
@ -168,29 +132,7 @@ public static class SurfaceDecoder
|
|||
return DecodedTexture.Magenta;
|
||||
|
||||
var rgba = new byte[rs.Width * rs.Height * 4];
|
||||
int paletteMax = palette.Colors.Count - 1;
|
||||
for (int i = 0; i < rs.Width * rs.Height; i++)
|
||||
{
|
||||
int idx = rs.SourceData[i];
|
||||
if (idx > paletteMax) idx = 0;
|
||||
var c = palette.Colors[idx];
|
||||
|
||||
int dst = i * 4;
|
||||
if (isClipMap && idx < 8)
|
||||
{
|
||||
rgba[dst + 0] = 0;
|
||||
rgba[dst + 1] = 0;
|
||||
rgba[dst + 2] = 0;
|
||||
rgba[dst + 3] = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
rgba[dst + 0] = c.Red;
|
||||
rgba[dst + 1] = c.Green;
|
||||
rgba[dst + 2] = c.Blue;
|
||||
rgba[dst + 3] = c.Alpha;
|
||||
}
|
||||
}
|
||||
TextureHelpers.FillP8(rs.SourceData, palette, rgba.AsSpan(), rs.Width, rs.Height, isClipMap);
|
||||
return new DecodedTexture(rgba, rs.Width, rs.Height);
|
||||
}
|
||||
|
||||
|
|
@ -207,16 +149,7 @@ public static class SurfaceDecoder
|
|||
return DecodedTexture.Magenta;
|
||||
|
||||
var rgba = new byte[rs.Width * rs.Height * 4];
|
||||
for (int i = 0; i < rs.Width * rs.Height; i++)
|
||||
{
|
||||
int src = i * 3;
|
||||
int dst = i * 4;
|
||||
// On-disk byte order: B, G, R (little-endian 24-bit BGR, same as DX PFID_R8G8B8)
|
||||
rgba[dst + 0] = rs.SourceData[src + 2]; // R
|
||||
rgba[dst + 1] = rs.SourceData[src + 1]; // G
|
||||
rgba[dst + 2] = rs.SourceData[src + 0]; // B
|
||||
rgba[dst + 3] = 0xFF; // A = opaque
|
||||
}
|
||||
TextureHelpers.FillR8G8B8(rs.SourceData, rgba.AsSpan(), rs.Width, rs.Height);
|
||||
return new DecodedTexture(rgba, rs.Width, rs.Height);
|
||||
}
|
||||
|
||||
|
|
@ -245,6 +178,28 @@ public static class SurfaceDecoder
|
|||
return new DecodedTexture(rgba, rs.Width, rs.Height);
|
||||
}
|
||||
|
||||
private static DecodedTexture DecodeR5G6B5(RenderSurface rs)
|
||||
{
|
||||
int expectedBytes = rs.Width * rs.Height * 2;
|
||||
if (rs.SourceData.Length < expectedBytes)
|
||||
return DecodedTexture.Magenta;
|
||||
|
||||
var rgba = new byte[rs.Width * rs.Height * 4];
|
||||
TextureHelpers.FillR5G6B5(rs.SourceData, rgba.AsSpan(), rs.Width, rs.Height);
|
||||
return new DecodedTexture(rgba, rs.Width, rs.Height);
|
||||
}
|
||||
|
||||
private static DecodedTexture DecodeA4R4G4B4(RenderSurface rs)
|
||||
{
|
||||
int expectedBytes = rs.Width * rs.Height * 2;
|
||||
if (rs.SourceData.Length < expectedBytes)
|
||||
return DecodedTexture.Magenta;
|
||||
|
||||
var rgba = new byte[rs.Width * rs.Height * 4];
|
||||
TextureHelpers.FillA4R4G4B4(rs.SourceData, rgba.AsSpan(), rs.Width, rs.Height);
|
||||
return new DecodedTexture(rgba, rs.Width, rs.Height);
|
||||
}
|
||||
|
||||
private static DecodedTexture DecodeBc(RenderSurface rs, CompressionFormat format, bool isClipMap)
|
||||
{
|
||||
var pixels = BcDecoder.DecodeRaw(rs.SourceData, rs.Width, rs.Height, format);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue