feat(rendering): contain incremental render scene
Pin Arch behind acdream-owned identities, deltas, queries, generation gates, and memory diagnostics. The new five-archetype store is unbound and non-drawing so static publication can be shadowed without changing production behavior. Co-authored-by: Erik Nilsson <erikn@users.noreply.github.com>
This commit is contained in:
parent
f9b68f8f2a
commit
dbd8318417
7 changed files with 1698 additions and 2 deletions
381
src/AcDream.App/Rendering/Scene/RenderSceneContracts.cs
Normal file
381
src/AcDream.App/Rendering/Scene/RenderSceneContracts.cs
Normal file
|
|
@ -0,0 +1,381 @@
|
|||
using System.Numerics;
|
||||
|
||||
namespace AcDream.App.Rendering.Scene;
|
||||
|
||||
internal readonly record struct RenderProjectionId
|
||||
{
|
||||
private readonly ulong _value;
|
||||
|
||||
private RenderProjectionId(ulong value) => _value = value;
|
||||
|
||||
internal static RenderProjectionId FromRaw(ulong value) => new(value);
|
||||
|
||||
internal ulong RawValue => _value;
|
||||
|
||||
public int CompareTo(RenderProjectionId other) =>
|
||||
_value.CompareTo(other._value);
|
||||
|
||||
public override string ToString() => $"projection:{_value:X16}";
|
||||
}
|
||||
|
||||
internal readonly record struct RenderOwnerIncarnation
|
||||
{
|
||||
private readonly ulong _value;
|
||||
|
||||
private RenderOwnerIncarnation(ulong value) => _value = value;
|
||||
|
||||
internal static RenderOwnerIncarnation FromRaw(ulong value) => new(value);
|
||||
|
||||
internal ulong RawValue => _value;
|
||||
|
||||
public int CompareTo(RenderOwnerIncarnation other) =>
|
||||
_value.CompareTo(other._value);
|
||||
|
||||
public override string ToString() => $"incarnation:{_value}";
|
||||
}
|
||||
|
||||
internal readonly record struct RenderSceneGeneration
|
||||
{
|
||||
private readonly ulong _value;
|
||||
|
||||
private RenderSceneGeneration(ulong value) => _value = value;
|
||||
|
||||
internal static RenderSceneGeneration FromRaw(ulong value) => new(value);
|
||||
|
||||
internal ulong RawValue => _value;
|
||||
|
||||
public int CompareTo(RenderSceneGeneration other) =>
|
||||
_value.CompareTo(other._value);
|
||||
|
||||
public override string ToString() => $"generation:{_value}";
|
||||
}
|
||||
|
||||
internal readonly record struct RenderSpatialBucket
|
||||
{
|
||||
private readonly ulong _value;
|
||||
|
||||
private RenderSpatialBucket(ulong value) => _value = value;
|
||||
|
||||
internal static RenderSpatialBucket FromRaw(ulong value) => new(value);
|
||||
|
||||
internal ulong RawValue => _value;
|
||||
|
||||
public int CompareTo(RenderSpatialBucket other) =>
|
||||
_value.CompareTo(other._value);
|
||||
|
||||
public override string ToString() => $"bucket:{_value:X16}";
|
||||
}
|
||||
|
||||
internal readonly record struct RenderAssetHandle
|
||||
{
|
||||
private readonly ulong _value;
|
||||
|
||||
private RenderAssetHandle(ulong value) => _value = value;
|
||||
|
||||
internal static RenderAssetHandle FromRaw(ulong value) => new(value);
|
||||
|
||||
internal ulong RawValue => _value;
|
||||
|
||||
public int CompareTo(RenderAssetHandle other) =>
|
||||
_value.CompareTo(other._value);
|
||||
|
||||
public override string ToString() => $"asset:{_value:X16}";
|
||||
}
|
||||
|
||||
internal enum RenderProjectionClass : byte
|
||||
{
|
||||
OutdoorStatic,
|
||||
IndoorCellStatic,
|
||||
LiveDynamicRoot,
|
||||
ActiveAnimatedStatic,
|
||||
EquippedChild,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum RenderProjectionFlags : uint
|
||||
{
|
||||
None = 0,
|
||||
Draw = 1 << 0,
|
||||
Hidden = 1 << 1,
|
||||
AncestorHidden = 1 << 2,
|
||||
Translucent = 1 << 3,
|
||||
Selectable = 1 << 4,
|
||||
LightCandidate = 1 << 5,
|
||||
PortalStraddling = 1 << 6,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum RenderDirtyMask : ushort
|
||||
{
|
||||
None = 0,
|
||||
Transform = 1 << 0,
|
||||
Appearance = 1 << 1,
|
||||
Flags = 1 << 2,
|
||||
SpatialResidency = 1 << 3,
|
||||
WorldBounds = 1 << 4,
|
||||
SortKey = 1 << 5,
|
||||
All = ushort.MaxValue,
|
||||
}
|
||||
|
||||
internal readonly record struct RenderTransform(Matrix4x4 LocalToWorld);
|
||||
|
||||
internal readonly record struct PreviousRenderTransform(Matrix4x4 LocalToWorld);
|
||||
|
||||
internal readonly record struct RenderMeshSet(
|
||||
RenderAssetHandle Handle,
|
||||
int MeshCount,
|
||||
ulong Revision);
|
||||
|
||||
internal readonly record struct RenderMaterialVariant(
|
||||
ulong PaletteKey,
|
||||
ulong TextureReplacementKey,
|
||||
float Opacity);
|
||||
|
||||
internal readonly record struct RenderSpatialResidency(
|
||||
RenderSpatialBucket Bucket,
|
||||
uint OwnerLandblockId,
|
||||
uint FullCellId);
|
||||
|
||||
internal readonly record struct RenderWorldBounds(Vector3 Minimum, Vector3 Maximum);
|
||||
|
||||
internal readonly record struct RenderDegradeState(byte Level, uint Revision);
|
||||
|
||||
internal readonly record struct RenderSortKey(ulong Value);
|
||||
|
||||
internal readonly record struct RenderProjectionRecord(
|
||||
RenderProjectionId Id,
|
||||
RenderProjectionClass ProjectionClass,
|
||||
RenderOwnerIncarnation OwnerIncarnation,
|
||||
RenderTransform Transform,
|
||||
PreviousRenderTransform PreviousTransform,
|
||||
RenderMeshSet MeshSet,
|
||||
RenderMaterialVariant Material,
|
||||
RenderSpatialResidency Residency,
|
||||
RenderWorldBounds Bounds,
|
||||
RenderProjectionFlags Flags,
|
||||
RenderDegradeState DegradeState,
|
||||
RenderSortKey SortKey,
|
||||
RenderDirtyMask DirtyMask);
|
||||
|
||||
internal enum RenderProjectionDeltaKind : byte
|
||||
{
|
||||
Register,
|
||||
UpdateTransform,
|
||||
UpdateAppearance,
|
||||
UpdateFlags,
|
||||
Rebucket,
|
||||
Unregister,
|
||||
}
|
||||
|
||||
internal readonly record struct RenderProjectionDelta(
|
||||
RenderProjectionDeltaKind Kind,
|
||||
RenderSceneGeneration Generation,
|
||||
ulong JournalSequence,
|
||||
RenderProjectionRecord Record)
|
||||
{
|
||||
public static RenderProjectionDelta Register(
|
||||
RenderSceneGeneration generation,
|
||||
ulong journalSequence,
|
||||
in RenderProjectionRecord record) =>
|
||||
new(
|
||||
RenderProjectionDeltaKind.Register,
|
||||
generation,
|
||||
journalSequence,
|
||||
record);
|
||||
|
||||
public static RenderProjectionDelta Update(
|
||||
RenderProjectionDeltaKind kind,
|
||||
RenderSceneGeneration generation,
|
||||
ulong journalSequence,
|
||||
in RenderProjectionRecord record)
|
||||
{
|
||||
if (kind is RenderProjectionDeltaKind.Register
|
||||
or RenderProjectionDeltaKind.Unregister)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(
|
||||
nameof(kind),
|
||||
kind,
|
||||
"Use Register or Unregister for structural deltas.");
|
||||
}
|
||||
|
||||
return new RenderProjectionDelta(kind, generation, journalSequence, record);
|
||||
}
|
||||
|
||||
public static RenderProjectionDelta Unregister(
|
||||
RenderSceneGeneration generation,
|
||||
ulong journalSequence,
|
||||
RenderProjectionId id,
|
||||
RenderOwnerIncarnation ownerIncarnation) =>
|
||||
new(
|
||||
RenderProjectionDeltaKind.Unregister,
|
||||
generation,
|
||||
journalSequence,
|
||||
new RenderProjectionRecord() with
|
||||
{
|
||||
Id = id,
|
||||
OwnerIncarnation = ownerIncarnation,
|
||||
});
|
||||
}
|
||||
|
||||
internal readonly record struct DynamicProjectionUpdate(
|
||||
RenderProjectionId Id,
|
||||
RenderOwnerIncarnation OwnerIncarnation,
|
||||
RenderTransform Transform,
|
||||
RenderWorldBounds Bounds);
|
||||
|
||||
internal readonly ref struct DynamicProjectionSyncInput
|
||||
{
|
||||
public DynamicProjectionSyncInput(
|
||||
RenderSceneGeneration generation,
|
||||
ReadOnlySpan<DynamicProjectionUpdate> updates)
|
||||
{
|
||||
Generation = generation;
|
||||
Updates = updates;
|
||||
}
|
||||
|
||||
public RenderSceneGeneration Generation { get; }
|
||||
|
||||
public ReadOnlySpan<DynamicProjectionUpdate> Updates { get; }
|
||||
}
|
||||
|
||||
internal readonly record struct RenderProjectionCounts(
|
||||
int Total,
|
||||
int OutdoorStatic,
|
||||
int IndoorCellStatic,
|
||||
int LiveDynamicRoot,
|
||||
int ActiveAnimatedStatic,
|
||||
int EquippedChild)
|
||||
{
|
||||
public int For(RenderProjectionClass projectionClass) =>
|
||||
projectionClass switch
|
||||
{
|
||||
RenderProjectionClass.OutdoorStatic => OutdoorStatic,
|
||||
RenderProjectionClass.IndoorCellStatic => IndoorCellStatic,
|
||||
RenderProjectionClass.LiveDynamicRoot => LiveDynamicRoot,
|
||||
RenderProjectionClass.ActiveAnimatedStatic => ActiveAnimatedStatic,
|
||||
RenderProjectionClass.EquippedChild => EquippedChild,
|
||||
_ => throw new ArgumentOutOfRangeException(
|
||||
nameof(projectionClass),
|
||||
projectionClass,
|
||||
null),
|
||||
};
|
||||
}
|
||||
|
||||
internal readonly record struct RenderDeltaApplyResult(
|
||||
int Applied,
|
||||
int Registered,
|
||||
int Updated,
|
||||
int Replaced,
|
||||
int Unregistered,
|
||||
int RejectedGeneration,
|
||||
int RejectedOutOfOrderSequence,
|
||||
int RejectedStaleIncarnation,
|
||||
int RejectedMissing)
|
||||
{
|
||||
public int Rejected =>
|
||||
RejectedGeneration
|
||||
+ RejectedOutOfOrderSequence
|
||||
+ RejectedStaleIncarnation
|
||||
+ RejectedMissing;
|
||||
}
|
||||
|
||||
internal readonly record struct RenderSceneMemoryAccounting(
|
||||
int EntityCount,
|
||||
int ArchEntityCapacity,
|
||||
int ArchetypeCount,
|
||||
int AllocatedChunkCount,
|
||||
long EstimatedChunkPayloadBytes,
|
||||
int ProjectionLookupCapacity,
|
||||
long EstimatedProjectionLookupBytes,
|
||||
long EstimatedIndexBytes,
|
||||
long EstimatedJournalBufferBytes,
|
||||
long EstimatedSynchronizationSourceBytes)
|
||||
{
|
||||
public long TotalEstimatedBytes =>
|
||||
EstimatedChunkPayloadBytes
|
||||
+ EstimatedProjectionLookupBytes
|
||||
+ EstimatedIndexBytes
|
||||
+ EstimatedJournalBufferBytes
|
||||
+ EstimatedSynchronizationSourceBytes;
|
||||
}
|
||||
|
||||
internal readonly record struct RenderSceneDigest(
|
||||
RenderSceneGeneration Generation,
|
||||
RenderProjectionCounts Counts,
|
||||
RenderSceneHash128 Hash);
|
||||
|
||||
internal sealed class RenderSceneDigestBuffer
|
||||
{
|
||||
internal List<RenderProjectionRecord> Records { get; } = [];
|
||||
|
||||
internal int Capacity => Records.Capacity;
|
||||
}
|
||||
|
||||
internal interface IRenderSceneQuerySource
|
||||
{
|
||||
RenderProjectionCounts GetCounts(RenderSceneGeneration generation);
|
||||
|
||||
bool TryGet(
|
||||
RenderSceneGeneration generation,
|
||||
RenderProjectionId id,
|
||||
out RenderProjectionRecord record);
|
||||
|
||||
int CopyTo(
|
||||
RenderSceneGeneration generation,
|
||||
RenderProjectionClass? projectionClass,
|
||||
Span<RenderProjectionRecord> destination);
|
||||
}
|
||||
|
||||
internal readonly struct RenderSceneQuery
|
||||
{
|
||||
private readonly IRenderSceneQuerySource? _source;
|
||||
|
||||
internal RenderSceneQuery(
|
||||
IRenderSceneQuerySource source,
|
||||
RenderSceneGeneration generation)
|
||||
{
|
||||
_source = source;
|
||||
Generation = generation;
|
||||
}
|
||||
|
||||
public RenderSceneGeneration Generation { get; }
|
||||
|
||||
public RenderProjectionCounts Counts =>
|
||||
Source.GetCounts(Generation);
|
||||
|
||||
public bool TryGet(
|
||||
RenderProjectionId id,
|
||||
out RenderProjectionRecord record) =>
|
||||
Source.TryGet(Generation, id, out record);
|
||||
|
||||
public int CopyTo(Span<RenderProjectionRecord> destination) =>
|
||||
Source.CopyTo(Generation, null, destination);
|
||||
|
||||
public int CopyTo(
|
||||
RenderProjectionClass projectionClass,
|
||||
Span<RenderProjectionRecord> destination) =>
|
||||
Source.CopyTo(Generation, projectionClass, destination);
|
||||
|
||||
private IRenderSceneQuerySource Source =>
|
||||
_source
|
||||
?? throw new InvalidOperationException("The render-scene query is uninitialized.");
|
||||
}
|
||||
|
||||
internal interface IRenderScene : IDisposable
|
||||
{
|
||||
RenderSceneGeneration Generation { get; }
|
||||
|
||||
RenderProjectionCounts Counts { get; }
|
||||
|
||||
RenderSceneMemoryAccounting Memory { get; }
|
||||
|
||||
RenderDeltaApplyResult Apply(ReadOnlySpan<RenderProjectionDelta> deltas);
|
||||
|
||||
void SynchronizeDynamicSources(in DynamicProjectionSyncInput input);
|
||||
|
||||
RenderSceneDigest BuildDigest(RenderSceneDigestBuffer reuse);
|
||||
|
||||
RenderSceneQuery OpenQuery();
|
||||
|
||||
void Clear(RenderSceneGeneration replacementGeneration);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue