feat(rendering): index incremental scene queries
Maintain packed render worksets as lifecycle deltas arrive and synchronize active animated statics without scanning the resident world. This keeps Slice F non-drawing while preparing exact spatial and feature queries for shadow comparison. Release: 8,206 passed, 5 skipped.
This commit is contained in:
parent
58e7c2eb99
commit
ff5d86175f
8 changed files with 745 additions and 16 deletions
|
|
@ -253,7 +253,8 @@ public sealed class ArchRenderSceneTests
|
|||
Assert.True(memory.EstimatedProjectionLookupBytes > 0);
|
||||
Assert.Equal(
|
||||
memory.EstimatedChunkPayloadBytes
|
||||
+ memory.EstimatedProjectionLookupBytes,
|
||||
+ memory.EstimatedProjectionLookupBytes
|
||||
+ memory.EstimatedIndexBytes,
|
||||
memory.TotalEstimatedBytes);
|
||||
}
|
||||
|
||||
|
|
@ -359,6 +360,182 @@ public sealed class ArchRenderSceneTests
|
|||
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);
|
||||
}
|
||||
|
||||
private static RenderProjectionRecord Record(
|
||||
ulong id,
|
||||
ulong incarnation,
|
||||
|
|
|
|||
|
|
@ -207,6 +207,62 @@ public sealed class StaticRenderProjectionJournalTests
|
|||
() => statics.Clear(Generation(7)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ActiveAnimatedSynchronization_ReclassifiesAndUpdatesOnlyActiveSource()
|
||||
{
|
||||
RenderSceneGeneration generation = Generation(8);
|
||||
var journal = new RenderProjectionJournal(generation);
|
||||
var statics = new StaticRenderProjectionJournal(journal);
|
||||
WorldEntity animated = Entity(1);
|
||||
WorldEntity ordinary = Entity(2);
|
||||
LandblockBuild build = Build(
|
||||
LandblockId,
|
||||
[animated, ordinary],
|
||||
includeShell: false);
|
||||
statics.Reconcile(build, Publication(build));
|
||||
using var scene = new ArchRenderScene(generation);
|
||||
journal.DrainTo(scene);
|
||||
RenderProjectionId animatedId =
|
||||
StaticRenderProjectionJournal.StaticEntityId(
|
||||
LandblockId,
|
||||
animated.Id);
|
||||
Assert.True(scene.OpenQuery().TryGet(animatedId, out var before));
|
||||
|
||||
statics.SynchronizeActiveAnimatedSources([animated]);
|
||||
Assert.Single(journal.Pending.ToArray());
|
||||
Assert.Equal(
|
||||
RenderProjectionDeltaKind.Register,
|
||||
journal.Pending[0].Kind);
|
||||
Assert.Equal(
|
||||
RenderProjectionClass.ActiveAnimatedStatic,
|
||||
journal.Pending[0].Record.ProjectionClass);
|
||||
Assert.Equal(
|
||||
before.OwnerIncarnation,
|
||||
journal.Pending[0].Record.OwnerIncarnation);
|
||||
journal.DrainTo(scene);
|
||||
|
||||
statics.SynchronizeActiveAnimatedSources([animated]);
|
||||
Assert.Equal(0, journal.Count);
|
||||
|
||||
animated.Rotation =
|
||||
Quaternion.CreateFromAxisAngle(Vector3.UnitZ, 0.75f);
|
||||
statics.SynchronizeActiveAnimatedSources([animated]);
|
||||
Assert.Single(journal.Pending.ToArray());
|
||||
Assert.Equal(
|
||||
RenderProjectionDeltaKind.UpdateTransform,
|
||||
journal.Pending[0].Kind);
|
||||
Assert.True(statics.TryGet(animatedId, out var updated));
|
||||
Assert.Equal(animated.Rotation, updated.Transform.Rotation);
|
||||
RenderProjectionId ordinaryId =
|
||||
StaticRenderProjectionJournal.StaticEntityId(
|
||||
LandblockId,
|
||||
ordinary.Id);
|
||||
Assert.True(statics.TryGet(ordinaryId, out var unchanged));
|
||||
Assert.Equal(
|
||||
RenderProjectionClass.OutdoorStatic,
|
||||
unchanged.ProjectionClass);
|
||||
}
|
||||
|
||||
private static GpuLandblockSpatialPublication Publication(
|
||||
LandblockBuild build,
|
||||
bool requiresActivation = true) =>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue