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
|
|
@ -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