feat(overhaul): select pack shadows from retail visibility
Borrow the exact prior-completed landscape visibility transaction and S2 CELLARRAY owner for the opt-in IA-24 directional-shadow pack. Select terrain by authored 1..64 cells, ordinary casters by CELLARRAY, and buildings by outdoor EffectCellId with no fallback. Keep retained caster/material/terrain topology stable across visibility-only frames. Publish exact arbitrary active instance runs and bounded terrain commands through separate selection sequences; preserve transform-journal, fade/retry, deferral, shader/RHI, ordinary world, and pack-off behavior. Amend IA-24 and the S5 ledger. Pre-commit gates: Release solution build 0 warnings/0 errors; focused visibility/frame/caster/prepared/GPU/terrain/pack lane 137/137; warmed caster/prepared/terrain selectors 0 B; git diff --check clean. Official hermetic and InstalledDat evidence intentionally run post-commit from this exact clean tree. Mutation evidence (each restored exactly): (1) CELLARRAY->Parent first failed PriorLandscapeSelection expected [201,202,203,205], actual [204,205]. (2) all resident terrain first failed Assert.Single with 3 commands. (3) building EffectCell->anchor first failed expected trailing 205, actual 206. (4) admit missing membership first failed with extra 204. (5) completed->building scratch first failed completed-view Assert.True, expected true/actual false. (6) selection advanced BuildSequence first failed expected 1/actual 2. (7) alternating->prefix first failed active command count expected 3/actual 1. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
5c106bcdff
commit
a4de2efc4e
19 changed files with 1198 additions and 100 deletions
|
|
@ -1,5 +1,7 @@
|
|||
using System.Numerics;
|
||||
using AcDream.App.Rendering;
|
||||
using AcDream.App.Rendering.Scene;
|
||||
using AcDream.App.Rendering.Vfx;
|
||||
using AcDream.Core.World;
|
||||
|
||||
namespace AcDream.App.Tests.Rendering;
|
||||
|
|
@ -106,6 +108,107 @@ public sealed class DirectionalShadowCasterFrameTests
|
|||
Assert.Equal(2, frame.Stats.IndexCopies);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PriorLandscapeSelection_UsesExactCellArrayAndBuildingEffectCell()
|
||||
{
|
||||
const uint visible = 0x12340002u;
|
||||
const uint outdoorAboveTerrainRange = 0x12340041u;
|
||||
RenderProjectionRecord[] statics =
|
||||
[
|
||||
Record(201, RenderProjectionClass.OutdoorStatic),
|
||||
Record(204, RenderProjectionClass.OutdoorStatic, parentCell: visible),
|
||||
Record(205, RenderProjectionClass.OutdoorStatic, building: true,
|
||||
effectCell: visible, buildingAnchor: 0x12340100u),
|
||||
Record(206, RenderProjectionClass.OutdoorStatic, building: true,
|
||||
effectCell: 0u, buildingAnchor: visible),
|
||||
];
|
||||
RenderProjectionRecord[] dynamics =
|
||||
[
|
||||
Record(202, RenderProjectionClass.LiveDynamicRoot),
|
||||
Record(203, RenderProjectionClass.EquippedChild),
|
||||
];
|
||||
var source = new QuerySource(statics, dynamics);
|
||||
var frame = new DirectionalShadowCasterFrame();
|
||||
var membership = new RecordingMembership(
|
||||
new Dictionary<uint, IReadOnlyList<uint>>
|
||||
{
|
||||
[201] = [0x12340001u, visible],
|
||||
[202] = [visible],
|
||||
[203] = [outdoorAboveTerrainRange],
|
||||
});
|
||||
var visibleCells = new HashSet<uint>
|
||||
{
|
||||
visible,
|
||||
outdoorAboveTerrainRange,
|
||||
};
|
||||
var visibility = new RetailLandscapeVisibilityFrame(
|
||||
visibleCells,
|
||||
HasCompletedWorldView: true);
|
||||
|
||||
frame.Build(new RenderSceneQuery(source, Generation));
|
||||
ulong topologySequence = frame.BuildSequence;
|
||||
frame.Select(in visibility, membership);
|
||||
|
||||
ulong[] selected = frame.Casters.ToArray()
|
||||
.Zip(frame.SelectedCasters.ToArray())
|
||||
.Where(static pair => pair.Second)
|
||||
.Select(static pair => pair.First.Projection.Id.RawValue)
|
||||
.ToArray();
|
||||
Assert.Equal([201ul, 202ul, 203ul, 205ul], selected);
|
||||
Assert.Equal(4, frame.Stats.ActiveSelected);
|
||||
Assert.Equal(topologySequence, frame.BuildSequence);
|
||||
Assert.Equal(0x12340100u,
|
||||
frame.Casters.ToArray().Single(c => c.Projection.Id.RawValue == 205)
|
||||
.Projection.Source.BuildingShellAnchorCellId);
|
||||
Assert.DoesNotContain(204ul, selected); // ParentCell is not CELLARRAY.
|
||||
Assert.DoesNotContain(206ul, selected); // Anchor is not placement.
|
||||
|
||||
var noCompletedView = new RetailLandscapeVisibilityFrame(
|
||||
visibleCells,
|
||||
HasCompletedWorldView: false);
|
||||
frame.Build(new RenderSceneQuery(source, Generation));
|
||||
frame.Select(in noCompletedView, membership);
|
||||
Assert.Equal(topologySequence, frame.BuildSequence);
|
||||
Assert.Equal(0, frame.Stats.Classifications);
|
||||
Assert.Equal(0, frame.Stats.ActiveSelected);
|
||||
Assert.DoesNotContain(true, frame.SelectedCasters.ToArray());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WarmPriorLandscapeSelection_AllocatesZeroWithStreamingBoundedScratch()
|
||||
{
|
||||
const uint visible = 0x12340002u;
|
||||
var statics = new RenderProjectionRecord[256];
|
||||
var cells = new Dictionary<uint, IReadOnlyList<uint>>(statics.Length);
|
||||
for (int index = 0; index < statics.Length; index++)
|
||||
{
|
||||
uint entityId = checked((uint)(30_000 + index));
|
||||
statics[index] = Record(entityId, RenderProjectionClass.OutdoorStatic);
|
||||
cells.Add(entityId, index % 2 == 0 ? [visible] : [0x12340003u]);
|
||||
}
|
||||
var source = new QuerySource(statics, []);
|
||||
var frame = new DirectionalShadowCasterFrame();
|
||||
RenderSceneQuery query = new(source, Generation);
|
||||
frame.Build(in query);
|
||||
frame.Build(in query);
|
||||
var membership = new RecordingMembership(cells);
|
||||
var visibility = new RetailLandscapeVisibilityFrame(
|
||||
new HashSet<uint> { visible },
|
||||
HasCompletedWorldView: true);
|
||||
frame.Select(in visibility, membership);
|
||||
long retainedBytes = frame.RetainedScratchBytes;
|
||||
|
||||
ZeroAllocationProbe.AssertAllocatesNothing(
|
||||
"DirectionalShadowCasterFrame.Select prior completed landscape",
|
||||
() => frame.Select(in visibility, membership),
|
||||
batchSize: 256);
|
||||
|
||||
Assert.Equal(128, frame.Stats.ActiveSelected);
|
||||
Assert.Equal(retainedBytes, frame.RetainedScratchBytes);
|
||||
Assert.Equal(1ul, frame.BuildSequence);
|
||||
Assert.Equal(0, frame.Stats.Classifications);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UnchangedSecondFrame_ReusesSortedTopologyWithoutIndexCopiesOrClassification()
|
||||
{
|
||||
|
|
@ -575,6 +678,8 @@ public sealed class DirectionalShadowCasterFrameTests
|
|||
bool building = false,
|
||||
RenderProjectionFlags extraFlags = RenderProjectionFlags.None,
|
||||
uint parentCell = 0,
|
||||
uint effectCell = 0,
|
||||
uint buildingAnchor = 0,
|
||||
ulong? sortKey = null,
|
||||
RenderCasterIdentityKind casterIdentity =
|
||||
RenderCasterIdentityKind.Unclassified)
|
||||
|
|
@ -606,8 +711,8 @@ public sealed class DirectionalShadowCasterFrameTests
|
|||
: 0,
|
||||
SourceId: (uint)id,
|
||||
ParentCellId: parentCell,
|
||||
EffectCellId: 0,
|
||||
BuildingShellAnchorCellId: 0,
|
||||
EffectCellId: effectCell,
|
||||
BuildingShellAnchorCellId: buildingAnchor,
|
||||
TransformFingerprint: default,
|
||||
GeometryFingerprint: default,
|
||||
AppearanceFingerprint: default),
|
||||
|
|
@ -619,6 +724,24 @@ public sealed class DirectionalShadowCasterFrameTests
|
|||
};
|
||||
}
|
||||
|
||||
private sealed class RecordingMembership(
|
||||
IReadOnlyDictionary<uint, IReadOnlyList<uint>> cellsByEntity)
|
||||
: IDirectionalShadowCellMembership
|
||||
{
|
||||
public bool TryGetRetailCellArray(
|
||||
uint entityId,
|
||||
out IReadOnlyList<uint> cells)
|
||||
{
|
||||
if (cellsByEntity.TryGetValue(entityId, out IReadOnlyList<uint>? found))
|
||||
{
|
||||
cells = found;
|
||||
return found.Count != 0;
|
||||
}
|
||||
cells = Array.Empty<uint>();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class QuerySource : IRenderSceneQuerySource
|
||||
{
|
||||
private RenderProjectionRecord[] _statics;
|
||||
|
|
|
|||
|
|
@ -939,6 +939,84 @@ public sealed class DirectionalShadowGpuTests
|
|||
allocation => allocation.Usage == GpuRingUsage.Indirect);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ActiveAlternatingRuns_UploadOnlySelectedTransformAddresses()
|
||||
{
|
||||
using var device = new RecordingGpuDevice();
|
||||
using var renderer = new DirectionalSunShadowRenderer(
|
||||
device,
|
||||
DirectionalShadowPreset.Medium);
|
||||
var world = new DirectionalShadowPreparedDraws();
|
||||
RenderSceneGeneration generation = RenderSceneGeneration.FromRaw(21);
|
||||
Assert.True(world.TryBegin(generation, 44, estimatedInstances: 6));
|
||||
for (int casterIndex = 0; casterIndex < 6; casterIndex++)
|
||||
{
|
||||
Matrix4x4 transform = Matrix4x4.CreateTranslation(casterIndex, 0f, 0f);
|
||||
DirectionalShadowTransformSource source =
|
||||
DirectionalShadowTransformSource.Static(casterIndex);
|
||||
world.Add(
|
||||
100,
|
||||
7,
|
||||
12,
|
||||
GpuTextureSlot.Unassigned,
|
||||
0,
|
||||
CullMode.CounterClockwise,
|
||||
DirectionalShadowCasterMaterial.Opaque,
|
||||
in transform,
|
||||
in source);
|
||||
}
|
||||
DirectionalShadowPreparationStats stats = default;
|
||||
world.Complete(generation, 44, in stats);
|
||||
world.ApplySelection(
|
||||
[true, false, true, false, true, false],
|
||||
casterSelectionSequence: 1);
|
||||
var terrain = new DirectionalShadowTerrainPreparedDraws();
|
||||
Assert.True(terrain.TryBegin(1, 0));
|
||||
terrain.Complete(1);
|
||||
using IGpuBuffer vertices = Buffer(device, "world-v", GpuBufferUsage.Vertex);
|
||||
using IGpuBuffer indices = Buffer(device, "world-i", GpuBufferUsage.Index);
|
||||
var geometry = new DirectionalShadowMeshGeometry(vertices, indices);
|
||||
|
||||
device.Clear();
|
||||
using IGpuFrame frame = device.BeginFrame();
|
||||
WorldTransformFrameSlice transforms = PublishSharedTransforms(frame, world.Transforms);
|
||||
DirectionalSunShadowDiagnostics diagnostics = renderer.RenderPrepared(
|
||||
frame,
|
||||
EnabledEnvironment(),
|
||||
Matrix4x4.Identity,
|
||||
Matrix4x4.CreatePerspectiveFieldOfView(
|
||||
MathF.PI / 3f,
|
||||
16f / 9f,
|
||||
0.1f,
|
||||
500f),
|
||||
cameraNearMeters: 0.1f,
|
||||
casterDepthPaddingMeters: 48f,
|
||||
world,
|
||||
terrain,
|
||||
geometry,
|
||||
terrainGeometry: null,
|
||||
transforms);
|
||||
|
||||
RecordingGpuBuffer commands = Assert.Single(
|
||||
device.CreatedBuffers,
|
||||
buffer => buffer.Name == "directional-shadow-world-commands-2");
|
||||
Span<byte> bytes = stackalloc byte[3 * 20];
|
||||
commands.Read(0, bytes);
|
||||
ReadOnlySpan<DrawElementsIndirectCommand> uploaded =
|
||||
MemoryMarshal.Cast<byte, DrawElementsIndirectCommand>(bytes);
|
||||
Assert.Equal([0u, 2u, 4u],
|
||||
uploaded.ToArray().Select(static command => command.BaseInstance));
|
||||
Assert.All(uploaded.ToArray(),
|
||||
static command => Assert.Equal(1u, command.InstanceCount));
|
||||
Assert.All(
|
||||
device.OfKind<GpuRecordedMultiDrawIndirect>(),
|
||||
static draw => Assert.Equal(3u, draw.DrawCount));
|
||||
Assert.Equal(6, diagnostics.ResidentWorldInstances);
|
||||
Assert.Equal(3, diagnostics.ActiveWorldInstances);
|
||||
Assert.Equal(1, diagnostics.ResidentWorldCommands);
|
||||
Assert.Equal(3, diagnostics.ActiveWorldCommands);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TopologyRebuild_SwapsRetainedBuffersAndDisposalReleasesTheCurrentSet()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1463,6 +1463,63 @@ public sealed class AtmosphericPostProcessGraphTests
|
|||
Assert.Equal(first.WindAmplitude, second.WindAmplitude);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuiltInAndDeclaredShadowGraphsUseTheSameTypedPriorVisibilitySelector()
|
||||
{
|
||||
string renderingRoot = Path.Combine(
|
||||
RepositoryRoot(),
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Rendering",
|
||||
"Packs");
|
||||
string builtIn = File.ReadAllText(
|
||||
Path.Combine(renderingRoot, "AtmosphericPostProcessGraph.cs"));
|
||||
string declared = File.ReadAllText(
|
||||
Path.Combine(renderingRoot, "DeclaredFullscreenRenderPackGraph.cs"));
|
||||
|
||||
AssertGraphUsesTypedSelection(builtIn, "RenderDirectionalShadows(");
|
||||
AssertGraphUsesTypedSelection(declared, "RenderDeclaredDirectionalShadows(");
|
||||
|
||||
string register = File.ReadAllText(Path.Combine(
|
||||
RepositoryRoot(),
|
||||
"docs",
|
||||
"architecture",
|
||||
"retail-divergence-register.md"));
|
||||
string ia24 = Assert.Single(register.Split('\n'), static line =>
|
||||
line.StartsWith("| IA-24 |", StringComparison.Ordinal));
|
||||
Assert.Contains("prior successfully completed", ia24,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains("S2's retained retail CELLARRAY", ia24,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains("building shells by their outdoor placement `EffectCellId`", ia24,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains("pack-off does not build, select, upload, or draw shadow work", ia24,
|
||||
StringComparison.Ordinal);
|
||||
|
||||
static void AssertGraphUsesTypedSelection(string source, string methodName)
|
||||
{
|
||||
int start = source.IndexOf(methodName, StringComparison.Ordinal);
|
||||
Assert.True(start >= 0, $"missing {methodName}");
|
||||
int end = source.IndexOf("public IGpuRenderTarget PrepareWorldTarget", start,
|
||||
StringComparison.Ordinal);
|
||||
Assert.True(end > start, $"could not bound {methodName}");
|
||||
string method = source[start..end];
|
||||
Assert.Contains(
|
||||
"RetailLandscapeVisibilityFrame priorLandscapeVisibility =",
|
||||
method,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains("world.PriorLandscapeVisibility", method,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains("_shadowCasters.Select(", method, StringComparison.Ordinal);
|
||||
Assert.Contains("world.DirectionalShadowCellMembership", method,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains("EmptyDirectionalShadowCellMembership.Instance", method,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains("PriorLandscapeVisibility: world.PriorLandscapeVisibility", method,
|
||||
StringComparison.Ordinal);
|
||||
}
|
||||
}
|
||||
|
||||
private static AtmosphericFrameUniforms RenderAndReadFrameBlock(
|
||||
RecordingGpuDevice device,
|
||||
AtmosphericPostProcessGraph graph,
|
||||
|
|
|
|||
|
|
@ -96,6 +96,56 @@ public sealed class ParticleVisibilityControllerTests
|
|||
Assert.True(Assert.Single(particles.EnumerateEmitters()).ViewEligible);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BorrowedLandscapeFrame_TracksOnlyCompletedTransactionsByReference()
|
||||
{
|
||||
var controller = new ParticleVisibilityController();
|
||||
RetailLandscapeVisibilityFrame first =
|
||||
controller.CaptureCompletedLandscapeVisibility();
|
||||
Assert.False(first.HasCompletedWorldView);
|
||||
Assert.Empty(first.CellIds);
|
||||
|
||||
// A completed null-root safety frame is authoritative and empty.
|
||||
controller.BeginFrame(Vector3.Zero);
|
||||
controller.UseWorldView();
|
||||
controller.CompleteFrame();
|
||||
RetailLandscapeVisibilityFrame completedEmpty =
|
||||
controller.CaptureCompletedLandscapeVisibility();
|
||||
Assert.True(completedEmpty.HasCompletedWorldView);
|
||||
Assert.Empty(completedEmpty.CellIds);
|
||||
Assert.Same(first.CellIds, completedEmpty.CellIds);
|
||||
|
||||
controller.BeginFrame(Vector3.One);
|
||||
controller.UseWorldView();
|
||||
controller.MarkVisibleLandscapeCells([0x12340001u]);
|
||||
RetailLandscapeVisibilityFrame whileBuilding =
|
||||
controller.CaptureCompletedLandscapeVisibility();
|
||||
Assert.Same(completedEmpty.CellIds, whileBuilding.CellIds);
|
||||
Assert.Empty(whileBuilding.CellIds);
|
||||
controller.AbortFrame();
|
||||
RetailLandscapeVisibilityFrame aborted =
|
||||
controller.CaptureCompletedLandscapeVisibility();
|
||||
Assert.Same(completedEmpty.CellIds, aborted.CellIds);
|
||||
Assert.True(aborted.HasCompletedWorldView);
|
||||
Assert.Empty(aborted.CellIds);
|
||||
|
||||
controller.BeginFrame(Vector3.One);
|
||||
controller.UseWorldView();
|
||||
controller.MarkVisibleLandscapeCells([0x12340001u]);
|
||||
controller.CompleteFrame();
|
||||
RetailLandscapeVisibilityFrame replaced =
|
||||
controller.CaptureCompletedLandscapeVisibility();
|
||||
Assert.Same(completedEmpty.CellIds, replaced.CellIds);
|
||||
Assert.Equal([0x12340001u], replaced.CellIds);
|
||||
|
||||
controller.Reset();
|
||||
RetailLandscapeVisibilityFrame reset =
|
||||
controller.CaptureCompletedLandscapeVisibility();
|
||||
Assert.Same(completedEmpty.CellIds, reset.CellIds);
|
||||
Assert.False(reset.HasCompletedWorldView);
|
||||
Assert.Empty(reset.CellIds);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CompleteAbortAndResetPublishOnlyCompleteLandscapeTransactions()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -262,6 +262,63 @@ public sealed class DirectionalShadowPreparedDrawTests
|
|||
Assert.Single(product.Commands.ToArray());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AlternatingCasterSelection_EmitsExactContiguousInstanceRuns()
|
||||
{
|
||||
var product = new DirectionalShadowPreparedDraws();
|
||||
RenderSceneGeneration generation = RenderSceneGeneration.FromRaw(18);
|
||||
Assert.True(product.TryBegin(generation, 33, estimatedInstances: 6));
|
||||
for (int casterIndex = 0; casterIndex < 6; casterIndex++)
|
||||
{
|
||||
Matrix4x4 transform = Matrix4x4.CreateTranslation(casterIndex, 0f, 0f);
|
||||
DirectionalShadowTransformSource source =
|
||||
DirectionalShadowTransformSource.Static(casterIndex);
|
||||
product.Add(
|
||||
firstIndex: 100,
|
||||
baseVertex: 7,
|
||||
indexCount: 12,
|
||||
GpuTextureSlot.Unassigned,
|
||||
textureLayer: 0,
|
||||
CullMode.CounterClockwise,
|
||||
DirectionalShadowCasterMaterial.Opaque,
|
||||
in transform,
|
||||
in source);
|
||||
}
|
||||
DirectionalShadowPreparationStats stats = default;
|
||||
product.Complete(generation, 33, in stats);
|
||||
ulong topologySequence = product.BuildSequence;
|
||||
bool[] alternating = [true, false, true, false, true, false];
|
||||
|
||||
product.ApplySelection(alternating, casterSelectionSequence: 1);
|
||||
|
||||
Assert.Single(product.Commands.ToArray());
|
||||
Assert.Equal(6u, product.Commands[0].InstanceCount);
|
||||
Assert.Equal(3, product.ActiveCommands.Length);
|
||||
Assert.Equal([0u, 2u, 4u],
|
||||
product.ActiveCommands.ToArray().Select(static command => command.BaseInstance));
|
||||
Assert.All(product.ActiveCommands.ToArray(),
|
||||
static command => Assert.Equal(1u, command.InstanceCount));
|
||||
Assert.Equal(3, product.ActiveBatches.Length);
|
||||
Assert.Equal(3, product.Stats.ActiveInstances);
|
||||
Assert.Equal(3, product.Stats.ActiveCommands);
|
||||
Assert.Equal(topologySequence, product.BuildSequence);
|
||||
|
||||
bool[] inverse = [false, true, false, true, false, true];
|
||||
product.ApplySelection(inverse, casterSelectionSequence: 2);
|
||||
Assert.Equal([1u, 3u, 5u],
|
||||
product.ActiveCommands.ToArray().Select(static command => command.BaseInstance));
|
||||
Assert.Equal(topologySequence, product.BuildSequence);
|
||||
|
||||
ulong selectionSequence = 2;
|
||||
ZeroAllocationProbe.AssertAllocatesNothing(
|
||||
"DirectionalShadowPreparedDraws.ApplySelection alternating",
|
||||
() => product.ApplySelection(
|
||||
(selectionSequence & 1ul) == 0ul ? alternating : inverse,
|
||||
++selectionSequence),
|
||||
batchSize: 256);
|
||||
Assert.Equal(topologySequence, product.BuildSequence);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StableTopology_ComposesSlimPoseWithoutMutatingCasterOrAllocating()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
using AcDream.App.Rendering;
|
||||
using AcDream.App.Rendering.Vfx;
|
||||
using AcDream.App.Rendering.Wb;
|
||||
|
||||
namespace AcDream.App.Tests.Rendering.Wb;
|
||||
|
||||
|
|
@ -72,4 +74,50 @@ public sealed class DirectionalShadowTerrainPreparedDrawTests
|
|||
Assert.Single(product.Commands.ToArray());
|
||||
Assert.Equal(20u, product.Commands[0].FirstIndex);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PriorLandscapeSelection_ScansExactAuthoredEightByEightCells()
|
||||
{
|
||||
var product = new DirectionalShadowTerrainPreparedDraws();
|
||||
Assert.True(product.TryBegin(frameSequence: 1, estimatedCommands: 3));
|
||||
DirectionalShadowTerrainRange[] resident =
|
||||
[
|
||||
new(FirstIndex: 0, IndexCount: 384, LandblockId: 0x1111FFFFu),
|
||||
new(FirstIndex: 384, IndexCount: 384, LandblockId: 0x2222FFFFu),
|
||||
new(FirstIndex: 768, IndexCount: 384, LandblockId: 0x3333FFFFu),
|
||||
];
|
||||
foreach (DirectionalShadowTerrainRange range in resident)
|
||||
product.Add(in range);
|
||||
product.Complete(frameSequence: 1);
|
||||
ulong topologySequence = product.BuildSequence;
|
||||
var visible = new HashSet<uint>
|
||||
{
|
||||
0x11110040u,
|
||||
0x22220041u, // valid outdoor family, but not this slot's 8x8 cells.
|
||||
};
|
||||
var visibility = new RetailLandscapeVisibilityFrame(
|
||||
visible,
|
||||
HasCompletedWorldView: true);
|
||||
|
||||
product.ApplySelection(in visibility);
|
||||
|
||||
DrawElementsIndirectCommand selected = Assert.Single(product.Commands.ToArray());
|
||||
Assert.Equal(0u, selected.FirstIndex);
|
||||
Assert.Equal(3, product.ResidentRanges.Length);
|
||||
Assert.Equal(topologySequence, product.BuildSequence);
|
||||
|
||||
var empty = new RetailLandscapeVisibilityFrame(
|
||||
new HashSet<uint>(),
|
||||
HasCompletedWorldView: true);
|
||||
product.ApplySelection(in empty);
|
||||
Assert.Empty(product.Commands.ToArray());
|
||||
Assert.Equal(topologySequence, product.BuildSequence);
|
||||
|
||||
product.ApplySelection(in visibility);
|
||||
ZeroAllocationProbe.AssertAllocatesNothing(
|
||||
"DirectionalShadowTerrainPreparedDraws.ApplySelection",
|
||||
() => product.ApplySelection(in visibility),
|
||||
batchSize: 256);
|
||||
Assert.Equal(topologySequence, product.BuildSequence);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using AcDream.App.Composition;
|
|||
using AcDream.App.Input;
|
||||
using AcDream.App.Rendering;
|
||||
using AcDream.App.Rendering.Wb;
|
||||
using AcDream.App.Rendering.Vfx;
|
||||
using AcDream.App.Streaming;
|
||||
using AcDream.App.Tests.Architecture;
|
||||
using AcDream.App.World;
|
||||
|
|
@ -46,7 +47,8 @@ public sealed class WorldRenderFrameBuilderTests
|
|||
new RecordingRoots(calls, roots),
|
||||
environment,
|
||||
new RecordingAnimated(calls, animated),
|
||||
new RecordingBuildings(calls, new WorldBuildingFrame(null, buildings)));
|
||||
new RecordingBuildings(calls, new WorldBuildingFrame(null, buildings)),
|
||||
new RecordingMembership());
|
||||
|
||||
WorldRenderFrame result = builder.Build(
|
||||
in foundation,
|
||||
|
|
@ -55,6 +57,7 @@ public sealed class WorldRenderFrameBuilderTests
|
|||
|
||||
Assert.Equal(
|
||||
[
|
||||
"visibility:capture",
|
||||
"camera",
|
||||
"visibility:begin",
|
||||
"settings",
|
||||
|
|
@ -83,21 +86,24 @@ public sealed class WorldRenderFrameBuilderTests
|
|||
var environment = new RecordingEnvironment([]);
|
||||
var animated = new RecordingAnimated([], []);
|
||||
var buildings = new RecordingBuildings([], default);
|
||||
var membership = new RecordingMembership();
|
||||
|
||||
Assert.Throws<ArgumentNullException>(() =>
|
||||
new WorldRenderFrameBuilder(null!, visibility, settings, roots, environment, animated, buildings));
|
||||
new WorldRenderFrameBuilder(null!, visibility, settings, roots, environment, animated, buildings, membership));
|
||||
Assert.Throws<ArgumentNullException>(() =>
|
||||
new WorldRenderFrameBuilder(camera, null!, settings, roots, environment, animated, buildings));
|
||||
new WorldRenderFrameBuilder(camera, null!, settings, roots, environment, animated, buildings, membership));
|
||||
Assert.Throws<ArgumentNullException>(() =>
|
||||
new WorldRenderFrameBuilder(camera, visibility, null!, roots, environment, animated, buildings));
|
||||
new WorldRenderFrameBuilder(camera, visibility, null!, roots, environment, animated, buildings, membership));
|
||||
Assert.Throws<ArgumentNullException>(() =>
|
||||
new WorldRenderFrameBuilder(camera, visibility, settings, null!, environment, animated, buildings));
|
||||
new WorldRenderFrameBuilder(camera, visibility, settings, null!, environment, animated, buildings, membership));
|
||||
Assert.Throws<ArgumentNullException>(() =>
|
||||
new WorldRenderFrameBuilder(camera, visibility, settings, roots, null!, animated, buildings));
|
||||
new WorldRenderFrameBuilder(camera, visibility, settings, roots, null!, animated, buildings, membership));
|
||||
Assert.Throws<ArgumentNullException>(() =>
|
||||
new WorldRenderFrameBuilder(camera, visibility, settings, roots, environment, null!, buildings));
|
||||
new WorldRenderFrameBuilder(camera, visibility, settings, roots, environment, null!, buildings, membership));
|
||||
Assert.Throws<ArgumentNullException>(() =>
|
||||
new WorldRenderFrameBuilder(camera, visibility, settings, roots, environment, animated, null!));
|
||||
new WorldRenderFrameBuilder(camera, visibility, settings, roots, environment, animated, null!, membership));
|
||||
Assert.Throws<ArgumentNullException>(() =>
|
||||
new WorldRenderFrameBuilder(camera, visibility, settings, roots, environment, animated, buildings, null!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -502,6 +508,12 @@ public sealed class WorldRenderFrameBuilderTests
|
|||
{
|
||||
public bool WaitingForLogin { get; private set; }
|
||||
|
||||
public RetailLandscapeVisibilityFrame CaptureCompletedLandscapeVisibility()
|
||||
{
|
||||
calls.Add("visibility:capture");
|
||||
return RetailLandscapeVisibilityFrame.None;
|
||||
}
|
||||
|
||||
public void Begin(in WorldCameraFrame camera, bool waitingForLogin)
|
||||
{
|
||||
calls.Add("visibility:begin");
|
||||
|
|
@ -512,6 +524,66 @@ public sealed class WorldRenderFrameBuilderTests
|
|||
calls.Add("visibility:projection");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Build_BorrowsExactPriorCompletedLandscapeBeforeCurrentBegin()
|
||||
{
|
||||
var particles = new ParticleVisibilityController();
|
||||
particles.BeginFrame(Vector3.Zero);
|
||||
particles.UseWorldView();
|
||||
particles.MarkVisibleLandscapeCells([0x12340002u]);
|
||||
particles.CompleteFrame();
|
||||
RetailLandscapeVisibilityFrame ownerBefore =
|
||||
particles.CaptureCompletedLandscapeVisibility();
|
||||
var visibility = new RuntimeWorldFrameVisibilityPreparation(
|
||||
selection: null,
|
||||
particles,
|
||||
terrain: null,
|
||||
reveal: null,
|
||||
environmentFrustum: null);
|
||||
var camera = new FlyCamera();
|
||||
var builder = new WorldRenderFrameBuilder(
|
||||
new RecordingCamera([], CameraFrame(camera)),
|
||||
visibility,
|
||||
new RecordingSettings([]),
|
||||
new RecordingRoots([], default),
|
||||
new RecordingEnvironment([]),
|
||||
new RecordingAnimated([], []),
|
||||
new RecordingBuildings([], default),
|
||||
new RecordingMembership());
|
||||
RenderFrameFoundation foundation = default;
|
||||
|
||||
WorldRenderFrame world = builder.Build(
|
||||
in foundation,
|
||||
waitingForLogin: false,
|
||||
activeDayGroup: null);
|
||||
|
||||
Assert.Same(ownerBefore.CellIds, world.PriorLandscapeVisibility.CellIds);
|
||||
Assert.True(world.PriorLandscapeVisibility.HasCompletedWorldView);
|
||||
Assert.Contains(0x12340002u, world.PriorLandscapeVisibility.CellIds);
|
||||
Assert.Same(
|
||||
ownerBefore.CellIds,
|
||||
particles.CaptureCompletedLandscapeVisibility().CellIds);
|
||||
|
||||
WorldRenderFrame login = builder.Build(
|
||||
in foundation,
|
||||
waitingForLogin: true,
|
||||
activeDayGroup: null);
|
||||
Assert.False(login.PriorLandscapeVisibility.HasCompletedWorldView);
|
||||
Assert.Empty(login.PriorLandscapeVisibility.CellIds);
|
||||
}
|
||||
|
||||
private sealed class RecordingMembership : IDirectionalShadowCellMembership
|
||||
{
|
||||
public bool TryGetRetailCellArray(
|
||||
uint entityId,
|
||||
out IReadOnlyList<uint> cells)
|
||||
{
|
||||
_ = entityId;
|
||||
cells = Array.Empty<uint>();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class RecordingSettings(List<string> calls)
|
||||
: IWorldFrameSettingsPreview
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue