fix(rendering): restore retail terrain texture tiling

Carry TerrainTex.TexTiling through the terrain atlas and upload a layer-indexed table to the modern bindless shader so base, overlay, and road textures repeat at retail scale. Keep alpha masks cell-scaled and preserve retail's verified source-level-zero high-detail selection.

Add the named-retail pseudocode, WorldBuilder/ACE/ACME cross-reference, adapter conformance tests, and inventory documentation so a future renderer migration keeps the contract.

Co-Authored-By: Codex <noreply@openai.com>
This commit is contained in:
Erik 2026-07-13 21:04:22 +02:00
parent 84b7d2d7cd
commit bb5acab9e6
7 changed files with 297 additions and 7 deletions

View file

@ -34,6 +34,12 @@ public sealed unsafe class TerrainAtlas : IDisposable
public uint GlTexture { get; } // terrain atlas, kept as GlTexture for back-compat with TerrainRenderer
public IReadOnlyDictionary<uint, uint> TerrainTypeToLayer { get; }
public int LayerCount { get; }
/// <summary>
/// UV repeat count for each terrain-array layer. Retail forwards
/// <c>TerrainTex::tex_tiling</c> to both base and merged terrain textures
/// (`TexMerge::CopyAndTile` 0x00503580, `TexMerge::Merge` 0x005038C0).
/// </summary>
public IReadOnlyList<float> TilingByLayer { get; }
// --- Alpha atlas (new in Phase 3c.2) ---
public uint GlAlphaTexture { get; }
@ -86,6 +92,7 @@ public sealed unsafe class TerrainAtlas : IDisposable
GL gl,
Wb.BindlessSupport? bindless,
uint glTexture, IReadOnlyDictionary<uint, uint> map, int layerCount,
IReadOnlyList<float> tilingByLayer,
uint glAlphaTexture, int alphaLayerCount,
IReadOnlyList<byte> cornerLayers, IReadOnlyList<byte> sideLayers, IReadOnlyList<byte> roadLayers,
IReadOnlyList<uint> cornerTCodes, IReadOnlyList<uint> sideTCodes, IReadOnlyList<uint> roadRCodes)
@ -95,6 +102,7 @@ public sealed unsafe class TerrainAtlas : IDisposable
GlTexture = glTexture;
TerrainTypeToLayer = map;
LayerCount = layerCount;
TilingByLayer = tilingByLayer;
GlAlphaTexture = glAlphaTexture;
AlphaLayerCount = alphaLayerCount;
CornerAlphaLayers = cornerLayers;
@ -125,6 +133,7 @@ public sealed unsafe class TerrainAtlas : IDisposable
// ---- Terrain atlas (unchanged Phase 2b logic) ----
var decodedByType = new Dictionary<uint, DecodedTexture>();
var tilingByType = new Dictionary<uint, uint>();
int maxW = 1, maxH = 1;
foreach (var tmtd in terrainDesc)
{
@ -132,6 +141,12 @@ public sealed unsafe class TerrainAtlas : IDisposable
if (decodedByType.ContainsKey(typeKey))
continue;
// Retail terrain composition repeats this surface exactly
// TerrainTex::tex_tiling times in each axis before applying the
// cell-scale alpha mask. Preserve the dat field alongside the
// decoded surface while layer assignment is still type-keyed.
tilingByType[typeKey] = tmtd.TerrainTex.TexTiling;
var surfaceTextureId = (uint)tmtd.TerrainTex.TextureId;
var st = dats.Get<SurfaceTexture>(surfaceTextureId);
if (st is null || st.Textures.Count == 0)
@ -141,6 +156,10 @@ public sealed unsafe class TerrainAtlas : IDisposable
continue;
}
// Retail ImgTex::GetSurfaceDID (0x0053F0E0) returns source level
// zero unless Render::ShouldDropHighDetail explicitly selects
// level one. acdream currently has no low-detail quality mode, so
// index zero is the retail high-detail source.
var rs = dats.Get<RenderSurface>((uint)st.Textures[0]);
if (rs is null)
{
@ -183,6 +202,12 @@ public sealed unsafe class TerrainAtlas : IDisposable
layerIdx++;
}
var tilingByLayer = TerrainTextureTilingTable.Build(
map.Select(entry =>
(entry.Value, tilingByType.TryGetValue(entry.Key, out uint repeatCount)
? repeatCount
: 1u)));
// A.5 T19: generate mipmaps + trilinear + 16x anisotropic for distant-LB quality.
gl.GenerateMipmap(TextureTarget.Texture2DArray);
gl.TexParameter(TextureTarget.Texture2DArray, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.LinearMipmapLinear);
@ -203,7 +228,7 @@ public sealed unsafe class TerrainAtlas : IDisposable
return new TerrainAtlas(
gl,
bindless,
tex, map, layerCount,
tex, map, layerCount, tilingByLayer,
alphaBuild.gl, alphaBuild.layerCount,
alphaBuild.corner, alphaBuild.side, alphaBuild.road,
alphaBuild.cornerTCodes, alphaBuild.sideTCodes, alphaBuild.roadRCodes);
@ -410,6 +435,7 @@ public sealed unsafe class TerrainAtlas : IDisposable
gl,
bindless,
tex, new Dictionary<uint, uint> { [0] = 0u }, 1,
TerrainTextureTilingTable.Build(Array.Empty<(uint Layer, uint RepeatCount)>()),
alphaTex, 1,
Array.Empty<byte>(), Array.Empty<byte>(), Array.Empty<byte>(),
Array.Empty<uint>(), Array.Empty<uint>(), Array.Empty<uint>());