using AcDream.App.Rendering.Wb;
namespace AcDream.App.Rendering.Packs;
///
/// One complete, unpublished receiver-pipeline product. Candidate resources
/// stay owned here until the render-pack controller commits them at a stable
/// frame boundary.
///
internal interface IRenderPackReceiverPipelineCandidate : IDisposable
{
}
internal interface IRenderPackReceiverPipelineCoordinator
{
IRenderPackReceiverPipelineCandidate Prepare(
IDirectionalShadowReceiverSource? source,
int sampleCount);
void Publish(IRenderPackReceiverPipelineCandidate candidate);
void Clear();
}
///
/// Couples terrain and world-mesh receiver pipelines into the same activation
/// transaction as their producing render-pack runtime. Preparation may compile
/// pipelines, publication only swaps already-complete state objects, and old
/// pipelines retire after both renderer owners point at the new generation.
///
internal sealed class RenderPackReceiverPipelineCoordinator(
TerrainModernRenderer terrain,
WbDrawDispatcher worldMeshes) : IRenderPackReceiverPipelineCoordinator
{
private readonly TerrainModernRenderer _terrain = terrain
?? throw new ArgumentNullException(nameof(terrain));
private readonly WbDrawDispatcher _worldMeshes = worldMeshes
?? throw new ArgumentNullException(nameof(worldMeshes));
public IRenderPackReceiverPipelineCandidate Prepare(
IDirectionalShadowReceiverSource? source,
int sampleCount)
{
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(sampleCount);
TerrainModernRenderer.DirectionalShadowReceiverPipelineState? terrainState =
_terrain.PrepareDirectionalShadowReceiver(source, sampleCount);
try
{
WbDrawDispatcher.DirectionalShadowReceiverPipelineState? worldState =
_worldMeshes.PrepareDirectionalShadowReceiver(source, sampleCount);
return new Candidate(this, terrainState, worldState);
}
catch
{
terrainState?.Dispose();
throw;
}
}
public void Publish(IRenderPackReceiverPipelineCandidate candidate)
{
ArgumentNullException.ThrowIfNull(candidate);
if (candidate is not Candidate prepared || !ReferenceEquals(prepared.Owner, this))
throw new ArgumentException("Receiver candidate belongs to another coordinator.", nameof(candidate));
(TerrainModernRenderer.DirectionalShadowReceiverPipelineState? terrainState,
WbDrawDispatcher.DirectionalShadowReceiverPipelineState? worldState) = prepared.Take();
TerrainModernRenderer.DirectionalShadowReceiverPipelineState? oldTerrain =
_terrain.SwapDirectionalShadowReceiver(terrainState);
WbDrawDispatcher.DirectionalShadowReceiverPipelineState? oldWorld =
_worldMeshes.SwapDirectionalShadowReceiver(worldState);
// Vulkan pipeline disposal is flight-fence retirement. Do this only
// after both owners publish the complete new generation.
oldTerrain?.Dispose();
oldWorld?.Dispose();
}
public void Clear()
{
TerrainModernRenderer.DirectionalShadowReceiverPipelineState? oldTerrain =
_terrain.SwapDirectionalShadowReceiver(null);
WbDrawDispatcher.DirectionalShadowReceiverPipelineState? oldWorld =
_worldMeshes.SwapDirectionalShadowReceiver(null);
oldTerrain?.Dispose();
oldWorld?.Dispose();
}
private sealed class Candidate(
RenderPackReceiverPipelineCoordinator owner,
TerrainModernRenderer.DirectionalShadowReceiverPipelineState? terrain,
WbDrawDispatcher.DirectionalShadowReceiverPipelineState? world) :
IRenderPackReceiverPipelineCandidate
{
private TerrainModernRenderer.DirectionalShadowReceiverPipelineState? _terrain = terrain;
private WbDrawDispatcher.DirectionalShadowReceiverPipelineState? _world = world;
private bool _taken;
internal RenderPackReceiverPipelineCoordinator Owner { get; } = owner;
internal (TerrainModernRenderer.DirectionalShadowReceiverPipelineState?,
WbDrawDispatcher.DirectionalShadowReceiverPipelineState?) Take()
{
ObjectDisposedException.ThrowIf(_taken, this);
_taken = true;
TerrainModernRenderer.DirectionalShadowReceiverPipelineState? terrainState = _terrain;
WbDrawDispatcher.DirectionalShadowReceiverPipelineState? worldState = _world;
_terrain = null;
_world = null;
return (terrainState, worldState);
}
public void Dispose()
{
if (_taken)
return;
_taken = true;
_terrain?.Dispose();
_world?.Dispose();
_terrain = null;
_world = null;
}
}
}