namespace AcDream.App.Rendering; /// /// Owns the two independent residency edges used by TerrainAtlas. Acquisition /// and release publish each successful edge immediately, so a partial failure /// retries only work that is still pending. /// internal sealed class BindlessTexturePair( uint firstTexture, uint secondTexture, Func acquire, Action release) { private ulong _firstHandle; private ulong _secondHandle; private bool _firstResident; private bool _secondResident; public bool IsFullyResident => _firstResident && _secondResident; public bool HasAnyResident => _firstResident || _secondResident; public (ulong First, ulong Second) Acquire() { if (!_firstResident) { _firstHandle = acquire(firstTexture); _firstResident = true; } if (!_secondResident) { try { _secondHandle = acquire(secondTexture); _secondResident = true; } catch (Exception acquisitionFailure) { try { Release(); } catch (Exception cleanupFailure) { throw new AggregateException( "Second bindless texture acquisition failed and the resident prefix did not cleanly roll back.", acquisitionFailure, cleanupFailure); } throw; } } return (_firstHandle, _secondHandle); } public void Release() { List? failures = null; if (_firstResident) { try { release(_firstHandle); _firstResident = false; _firstHandle = 0; } catch (Exception failure) { (failures ??= []).Add(failure); } } if (_secondResident) { try { release(_secondHandle); _secondResident = false; _secondHandle = 0; } catch (Exception failure) { (failures ??= []).Add(failure); } } if (failures is not null) throw new AggregateException( "One or more bindless texture handles could not be made non-resident.", failures); } }