acdream/tests/AcDream.App.Tests/Rendering/ArchRenderSceneTests.cs
Erik 1d73ce524c test(app): measure the warmed path, not the path being warmed (#250)
The zero-allocation family failed about one full-suite run in three, on
unchanged trees, and had been dismissed as inherent noise in
`GC.GetAllocatedBytesForCurrentThread` three separate times. It is not noise.
Reading the four members side by side, they share one root: **the measured
window was never the warmed path.**

  UiDatFontTests            1 warm call, then a 10,000-iteration loop inline
  RenderFrameProductTests   8 warm calls, then a 1,000-iteration loop inline
  OracleTests               1 warm call, 1 measured call
  ArchRenderSceneTests      warms Apply(registrations), measures Apply(updates)

Two mechanisms come out of that table. A test method is JIT-compiled at tier 0
like anything else, and a long-running loop in tier-0 code gets replaced
mid-flight by on-stack replacement — which compiles on the thread running the
loop, so its bookkeeping is charged to the window being measured. That is the
first two. And `ArchRenderSceneTests` warmed one arm of a switch and measured
the other, so the measured call was the first ever into `ApplyUpdate` and paid
that arm's JIT, type loads and static initialisation inside the window;
`RenderFrameProductTests` warmed 8 times, below the tier-0 call-counting
threshold of 30, so promotion was still pending when measurement began.

That also explains the signature nobody could account for. Alone, the process is
quiet and the runtime has finished before the assertion arrives. Alongside eight
other test assemblies, tier-0 compilation never stops, the call-counting delay is
re-armed continually, and the work slides into the window. Clean in isolation,
failing under load, on a tree that changed nothing.

`ZeroAllocationProbe` invokes the step many times before measuring anything, then
measures windows that run the same already-warmed loop over the same
already-taken path. Each window is a batch of 32 invocations and it reports the
minimum across 4 of them. Both halves are load-bearing: the minimum is what
excludes a one-time cost, and the batch is what keeps the assertion as strong as
the loops it replaces — minimising over *single* invocations would report zero
for a path that allocates every tenth call, which is a real regression made
invisible. I had written it that way first and the apparatus test caught it.

**The bound is untouched: exactly zero, no tolerance, no retry, no assertion
relaxed.** `ZeroAllocationProbeTests` proves the apparatus can still fail — a
step allocating every call reads above zero and does throw, a first-invocation
cost reads as zero, a cost every tenth call is caught, and the one stated limit
(the batch must cover the period) is pinned as a test rather than left as prose.
Without those, a later edit could quietly make the whole family unfailable.

Twelve further sites in this assembly still use the hand-rolled shape. None has
been observed failing, and each needs its own repeatability analysis — several
mutate state or consume monotonic sequences — so they are listed in the issue
for adoption when next touched rather than converted blind at scale.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-29 03:42:08 +02:00

707 lines
25 KiB
C#

using System.Numerics;
using AcDream.App.Rendering.Scene;
using AcDream.App.Rendering.Scene.Arch;
namespace AcDream.App.Tests.Rendering;
public sealed class ArchRenderSceneTests
{
[Fact]
public void Apply_RegisterChannelUpdatesAndUnregister_PreservesTypedRecord()
{
RenderSceneGeneration generation = Generation(1);
using var scene = new ArchRenderScene(generation);
RenderProjectionRecord original = Record(
id: 10,
incarnation: 3,
RenderProjectionClass.LiveDynamicRoot,
x: 1,
bucket: 0xAABB);
RenderDeltaApplyResult register = scene.Apply(
[RenderProjectionDelta.Register(generation, 1, original)]);
Assert.Equal(1, register.Applied);
Assert.Equal(1, register.Registered);
Assert.Equal(
new RenderProjectionCounts(1, 0, 0, 1, 0, 0),
scene.Counts);
RenderProjectionRecord transformed = original with
{
PreviousTransform = new PreviousRenderTransform(
original.Transform.LocalToWorld),
Transform = new RenderTransform(
Matrix4x4.CreateTranslation(4, 5, 6)),
Bounds = new RenderWorldBounds(
new Vector3(3, 4, 5),
new Vector3(5, 6, 7)),
SortKey = new RenderSortKey(99),
};
RenderProjectionRecord appearance = transformed with
{
MeshSet = new RenderMeshSet(Asset(80), 4, 7),
Material = new RenderMaterialVariant(12, 13, 0.5f),
DegradeState = new RenderDegradeState(2, 8),
};
RenderProjectionRecord flags = appearance with
{
Flags = RenderProjectionFlags.Draw
| RenderProjectionFlags.Translucent,
};
RenderProjectionRecord rebucketed = flags with
{
Residency = new RenderSpatialResidency(
Bucket(0xCCDD),
0xCCDD0000,
0xCCDD0102),
};
RenderDeltaApplyResult updates = scene.Apply(
[
RenderProjectionDelta.Update(
RenderProjectionDeltaKind.UpdateTransform,
generation,
2,
transformed),
RenderProjectionDelta.Update(
RenderProjectionDeltaKind.UpdateAppearance,
generation,
3,
appearance),
RenderProjectionDelta.Update(
RenderProjectionDeltaKind.UpdateFlags,
generation,
4,
flags),
RenderProjectionDelta.Update(
RenderProjectionDeltaKind.Rebucket,
generation,
5,
rebucketed),
]);
Assert.Equal(4, updates.Applied);
Assert.Equal(4, updates.Updated);
Assert.True(scene.OpenQuery().TryGet(original.Id, out var stored));
Assert.Equal(rebucketed.Transform, stored.Transform);
Assert.Equal(rebucketed.Bounds, stored.Bounds);
Assert.Equal(rebucketed.MeshSet, stored.MeshSet);
Assert.Equal(rebucketed.Material, stored.Material);
Assert.Equal(rebucketed.Flags, stored.Flags);
Assert.Equal(rebucketed.Residency, stored.Residency);
Assert.Equal(
RenderDirtyMask.All,
stored.DirtyMask);
RenderDeltaApplyResult unregister = scene.Apply(
[
RenderProjectionDelta.Unregister(
generation,
6,
original.Id,
original.OwnerIncarnation),
]);
Assert.Equal(1, unregister.Applied);
Assert.Equal(1, unregister.Unregistered);
Assert.Equal(default, scene.Counts);
Assert.False(scene.OpenQuery().TryGet(original.Id, out _));
}
[Fact]
public void Register_NewerIncarnationReplacesAndOldCallbacksCannotRemoveIt()
{
RenderSceneGeneration generation = Generation(4);
using var scene = new ArchRenderScene(generation);
RenderProjectionRecord first = Record(
44,
10,
RenderProjectionClass.OutdoorStatic);
RenderProjectionRecord replacement = Record(
44,
11,
RenderProjectionClass.ActiveAnimatedStatic,
x: 9);
scene.Apply([RenderProjectionDelta.Register(generation, 1, first)]);
RenderDeltaApplyResult replaced = scene.Apply(
[RenderProjectionDelta.Register(generation, 2, replacement)]);
RenderDeltaApplyResult staleRegister = scene.Apply(
[RenderProjectionDelta.Register(generation, 3, first)]);
RenderDeltaApplyResult staleDelete = scene.Apply(
[
RenderProjectionDelta.Unregister(
generation,
4,
first.Id,
first.OwnerIncarnation),
]);
Assert.Equal(1, replaced.Replaced);
Assert.Equal(1, replaced.Registered);
Assert.Equal(1, staleRegister.RejectedStaleIncarnation);
Assert.Equal(1, staleDelete.RejectedStaleIncarnation);
Assert.Equal(
new RenderProjectionCounts(1, 0, 0, 0, 1, 0),
scene.Counts);
Assert.True(scene.OpenQuery().TryGet(first.Id, out var current));
Assert.Equal(replacement.OwnerIncarnation, current.OwnerIncarnation);
Assert.Equal(replacement.Transform, current.Transform);
}
[Fact]
public void Apply_RejectsWrongGenerationOutOfOrderAndMissingDeltas()
{
RenderSceneGeneration generation = Generation(7);
using var scene = new ArchRenderScene(generation);
RenderProjectionRecord record = Record(
70,
1,
RenderProjectionClass.IndoorCellStatic);
RenderDeltaApplyResult result = scene.Apply(
[
RenderProjectionDelta.Register(Generation(6), 1, record),
RenderProjectionDelta.Register(generation, 2, record),
RenderProjectionDelta.Update(
RenderProjectionDeltaKind.UpdateFlags,
generation,
2,
record),
RenderProjectionDelta.Unregister(
generation,
3,
Projection(999),
Incarnation(1)),
]);
Assert.Equal(1, result.Applied);
Assert.Equal(1, result.RejectedGeneration);
Assert.Equal(1, result.RejectedOutOfOrderSequence);
Assert.Equal(1, result.RejectedMissing);
Assert.Equal(1, scene.Counts.Total);
}
[Fact]
public void Clear_AdvancesGenerationReclaimsRecordsAndInvalidatesBorrowedQuery()
{
RenderSceneGeneration firstGeneration = Generation(1);
using var scene = new ArchRenderScene(firstGeneration);
RenderProjectionRecord record = Record(
1,
1,
RenderProjectionClass.EquippedChild);
scene.Apply(
[RenderProjectionDelta.Register(firstGeneration, 1, record)]);
RenderSceneQuery borrowed = scene.OpenQuery();
scene.Clear(Generation(2));
Assert.Equal(Generation(2), scene.Generation);
Assert.Equal(default, scene.Counts);
Assert.Equal(0, scene.Memory.EntityCount);
Assert.Throws<InvalidOperationException>(() => _ = borrowed.Counts);
Assert.Throws<ArgumentOutOfRangeException>(
() => scene.Clear(Generation(2)));
RenderProjectionRecord next = record with
{
OwnerIncarnation = Incarnation(2),
};
RenderDeltaApplyResult applied = scene.Apply(
[RenderProjectionDelta.Register(Generation(2), 1, next)]);
Assert.Equal(1, applied.Registered);
}
[Fact]
public void FiveProjectionClasses_UseNarrowArchetypesAndReportRetainedMemory()
{
RenderSceneGeneration generation = Generation(9);
using var scene = new ArchRenderScene(generation);
RenderProjectionClass[] classes =
[
RenderProjectionClass.OutdoorStatic,
RenderProjectionClass.IndoorCellStatic,
RenderProjectionClass.LiveDynamicRoot,
RenderProjectionClass.ActiveAnimatedStatic,
RenderProjectionClass.EquippedChild,
];
var deltas = new RenderProjectionDelta[classes.Length];
for (int i = 0; i < classes.Length; i++)
{
RenderProjectionRecord record = Record(
(ulong)i + 1,
1,
classes[i]);
deltas[i] = RenderProjectionDelta.Register(
generation,
(ulong)i + 1,
record);
}
scene.Apply(deltas);
RenderSceneMemoryAccounting memory = scene.Memory;
Assert.Equal(5, scene.Counts.Total);
Assert.Equal(5, memory.EntityCount);
Assert.Equal(5, memory.ArchetypeCount);
Assert.Equal(5, memory.AllocatedChunkCount);
Assert.True(memory.ArchEntityCapacity >= memory.EntityCount);
Assert.True(memory.EstimatedChunkPayloadBytes > 0);
Assert.True(memory.ProjectionLookupCapacity >= memory.EntityCount);
Assert.True(memory.EstimatedProjectionLookupBytes > 0);
Assert.Equal(
memory.EstimatedChunkPayloadBytes
+ memory.EstimatedProjectionLookupBytes
+ memory.EstimatedIndexBytes,
memory.TotalEstimatedBytes);
}
[Fact]
public void DynamicSynchronization_UpdatesOnlyMatchingLiveIncarnation()
{
RenderSceneGeneration generation = Generation(10);
using var scene = new ArchRenderScene(generation);
RenderProjectionRecord record = Record(
100,
5,
RenderProjectionClass.LiveDynamicRoot);
scene.Apply([RenderProjectionDelta.Register(generation, 1, record)]);
DynamicProjectionUpdate stale = new(
record.Id,
Incarnation(4),
new RenderTransform(Matrix4x4.CreateTranslation(100, 0, 0)),
record.Bounds);
DynamicProjectionUpdate current = new(
record.Id,
record.OwnerIncarnation,
new RenderTransform(Matrix4x4.CreateTranslation(8, 9, 10)),
new RenderWorldBounds(
new Vector3(7, 8, 9),
new Vector3(9, 10, 11)));
scene.SynchronizeDynamicSources(
new DynamicProjectionSyncInput(generation, [stale, current]));
Assert.True(scene.OpenQuery().TryGet(record.Id, out var stored));
Assert.Equal(record.Transform.LocalToWorld, stored.PreviousTransform.LocalToWorld);
Assert.Equal(current.Transform, stored.Transform);
Assert.Equal(current.Bounds, stored.Bounds);
}
[Fact]
public void TransformUpdateBatch_ReusesRetainedStorage()
{
const int count = 1_000;
RenderSceneGeneration generation = Generation(11);
using var scene = new ArchRenderScene(generation);
var registrations = new RenderProjectionDelta[count];
var updates = new RenderProjectionDelta[count];
for (int index = 0; index < count; index++)
{
RenderProjectionRecord record = Record(
(ulong)index + 1,
1,
RenderProjectionClass.ActiveAnimatedStatic,
x: index);
registrations[index] = RenderProjectionDelta.Register(
generation,
(ulong)index + 1,
record);
updates[index] = RenderProjectionDelta.Update(
RenderProjectionDeltaKind.UpdateTransform,
generation,
(ulong)count + (ulong)index + 1,
record with
{
Transform = new RenderTransform(
Matrix4x4.CreateTranslation(index + 1, 0, 0)),
});
}
scene.Apply(registrations);
// #250: the old shape warmed with Apply(registrations) and measured
// Apply(updates) — a different arm of the same switch, so the measured
// call was the first-ever call into ApplyUpdate and paid that arm's
// tier-0 JIT, type loads and static initialisation inside the window.
//
// Apply rejects any delta whose JournalSequence is not greater than the
// last one applied, so re-running the same batch would be a no-op that
// warms nothing. Each invocation therefore restamps the batch with a
// fresh monotonic block. The restamp is deliberately part of the step
// rather than hoisted out of it: it must cost the same in a measured
// window as in a warm one, or it would be exactly the novel work this
// probe exists to exclude.
ulong nextSequence = (ulong)count + 1;
ZeroAllocationProbe.AssertAllocatesNothing(
"ArchRenderScene.Apply(transform updates)",
() =>
{
for (int index = 0; index < count; index++)
{
updates[index] = RenderProjectionDelta.Update(
RenderProjectionDeltaKind.UpdateTransform,
generation,
nextSequence + (ulong)index,
updates[index].Record);
}
nextSequence += (ulong)count;
scene.Apply(updates);
});
// The batch really was applied, rather than rejected as stale.
Assert.True(scene.OpenQuery().TryGet(updates[0].Record.Id, out var applied));
Assert.Equal(
Matrix4x4.CreateTranslation(1, 0, 0),
applied.Transform.LocalToWorld);
}
[Fact]
public void Digest_IsStableAcrossRegistrationOrderAndBufferReuse()
{
RenderSceneGeneration generation = Generation(12);
using var first = new ArchRenderScene(generation);
using var second = new ArchRenderScene(generation);
RenderProjectionRecord a = Record(
1,
1,
RenderProjectionClass.OutdoorStatic);
RenderProjectionRecord b = Record(
2,
1,
RenderProjectionClass.LiveDynamicRoot);
first.Apply(
[
RenderProjectionDelta.Register(generation, 1, a),
RenderProjectionDelta.Register(generation, 2, b),
]);
second.Apply(
[
RenderProjectionDelta.Register(generation, 1, b),
RenderProjectionDelta.Register(generation, 2, a),
]);
var buffer = new RenderSceneDigestBuffer();
RenderSceneDigest firstDigest = first.BuildDigest(buffer);
int retainedCapacity = buffer.Capacity;
RenderSceneDigest repeatedDigest = first.BuildDigest(buffer);
RenderSceneDigest secondDigest =
second.BuildDigest(new RenderSceneDigestBuffer());
Assert.Equal(firstDigest, repeatedDigest);
Assert.Equal(firstDigest, secondDigest);
Assert.Equal(retainedCapacity, buffer.Capacity);
}
[Fact]
public void Mutation_FromAnotherThread_IsRejected()
{
RenderSceneGeneration generation = Generation(15);
using var scene = new ArchRenderScene(generation);
RenderProjectionRecord record = Record(
15,
1,
RenderProjectionClass.LiveDynamicRoot);
Exception? captured = null;
var mutationThread = new Thread(() =>
{
try
{
scene.Apply(
[RenderProjectionDelta.Register(generation, 1, record)]);
}
catch (Exception error)
{
captured = error;
}
});
mutationThread.Start();
mutationThread.Join();
InvalidOperationException error =
Assert.IsType<InvalidOperationException>(captured);
Assert.Contains("owning update thread", error.Message);
Assert.Equal(0, scene.Counts.Total);
}
[Fact]
public void IncrementalIndices_PreserveOutdoorCellDynamicAndFeatureMembership()
{
const uint indoorCell = 0x12340100u;
RenderSceneGeneration generation = Generation(16);
using var scene = new ArchRenderScene(generation);
RenderProjectionRecord outdoorStatic = Record(
1,
1,
RenderProjectionClass.OutdoorStatic);
RenderProjectionRecord indoorStatic = Record(
2,
1,
RenderProjectionClass.IndoorCellStatic) with
{
Residency = new RenderSpatialResidency(
Bucket(indoorCell),
0x1234FFFFu,
indoorCell),
Source = new RenderSourceMetadata() with
{
ParentCellId = indoorCell,
},
};
RenderProjectionRecord animatedStatic = Record(
3,
1,
RenderProjectionClass.ActiveAnimatedStatic);
RenderProjectionRecord outdoorDynamic = Record(
4,
1,
RenderProjectionClass.LiveDynamicRoot);
RenderProjectionRecord cellDynamic = Record(
5,
1,
RenderProjectionClass.LiveDynamicRoot) with
{
Residency = new RenderSpatialResidency(
Bucket(indoorCell),
0x1234FFFFu,
indoorCell),
Source = new RenderSourceMetadata() with
{
ParentCellId = indoorCell,
},
};
RenderProjectionRecord equipped = Record(
6,
1,
RenderProjectionClass.EquippedChild) with
{
Flags = RenderProjectionFlags.Draw
| RenderProjectionFlags.Selectable
| RenderProjectionFlags.Translucent
| RenderProjectionFlags.LightCandidate
| RenderProjectionFlags.PortalStraddling,
};
RenderProjectionRecord[] records =
[
outdoorStatic,
indoorStatic,
animatedStatic,
outdoorDynamic,
cellDynamic,
equipped,
];
var deltas = new RenderProjectionDelta[records.Length];
for (int i = 0; i < records.Length; i++)
{
deltas[i] = RenderProjectionDelta.Register(
generation,
(ulong)i + 1,
records[i]);
}
scene.Apply(deltas);
RenderSceneQuery query = scene.OpenQuery();
Assert.Equal(
new RenderSceneIndexCounts(
OutdoorStatic: 2,
IndoorCellStatic: 1,
Dynamic: 3,
OutdoorDynamic: 2,
PortalStraddlingDynamic: 1,
Translucent: 1,
Selectable: 6,
LightCandidate: 1,
Dirty: 6),
query.IndexCounts);
Assert.Equal(1, query.GetCellStaticCount(indoorCell));
Assert.Equal(1, query.GetCellDynamicCount(indoorCell));
var cellRecords = new RenderProjectionRecord[1];
Assert.Equal(1, query.CopyCellStaticsTo(indoorCell, cellRecords));
Assert.Equal(indoorStatic.Id, cellRecords[0].Id);
Assert.Equal(1, query.CopyCellDynamicsTo(indoorCell, cellRecords));
Assert.Equal(cellDynamic.Id, cellRecords[0].Id);
Assert.True(scene.Memory.EstimatedIndexBytes > 0);
}
[Fact]
public void IncrementalIndices_UpdateRebucketReplaceRemoveAndDirtyAcknowledge()
{
const uint indoorCell = 0x22220100u;
RenderSceneGeneration generation = Generation(17);
using var scene = new ArchRenderScene(generation);
RenderProjectionRecord original = Record(
17,
1,
RenderProjectionClass.LiveDynamicRoot);
scene.Apply(
[RenderProjectionDelta.Register(generation, 1, original)]);
scene.ClearDirty();
Assert.Equal(0, scene.OpenQuery().IndexCounts.Dirty);
RenderProjectionRecord flagged = original with
{
Flags = original.Flags
| RenderProjectionFlags.Translucent
| RenderProjectionFlags.LightCandidate
| RenderProjectionFlags.PortalStraddling,
Source = original.Source with { ParentCellId = indoorCell },
};
RenderProjectionRecord rebucketed = flagged with
{
Residency = new RenderSpatialResidency(
Bucket(indoorCell),
0x2222FFFFu,
indoorCell),
};
scene.Apply(
[
RenderProjectionDelta.Update(
RenderProjectionDeltaKind.UpdateFlags,
generation,
2,
flagged),
RenderProjectionDelta.Update(
RenderProjectionDeltaKind.Rebucket,
generation,
3,
rebucketed),
]);
RenderSceneQuery updated = scene.OpenQuery();
Assert.Equal(0, updated.IndexCounts.OutdoorDynamic);
Assert.Equal(1, updated.IndexCounts.PortalStraddlingDynamic);
Assert.Equal(1, updated.IndexCounts.Translucent);
Assert.Equal(1, updated.IndexCounts.LightCandidate);
Assert.Equal(1, updated.IndexCounts.Dirty);
Assert.Equal(1, updated.GetCellDynamicCount(indoorCell));
RenderProjectionRecord replacement = rebucketed with
{
ProjectionClass = RenderProjectionClass.IndoorCellStatic,
OwnerIncarnation = Incarnation(2),
};
scene.Apply(
[RenderProjectionDelta.Register(generation, 4, replacement)]);
RenderSceneQuery replaced = scene.OpenQuery();
Assert.Equal(0, replaced.IndexCounts.Dynamic);
Assert.Equal(1, replaced.IndexCounts.IndoorCellStatic);
Assert.Equal(1, replaced.GetCellStaticCount(indoorCell));
scene.Apply(
[
RenderProjectionDelta.Unregister(
generation,
5,
replacement.Id,
replacement.OwnerIncarnation),
]);
Assert.Equal(default, scene.OpenQuery().IndexCounts);
Assert.True(scene.Memory.EstimatedIndexBytes > 0);
}
[Fact]
public void IndexRevision_AdvancesOnlyWhenOrderedMembershipCanChange()
{
RenderSceneGeneration generation = Generation(18);
using var scene = new ArchRenderScene(generation);
RenderProjectionRecord original = Record(
18,
1,
RenderProjectionClass.OutdoorStatic);
scene.Apply(
[RenderProjectionDelta.Register(generation, 1, original)]);
scene.ClearDirty();
ulong registeredRevision = scene.OpenQuery().IndexRevision;
Matrix4x4 movedTransform =
Matrix4x4.CreateTranslation(4, 5, 6);
RenderProjectionRecord moved = original with
{
Transform = new RenderTransform(movedTransform),
PreviousTransform =
new PreviousRenderTransform(
original.Transform.LocalToWorld),
Bounds = new RenderWorldBounds(
new Vector3(3, 4, 5),
new Vector3(5, 6, 7)),
};
scene.Apply(
[
RenderProjectionDelta.Update(
RenderProjectionDeltaKind.UpdateTransform,
generation,
2,
moved),
]);
RenderSceneQuery movedQuery = scene.OpenQuery();
Assert.Equal(registeredRevision, movedQuery.IndexRevision);
Assert.Equal(1, movedQuery.IndexCounts.Dirty);
RenderProjectionRecord reordered = moved with
{
SortKey = new RenderSortKey(moved.SortKey.Value + 1),
};
scene.Apply(
[
RenderProjectionDelta.Update(
RenderProjectionDeltaKind.UpdateTransform,
generation,
3,
reordered),
]);
Assert.True(
scene.OpenQuery().IndexRevision > registeredRevision);
}
private static RenderProjectionRecord Record(
ulong id,
ulong incarnation,
RenderProjectionClass projectionClass,
float x = 0,
ulong bucket = 1)
{
Matrix4x4 transform = Matrix4x4.CreateTranslation(x, 2, 3);
return new RenderProjectionRecord(
Projection(id),
projectionClass,
Incarnation(incarnation),
new RenderTransform(transform),
new PreviousRenderTransform(transform),
new RenderMeshSet(Asset(id + 100), 2, 1),
new RenderMaterialVariant(id + 200, id + 300, 1),
new RenderSpatialResidency(
Bucket(bucket),
(uint)(bucket << 16),
(uint)((bucket << 16) | 0x101)),
new RenderWorldBounds(
new Vector3(x - 1, 1, 2),
new Vector3(x + 1, 3, 4)),
RenderProjectionFlags.Draw | RenderProjectionFlags.Selectable,
new RenderDegradeState(0, 1),
new RenderSortKey(id + 400),
RenderDirtyMask.All);
}
private static RenderProjectionId Projection(ulong value) =>
RenderProjectionId.FromRaw(value);
private static RenderOwnerIncarnation Incarnation(ulong value) =>
RenderOwnerIncarnation.FromRaw(value);
private static RenderSceneGeneration Generation(ulong value) =>
RenderSceneGeneration.FromRaw(value);
private static RenderSpatialBucket Bucket(ulong value) =>
RenderSpatialBucket.FromRaw(value);
private static RenderAssetHandle Asset(ulong value) =>
RenderAssetHandle.FromRaw(value);
}