refactor(lifetime): own render resource prefixes
Give terrain, sky, retained UI, portal preparation, and the update/render frame pair explicit single owners. Make shader, texture, text, bindless, and GL construction prefixes checked and retryable so partial failure cannot lose or replay resource ownership. Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
parent
fec0d94148
commit
c87b15303d
41 changed files with 3768 additions and 355 deletions
|
|
@ -61,12 +61,10 @@ public sealed unsafe class TerrainAtlas : IDisposable
|
|||
public IReadOnlyList<uint> 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;
|
||||
private readonly BindlessTexturePair? _bindlessHandles;
|
||||
private readonly BindlessTextureMutationGuard? _bindlessMutation;
|
||||
private readonly RestoredTextureBindingMutation _anisotropyBindingMutation = new();
|
||||
private ResourceShutdownTransaction? _shutdown;
|
||||
|
||||
/// <summary>
|
||||
/// Get 64-bit bindless handles for the terrain + alpha texture arrays.
|
||||
|
|
@ -80,13 +78,8 @@ public sealed unsafe class TerrainAtlas : IDisposable
|
|||
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);
|
||||
(ulong terrain, ulong alpha) = _bindlessHandles!.Acquire();
|
||||
return (terrain, alpha);
|
||||
}
|
||||
|
||||
private TerrainAtlas(
|
||||
|
|
@ -112,6 +105,15 @@ public sealed unsafe class TerrainAtlas : IDisposable
|
|||
CornerAlphaTCodes = cornerTCodes;
|
||||
SideAlphaTCodes = sideTCodes;
|
||||
RoadAlphaRCodes = roadRCodes;
|
||||
if (bindless is not null)
|
||||
{
|
||||
_bindlessHandles = new BindlessTexturePair(
|
||||
glTexture,
|
||||
glAlphaTexture,
|
||||
bindless.GetResidentHandle,
|
||||
bindless.MakeNonResident);
|
||||
_bindlessMutation = new BindlessTextureMutationGuard(_bindlessHandles);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -120,6 +122,37 @@ public sealed unsafe class TerrainAtlas : IDisposable
|
|||
/// to RGBA8, and uploading as layers in a single GL_TEXTURE_2D_ARRAY.
|
||||
/// </summary>
|
||||
public static TerrainAtlas Build(GL gl, IDatReaderWriter dats, Wb.BindlessSupport? bindless = null)
|
||||
{
|
||||
var textures = new GlTextureConstructionTransaction(new GlTextureNameApi(gl));
|
||||
try
|
||||
{
|
||||
TerrainAtlas atlas = BuildCore(gl, dats, bindless, textures);
|
||||
textures.Commit();
|
||||
return atlas;
|
||||
}
|
||||
catch (Exception constructionFailure)
|
||||
{
|
||||
try
|
||||
{
|
||||
textures.Rollback();
|
||||
}
|
||||
catch (Exception cleanupFailure)
|
||||
{
|
||||
throw new GlResourceConstructionException(
|
||||
"TerrainAtlas construction failed and its allocated OpenGL texture names did not cleanly roll back.",
|
||||
textures,
|
||||
[constructionFailure, cleanupFailure]);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private static TerrainAtlas BuildCore(
|
||||
GL gl,
|
||||
IDatReaderWriter dats,
|
||||
Wb.BindlessSupport? bindless,
|
||||
GlTextureConstructionTransaction textures)
|
||||
{
|
||||
var region = dats.Get<Region>(0x13000000u)
|
||||
?? throw new InvalidOperationException("Region dat id 0x13000000 missing");
|
||||
|
|
@ -129,7 +162,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, bindless);
|
||||
return BuildFallback(gl, bindless, textures);
|
||||
}
|
||||
|
||||
// ---- Terrain atlas (unchanged Phase 2b logic) ----
|
||||
|
|
@ -179,29 +212,45 @@ public sealed unsafe class TerrainAtlas : IDisposable
|
|||
}
|
||||
|
||||
int layerCount = decodedByType.Count;
|
||||
uint tex = gl.GenTexture();
|
||||
gl.BindTexture(TextureTarget.Texture2DArray, tex);
|
||||
gl.TexImage3D(
|
||||
TextureTarget.Texture2DArray, 0, InternalFormat.Rgba8,
|
||||
(uint)maxW, (uint)maxH, (uint)layerCount,
|
||||
0, GLPixelFormat.Rgba, PixelType.UnsignedByte, null);
|
||||
|
||||
var map = new Dictionary<uint, uint>();
|
||||
int layerIdx = 0;
|
||||
foreach (var kvp in decodedByType)
|
||||
uint tex = TrackedTextureConstruction.Create(
|
||||
textures,
|
||||
gl,
|
||||
"upload terrain atlas texture array",
|
||||
texture =>
|
||||
{
|
||||
byte[] buffer = ResizeRgba8Nearest(kvp.Value, maxW, maxH);
|
||||
fixed (byte* p = buffer)
|
||||
gl.BindTexture(TextureTarget.Texture2DArray, texture);
|
||||
gl.TexImage3D(
|
||||
TextureTarget.Texture2DArray, 0, InternalFormat.Rgba8,
|
||||
(uint)maxW, (uint)maxH, (uint)layerCount,
|
||||
0, GLPixelFormat.Rgba, PixelType.UnsignedByte, null);
|
||||
|
||||
int layerIdx = 0;
|
||||
foreach (var kvp in decodedByType)
|
||||
{
|
||||
gl.TexSubImage3D(
|
||||
TextureTarget.Texture2DArray, 0,
|
||||
0, 0, layerIdx,
|
||||
(uint)maxW, (uint)maxH, 1,
|
||||
GLPixelFormat.Rgba, PixelType.UnsignedByte, p);
|
||||
byte[] buffer = ResizeRgba8Nearest(kvp.Value, maxW, maxH);
|
||||
fixed (byte* p = buffer)
|
||||
{
|
||||
gl.TexSubImage3D(
|
||||
TextureTarget.Texture2DArray, 0,
|
||||
0, 0, layerIdx,
|
||||
(uint)maxW, (uint)maxH, 1,
|
||||
GLPixelFormat.Rgba, PixelType.UnsignedByte, p);
|
||||
}
|
||||
map[kvp.Key] = (uint)layerIdx;
|
||||
layerIdx++;
|
||||
}
|
||||
map[kvp.Key] = (uint)layerIdx;
|
||||
layerIdx++;
|
||||
}
|
||||
|
||||
// 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);
|
||||
gl.TexParameter(TextureTarget.Texture2DArray, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
|
||||
gl.TexParameter(TextureTarget.Texture2DArray, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Repeat);
|
||||
gl.TexParameter(TextureTarget.Texture2DArray, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat);
|
||||
// GL_TEXTURE_MAX_ANISOTROPY = 0x84FE (GL_EXT_texture_filter_anisotropic / ARB_texture_filter_anisotropic).
|
||||
gl.TexParameter(TextureTarget.Texture2DArray, (TextureParameterName)0x84FE, 16.0f);
|
||||
gl.BindTexture(TextureTarget.Texture2DArray, 0);
|
||||
});
|
||||
|
||||
var tilingByLayer = TerrainTextureTilingTable.Build(
|
||||
map.Select(entry =>
|
||||
|
|
@ -209,22 +258,12 @@ public sealed unsafe class TerrainAtlas : IDisposable
|
|||
? 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);
|
||||
gl.TexParameter(TextureTarget.Texture2DArray, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
|
||||
gl.TexParameter(TextureTarget.Texture2DArray, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Repeat);
|
||||
gl.TexParameter(TextureTarget.Texture2DArray, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat);
|
||||
// GL_TEXTURE_MAX_ANISOTROPY = 0x84FE (GL_EXT_texture_filter_anisotropic / ARB_texture_filter_anisotropic).
|
||||
gl.TexParameter(TextureTarget.Texture2DArray, (TextureParameterName)0x84FE, 16.0f);
|
||||
gl.BindTexture(TextureTarget.Texture2DArray, 0);
|
||||
|
||||
Console.WriteLine($"TerrainAtlas: {layerCount} terrain layers at {maxW}x{maxH} (mipmaps+aniso16x)");
|
||||
|
||||
// ---- Alpha atlas (new in Phase 3c.2) ----
|
||||
// texMerge is guaranteed non-null here: the early return above exited
|
||||
// if texMerge?.TerrainDesc was null.
|
||||
var alphaBuild = BuildAlphaAtlas(gl, dats, texMerge!);
|
||||
var alphaBuild = BuildAlphaAtlas(gl, dats, texMerge!, textures);
|
||||
|
||||
return new TerrainAtlas(
|
||||
gl,
|
||||
|
|
@ -252,7 +291,10 @@ public sealed unsafe class TerrainAtlas : IDisposable
|
|||
IReadOnlyList<uint> cornerTCodes, IReadOnlyList<uint> sideTCodes, IReadOnlyList<uint> roadRCodes);
|
||||
|
||||
private static AlphaAtlasBuildResult BuildAlphaAtlas(
|
||||
GL gl, IDatReaderWriter dats, DatReaderWriter.Types.TexMerge texMerge)
|
||||
GL gl,
|
||||
IDatReaderWriter dats,
|
||||
DatReaderWriter.Types.TexMerge texMerge,
|
||||
GlTextureConstructionTransaction textures)
|
||||
{
|
||||
var decoded = new List<DecodedTexture>();
|
||||
var cornerLayers = new List<byte>();
|
||||
|
|
@ -305,15 +347,21 @@ public sealed unsafe class TerrainAtlas : IDisposable
|
|||
if (decoded.Count == 0)
|
||||
{
|
||||
Console.WriteLine("WARN: no alpha maps loaded; alpha atlas will be a 1x1 white fallback");
|
||||
uint fallbackAlpha = gl.GenTexture();
|
||||
gl.BindTexture(TextureTarget.Texture2DArray, fallbackAlpha);
|
||||
gl.TexImage3D(TextureTarget.Texture2DArray, 0, InternalFormat.Rgba8, 1, 1, 1, 0,
|
||||
GLPixelFormat.Rgba, PixelType.UnsignedByte, null);
|
||||
var white = new byte[] { 0xFF, 0xFF, 0xFF, 0xFF };
|
||||
fixed (byte* p = white)
|
||||
gl.TexSubImage3D(TextureTarget.Texture2DArray, 0, 0, 0, 0, 1, 1, 1,
|
||||
GLPixelFormat.Rgba, PixelType.UnsignedByte, p);
|
||||
gl.BindTexture(TextureTarget.Texture2DArray, 0);
|
||||
uint fallbackAlpha = TrackedTextureConstruction.Create(
|
||||
textures,
|
||||
gl,
|
||||
"upload fallback terrain alpha texture array",
|
||||
texture =>
|
||||
{
|
||||
gl.BindTexture(TextureTarget.Texture2DArray, texture);
|
||||
gl.TexImage3D(TextureTarget.Texture2DArray, 0, InternalFormat.Rgba8, 1, 1, 1, 0,
|
||||
GLPixelFormat.Rgba, PixelType.UnsignedByte, null);
|
||||
var white = new byte[] { 0xFF, 0xFF, 0xFF, 0xFF };
|
||||
fixed (byte* p = white)
|
||||
gl.TexSubImage3D(TextureTarget.Texture2DArray, 0, 0, 0, 0, 1, 1, 1,
|
||||
GLPixelFormat.Rgba, PixelType.UnsignedByte, p);
|
||||
gl.BindTexture(TextureTarget.Texture2DArray, 0);
|
||||
});
|
||||
return new AlphaAtlasBuildResult(
|
||||
fallbackAlpha, 1,
|
||||
cornerLayers, sideLayers, roadLayers,
|
||||
|
|
@ -329,31 +377,37 @@ public sealed unsafe class TerrainAtlas : IDisposable
|
|||
if (d.Height > aMaxH) aMaxH = d.Height;
|
||||
}
|
||||
|
||||
uint glAlpha = gl.GenTexture();
|
||||
gl.BindTexture(TextureTarget.Texture2DArray, glAlpha);
|
||||
gl.TexImage3D(
|
||||
TextureTarget.Texture2DArray, 0, InternalFormat.Rgba8,
|
||||
(uint)aMaxW, (uint)aMaxH, (uint)decoded.Count,
|
||||
0, GLPixelFormat.Rgba, PixelType.UnsignedByte, null);
|
||||
|
||||
for (int i = 0; i < decoded.Count; i++)
|
||||
uint glAlpha = TrackedTextureConstruction.Create(
|
||||
textures,
|
||||
gl,
|
||||
"upload terrain alpha texture array",
|
||||
texture =>
|
||||
{
|
||||
var buffer = ResizeRgba8Nearest(decoded[i], aMaxW, aMaxH);
|
||||
fixed (byte* p = buffer)
|
||||
{
|
||||
gl.TexSubImage3D(
|
||||
TextureTarget.Texture2DArray, 0,
|
||||
0, 0, i,
|
||||
(uint)aMaxW, (uint)aMaxH, 1,
|
||||
GLPixelFormat.Rgba, PixelType.UnsignedByte, p);
|
||||
}
|
||||
}
|
||||
gl.BindTexture(TextureTarget.Texture2DArray, texture);
|
||||
gl.TexImage3D(
|
||||
TextureTarget.Texture2DArray, 0, InternalFormat.Rgba8,
|
||||
(uint)aMaxW, (uint)aMaxH, (uint)decoded.Count,
|
||||
0, GLPixelFormat.Rgba, PixelType.UnsignedByte, null);
|
||||
|
||||
gl.TexParameter(TextureTarget.Texture2DArray, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
|
||||
gl.TexParameter(TextureTarget.Texture2DArray, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
|
||||
gl.TexParameter(TextureTarget.Texture2DArray, TextureParameterName.TextureWrapS, (int)TextureWrapMode.ClampToEdge);
|
||||
gl.TexParameter(TextureTarget.Texture2DArray, TextureParameterName.TextureWrapT, (int)TextureWrapMode.ClampToEdge);
|
||||
gl.BindTexture(TextureTarget.Texture2DArray, 0);
|
||||
for (int i = 0; i < decoded.Count; i++)
|
||||
{
|
||||
var buffer = ResizeRgba8Nearest(decoded[i], aMaxW, aMaxH);
|
||||
fixed (byte* p = buffer)
|
||||
{
|
||||
gl.TexSubImage3D(
|
||||
TextureTarget.Texture2DArray, 0,
|
||||
0, 0, i,
|
||||
(uint)aMaxW, (uint)aMaxH, 1,
|
||||
GLPixelFormat.Rgba, PixelType.UnsignedByte, p);
|
||||
}
|
||||
}
|
||||
|
||||
gl.TexParameter(TextureTarget.Texture2DArray, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
|
||||
gl.TexParameter(TextureTarget.Texture2DArray, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
|
||||
gl.TexParameter(TextureTarget.Texture2DArray, TextureParameterName.TextureWrapS, (int)TextureWrapMode.ClampToEdge);
|
||||
gl.TexParameter(TextureTarget.Texture2DArray, TextureParameterName.TextureWrapT, (int)TextureWrapMode.ClampToEdge);
|
||||
gl.BindTexture(TextureTarget.Texture2DArray, 0);
|
||||
});
|
||||
|
||||
Console.WriteLine(
|
||||
$"AlphaAtlas: {decoded.Count} layers at {aMaxW}x{aMaxH} "
|
||||
|
|
@ -412,25 +466,40 @@ public sealed unsafe class TerrainAtlas : IDisposable
|
|||
return dst;
|
||||
}
|
||||
|
||||
private static TerrainAtlas BuildFallback(GL gl, Wb.BindlessSupport? bindless = null)
|
||||
private static TerrainAtlas BuildFallback(
|
||||
GL gl,
|
||||
Wb.BindlessSupport? bindless,
|
||||
GlTextureConstructionTransaction textures)
|
||||
{
|
||||
uint tex = gl.GenTexture();
|
||||
gl.BindTexture(TextureTarget.Texture2DArray, tex);
|
||||
var white = new byte[] { 0xFF, 0xFF, 0xFF, 0xFF };
|
||||
gl.TexImage3D(TextureTarget.Texture2DArray, 0, InternalFormat.Rgba8, 1, 1, 1, 0, GLPixelFormat.Rgba, PixelType.UnsignedByte, null);
|
||||
fixed (byte* p = white)
|
||||
gl.TexSubImage3D(TextureTarget.Texture2DArray, 0, 0, 0, 0, 1, 1, 1, GLPixelFormat.Rgba, PixelType.UnsignedByte, p);
|
||||
gl.TexParameter(TextureTarget.Texture2DArray, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
|
||||
gl.TexParameter(TextureTarget.Texture2DArray, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
|
||||
gl.BindTexture(TextureTarget.Texture2DArray, 0);
|
||||
uint tex = TrackedTextureConstruction.Create(
|
||||
textures,
|
||||
gl,
|
||||
"upload fallback terrain texture array",
|
||||
texture =>
|
||||
{
|
||||
gl.BindTexture(TextureTarget.Texture2DArray, texture);
|
||||
gl.TexImage3D(TextureTarget.Texture2DArray, 0, InternalFormat.Rgba8, 1, 1, 1, 0, GLPixelFormat.Rgba, PixelType.UnsignedByte, null);
|
||||
fixed (byte* p = white)
|
||||
gl.TexSubImage3D(TextureTarget.Texture2DArray, 0, 0, 0, 0, 1, 1, 1, GLPixelFormat.Rgba, PixelType.UnsignedByte, p);
|
||||
gl.TexParameter(TextureTarget.Texture2DArray, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
|
||||
gl.TexParameter(TextureTarget.Texture2DArray, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
|
||||
gl.BindTexture(TextureTarget.Texture2DArray, 0);
|
||||
});
|
||||
|
||||
// Fallback alpha atlas: 1x1 white, no layers tracked
|
||||
uint alphaTex = gl.GenTexture();
|
||||
gl.BindTexture(TextureTarget.Texture2DArray, alphaTex);
|
||||
gl.TexImage3D(TextureTarget.Texture2DArray, 0, InternalFormat.Rgba8, 1, 1, 1, 0, GLPixelFormat.Rgba, PixelType.UnsignedByte, null);
|
||||
fixed (byte* p = white)
|
||||
gl.TexSubImage3D(TextureTarget.Texture2DArray, 0, 0, 0, 0, 1, 1, 1, GLPixelFormat.Rgba, PixelType.UnsignedByte, p);
|
||||
gl.BindTexture(TextureTarget.Texture2DArray, 0);
|
||||
uint alphaTex = TrackedTextureConstruction.Create(
|
||||
textures,
|
||||
gl,
|
||||
"upload fallback alpha texture array",
|
||||
texture =>
|
||||
{
|
||||
gl.BindTexture(TextureTarget.Texture2DArray, texture);
|
||||
gl.TexImage3D(TextureTarget.Texture2DArray, 0, InternalFormat.Rgba8, 1, 1, 1, 0, GLPixelFormat.Rgba, PixelType.UnsignedByte, null);
|
||||
fixed (byte* p = white)
|
||||
gl.TexSubImage3D(TextureTarget.Texture2DArray, 0, 0, 0, 0, 1, 1, 1, GLPixelFormat.Rgba, PixelType.UnsignedByte, p);
|
||||
gl.BindTexture(TextureTarget.Texture2DArray, 0);
|
||||
});
|
||||
|
||||
return new TerrainAtlas(
|
||||
gl,
|
||||
|
|
@ -453,46 +522,65 @@ public sealed unsafe class TerrainAtlas : IDisposable
|
|||
/// </summary>
|
||||
public void SetAnisotropic(int level)
|
||||
{
|
||||
// If bindless handles are live we must make them non-resident before
|
||||
// mutating texture state, then re-resident after.
|
||||
bool wasResident = _handlesGenerated && _bindless is not null;
|
||||
if (wasResident)
|
||||
void Mutate()
|
||||
{
|
||||
_bindless!.MakeNonResident(_terrainHandle);
|
||||
// Alpha texture is not affected by anisotropic but we must keep
|
||||
// residency symmetric — re-generate both handles after.
|
||||
_bindless.MakeNonResident(_alphaHandle);
|
||||
_handlesGenerated = false;
|
||||
_anisotropyBindingMutation.Execute(
|
||||
() => unchecked((uint)GlResourceCommand.Execute(
|
||||
_gl,
|
||||
"read terrain-array binding before anisotropy mutation",
|
||||
() => _gl.GetInteger(GetPName.TextureBinding2DArray))),
|
||||
binding => GlResourceCommand.Execute(
|
||||
_gl,
|
||||
"set terrain-array binding for anisotropy mutation",
|
||||
() => _gl.BindTexture(TextureTarget.Texture2DArray, binding)),
|
||||
GlTexture,
|
||||
() => GlResourceCommand.Execute(
|
||||
_gl,
|
||||
"set terrain atlas anisotropy",
|
||||
() =>
|
||||
{
|
||||
// GL_TEXTURE_MAX_ANISOTROPY = 0x84FE
|
||||
_gl.TexParameter(
|
||||
TextureTarget.Texture2DArray,
|
||||
(TextureParameterName)0x84FE,
|
||||
(float)level);
|
||||
}));
|
||||
}
|
||||
|
||||
_gl.BindTexture(TextureTarget.Texture2DArray, GlTexture);
|
||||
// GL_TEXTURE_MAX_ANISOTROPY = 0x84FE
|
||||
_gl.TexParameter(TextureTarget.Texture2DArray, (TextureParameterName)0x84FE, (float)level);
|
||||
_gl.BindTexture(TextureTarget.Texture2DArray, 0);
|
||||
|
||||
// Re-generate bindless handles if they were live before.
|
||||
if (wasResident)
|
||||
{
|
||||
// GetBindlessHandles regenerates and makes resident.
|
||||
_ = GetBindlessHandles();
|
||||
}
|
||||
if (_bindlessMutation is not null)
|
||||
_bindlessMutation.Execute(Mutate);
|
||||
else
|
||||
Mutate();
|
||||
|
||||
Console.WriteLine($"TerrainAtlas: anisotropic updated to {level}x");
|
||||
}
|
||||
|
||||
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);
|
||||
_shutdown ??= new ResourceShutdownTransaction(
|
||||
new ResourceShutdownStage(
|
||||
"terrain atlas bindless residency",
|
||||
[
|
||||
new ResourceShutdownOperation(
|
||||
"release terrain and alpha handles",
|
||||
() => _bindlessHandles?.Release()),
|
||||
]),
|
||||
new ResourceShutdownStage(
|
||||
"terrain atlas textures",
|
||||
[
|
||||
new ResourceShutdownOperation(
|
||||
"delete terrain texture",
|
||||
() => GlResourceCommand.DeleteTexture(
|
||||
_gl,
|
||||
GlTexture,
|
||||
$"delete terrain atlas texture {GlTexture}")),
|
||||
new ResourceShutdownOperation(
|
||||
"delete alpha texture",
|
||||
() => GlResourceCommand.DeleteTexture(
|
||||
_gl,
|
||||
GlAlphaTexture,
|
||||
$"delete terrain alpha texture {GlAlphaTexture}")),
|
||||
]));
|
||||
_shutdown.CompleteOrThrow();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue