diff --git a/src/AcDream.App/Rendering/TerrainAtlas.cs b/src/AcDream.App/Rendering/TerrainAtlas.cs
index faa3a6e..d49610e 100644
--- a/src/AcDream.App/Rendering/TerrainAtlas.cs
+++ b/src/AcDream.App/Rendering/TerrainAtlas.cs
@@ -53,14 +53,45 @@ public sealed unsafe class TerrainAtlas : IDisposable
/// RCode for each RoadMap, parallel to .
public IReadOnlyList RoadAlphaRCodes { get; }
+ private readonly Wb.BindlessSupport? _bindless;
+
+ // Cached bindless handles. Generated lazily on first GetBindlessHandles() call;
+ // reused for the lifetime of the atlas.
+ private ulong _terrainHandle;
+ private ulong _alphaHandle;
+ private bool _handlesGenerated;
+
+ ///
+ /// Get 64-bit bindless handles for the terrain + alpha texture arrays.
+ /// Throws if the atlas was constructed
+ /// without a instance. Handles are generated
+ /// lazily on first call and cached for the atlas's lifetime; both textures
+ /// are made resident.
+ ///
+ public (ulong terrain, ulong alpha) GetBindlessHandles()
+ {
+ if (_bindless is null)
+ throw new InvalidOperationException(
+ "TerrainAtlas was constructed without BindlessSupport; cannot return bindless handles.");
+ if (!_handlesGenerated)
+ {
+ _terrainHandle = _bindless.GetResidentHandle(GlTexture);
+ _alphaHandle = _bindless.GetResidentHandle(GlAlphaTexture);
+ _handlesGenerated = true;
+ }
+ return (_terrainHandle, _alphaHandle);
+ }
+
private TerrainAtlas(
GL gl,
+ Wb.BindlessSupport? bindless,
uint glTexture, IReadOnlyDictionary map, int layerCount,
uint glAlphaTexture, int alphaLayerCount,
IReadOnlyList cornerLayers, IReadOnlyList sideLayers, IReadOnlyList roadLayers,
IReadOnlyList cornerTCodes, IReadOnlyList sideTCodes, IReadOnlyList roadRCodes)
{
_gl = gl;
+ _bindless = bindless;
GlTexture = glTexture;
TerrainTypeToLayer = map;
LayerCount = layerCount;
@@ -79,7 +110,7 @@ public sealed unsafe class TerrainAtlas : IDisposable
/// for the mapping from TerrainTextureType to SurfaceTexture id, decoding each
/// to RGBA8, and uploading as layers in a single GL_TEXTURE_2D_ARRAY.
///
- public static TerrainAtlas Build(GL gl, DatCollection dats)
+ public static TerrainAtlas Build(GL gl, DatCollection dats, Wb.BindlessSupport? bindless = null)
{
var region = dats.Get(0x13000000u)
?? throw new InvalidOperationException("Region dat id 0x13000000 missing");
@@ -89,7 +120,7 @@ public sealed unsafe class TerrainAtlas : IDisposable
if (terrainDesc is null || terrainDesc.Count == 0)
{
Console.WriteLine("WARN: TerrainDesc missing, using single white fallback layer");
- return BuildFallback(gl);
+ return BuildFallback(gl, bindless);
}
// ---- Terrain atlas (unchanged Phase 2b logic) ----
@@ -167,6 +198,7 @@ public sealed unsafe class TerrainAtlas : IDisposable
return new TerrainAtlas(
gl,
+ bindless,
tex, map, layerCount,
alphaBuild.gl, alphaBuild.layerCount,
alphaBuild.corner, alphaBuild.side, alphaBuild.road,
@@ -350,7 +382,7 @@ public sealed unsafe class TerrainAtlas : IDisposable
return dst;
}
- private static TerrainAtlas BuildFallback(GL gl)
+ private static TerrainAtlas BuildFallback(GL gl, Wb.BindlessSupport? bindless = null)
{
uint tex = gl.GenTexture();
gl.BindTexture(TextureTarget.Texture2DArray, tex);
@@ -372,6 +404,7 @@ public sealed unsafe class TerrainAtlas : IDisposable
return new TerrainAtlas(
gl,
+ bindless,
tex, new Dictionary { [0] = 0u }, 1,
alphaTex, 1,
Array.Empty(), Array.Empty(), Array.Empty(),
@@ -380,6 +413,16 @@ public sealed unsafe class TerrainAtlas : IDisposable
public void Dispose()
{
+ // Phase 1: release bindless residency BEFORE deleting textures.
+ // ARB_bindless_texture requires this ordering; interleaving is UB.
+ if (_handlesGenerated && _bindless is not null)
+ {
+ _bindless.MakeNonResident(_terrainHandle);
+ _bindless.MakeNonResident(_alphaHandle);
+ _handlesGenerated = false;
+ }
+
+ // Phase 2: delete the underlying GL textures.
_gl.DeleteTexture(GlTexture);
_gl.DeleteTexture(GlAlphaTexture);
}