fix(rendering): port retail shared alpha list
Queue translucent world GfxObj batches and scene particles in one stable far-to-near stream using transformed DAT sort centers, then drain it at retail's landscape and final-world boundaries. Preserve authored blend, cull, lighting, opacity, and adjacent-only batching so particles behind lifestones are composited through the crystal instead of overpainting it. Release build succeeds and all 5,914 tests pass with five intentional skips. Co-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
parent
ec1bb19609
commit
6b0472ee32
14 changed files with 1083 additions and 34 deletions
128
tests/AcDream.App.Tests/Rendering/RetailAlphaQueueTests.cs
Normal file
128
tests/AcDream.App.Tests/Rendering/RetailAlphaQueueTests.cs
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
using System.Numerics;
|
||||
using AcDream.App.Rendering;
|
||||
|
||||
namespace AcDream.App.Tests.Rendering;
|
||||
|
||||
public sealed class RetailAlphaQueueTests
|
||||
{
|
||||
[Fact]
|
||||
public void Flush_InterleavesRenderersInDescendingCyptOrder()
|
||||
{
|
||||
var log = new List<string>();
|
||||
var objects = new RecordingSource("object", log);
|
||||
var particles = new RecordingSource("particle", log);
|
||||
var queue = new RetailAlphaQueue();
|
||||
|
||||
queue.BeginFrame();
|
||||
queue.Submit(objects, token: 0, viewerDistance: 10f); // near crystal
|
||||
queue.Submit(particles, token: 0, viewerDistance: 30f); // far smoke
|
||||
queue.Submit(objects, token: 1, viewerDistance: 25f);
|
||||
queue.Submit(particles, token: 1, viewerDistance: 20f);
|
||||
queue.Submit(objects, token: 2, viewerDistance: 5f);
|
||||
queue.EndFrame();
|
||||
|
||||
Assert.Equal(
|
||||
new[]
|
||||
{
|
||||
"particle:0",
|
||||
"object:1",
|
||||
"particle:1",
|
||||
"object:0",
|
||||
"object:2",
|
||||
},
|
||||
log);
|
||||
Assert.Equal(1, objects.ResetCount);
|
||||
Assert.Equal(1, particles.ResetCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Flush_EqualDistancePreservesSubmissionOrderAcrossRenderers()
|
||||
{
|
||||
var log = new List<string>();
|
||||
var objects = new RecordingSource("object", log);
|
||||
var particles = new RecordingSource("particle", log);
|
||||
var queue = new RetailAlphaQueue();
|
||||
|
||||
queue.BeginFrame();
|
||||
queue.Submit(objects, 7, 12f);
|
||||
queue.Submit(particles, 4, 12f);
|
||||
queue.Submit(objects, 8, 12f);
|
||||
queue.EndFrame();
|
||||
|
||||
Assert.Equal(new[] { "object:7", "particle:4", "object:8" }, log);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Flush_KeepsFrameOpenForPostDepthClearScope()
|
||||
{
|
||||
var log = new List<string>();
|
||||
var source = new RecordingSource("alpha", log);
|
||||
var queue = new RetailAlphaQueue();
|
||||
|
||||
queue.BeginFrame();
|
||||
queue.Submit(source, 1, 8f);
|
||||
queue.Flush();
|
||||
|
||||
Assert.True(queue.IsCollecting);
|
||||
Assert.Equal(0, queue.PendingCount);
|
||||
queue.Submit(source, 2, 3f);
|
||||
queue.EndFrame();
|
||||
|
||||
Assert.False(queue.IsCollecting);
|
||||
Assert.Equal(new[] { "alpha:1", "alpha:2" }, log);
|
||||
Assert.Equal(2, source.ResetCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Flush_BatchesOnlyAdjacentEntriesFromSameRenderer()
|
||||
{
|
||||
var log = new List<string>();
|
||||
var objects = new RecordingSource("object", log);
|
||||
var particles = new RecordingSource("particle", log);
|
||||
var queue = new RetailAlphaQueue();
|
||||
|
||||
queue.BeginFrame();
|
||||
queue.Submit(objects, 0, 40f);
|
||||
queue.Submit(objects, 1, 35f);
|
||||
queue.Submit(particles, 0, 30f);
|
||||
queue.Submit(objects, 2, 25f);
|
||||
queue.EndFrame();
|
||||
|
||||
Assert.Equal(new[] { 2, 1 }, objects.BatchSizes);
|
||||
Assert.Equal(new[] { 1 }, particles.BatchSizes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComputeViewerDistance_UsesTransformedGfxSortCenter()
|
||||
{
|
||||
// Retail CPhysicsPart::UpdateViewerDistance 0x0050E030 applies scale,
|
||||
// orientation, and translation to gfxobj.sort_center before CYpt.
|
||||
var model = Matrix4x4.CreateScale(2f)
|
||||
* Matrix4x4.CreateRotationZ(MathF.PI / 2f)
|
||||
* Matrix4x4.CreateTranslation(10f, 20f, 30f);
|
||||
Vector3 localSortCenter = new(1f, 0f, 0f);
|
||||
Vector3 camera = new(10f, 20f, 30f);
|
||||
|
||||
float cypt = RetailAlphaOrdering.ComputeViewerDistance(
|
||||
localSortCenter,
|
||||
model,
|
||||
camera);
|
||||
|
||||
Assert.Equal(2f, cypt, precision: 5);
|
||||
}
|
||||
|
||||
private sealed class RecordingSource(string name, List<string> log) : IRetailAlphaDrawSource
|
||||
{
|
||||
public List<int> BatchSizes { get; } = new();
|
||||
public int ResetCount { get; private set; }
|
||||
|
||||
public void DrawAlphaBatch(ReadOnlySpan<int> tokens)
|
||||
{
|
||||
BatchSizes.Add(tokens.Length);
|
||||
foreach (int token in tokens)
|
||||
log.Add($"{name}:{token}");
|
||||
}
|
||||
|
||||
public void ResetAlphaSubmissions() => ResetCount++;
|
||||
}
|
||||
}
|
||||
|
|
@ -7,8 +7,9 @@ namespace AcDream.App.Tests.Rendering.Wb;
|
|||
public class InstanceGroupClearTests
|
||||
{
|
||||
// #193 (regression from #188, 2026-07-09): WbDrawDispatcher's InstanceGroup holds
|
||||
// six per-instance parallel lists — Matrices, Slots, LightSets, IndoorFlags,
|
||||
// Opacities, SelectionLighting — appended in lockstep (one entry per drawn instance) every frame. The
|
||||
// seven per-instance parallel lists — Matrices, LocalSortCenters, Slots,
|
||||
// LightSets, IndoorFlags, Opacities, SelectionLighting — appended in lockstep
|
||||
// (one entry per drawn instance) every frame. The
|
||||
// per-frame reset must clear ALL of them. #188 added Opacities but left it out of
|
||||
// the inline clear loop, so it grew one float per instance per frame forever; as
|
||||
// List<float>'s backing array doubled it produced ~128 MB / ~512 MB LOH float[]
|
||||
|
|
@ -19,6 +20,7 @@ public class InstanceGroupClearTests
|
|||
{
|
||||
var grp = new WbDrawDispatcher.InstanceGroup();
|
||||
grp.Matrices.Add(Matrix4x4.Identity);
|
||||
grp.LocalSortCenters.Add(Vector3.Zero);
|
||||
grp.Slots.Add(1u);
|
||||
grp.LightSets.Add(-1);
|
||||
grp.IndoorFlags.Add(0u);
|
||||
|
|
@ -28,6 +30,7 @@ public class InstanceGroupClearTests
|
|||
grp.ClearPerInstanceData();
|
||||
|
||||
Assert.Empty(grp.Matrices);
|
||||
Assert.Empty(grp.LocalSortCenters);
|
||||
Assert.Empty(grp.Slots);
|
||||
Assert.Empty(grp.LightSets);
|
||||
Assert.Empty(grp.IndoorFlags);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue