using System.Numerics;
using System.Runtime.InteropServices;
using AcDream.App.Rendering.Gpu;
namespace AcDream.App.Rendering.Wb;
///
/// Stable non-ref view of the one pack-on world-transform allocation for a
/// render frame. Shadow and ordinary world commands address different matrix
/// ranges through BaseInstance, but bind this exact buffer, offset, and
/// range. The matrices remain the already-published N.5 values; this type owns
/// no animation or scene projection.
///
internal readonly record struct WorldTransformFrameSlice(
long FrameSerial,
IGpuBuffer Buffer,
uint BaseOffsetBytes,
uint BindingSizeBytes,
uint FirstInstance,
uint InstanceCount)
{
internal bool IsValidFor(IGpuFrame frame) =>
FrameSerial == frame.Serial
&& Buffer is not null
&& BindingSizeBytes >= checked(
(FirstInstance + InstanceCount) * WorldTransformCapacityPolicy.MatrixBytes)
&& BindingSizeBytes % WorldTransformCapacityPolicy.MatrixBytes == 0u
&& BaseOffsetBytes % WorldTransformCapacityPolicy.MatrixBytes == 0u
&& checked((long)BaseOffsetBytes + BindingSizeBytes) <= Buffer.SizeBytes;
}
///
/// Demand-sized policy for the one enhanced-frame pose address space. The
/// 68,395-matrix connected dense row is the bootstrap observation, not a hard
/// ceiling: allocations grow in 64 KiB pages and stop only at the adapter's
/// probed maxStorageBufferRange. Vulkan guarantees that limit is at least
/// 128 MiB (2,097,152 matrices).
///
internal static class WorldTransformCapacityPolicy
{
internal const uint MatrixBytes = 64u;
internal const uint ConnectedDenseBootstrapInstances = 68_395u;
internal const uint AllocationQuantumBytes = 64u * 1024u;
internal const uint InitialBindingSizeBytes =
((ConnectedDenseBootstrapInstances * MatrixBytes
+ AllocationQuantumBytes - 1u) / AllocationQuantumBytes)
* AllocationQuantumBytes;
internal const uint VulkanGuaranteedMaxStorageBufferRangeBytes =
128u * 1024u * 1024u;
internal static uint ResolveBindingSizeBytes(
uint requiredInstances,
uint maxStorageBufferRangeBytes)
{
uint maximum = maxStorageBufferRangeBytes
- (maxStorageBufferRangeBytes % MatrixBytes);
ulong requiredBytes = (ulong)requiredInstances * MatrixBytes;
if (maximum < MatrixBytes || requiredBytes > maximum)
{
throw new NotSupportedException(
$"The enhanced frame needs {requiredInstances:N0} world matrices "
+ $"({requiredBytes:N0} bytes), but this adapter exposes only "
+ $"{maximum:N0} bytes through one storage-buffer binding. "
+ "The pack will fail safe rather than split the authoritative pose buffer.");
}
ulong targetBytes = Math.Max(
requiredBytes,
(ulong)ConnectedDenseBootstrapInstances * MatrixBytes);
ulong growthBytes = checked(
((targetBytes + AllocationQuantumBytes - 1u)
/ AllocationQuantumBytes)
* AllocationQuantumBytes);
// An implementation is allowed to expose a non-page-aligned maximum.
// Use all of it when it is below the preferred growth page, provided
// the current demand still fits.
return (uint)Math.Min(growthBytes, maximum);
}
internal static void ValidateBindingSizeBytes(
uint bindingSizeBytes,
uint requiredInstances,
uint maxStorageBufferRangeBytes)
{
ulong requiredBytes = (ulong)requiredInstances * MatrixBytes;
if (bindingSizeBytes < requiredBytes
|| bindingSizeBytes % MatrixBytes != 0u
|| bindingSizeBytes > maxStorageBufferRangeBytes)
{
throw new ArgumentOutOfRangeException(
nameof(bindingSizeBytes),
bindingSizeBytes,
$"A shared world-transform binding must be matrix-aligned, contain "
+ $"all {requiredInstances:N0} matrices, and not exceed the adapter's "
+ $"{maxStorageBufferRangeBytes:N0}-byte storage range.");
}
}
}
///
/// Frame-local address allocator over the pack-on N.5 transform block. The
/// backing buffer may be a frame-ring allocation or the active pack's retained
/// flight-slot arena. It publishes the prepared shadow prefix once and then
/// only appends already-built ordinary world matrices. It deliberately has no
/// scene, animation, or transform-derivation dependency.
///
internal sealed class WorldTransformFrameArena
{
private WorldTransformFrameSlice _allocation;
private uint _usedBytes;
internal bool IsActive => _allocation.Buffer is not null;
internal uint UsedInstances => _usedBytes / 64u;
internal WorldTransformFrameSlice Begin(
IGpuFrame frame,
ReadOnlySpan transforms) => Begin(
frame,
transforms,
WorldTransformCapacityPolicy.ResolveBindingSizeBytes(
checked((uint)transforms.Length),
WorldTransformCapacityPolicy.VulkanGuaranteedMaxStorageBufferRangeBytes));
internal WorldTransformFrameSlice Begin(
IGpuFrame frame,
ReadOnlySpan transforms,
uint bindingSizeBytes)
{
ArgumentNullException.ThrowIfNull(frame);
ResetIfStale(frame.Serial);
if (IsActive)
{
throw new InvalidOperationException(
"The directional-shadow transform frame was already published.");
}
uint byteCount = checked((uint)(transforms.Length * WorldTransformCapacityPolicy.MatrixBytes));
if (bindingSizeBytes < byteCount
|| bindingSizeBytes % WorldTransformCapacityPolicy.MatrixBytes != 0u)
throw new ArgumentOutOfRangeException(nameof(bindingSizeBytes));
GpuRingAllocation allocation = frame.AllocateRing(
checked((int)bindingSizeBytes),
GpuRingUsage.Storage);
if (!transforms.IsEmpty)
MemoryMarshal.AsBytes(transforms).CopyTo(allocation.Data);
_allocation = new WorldTransformFrameSlice(
frame.Serial,
allocation.Buffer,
allocation.OffsetBytes,
bindingSizeBytes,
FirstInstance: 0,
checked((uint)transforms.Length));
_usedBytes = byteCount;
return _allocation;
}
///
/// Activates a pack-owned flight-slot arena whose shadow prefix was already
/// published. Ordinary N.5 matrices append after that prefix and bind this
/// exact buffer/range, preserving the one authoritative pose SSBO without
/// copying stable shadow matrices through the frame ring.
///
internal WorldTransformFrameSlice BeginRetained(
IGpuFrame frame,
in WorldTransformFrameSlice shadowPrefix)
{
ArgumentNullException.ThrowIfNull(frame);
ResetIfStale(frame.Serial);
if (IsActive)
{
throw new InvalidOperationException(
"The directional-shadow transform frame was already published.");
}
if (!shadowPrefix.IsValidFor(frame)
|| shadowPrefix.FirstInstance != 0
|| shadowPrefix.Buffer.Residency != GpuMemoryResidency.HostWritable
|| !shadowPrefix.Buffer.HostWritesAreCoherent
|| !shadowPrefix.Buffer.Usage.HasFlag(GpuBufferUsage.Storage))
{
throw new ArgumentException(
"The retained shadow prefix must be this frame's host-writable "
+ "matrix-aligned storage arena at base instance zero.",
nameof(shadowPrefix));
}
uint byteCount = checked(
shadowPrefix.InstanceCount * WorldTransformCapacityPolicy.MatrixBytes);
if (byteCount > shadowPrefix.BindingSizeBytes)
{
throw new ArgumentException(
"The retained shadow prefix exceeds its storage binding.",
nameof(shadowPrefix));
}
_allocation = shadowPrefix;
_usedBytes = byteCount;
return _allocation;
}
internal WorldTransformFrameSlice Append(
IGpuFrame frame,
ReadOnlySpan transforms)
{
ArgumentNullException.ThrowIfNull(frame);
ResetIfStale(frame.Serial);
if (!IsActive)
{
throw new InvalidOperationException(
"The shared world-transform frame has not been published.");
}
uint byteCount = checked(
(uint)(transforms.Length * WorldTransformCapacityPolicy.MatrixBytes));
uint start = _usedBytes;
uint end = checked(start + byteCount);
if (end > _allocation.BindingSizeBytes)
{
throw new InvalidOperationException(
$"The enhanced frame needs {end / WorldTransformCapacityPolicy.MatrixBytes:N0} world matrices; "
+ $"this frame's shared transform binding contains "
+ $"{_allocation.BindingSizeBytes / WorldTransformCapacityPolicy.MatrixBytes:N0}. "
+ "The pack will fail safe rather than bind a second pose buffer.");
}
if (!transforms.IsEmpty)
{
_allocation.Buffer.Upload(
checked((long)_allocation.BaseOffsetBytes + start),
MemoryMarshal.AsBytes(transforms));
}
_usedBytes = end;
return _allocation with
{
FirstInstance = start / WorldTransformCapacityPolicy.MatrixBytes,
InstanceCount = checked((uint)transforms.Length),
};
}
internal bool IsActiveFor(long frameSerial) =>
IsActive && _allocation.FrameSerial == frameSerial;
internal void Cancel(IGpuFrame frame)
{
ArgumentNullException.ThrowIfNull(frame);
if (IsActiveFor(frame.Serial))
Reset();
}
internal void ResetIfStale(long frameSerial)
{
if (IsActive && _allocation.FrameSerial != frameSerial)
Reset();
}
internal void Reset()
{
_allocation = default;
_usedBytes = 0;
}
}