fix(render): restore landscape objects and walk alpha order
This commit is contained in:
parent
4683ac6f72
commit
e880860291
5 changed files with 275 additions and 21 deletions
|
|
@ -177,7 +177,10 @@ public sealed class RetailFrameWalk
|
|||
// outdoor-static turn.
|
||||
if (block.CellBuildings[cellIndex] is WalkBuilding building)
|
||||
DrawBuilding(building, activeViews, ctx, sink);
|
||||
sink.OnLandscapeCellTurn((block.LandblockId & 0xFFFF0000u) | (uint)(cellIndex + 1));
|
||||
sink.OnLandscapeCellTurn(
|
||||
block.LandblockId,
|
||||
block.SideCellCount,
|
||||
cellIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -99,6 +99,19 @@ public interface IWalkEventSink
|
|||
/// </summary>
|
||||
void OnLandscapeCellTurn(uint cellId) { }
|
||||
|
||||
/// <summary>
|
||||
/// The LOD-aware sibling of <see cref="OnLandscapeCellTurn(uint)"/>.
|
||||
/// <paramref name="cellIndex"/> names one cell in the block's CURRENT
|
||||
/// <paramref name="sideCellCount"/>×<paramref name="sideCellCount"/>
|
||||
/// coarse terrain grid. Retail attaches every outdoor object's shadow to
|
||||
/// that coarse <c>CObjCell</c>; acdream retains the authoritative 8×8
|
||||
/// owner buckets, so production consumers expand this turn back to the
|
||||
/// covered original cells. The default preserves the FW1 vocabulary and
|
||||
/// every diagnostic sink that only needs the coarse turn.</summary>
|
||||
void OnLandscapeCellTurn(uint landblockId, int sideCellCount, int cellIndex) =>
|
||||
OnLandscapeCellTurn(
|
||||
(landblockId & 0xFFFF0000u) | checked((uint)(cellIndex + 1)));
|
||||
|
||||
/// <summary>
|
||||
/// Fires at <see cref="RetailFrameWalk.DrawBuilding"/> once retail's own
|
||||
/// gate has passed — <c>RenderDeviceD3D::DrawBuilding</c> @0x0059f2a0
|
||||
|
|
|
|||
|
|
@ -223,6 +223,13 @@ internal enum WalkFrameEventKind : byte
|
|||
/// was recorded.</summary>
|
||||
StreamMark,
|
||||
|
||||
/// <summary>Submits every retained translucent walk batch through
|
||||
/// <see cref="WbDrawDispatcher.SubmitWalkAlphaInstance"/> up to the
|
||||
/// exclusive-end index in <see cref="WalkFrameEvent.IntArg"/>. This is a
|
||||
/// replay-time event: collecting the complete frame must not make alpha
|
||||
/// from a later/near cell visible to an earlier building barrier.</summary>
|
||||
AlphaSubmitMark,
|
||||
|
||||
/// <summary><see cref="IWalkFrameLeafRenderer.DrawSky"/>.</summary>
|
||||
Sky,
|
||||
|
||||
|
|
@ -351,6 +358,9 @@ internal readonly struct WalkFrameEvent
|
|||
internal static WalkFrameEvent Mark(int exclusiveEnd) =>
|
||||
new(WalkFrameEventKind.StreamMark, exclusiveEnd, 0, 0f, null);
|
||||
|
||||
internal static WalkFrameEvent AlphaSubmitMark(int exclusiveEnd) =>
|
||||
new(WalkFrameEventKind.AlphaSubmitMark, exclusiveEnd, 0, 0f, null);
|
||||
|
||||
internal static WalkFrameEvent Sky() =>
|
||||
new(WalkFrameEventKind.Sky, 0, 0, 0f, null);
|
||||
|
||||
|
|
@ -472,6 +482,8 @@ internal sealed class WalkFrameDriver : IWalkEventSink, IWalkLookInViewSource
|
|||
private readonly OrderedDrawStream _stream = new();
|
||||
private readonly List<WalkFrameEvent> _events = new();
|
||||
private readonly List<int> _markPositions = new();
|
||||
private readonly List<WbDrawDispatcher.WalkClassifiedBatch> _alphaSubmissions = new();
|
||||
private int _alphaSubmitMark;
|
||||
|
||||
// Campaign FW3.4a: visited-set collection, absorbed from the renderer's
|
||||
// former dedicated set-collecting sink (RetailPViewRenderer's old
|
||||
|
|
@ -603,6 +615,8 @@ internal sealed class WalkFrameDriver : IWalkEventSink, IWalkLookInViewSource
|
|||
_stream.Reset();
|
||||
_events.Clear();
|
||||
_markPositions.Clear();
|
||||
_alphaSubmissions.Clear();
|
||||
_alphaSubmitMark = 0;
|
||||
VisitedCells.Clear();
|
||||
LookInCellTurns.Clear();
|
||||
_lookInTurns.Clear();
|
||||
|
|
@ -736,6 +750,8 @@ internal sealed class WalkFrameDriver : IWalkEventSink, IWalkLookInViewSource
|
|||
_stream.Reset();
|
||||
_events.Clear();
|
||||
_markPositions.Clear();
|
||||
_alphaSubmissions.Clear();
|
||||
_alphaSubmitMark = 0;
|
||||
VisitedCells.Clear();
|
||||
LookInCellTurns.Clear();
|
||||
_lookInTurns.Clear();
|
||||
|
|
@ -764,6 +780,7 @@ internal sealed class WalkFrameDriver : IWalkEventSink, IWalkLookInViewSource
|
|||
try
|
||||
{
|
||||
MarkIfGrown();
|
||||
MarkAlphaIfGrown();
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
@ -804,6 +821,7 @@ internal sealed class WalkFrameDriver : IWalkEventSink, IWalkLookInViewSource
|
|||
_dispatcher.PrepareOrderedStream(frame, _stream, _viewProjection, _markPositions);
|
||||
|
||||
int cursor = 0;
|
||||
int alphaCursor = 0;
|
||||
for (int i = 0; i < _events.Count; i++)
|
||||
{
|
||||
WalkFrameEvent e = _events[i];
|
||||
|
|
@ -817,6 +835,18 @@ internal sealed class WalkFrameDriver : IWalkEventSink, IWalkLookInViewSource
|
|||
_dispatcher.DrawOrderedRange(encoder, cursor, count);
|
||||
cursor = end;
|
||||
break;
|
||||
case WalkFrameEventKind.AlphaSubmitMark:
|
||||
int alphaEnd = e.IntArg;
|
||||
for (; alphaCursor < alphaEnd; alphaCursor++)
|
||||
{
|
||||
WbDrawDispatcher.WalkClassifiedBatch batch =
|
||||
_alphaSubmissions[alphaCursor];
|
||||
_dispatcher.SubmitWalkAlphaInstance(
|
||||
in batch,
|
||||
_cameraWorldPosition,
|
||||
_viewProjection);
|
||||
}
|
||||
break;
|
||||
case WalkFrameEventKind.Sky:
|
||||
_leafRenderer.DrawSky();
|
||||
break;
|
||||
|
|
@ -883,6 +913,8 @@ internal sealed class WalkFrameDriver : IWalkEventSink, IWalkLookInViewSource
|
|||
_stream.Reset();
|
||||
_events.Clear();
|
||||
_markPositions.Clear();
|
||||
_alphaSubmissions.Clear();
|
||||
_alphaSubmitMark = 0;
|
||||
_readyToReplay = false;
|
||||
_ctx = null;
|
||||
}
|
||||
|
|
@ -916,6 +948,50 @@ internal sealed class WalkFrameDriver : IWalkEventSink, IWalkLookInViewSource
|
|||
}
|
||||
|
||||
void IWalkEventSink.OnLandscapeCellTurn(uint cellId)
|
||||
=> HandleLandscapeCellTurn(cellId);
|
||||
|
||||
void IWalkEventSink.OnLandscapeCellTurn(
|
||||
uint landblockId,
|
||||
int sideCellCount,
|
||||
int cellIndex)
|
||||
{
|
||||
if (sideCellCount is not (1 or 2 or 4 or 8))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(
|
||||
nameof(sideCellCount),
|
||||
sideCellCount,
|
||||
"A landscape LOD grid must be 1, 2, 4, or 8 cells per side.");
|
||||
}
|
||||
if ((uint)cellIndex >= (uint)(sideCellCount * sideCellCount))
|
||||
throw new ArgumentOutOfRangeException(nameof(cellIndex));
|
||||
|
||||
uint blockPrefix = landblockId & 0xFFFF0000u;
|
||||
if (sideCellCount == 8)
|
||||
{
|
||||
HandleLandscapeCellTurn(blockPrefix | checked((uint)(cellIndex + 1)));
|
||||
return;
|
||||
}
|
||||
|
||||
// CLandBlock's coarse DrawSortCell owns the shadows from every
|
||||
// original 24 m land cell it covers. Our render journal deliberately
|
||||
// retains those authoritative 8x8 owner buckets, so turn all covered
|
||||
// buckets here while preserving the coarse cell's single walk turn.
|
||||
int span = 8 / sideCellCount;
|
||||
int coarseX = cellIndex / sideCellCount;
|
||||
int coarseY = cellIndex % sideCellCount;
|
||||
int firstX = coarseX * span;
|
||||
int firstY = coarseY * span;
|
||||
for (int x = firstX; x < firstX + span; x++)
|
||||
{
|
||||
for (int y = firstY; y < firstY + span; y++)
|
||||
{
|
||||
HandleLandscapeCellTurn(
|
||||
blockPrefix | checked((uint)(x * 8 + y + 1)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleLandscapeCellTurn(uint cellId)
|
||||
{
|
||||
RequireOpenFrame();
|
||||
if (_landscapeViewRouteIndex < 0)
|
||||
|
|
@ -930,11 +1006,18 @@ internal sealed class WalkFrameDriver : IWalkEventSink, IWalkLookInViewSource
|
|||
_populator.PopulateOutdoorStatics(
|
||||
_stream, cellId, records.Records, records.TupleLandblockId,
|
||||
_cameraWorldPosition, _viewProjection,
|
||||
this, _landscapeViewRouteIndex, _outdoorDrawnThisFrame);
|
||||
this, _landscapeViewRouteIndex, _outdoorDrawnThisFrame,
|
||||
_alphaSubmissions);
|
||||
_populator.PopulateCellDynamics(
|
||||
_stream, cellId, dynamics.Records, dynamics.TupleLandblockId,
|
||||
_cameraWorldPosition, _viewProjection,
|
||||
this, _landscapeViewRouteIndex, _outdoorDrawnThisFrame);
|
||||
this, _landscapeViewRouteIndex, _outdoorDrawnThisFrame,
|
||||
_alphaSubmissions);
|
||||
if (_alphaSubmissions.Count != _alphaSubmitMark)
|
||||
{
|
||||
MarkIfGrown();
|
||||
MarkAlphaIfGrown();
|
||||
}
|
||||
// FW4 (the #132 positional invariant): this cell's emitter owners
|
||||
// submit AT THIS TURN, so nearer buildings' pre-punch barriers
|
||||
// drain them against still-true depth — see
|
||||
|
|
@ -977,6 +1060,7 @@ internal sealed class WalkFrameDriver : IWalkEventSink, IWalkLookInViewSource
|
|||
// look-ins) follows this call; the building's own shell content is
|
||||
// appended only once that pass completes (OnBuildingShellTurn).
|
||||
MarkIfGrown();
|
||||
MarkAlphaIfGrown();
|
||||
_events.Add(WalkFrameEvent.AlphaBarrier());
|
||||
|
||||
_currentDcStage = WalkDrawStage.LookInStatic;
|
||||
|
|
@ -996,7 +1080,13 @@ internal sealed class WalkFrameDriver : IWalkEventSink, IWalkLookInViewSource
|
|||
WalkFrameStaticRecords shell = _worldData.GetBuildingShellStatics(building);
|
||||
_populator.PopulateCell(
|
||||
_stream, WalkDrawStage.BuildingShell, building.PositionCellId,
|
||||
shell.Records, shell.TupleLandblockId, _cameraWorldPosition, _viewProjection);
|
||||
shell.Records, shell.TupleLandblockId, _cameraWorldPosition, _viewProjection,
|
||||
alphaSubmissions: _alphaSubmissions);
|
||||
if (_alphaSubmissions.Count != _alphaSubmitMark)
|
||||
{
|
||||
MarkIfGrown();
|
||||
MarkAlphaIfGrown();
|
||||
}
|
||||
// FW4 (the #132 positional invariant): the building's own shell
|
||||
// emitters submit at the shell turn, after the shell content
|
||||
// flushes — see WalkFrameEventKind.StaticParticles.
|
||||
|
|
@ -1166,8 +1256,9 @@ internal sealed class WalkFrameDriver : IWalkEventSink, IWalkLookInViewSource
|
|||
_populator.PopulateCell(
|
||||
_stream, stage, cellId, records.Records, records.TupleLandblockId,
|
||||
_cameraWorldPosition, _viewProjection,
|
||||
this, viewRouteIndex);
|
||||
this, viewRouteIndex, _alphaSubmissions);
|
||||
MarkIfGrown();
|
||||
MarkAlphaIfGrown();
|
||||
|
||||
if (stage == WalkDrawStage.LookInStatic)
|
||||
{
|
||||
|
|
@ -1184,8 +1275,10 @@ internal sealed class WalkFrameDriver : IWalkEventSink, IWalkLookInViewSource
|
|||
_cameraWorldPosition,
|
||||
_viewProjection,
|
||||
this,
|
||||
viewRouteIndex);
|
||||
viewRouteIndex,
|
||||
alphaSubmissions: _alphaSubmissions);
|
||||
MarkIfGrown();
|
||||
MarkAlphaIfGrown();
|
||||
if (HasAnyOwner(records) || HasAnyOwner(dynamics))
|
||||
_events.Add(WalkFrameEvent.CellParticles(cellId));
|
||||
}
|
||||
|
|
@ -1364,6 +1457,20 @@ internal sealed class WalkFrameDriver : IWalkEventSink, IWalkLookInViewSource
|
|||
_events.Add(WalkFrameEvent.Mark(count));
|
||||
}
|
||||
|
||||
/// <summary>Records translucent walk batches at the same temporal point
|
||||
/// as their cell turn. Replay submits them only when it reaches this
|
||||
/// marker, so an earlier building's full alpha barrier cannot see content
|
||||
/// that the far-to-near walk has not reached yet.</summary>
|
||||
private void MarkAlphaIfGrown()
|
||||
{
|
||||
int count = _alphaSubmissions.Count;
|
||||
if (count == _alphaSubmitMark)
|
||||
return;
|
||||
|
||||
_alphaSubmitMark = count;
|
||||
_events.Add(WalkFrameEvent.AlphaSubmitMark(count));
|
||||
}
|
||||
|
||||
private IWalkBuildingFrameContext RequireOpenFrame() =>
|
||||
_ctx ?? throw new InvalidOperationException(
|
||||
"WalkFrameDriver received a walk turn outside BeginFrame/EndFrame — call "
|
||||
|
|
|
|||
|
|
@ -66,7 +66,8 @@ internal sealed class WalkStaticStreamPopulator
|
|||
Vector3 cameraWorldPosition,
|
||||
Matrix4x4 viewProjection,
|
||||
IWalkLookInViewSource? views = null,
|
||||
int viewRouteIndex = -1)
|
||||
int viewRouteIndex = -1,
|
||||
List<WbDrawDispatcher.WalkClassifiedBatch>? alphaSubmissions = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(stream);
|
||||
for (int i = 0; i < records.Length; i++)
|
||||
|
|
@ -74,7 +75,7 @@ internal sealed class WalkStaticStreamPopulator
|
|||
ClassifyAndAppend(
|
||||
stream, stage, cellId, in records[i], tupleLandblockId,
|
||||
cameraWorldPosition, viewProjection,
|
||||
liveDynamic: false, views, viewRouteIndex);
|
||||
liveDynamic: false, views, viewRouteIndex, alphaSubmissions);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -98,7 +99,8 @@ internal sealed class WalkStaticStreamPopulator
|
|||
Matrix4x4 viewProjection,
|
||||
IWalkLookInViewSource? views = null,
|
||||
int viewRouteIndex = -1,
|
||||
ISet<RenderProjectionId>? drawnOnce = null)
|
||||
ISet<RenderProjectionId>? drawnOnce = null,
|
||||
List<WbDrawDispatcher.WalkClassifiedBatch>? alphaSubmissions = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(stream);
|
||||
for (int i = 0; i < records.Length; i++)
|
||||
|
|
@ -108,7 +110,7 @@ internal sealed class WalkStaticStreamPopulator
|
|||
ClassifyAndAppend(
|
||||
stream, WalkDrawStage.OutdoorStatic, cellId, in records[i],
|
||||
tupleLandblockId, cameraWorldPosition, viewProjection,
|
||||
liveDynamic: false, views, viewRouteIndex);
|
||||
liveDynamic: false, views, viewRouteIndex, alphaSubmissions);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -121,7 +123,8 @@ internal sealed class WalkStaticStreamPopulator
|
|||
Matrix4x4 viewProjection,
|
||||
IWalkLookInViewSource? lookInViews = null,
|
||||
int lookInRouteIndex = -1,
|
||||
ISet<RenderProjectionId>? drawnOnce = null)
|
||||
ISet<RenderProjectionId>? drawnOnce = null,
|
||||
List<WbDrawDispatcher.WalkClassifiedBatch>? alphaSubmissions = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(stream);
|
||||
for (int i = 0; i < records.Length; i++)
|
||||
|
|
@ -138,7 +141,8 @@ internal sealed class WalkStaticStreamPopulator
|
|||
viewProjection,
|
||||
liveDynamic: true,
|
||||
lookInViews,
|
||||
lookInRouteIndex);
|
||||
lookInRouteIndex,
|
||||
alphaSubmissions);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -152,7 +156,8 @@ internal sealed class WalkStaticStreamPopulator
|
|||
Matrix4x4 viewProjection,
|
||||
bool liveDynamic = false,
|
||||
IWalkLookInViewSource? lookInViews = null,
|
||||
int lookInRouteIndex = -1)
|
||||
int lookInRouteIndex = -1,
|
||||
List<WbDrawDispatcher.WalkClassifiedBatch>? alphaSubmissions = null)
|
||||
{
|
||||
_batchScratch.Clear();
|
||||
_selectionScratch.Clear();
|
||||
|
|
@ -178,8 +183,15 @@ internal sealed class WalkStaticStreamPopulator
|
|||
}
|
||||
else
|
||||
{
|
||||
_dispatcher.SubmitWalkAlphaInstance(
|
||||
in batch, cameraWorldPosition, viewProjection);
|
||||
if (alphaSubmissions is null)
|
||||
{
|
||||
_dispatcher.SubmitWalkAlphaInstance(
|
||||
in batch, cameraWorldPosition, viewProjection);
|
||||
}
|
||||
else
|
||||
{
|
||||
alphaSubmissions.Add(batch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -40,10 +40,13 @@ public sealed class WalkFrameDriverTests
|
|||
// interleave (stream flushes interleaved with sky/terrain/shell/punch/
|
||||
// alpha-barrier), not just each half in isolation. ─────────────────────
|
||||
|
||||
private sealed class RecordingLeafRenderer(List<string> log) : IWalkFrameLeafRenderer
|
||||
private sealed class RecordingLeafRenderer(
|
||||
List<string> log,
|
||||
RetailAlphaQueue? alpha = null) : IWalkFrameLeafRenderer
|
||||
{
|
||||
public readonly List<WalkPolygon> Punches = new();
|
||||
public readonly List<(uint CellId, uint ClipSlot)> Shells = new();
|
||||
public readonly List<int> AlphaPendingAtBarrier = new();
|
||||
|
||||
public void DrawSky() => log.Add("SKY");
|
||||
|
||||
|
|
@ -65,7 +68,18 @@ public sealed class WalkFrameDriverTests
|
|||
log.Add($"PUNCH:{worldPolygon.Vertices.Length}@v{activeViewIndex}");
|
||||
}
|
||||
|
||||
public void AlphaBarrier() => log.Add("ALPHA");
|
||||
public void AlphaBarrier()
|
||||
{
|
||||
if (alpha is null)
|
||||
{
|
||||
log.Add("ALPHA");
|
||||
return;
|
||||
}
|
||||
|
||||
AlphaPendingAtBarrier.Add(alpha.PendingCount);
|
||||
log.Add($"ALPHA:{alpha.PendingCount}");
|
||||
alpha.Flush();
|
||||
}
|
||||
|
||||
public void DrawStaticParticles(IReadOnlySet<uint> ownerIds)
|
||||
{
|
||||
|
|
@ -762,6 +776,89 @@ public sealed class WalkFrameDriverTests
|
|||
Assert.Equal(1u, mdi[0].DrawCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CoarseLandscapeCellTurn_ExpandsToEveryCoveredAuthoritativeOwnerCell()
|
||||
{
|
||||
using var fx = new DispatcherFixture();
|
||||
const ulong gfxObj = 0x0200_0022UL;
|
||||
InjectRenderData(fx.Manager, gfxObj, MakeFlatMesh(
|
||||
MakeBatch(0x08100022u, TranslucencyKind.Opaque, 0, 0, 3, 1)));
|
||||
|
||||
var worldData = new FakeWorldData();
|
||||
worldData.OutdoorStaticsByCell[0xE43D0040u] = new WalkFrameStaticRecords(
|
||||
new[] { MakeRecord(304, 0, Vector3.Zero, [new MeshRef((uint)gfxObj, Matrix4x4.Identity)]) },
|
||||
0xE43Du);
|
||||
var driver = new WalkFrameDriver(
|
||||
fx.Dispatcher,
|
||||
new RecordingLeafRenderer(new List<string>()),
|
||||
worldData);
|
||||
var ctx = new TestContext();
|
||||
|
||||
using DrawScope draw = fx.BeginDraw();
|
||||
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);
|
||||
// A 2x2 coarse grid's cell 3 covers authoritative x/y cells 4..7,
|
||||
// including original cell 64. The old cellIndex+1 lookup only asked
|
||||
// for ids 1..4 and silently lost this record.
|
||||
((IWalkEventSink)driver).OnLandscapeCellTurn(0xE43DFFFFu, 2, 3);
|
||||
driver.EndFrame();
|
||||
driver.Replay(draw.Frame, draw.Pass);
|
||||
|
||||
Assert.Contains(0xE43D0040u, driver.VisitedLandscapeCellIds);
|
||||
GpuRecordedMultiDrawIndirect call = Assert.Single(
|
||||
fx.Device.Calls.OfType<GpuRecordedMultiDrawIndirect>());
|
||||
Assert.Equal(1u, call.DrawCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Collect_DoesNotExposeNearAlphaToEarlierBuildingBarrier()
|
||||
{
|
||||
using var fx = new DispatcherFixture();
|
||||
const ulong gfxObj = 0x0200_0023UL;
|
||||
InjectRenderData(fx.Manager, gfxObj, MakeFlatMesh(
|
||||
MakeBatch(0x08100023u, TranslucencyKind.AlphaBlend, 0, 0, 3, 1)));
|
||||
|
||||
var worldData = new FakeWorldData();
|
||||
worldData.OutdoorStaticsByCell[0xE43D0001u] = new WalkFrameStaticRecords(
|
||||
new[] { MakeRecord(305, 0, new Vector3(0, 0, -20), [new MeshRef((uint)gfxObj, Matrix4x4.Identity)]) },
|
||||
0xE43Du);
|
||||
worldData.OutdoorStaticsByCell[0xE43D0002u] = new WalkFrameStaticRecords(
|
||||
new[] { MakeRecord(306, 0, new Vector3(0, 0, -2), [new MeshRef((uint)gfxObj, Matrix4x4.Identity)]) },
|
||||
0xE43Du);
|
||||
var log = new List<string>();
|
||||
var leaf = new RecordingLeafRenderer(log, fx.AlphaQueue);
|
||||
var driver = new WalkFrameDriver(fx.Dispatcher, leaf, worldData);
|
||||
var ctx = new TestContext();
|
||||
|
||||
using DrawScope draw = fx.BeginDraw(beginAlpha: true);
|
||||
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(0xE43D0001u);
|
||||
((IWalkEventSink)driver).OnBuildingTurn(new WalkBuilding());
|
||||
((IWalkEventSink)driver).OnLandscapeCellTurn(0xE43D0002u);
|
||||
driver.EndFrame();
|
||||
|
||||
Assert.Equal(0, fx.AlphaQueue.PendingCount);
|
||||
driver.Replay(draw.Frame, draw.Pass);
|
||||
|
||||
Assert.Equal(new[] { 1 }, leaf.AlphaPendingAtBarrier);
|
||||
Assert.Equal(1, fx.AlphaQueue.PendingCount);
|
||||
}
|
||||
|
||||
// ── Fixture (mirrors WalkStaticStreamPopulatorTests' DispatcherFixture —
|
||||
// FW3.2a's own referee) ─────────────────────────────────────────────────
|
||||
|
||||
|
|
@ -838,12 +935,18 @@ public sealed class WalkFrameDriverTests
|
|||
{
|
||||
private readonly IDisposable _publication;
|
||||
private readonly IGpuPassEncoder _pass;
|
||||
private readonly RetailAlphaQueue? _alpha;
|
||||
|
||||
public DrawScope(IGpuFrame frame, IGpuPassEncoder pass, IDisposable publication)
|
||||
public DrawScope(
|
||||
IGpuFrame frame,
|
||||
IGpuPassEncoder pass,
|
||||
IDisposable publication,
|
||||
RetailAlphaQueue? alpha = null)
|
||||
{
|
||||
Frame = frame;
|
||||
_pass = pass;
|
||||
_publication = publication;
|
||||
_alpha = alpha;
|
||||
}
|
||||
|
||||
public IGpuFrame Frame { get; }
|
||||
|
|
@ -852,6 +955,8 @@ public sealed class WalkFrameDriverTests
|
|||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_alpha?.IsCollecting == true)
|
||||
_alpha.EndFrame();
|
||||
_publication.Dispose();
|
||||
_pass.Dispose();
|
||||
}
|
||||
|
|
@ -886,7 +991,8 @@ public sealed class WalkFrameDriverTests
|
|||
_meshAdapter,
|
||||
entitySpawnAdapter,
|
||||
new EntityClassificationCache(),
|
||||
new AcDream.Core.Rendering.TranslucencyFadeManager());
|
||||
new AcDream.Core.Rendering.TranslucencyFadeManager(),
|
||||
alphaQueue: AlphaQueue);
|
||||
}
|
||||
|
||||
public RecordingGpuDevice Device { get; }
|
||||
|
|
@ -897,10 +1003,17 @@ public sealed class WalkFrameDriverTests
|
|||
|
||||
public WbDrawDispatcher Dispatcher { get; }
|
||||
|
||||
public RetailAlphaQueue AlphaQueue { get; } = new();
|
||||
|
||||
public ObjectMeshManager Manager => _meshAdapter.MeshManager!;
|
||||
|
||||
public DrawScope BeginDraw()
|
||||
public DrawScope BeginDraw(bool beginAlpha = false)
|
||||
{
|
||||
if (beginAlpha)
|
||||
{
|
||||
Dispatcher.BeginFrame(frameSlot: 0);
|
||||
AlphaQueue.BeginFrame();
|
||||
}
|
||||
FrameLifetime.BeginFrame();
|
||||
IGpuFrame frame = FrameLifetime.CurrentFrame!;
|
||||
IGpuPassEncoder pass = frame.BeginPass(
|
||||
|
|
@ -908,11 +1021,17 @@ public sealed class WalkFrameDriverTests
|
|||
"fw3-2b-1-walk-frame-driver-test", Vector4.Zero, sampleCount: 1));
|
||||
IDisposable publication = Scope.Publish(pass);
|
||||
Device.Clear();
|
||||
return new DrawScope(frame, pass, publication);
|
||||
return new DrawScope(
|
||||
frame,
|
||||
pass,
|
||||
publication,
|
||||
beginAlpha ? AlphaQueue : null);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (AlphaQueue.IsCollecting)
|
||||
AlphaQueue.AbortFrame();
|
||||
Dispatcher.Dispose();
|
||||
_meshAdapter.Dispose();
|
||||
_textures.Dispose();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue