refactor(render): move outdoor dynamics onto landscape turns
This commit is contained in:
parent
474b05e7dc
commit
2127c955f6
8 changed files with 151 additions and 12 deletions
|
|
@ -1211,8 +1211,17 @@ internal sealed class RenderScenePViewFrameBuilder
|
||||||
// statics, live dynamics, fades, picking, and particles. Do not
|
// statics, live dynamics, fades, picking, and particles. Do not
|
||||||
// advertise a packed LookInObject range the dispatcher cannot and
|
// advertise a packed LookInObject range the dispatcher cannot and
|
||||||
// must not consume a second time.
|
// must not consume a second time.
|
||||||
BuildOutsideDynamicRoutes(writer, in input);
|
// FW4 cutover: once a walk view source is present, every live
|
||||||
BuildDynamicLastRoute(writer, in input);
|
// object is emitted at its retail cell turn (landscape, root
|
||||||
|
// flood, or building look-in). The packed dynamic ranges exist
|
||||||
|
// only for non-walk diagnostic callers during this atomic slice;
|
||||||
|
// advertising either range in production would draw the same
|
||||||
|
// object a second time after its owner cell.
|
||||||
|
if (input.WalkLookInViews is null)
|
||||||
|
{
|
||||||
|
BuildOutsideDynamicRoutes(writer, in input);
|
||||||
|
BuildDynamicLastRoute(writer, in input);
|
||||||
|
}
|
||||||
writer.Publish();
|
writer.Publish();
|
||||||
AcknowledgeCachedDirtyRecords();
|
AcknowledgeCachedDirtyRecords();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -148,6 +148,7 @@ public sealed class RetailFrameWalk
|
||||||
IRetailFrameWalkContext ctx, IWalkEventSink sink)
|
IRetailFrameWalkContext ctx, IWalkEventSink sink)
|
||||||
{
|
{
|
||||||
sink.Emit(WalkEvent.Landscape(activeViews.ViewCount));
|
sink.Emit(WalkEvent.Landscape(activeViews.ViewCount));
|
||||||
|
sink.OnLandscapeViews(activeViews);
|
||||||
landscape.CalcDrawOrder();
|
landscape.CalcDrawOrder();
|
||||||
landscape.CheckBlocks(ctx.CyPlane, activeViews);
|
landscape.CheckBlocks(ctx.CyPlane, activeViews);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,14 @@ public interface IWalkEventSink
|
||||||
{
|
{
|
||||||
void Emit(in WalkEvent walkEvent);
|
void Emit(in WalkEvent walkEvent);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Installs the exact view set <c>LScape::draw</c> is walking. The same
|
||||||
|
/// view set gates every landscape object's drawing sphere at its cell
|
||||||
|
/// turn (<c>Render::viewconeCheck</c>); consumers must copy any data they
|
||||||
|
/// retain because the walk owns and reuses this object.
|
||||||
|
/// </summary>
|
||||||
|
void OnLandscapeViews(WalkPortalView activeViews) { }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fires once per visited landscape cell, AFTER that cell's building
|
/// Fires once per visited landscape cell, AFTER that cell's building
|
||||||
/// turn (if any) — <c>RenderDeviceD3D::DrawSortCell</c> @0x0059f140
|
/// turn (if any) — <c>RenderDeviceD3D::DrawSortCell</c> @0x0059f140
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,11 @@ internal interface IWalkFrameWorldData
|
||||||
/// <see cref="IWalkEventSink.OnLandscapeCellTurn"/> computes.</summary>
|
/// <see cref="IWalkEventSink.OnLandscapeCellTurn"/> computes.</summary>
|
||||||
WalkFrameStaticRecords GetOutdoorStatics(uint cellId);
|
WalkFrameStaticRecords GetOutdoorStatics(uint cellId);
|
||||||
|
|
||||||
|
/// <summary>One visited landscape cell's live dynamic occupants. The
|
||||||
|
/// walk draws these at the same <c>DrawObjCell</c> turn as its statics,
|
||||||
|
/// through the landscape's currently installed view set.</summary>
|
||||||
|
WalkFrameStaticRecords GetOutdoorDynamics(uint cellId);
|
||||||
|
|
||||||
/// <summary>One building's own exterior shell content (<c>IsBuildingShell</c>
|
/// <summary>One building's own exterior shell content (<c>IsBuildingShell</c>
|
||||||
/// records anchored at the building's position cell).</summary>
|
/// records anchored at the building's position cell).</summary>
|
||||||
WalkFrameStaticRecords GetBuildingShellStatics(WalkBuilding building);
|
WalkFrameStaticRecords GetBuildingShellStatics(WalkBuilding building);
|
||||||
|
|
@ -519,6 +524,7 @@ internal sealed class WalkFrameDriver : IWalkEventSink, IWalkLookInViewSource
|
||||||
private WalkDrawStage? _currentDcStage;
|
private WalkDrawStage? _currentDcStage;
|
||||||
private bool _readyToReplay;
|
private bool _readyToReplay;
|
||||||
private int _cellViewRouteIndex;
|
private int _cellViewRouteIndex;
|
||||||
|
private int _landscapeViewRouteIndex;
|
||||||
|
|
||||||
internal WalkFrameDriver(
|
internal WalkFrameDriver(
|
||||||
WbDrawDispatcher dispatcher,
|
WbDrawDispatcher dispatcher,
|
||||||
|
|
@ -627,6 +633,7 @@ internal sealed class WalkFrameDriver : IWalkEventSink, IWalkLookInViewSource
|
||||||
VisitedLandscapeCellIds.Clear();
|
VisitedLandscapeCellIds.Clear();
|
||||||
InteriorFloodCells.Clear();
|
InteriorFloodCells.Clear();
|
||||||
_cellViewRouteIndex = 0;
|
_cellViewRouteIndex = 0;
|
||||||
|
_landscapeViewRouteIndex = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Records the final segment mark (plan §FW3.2b-1's "at frame
|
/// <summary>Records the final segment mark (plan §FW3.2b-1's "at frame
|
||||||
|
|
@ -719,6 +726,12 @@ internal sealed class WalkFrameDriver : IWalkEventSink, IWalkLookInViewSource
|
||||||
? _worldData.GetBuildingShellStatics(shellOwner)
|
? _worldData.GetBuildingShellStatics(shellOwner)
|
||||||
: _worldData.GetOutdoorStatics(e.CellId),
|
: _worldData.GetOutdoorStatics(e.CellId),
|
||||||
_staticParticleOwnerScratch);
|
_staticParticleOwnerScratch);
|
||||||
|
if (e.Building is null)
|
||||||
|
{
|
||||||
|
UnionOwners(
|
||||||
|
_worldData.GetOutdoorDynamics(e.CellId),
|
||||||
|
_staticParticleOwnerScratch);
|
||||||
|
}
|
||||||
if (_staticParticleOwnerScratch.Count > 0)
|
if (_staticParticleOwnerScratch.Count > 0)
|
||||||
_leafRenderer.DrawStaticParticles(_staticParticleOwnerScratch);
|
_leafRenderer.DrawStaticParticles(_staticParticleOwnerScratch);
|
||||||
break;
|
break;
|
||||||
|
|
@ -776,24 +789,44 @@ internal sealed class WalkFrameDriver : IWalkEventSink, IWalkLookInViewSource
|
||||||
void IWalkEventSink.OnLandscapeCellTurn(uint cellId)
|
void IWalkEventSink.OnLandscapeCellTurn(uint cellId)
|
||||||
{
|
{
|
||||||
RequireOpenFrame();
|
RequireOpenFrame();
|
||||||
|
if (_landscapeViewRouteIndex < 0)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
"A landscape cell turn fired before RetailFrameWalk installed its active "
|
||||||
|
+ "view set — the walk and draw driver are desynchronized.");
|
||||||
|
}
|
||||||
VisitedLandscapeCellIds.Add(cellId);
|
VisitedLandscapeCellIds.Add(cellId);
|
||||||
WalkFrameStaticRecords records = _worldData.GetOutdoorStatics(cellId);
|
WalkFrameStaticRecords records = _worldData.GetOutdoorStatics(cellId);
|
||||||
|
WalkFrameStaticRecords dynamics = _worldData.GetOutdoorDynamics(cellId);
|
||||||
_populator.PopulateOutdoorStatics(
|
_populator.PopulateOutdoorStatics(
|
||||||
_stream, cellId, records.Records, records.TupleLandblockId,
|
_stream, cellId, records.Records, records.TupleLandblockId,
|
||||||
_cameraWorldPosition, _viewProjection);
|
_cameraWorldPosition, _viewProjection,
|
||||||
|
this, _landscapeViewRouteIndex);
|
||||||
|
_populator.PopulateCellDynamics(
|
||||||
|
_stream, cellId, dynamics.Records, dynamics.TupleLandblockId,
|
||||||
|
_cameraWorldPosition, _viewProjection,
|
||||||
|
this, _landscapeViewRouteIndex);
|
||||||
// FW4 (the #132 positional invariant): this cell's emitter owners
|
// FW4 (the #132 positional invariant): this cell's emitter owners
|
||||||
// submit AT THIS TURN, so nearer buildings' pre-punch barriers
|
// submit AT THIS TURN, so nearer buildings' pre-punch barriers
|
||||||
// drain them against still-true depth — see
|
// drain them against still-true depth — see
|
||||||
// WalkFrameEventKind.StaticParticles. Mark first so the cell's own
|
// WalkFrameEventKind.StaticParticles. Mark first so the cell's own
|
||||||
// meshes flush ahead of its particle submission (retail's
|
// meshes flush ahead of its particle submission (retail's
|
||||||
// per-object DrawObjCell order).
|
// per-object DrawObjCell order).
|
||||||
if (HasAnyOwner(records))
|
if (HasAnyOwner(records) || HasAnyOwner(dynamics))
|
||||||
{
|
{
|
||||||
MarkIfGrown();
|
MarkIfGrown();
|
||||||
_events.Add(WalkFrameEvent.LandscapeCellParticles(cellId));
|
_events.Add(WalkFrameEvent.LandscapeCellParticles(cellId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void IWalkEventSink.OnLandscapeViews(WalkPortalView activeViews)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(activeViews);
|
||||||
|
RequireOpenFrame();
|
||||||
|
_landscapeViewRouteIndex = _cellViewRouteIndex++;
|
||||||
|
CaptureViews(0, activeViews);
|
||||||
|
}
|
||||||
|
|
||||||
private static bool HasAnyOwner(in WalkFrameStaticRecords records)
|
private static bool HasAnyOwner(in WalkFrameStaticRecords records)
|
||||||
{
|
{
|
||||||
foreach (RenderProjectionRecord record in records.Records)
|
foreach (RenderProjectionRecord record in records.Records)
|
||||||
|
|
@ -1020,7 +1053,11 @@ internal sealed class WalkFrameDriver : IWalkEventSink, IWalkLookInViewSource
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
WalkPortalView portalView = cell.TopView;
|
CaptureViews(cellId, cell.TopView);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CaptureViews(uint cellId, WalkPortalView portalView)
|
||||||
|
{
|
||||||
int sliceStart = _lookInSlices.Count;
|
int sliceStart = _lookInSlices.Count;
|
||||||
for (int sliceIndex = 0; sliceIndex < portalView.ViewCount; sliceIndex++)
|
for (int sliceIndex = 0; sliceIndex < portalView.ViewCount; sliceIndex++)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -63,10 +63,13 @@ internal sealed class WalkProductionWorldData : IWalkFrameWorldData
|
||||||
private readonly Dictionary<uint, WalkFrameStaticRecords> _cellCache = new();
|
private readonly Dictionary<uint, WalkFrameStaticRecords> _cellCache = new();
|
||||||
private readonly Dictionary<uint, WalkFrameStaticRecords> _cellDynamicCache = new();
|
private readonly Dictionary<uint, WalkFrameStaticRecords> _cellDynamicCache = new();
|
||||||
private readonly Dictionary<uint, List<RenderProjectionRecord>> _outdoorByCell = new();
|
private readonly Dictionary<uint, List<RenderProjectionRecord>> _outdoorByCell = new();
|
||||||
|
private readonly Dictionary<uint, List<RenderProjectionRecord>> _outdoorDynamicsByCell = new();
|
||||||
private readonly Dictionary<uint, List<RenderProjectionRecord>> _shellsByAnchor = new();
|
private readonly Dictionary<uint, List<RenderProjectionRecord>> _shellsByAnchor = new();
|
||||||
private readonly Dictionary<uint, WalkFrameStaticRecords> _outdoorMaterialized = new();
|
private readonly Dictionary<uint, WalkFrameStaticRecords> _outdoorMaterialized = new();
|
||||||
|
private readonly Dictionary<uint, WalkFrameStaticRecords> _outdoorDynamicsMaterialized = new();
|
||||||
private readonly Dictionary<uint, WalkFrameStaticRecords> _shellMaterialized = new();
|
private readonly Dictionary<uint, WalkFrameStaticRecords> _shellMaterialized = new();
|
||||||
private RenderProjectionRecord[] _sweepScratch = new RenderProjectionRecord[1024];
|
private RenderProjectionRecord[] _sweepScratch = new RenderProjectionRecord[1024];
|
||||||
|
private RenderProjectionRecord[] _dynamicSweepScratch = new RenderProjectionRecord[256];
|
||||||
private RenderProjectionRecord[] _cellScratch = new RenderProjectionRecord[256];
|
private RenderProjectionRecord[] _cellScratch = new RenderProjectionRecord[256];
|
||||||
private RenderProjectionRecord[] _cellDynamicScratch = new RenderProjectionRecord[256];
|
private RenderProjectionRecord[] _cellDynamicScratch = new RenderProjectionRecord[256];
|
||||||
|
|
||||||
|
|
@ -102,10 +105,13 @@ internal sealed class WalkProductionWorldData : IWalkFrameWorldData
|
||||||
_cellCache.Clear();
|
_cellCache.Clear();
|
||||||
_cellDynamicCache.Clear();
|
_cellDynamicCache.Clear();
|
||||||
_outdoorMaterialized.Clear();
|
_outdoorMaterialized.Clear();
|
||||||
|
_outdoorDynamicsMaterialized.Clear();
|
||||||
_shellMaterialized.Clear();
|
_shellMaterialized.Clear();
|
||||||
_arenaLength = 0;
|
_arenaLength = 0;
|
||||||
foreach (List<RenderProjectionRecord> bucket in _outdoorByCell.Values)
|
foreach (List<RenderProjectionRecord> bucket in _outdoorByCell.Values)
|
||||||
bucket.Clear();
|
bucket.Clear();
|
||||||
|
foreach (List<RenderProjectionRecord> bucket in _outdoorDynamicsByCell.Values)
|
||||||
|
bucket.Clear();
|
||||||
foreach (List<RenderProjectionRecord> bucket in _shellsByAnchor.Values)
|
foreach (List<RenderProjectionRecord> bucket in _shellsByAnchor.Values)
|
||||||
bucket.Clear();
|
bucket.Clear();
|
||||||
|
|
||||||
|
|
@ -137,6 +143,29 @@ internal sealed class WalkProductionWorldData : IWalkFrameWorldData
|
||||||
_outdoorByCell[cellId] = bucket = new List<RenderProjectionRecord>();
|
_outdoorByCell[cellId] = bucket = new List<RenderProjectionRecord>();
|
||||||
bucket.Add(record);
|
bucket.Add(record);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
required = _scene.IndexCounts.For(RenderSceneIndex.OutdoorDynamic);
|
||||||
|
if (required > _dynamicSweepScratch.Length)
|
||||||
|
{
|
||||||
|
_dynamicSweepScratch = new RenderProjectionRecord[
|
||||||
|
Math.Max(required, _dynamicSweepScratch.Length * 2)];
|
||||||
|
}
|
||||||
|
count = _scene.CopyIndexTo(
|
||||||
|
RenderSceneIndex.OutdoorDynamic,
|
||||||
|
_dynamicSweepScratch);
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
ref readonly RenderProjectionRecord record = ref _dynamicSweepScratch[i];
|
||||||
|
uint cellId = LandscapeCellId(
|
||||||
|
record.Transform.Position, _renderCenterLbX, _renderCenterLbY);
|
||||||
|
if (!_outdoorDynamicsByCell.TryGetValue(
|
||||||
|
cellId,
|
||||||
|
out List<RenderProjectionRecord>? bucket))
|
||||||
|
{
|
||||||
|
_outdoorDynamicsByCell[cellId] = bucket = new List<RenderProjectionRecord>();
|
||||||
|
}
|
||||||
|
bucket.Add(record);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>The landscape cell owning a RENDER-ORIGIN-RELATIVE position
|
/// <summary>The landscape cell owning a RENDER-ORIGIN-RELATIVE position
|
||||||
|
|
@ -216,6 +245,27 @@ internal sealed class WalkProductionWorldData : IWalkFrameWorldData
|
||||||
return records;
|
return records;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public WalkFrameStaticRecords GetOutdoorDynamics(uint cellId)
|
||||||
|
{
|
||||||
|
if (_outdoorDynamicsMaterialized.TryGetValue(
|
||||||
|
cellId,
|
||||||
|
out WalkFrameStaticRecords cached))
|
||||||
|
{
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
WalkFrameStaticRecords records =
|
||||||
|
_outdoorDynamicsByCell.TryGetValue(
|
||||||
|
cellId,
|
||||||
|
out List<RenderProjectionRecord>? bucket)
|
||||||
|
&& bucket.Count > 0
|
||||||
|
? new WalkFrameStaticRecords(
|
||||||
|
AppendToArena(CollectionsMarshal.AsSpan(bucket)), _tupleLandblockId)
|
||||||
|
: WalkFrameStaticRecords.Empty with { TupleLandblockId = _tupleLandblockId };
|
||||||
|
_outdoorDynamicsMaterialized[cellId] = records;
|
||||||
|
return records;
|
||||||
|
}
|
||||||
|
|
||||||
public WalkFrameStaticRecords GetBuildingShellStatics(WalkBuilding building)
|
public WalkFrameStaticRecords GetBuildingShellStatics(WalkBuilding building)
|
||||||
{
|
{
|
||||||
uint anchor = AnchorCellId(building);
|
uint anchor = AnchorCellId(building);
|
||||||
|
|
|
||||||
|
|
@ -92,10 +92,19 @@ internal sealed class WalkStaticStreamPopulator
|
||||||
ReadOnlySpan<RenderProjectionRecord> records,
|
ReadOnlySpan<RenderProjectionRecord> records,
|
||||||
uint tupleLandblockId,
|
uint tupleLandblockId,
|
||||||
Vector3 cameraWorldPosition,
|
Vector3 cameraWorldPosition,
|
||||||
Matrix4x4 viewProjection) =>
|
Matrix4x4 viewProjection,
|
||||||
PopulateCell(
|
IWalkLookInViewSource? views = null,
|
||||||
stream, WalkDrawStage.OutdoorStatic, cellId, records,
|
int viewRouteIndex = -1)
|
||||||
tupleLandblockId, cameraWorldPosition, viewProjection);
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(stream);
|
||||||
|
for (int i = 0; i < records.Length; i++)
|
||||||
|
{
|
||||||
|
ClassifyAndAppend(
|
||||||
|
stream, WalkDrawStage.OutdoorStatic, cellId, in records[i],
|
||||||
|
tupleLandblockId, cameraWorldPosition, viewProjection,
|
||||||
|
liveDynamic: false, views, viewRouteIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal void PopulateCellDynamics(
|
internal void PopulateCellDynamics(
|
||||||
OrderedDrawStream stream,
|
OrderedDrawStream stream,
|
||||||
|
|
|
||||||
|
|
@ -273,10 +273,14 @@ public sealed class RenderScenePViewFrameProductTests
|
||||||
0x0100_0000_0000_0023,
|
0x0100_0000_0000_0023,
|
||||||
RenderProjectionClass.LiveDynamicRoot,
|
RenderProjectionClass.LiveDynamicRoot,
|
||||||
parentCell: Cell);
|
parentCell: Cell);
|
||||||
|
RenderProjectionRecord outdoorDynamic = Record(
|
||||||
|
0x0100_0000_0000_0024,
|
||||||
|
RenderProjectionClass.LiveDynamicRoot);
|
||||||
scene.Apply(
|
scene.Apply(
|
||||||
[
|
[
|
||||||
RenderProjectionDelta.Register(Generation, 1, lookInDynamic),
|
RenderProjectionDelta.Register(Generation, 1, lookInDynamic),
|
||||||
RenderProjectionDelta.Register(Generation, 2, rootDynamic),
|
RenderProjectionDelta.Register(Generation, 2, rootDynamic),
|
||||||
|
RenderProjectionDelta.Register(Generation, 3, outdoorDynamic),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
PortalVisibilityFrame portal = Portal(Cell);
|
PortalVisibilityFrame portal = Portal(Cell);
|
||||||
|
|
@ -309,6 +313,9 @@ public sealed class RenderScenePViewFrameProductTests
|
||||||
Assert.DoesNotContain(
|
Assert.DoesNotContain(
|
||||||
frame.RouteCandidates.ToArray(),
|
frame.RouteCandidates.ToArray(),
|
||||||
candidate => candidate.Id == rootDynamic.Id);
|
candidate => candidate.Id == rootDynamic.Id);
|
||||||
|
Assert.DoesNotContain(
|
||||||
|
frame.RouteCandidates.ToArray(),
|
||||||
|
candidate => candidate.Id == outdoorDynamic.Id);
|
||||||
Assert.NotNull(frame.WalkLookInViews);
|
Assert.NotNull(frame.WalkLookInViews);
|
||||||
Assert.False(frame.WalkLookInViews!.SphereVisibleInLookInTurn(
|
Assert.False(frame.WalkLookInViews!.SphereVisibleInLookInTurn(
|
||||||
routeIndex: 0,
|
routeIndex: 0,
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,7 @@ public sealed class WalkFrameDriverTests
|
||||||
public readonly Dictionary<uint, WalkFrameStaticRecords> CellStaticsByCell = new();
|
public readonly Dictionary<uint, WalkFrameStaticRecords> CellStaticsByCell = new();
|
||||||
public readonly Dictionary<uint, WalkFrameStaticRecords> CellDynamicsByCell = new();
|
public readonly Dictionary<uint, WalkFrameStaticRecords> CellDynamicsByCell = new();
|
||||||
public readonly Dictionary<uint, WalkFrameStaticRecords> OutdoorStaticsByCell = new();
|
public readonly Dictionary<uint, WalkFrameStaticRecords> OutdoorStaticsByCell = new();
|
||||||
|
public readonly Dictionary<uint, WalkFrameStaticRecords> OutdoorDynamicsByCell = new();
|
||||||
public readonly Dictionary<WalkBuilding, WalkFrameStaticRecords> ShellByBuilding = new();
|
public readonly Dictionary<WalkBuilding, WalkFrameStaticRecords> ShellByBuilding = new();
|
||||||
public readonly Dictionary<WalkBuilding, Matrix4x4> WorldTransformByBuilding = new();
|
public readonly Dictionary<WalkBuilding, Matrix4x4> WorldTransformByBuilding = new();
|
||||||
|
|
||||||
|
|
@ -104,6 +105,9 @@ public sealed class WalkFrameDriverTests
|
||||||
public WalkFrameStaticRecords GetOutdoorStatics(uint cellId) =>
|
public WalkFrameStaticRecords GetOutdoorStatics(uint cellId) =>
|
||||||
OutdoorStaticsByCell.GetValueOrDefault(cellId, WalkFrameStaticRecords.Empty);
|
OutdoorStaticsByCell.GetValueOrDefault(cellId, WalkFrameStaticRecords.Empty);
|
||||||
|
|
||||||
|
public WalkFrameStaticRecords GetOutdoorDynamics(uint cellId) =>
|
||||||
|
OutdoorDynamicsByCell.GetValueOrDefault(cellId, WalkFrameStaticRecords.Empty);
|
||||||
|
|
||||||
public WalkFrameStaticRecords GetBuildingShellStatics(WalkBuilding building) =>
|
public WalkFrameStaticRecords GetBuildingShellStatics(WalkBuilding building) =>
|
||||||
ShellByBuilding.GetValueOrDefault(building, WalkFrameStaticRecords.Empty);
|
ShellByBuilding.GetValueOrDefault(building, WalkFrameStaticRecords.Empty);
|
||||||
|
|
||||||
|
|
@ -601,12 +605,22 @@ public sealed class WalkFrameDriverTests
|
||||||
var worldData = new FakeWorldData();
|
var worldData = new FakeWorldData();
|
||||||
worldData.OutdoorStaticsByCell[0x8C040005u] = new WalkFrameStaticRecords(
|
worldData.OutdoorStaticsByCell[0x8C040005u] = new WalkFrameStaticRecords(
|
||||||
new[] { MakeRecord(301, 0, Vector3.Zero, [new MeshRef((uint)gfxObj, Matrix4x4.Identity)]) }, 0x8C04u);
|
new[] { MakeRecord(301, 0, Vector3.Zero, [new MeshRef((uint)gfxObj, Matrix4x4.Identity)]) }, 0x8C04u);
|
||||||
|
worldData.OutdoorDynamicsByCell[0x8C040005u] = new WalkFrameStaticRecords(
|
||||||
|
new[] { MakeRecord(302, 0x50000001, Vector3.Zero, [new MeshRef((uint)gfxObj, Matrix4x4.Identity)]) }, 0x8C04u);
|
||||||
|
|
||||||
var driver = new WalkFrameDriver(
|
var driver = new WalkFrameDriver(
|
||||||
fx.Dispatcher, new RecordingLeafRenderer(log), worldData, new RecordingTrace(log));
|
fx.Dispatcher, new RecordingLeafRenderer(log), worldData, new RecordingTrace(log));
|
||||||
|
|
||||||
using DrawScope draw = fx.BeginDraw();
|
using DrawScope draw = fx.BeginDraw();
|
||||||
driver.BeginFrame(ctx, Matrix4x4.Identity, Vector3.Zero);
|
driver.BeginFrame(ctx, Matrix4x4.Identity, Vector3.Zero);
|
||||||
|
var activeViews = new WalkPortalView();
|
||||||
|
WalkCopyView.AppendFullViewportQuad(
|
||||||
|
activeViews,
|
||||||
|
ctx.Rays,
|
||||||
|
ctx.WorldViewpoint,
|
||||||
|
ctx.ViewportWidth,
|
||||||
|
ctx.ViewportHeight);
|
||||||
|
((IWalkEventSink)driver).OnLandscapeViews(activeViews);
|
||||||
((IWalkEventSink)driver).OnLandscapeCellTurn(0x8C040005u);
|
((IWalkEventSink)driver).OnLandscapeCellTurn(0x8C040005u);
|
||||||
Assert.Empty(log); // no GPU work at Collect time; nothing recorded to the log yet
|
Assert.Empty(log); // no GPU work at Collect time; nothing recorded to the log yet
|
||||||
driver.EndFrame();
|
driver.EndFrame();
|
||||||
|
|
@ -615,9 +629,13 @@ public sealed class WalkFrameDriverTests
|
||||||
|
|
||||||
// FW4 #132 positional invariant: the cell's emitters submit at its
|
// FW4 #132 positional invariant: the cell's emitters submit at its
|
||||||
// own landscape turn, after its meshes flush.
|
// own landscape turn, after its meshes flush.
|
||||||
Assert.Equal(new[] { "FLUSH:1:OutdoorStatic", "PARTICLES:12d" }, log);
|
Assert.Equal(
|
||||||
GpuRecordedMultiDrawIndirect mdi = Assert.Single(fx.Device.Calls.OfType<GpuRecordedMultiDrawIndirect>());
|
new[] { "FLUSH:2:OutdoorStatic,Dynamic", "PARTICLES:12d,12e" },
|
||||||
Assert.Equal(1u, mdi.DrawCount);
|
log);
|
||||||
|
GpuRecordedMultiDrawIndirect[] mdi =
|
||||||
|
fx.Device.Calls.OfType<GpuRecordedMultiDrawIndirect>().ToArray();
|
||||||
|
Assert.Equal(2, mdi.Length);
|
||||||
|
Assert.Equal(2u, mdi.Aggregate(0u, static (sum, call) => sum + call.DrawCount));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Fixture (mirrors WalkStaticStreamPopulatorTests' DispatcherFixture —
|
// ── Fixture (mirrors WalkStaticStreamPopulatorTests' DispatcherFixture —
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue