acdream/src/AcDream.App/Rendering/TerrainModernRenderer.DirectionalShadows.cs

146 lines
4.7 KiB
C#

using System.Runtime.CompilerServices;
using AcDream.App.Rendering.Wb;
using AcDream.App.Rendering.Gpu;
namespace AcDream.App.Rendering;
internal readonly record struct DirectionalShadowTerrainRange(
uint FirstIndex,
int IndexCount);
internal readonly record struct DirectionalShadowTerrainGeometry(
IGpuBuffer VertexBuffer,
IGpuBuffer IndexBuffer);
/// <summary>
/// The complete resident terrain arena expressed once as indirect commands.
/// It deliberately has no camera, PView, portal, or cascade input.
/// </summary>
internal sealed class DirectionalShadowTerrainPreparedDraws
{
private DrawElementsIndirectCommand[] _commands = [];
private int _count;
private bool _building;
public long SourceFrameSequence { get; private set; }
public ulong BuildSequence { get; private set; }
public ReadOnlySpan<DrawElementsIndirectCommand> Commands =>
_commands.AsSpan(0, _count);
public long RetainedScratchBytes =>
checked((long)_commands.Length
* Unsafe.SizeOf<DrawElementsIndirectCommand>());
public bool TryBegin(long frameSequence, int estimatedCommands)
{
if (_building)
throw new InvalidOperationException(
"A terrain shadow draw build is already active.");
if (frameSequence <= 0)
throw new ArgumentOutOfRangeException(nameof(frameSequence));
ArgumentOutOfRangeException.ThrowIfNegative(estimatedCommands);
if (SourceFrameSequence == frameSequence)
return false;
EnsureCapacity(estimatedCommands);
_count = 0;
_building = true;
return true;
}
public void Add(in DirectionalShadowTerrainRange range)
{
if (!_building)
throw new InvalidOperationException(
"Begin a terrain shadow draw build before adding ranges.");
if (range.IndexCount <= 0)
throw new ArgumentOutOfRangeException(nameof(range));
EnsureCapacity(checked(_count + 1));
_commands[_count++] = new DrawElementsIndirectCommand
{
Count = checked((uint)range.IndexCount),
InstanceCount = 1,
FirstIndex = range.FirstIndex,
BaseVertex = 0,
BaseInstance = 0,
};
}
public void Complete(long frameSequence)
{
if (!_building)
throw new InvalidOperationException(
"No terrain shadow draw build is active.");
if (frameSequence <= 0)
throw new ArgumentOutOfRangeException(nameof(frameSequence));
SourceFrameSequence = frameSequence;
BuildSequence = checked(BuildSequence + 1);
_building = false;
}
public void Abort()
{
_count = 0;
_building = false;
}
private void EnsureCapacity(int required)
{
if (_commands.Length >= required)
return;
int capacity = _commands.Length == 0 ? 16 : _commands.Length;
while (capacity < required)
capacity = checked(capacity * 2);
Array.Resize(ref _commands, capacity);
}
}
public sealed partial class TerrainModernRenderer
{
private readonly DirectionalShadowTerrainPreparedDraws
_directionalShadowTerrainDraws = new();
private long _directionalShadowFrameSequence;
internal DirectionalShadowTerrainGeometry GetDirectionalShadowGeometry() => new(
_vertexStore ?? throw new InvalidOperationException("Terrain has no vertex store."),
_indexStore ?? throw new InvalidOperationException("Terrain has no index store."));
/// <summary>
/// Builds one all-resident indirect list for the current frame. Repeated
/// calls by individual cascades return the same retained product.
/// </summary>
internal DirectionalShadowTerrainPreparedDraws
PrepareDirectionalShadowDraws()
{
if (!_directionalShadowTerrainDraws.TryBegin(
_directionalShadowFrameSequence,
_alloc.LoadedCount))
{
return _directionalShadowTerrainDraws;
}
try
{
for (int slot = 0; slot < _slots.Length; slot++)
{
SlotData? data = _slots[slot];
if (data is null)
continue;
var range = new DirectionalShadowTerrainRange(
data.FirstIndex,
data.IndexCount);
_directionalShadowTerrainDraws.Add(in range);
}
_directionalShadowTerrainDraws.Complete(
_directionalShadowFrameSequence);
return _directionalShadowTerrainDraws;
}
catch
{
_directionalShadowTerrainDraws.Abort();
throw;
}
}
}