using AcDream.App.Rendering.Gpu;
namespace AcDream.App.Rendering;
public sealed partial class TerrainModernRenderer
{
internal sealed class DirectionalShadowReceiverPipelineState(
IDirectionalShadowReceiverSource source,
IGpuPipeline pipeline) : IDisposable
{
internal IDirectionalShadowReceiverSource Source { get; } = source;
internal IGpuPipeline Pipeline { get; } = pipeline;
public void Dispose() => Pipeline.Dispose();
}
///
/// Constructs the complete opt-in receiver state without publishing it.
/// The controller couples this candidate with the pack runtime and world
/// receiver set at one stable-boundary commit.
///
internal DirectionalShadowReceiverPipelineState? PrepareDirectionalShadowReceiver(
IDirectionalShadowReceiverSource? source,
int sampleCount)
{
if (source is null)
return null;
IGpuDevice device = _device
?? throw new InvalidOperationException("Directional receivers require the modern RHI device.");
if (_scope is null || sampleCount != _scope.SampleCount)
throw new InvalidOperationException("Receiver and world-pass sample counts must match.");
return new DirectionalShadowReceiverPipelineState(
source,
device.CreatePipeline(
new GpuPipelineDescription
{
Name = "terrain-atmospheric",
Shaders = source.PipelineShaders.TerrainReceiver,
VertexLayout = TerrainVertexLayout,
Topology = GpuPrimitiveTopology.TriangleList,
Blend = GpuBlendMode.None,
Depth = new GpuDepthState(true, true, GpuCompareOp.Less),
Cull = GpuCullMode.Back,
FrontFace = GpuFrontFace.CounterClockwise,
AlphaToCoverage = false,
ColorWrite = true,
UsesRenderPackShaderAbi = true,
SampleCount = sampleCount,
}));
}
/// Assignment-only publication; returned state retires after the coupled swap.
internal DirectionalShadowReceiverPipelineState? SwapDirectionalShadowReceiver(
DirectionalShadowReceiverPipelineState? candidate)
{
DirectionalShadowReceiverPipelineState? prior = _directionalShadowReceiver;
_directionalShadowReceiver = candidate;
return prior;
}
}