The walk-order submission layer over the existing RHI (plan section FW2): - OrderedDrawStream: append-only walk-ordered draw commands (GroupKey + transform + per-instance data + WalkDrawStage + cell provenance), struct-of-arrays with one lockstep Reset (#193 shape). The PortalPunch stage exists but has no FW2 submission path - the submitter throws on it; punch emission lands with FW3 wiring. - WbDrawDispatcher.OrderedStream partial: per-instance-first emission (the deferred-alpha shape - command i owns instance i, walk order survives into the indirect array), each SSBO section written once, then one DrawIndirectRangeRhi call per maximal merge run. Runs are built by pure-CPU BuildOrderedMergeRuns and may never span a stage, pipeline-bucket, or cull boundary; ValidateMergeRun re-checks every emitted run and throws (the campaign fail-loud rule). Nothing is sorted, reordered, or dropped: N commands in, N indirect commands out, covered exactly once. - WorldDepthContract: retail world depth verified verbatim from the decomp - Render::zfuncVal @0x00820e1c = 0x2, SetDepthBufferMode @0x005a2d10 writes the enum directly as D3DRS_ZFUNC so the value IS D3DCMP_LESS, applied by the surface-state applier @0x0059c80a with Z-write toggled by blend; the LESSEQUAL sites are GameSky::Draw-local. Seven world pipeline sites now cite the named constant (no value changes). - Plan updated: FW1 status block + gate amendment (the ten pose-stamped retail traces supersede re-expressing the old-builder replay fixtures; those retire with the old builder at FW4 and their scenario classes re-verify at the FW3/FW4 connected gates). Known FW2 scope notes recorded in the code: the building-detail overlay replay is production wiring (FW3); the _drawCullModes scratch may not interleave with a mid-flight RetailAlphaQueue scope (FW3 sequencing constraint). The pixel A/B equivalence proof rides FW3's cutover toggle where a walk-driven scene first exists. Suites: full Release build 0 warnings; Walk lane 154/1 skip; hermetic 6,714/0 (+27 new). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
634 lines
22 KiB
C#
634 lines
22 KiB
C#
using System.Collections.ObjectModel;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Numerics;
|
|
using AcDream.App.Rendering;
|
|
using AcDream.App.Rendering.Gpu;
|
|
using AcDream.App.Rendering.Gpu.Vk;
|
|
using AcDream.App.Rendering.Wb;
|
|
using AcDream.App.Rendering.Walk;
|
|
using AcDream.App.Tests.Rendering.Gpu;
|
|
using AcDream.Content;
|
|
using AcDream.Core.Meshing;
|
|
using DatReaderWriter;
|
|
using DatReaderWriter.DBObjs;
|
|
using DatReaderWriter.Enums;
|
|
using DatReaderWriter.Lib.IO;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
|
|
namespace AcDream.App.Tests.Rendering.Walk;
|
|
|
|
/// <summary>
|
|
/// Campaign FW stage FW2: <see cref="WbDrawDispatcher.BuildOrderedMergeRuns"/>
|
|
/// (pure CPU merge-run legality) and <see cref="WbDrawDispatcher.SubmitOrderedStream"/>
|
|
/// (the same legality proven through actual recorded RHI calls against
|
|
/// <see cref="RecordingGpuDevice"/>).
|
|
/// </summary>
|
|
public sealed class OrderPreservingSubmitterTests
|
|
{
|
|
private static OrderedDrawCommand MakeCommand(
|
|
int index,
|
|
WalkDrawStage stage = WalkDrawStage.Terrain,
|
|
TranslucencyKind translucency = TranslucencyKind.Opaque,
|
|
CullMode cullMode = CullMode.CounterClockwise,
|
|
uint detailCategory = 0) =>
|
|
new(
|
|
Key: new GroupKey(
|
|
FirstIndex: (uint)index * 3,
|
|
BaseVertex: index * 4,
|
|
IndexCount: 3,
|
|
TextureSlot: new GpuTextureSlot((uint)index),
|
|
TextureLayer: 0,
|
|
Translucency: translucency,
|
|
FoliageFlags: 0,
|
|
CullMode: cullMode),
|
|
Transform: Matrix4x4.CreateTranslation(index, index * 2, index * 3),
|
|
Stage: stage,
|
|
CellId: 0x8C040100u + (uint)index,
|
|
ClipSlot: 0,
|
|
Lights: WbDrawDispatcher.InstanceLightSet.Disabled,
|
|
IndoorFlag: 0,
|
|
Alpha: 1f,
|
|
SelectionLighting: Vector2.Zero,
|
|
DetailCategory: detailCategory);
|
|
|
|
private static OrderedDrawStream StreamOf(params OrderedDrawCommand[] commands)
|
|
{
|
|
var stream = new OrderedDrawStream();
|
|
foreach (OrderedDrawCommand command in commands)
|
|
stream.Append(command);
|
|
return stream;
|
|
}
|
|
|
|
// ── Pure BuildOrderedMergeRuns — no GPU device ─────────────────────────
|
|
|
|
[Fact]
|
|
public void BuildOrderedMergeRuns_MergesThreeAdjacentSameStateCommandsIntoOneRun()
|
|
{
|
|
OrderedDrawStream stream = StreamOf(
|
|
MakeCommand(0), MakeCommand(1), MakeCommand(2));
|
|
|
|
List<WbDrawDispatcher.OrderedMergeRun> runs =
|
|
WbDrawDispatcher.BuildOrderedMergeRuns(stream);
|
|
|
|
WbDrawDispatcher.OrderedMergeRun run = Assert.Single(runs);
|
|
Assert.Equal(0, run.FirstCommand);
|
|
Assert.Equal(3, run.CommandCount);
|
|
}
|
|
|
|
[Fact]
|
|
public void BuildOrderedMergeRuns_SplitsOnAPipelineBucketChange()
|
|
{
|
|
OrderedDrawStream stream = StreamOf(
|
|
MakeCommand(0, translucency: TranslucencyKind.Opaque),
|
|
MakeCommand(1, translucency: TranslucencyKind.Opaque),
|
|
MakeCommand(2, translucency: TranslucencyKind.AlphaBlend));
|
|
|
|
List<WbDrawDispatcher.OrderedMergeRun> runs =
|
|
WbDrawDispatcher.BuildOrderedMergeRuns(stream);
|
|
|
|
Assert.Equal(
|
|
[
|
|
new WbDrawDispatcher.OrderedMergeRun(0, 2),
|
|
new WbDrawDispatcher.OrderedMergeRun(2, 1),
|
|
],
|
|
runs);
|
|
}
|
|
|
|
[Fact]
|
|
public void BuildOrderedMergeRuns_SplitsOnACullModeChange()
|
|
{
|
|
OrderedDrawStream stream = StreamOf(
|
|
MakeCommand(0, cullMode: CullMode.None),
|
|
MakeCommand(1, cullMode: CullMode.None),
|
|
MakeCommand(2, cullMode: CullMode.Clockwise));
|
|
|
|
List<WbDrawDispatcher.OrderedMergeRun> runs =
|
|
WbDrawDispatcher.BuildOrderedMergeRuns(stream);
|
|
|
|
Assert.Equal(
|
|
[
|
|
new WbDrawDispatcher.OrderedMergeRun(0, 2),
|
|
new WbDrawDispatcher.OrderedMergeRun(2, 1),
|
|
],
|
|
runs);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The load-bearing new assertion: two commands whose material state
|
|
/// (bucket, cull mode, detail category) is IDENTICAL still split into two
|
|
/// runs when their <see cref="WalkDrawStage"/> differs. Nothing about the
|
|
/// deferred-alpha template this submitter borrows from ever had to
|
|
/// consider stage — walk order introduces it.
|
|
/// </summary>
|
|
[Fact]
|
|
public void BuildOrderedMergeRuns_SplitsOnAStageChangeEvenWithIdenticalMaterialState()
|
|
{
|
|
OrderedDrawStream stream = StreamOf(
|
|
MakeCommand(0, stage: WalkDrawStage.Terrain),
|
|
MakeCommand(1, stage: WalkDrawStage.CellStatic));
|
|
|
|
List<WbDrawDispatcher.OrderedMergeRun> runs =
|
|
WbDrawDispatcher.BuildOrderedMergeRuns(stream);
|
|
|
|
Assert.Equal(
|
|
[
|
|
new WbDrawDispatcher.OrderedMergeRun(0, 1),
|
|
new WbDrawDispatcher.OrderedMergeRun(1, 1),
|
|
],
|
|
runs);
|
|
}
|
|
|
|
[Fact]
|
|
public void BuildOrderedMergeRuns_ADetailCategoryCommandIsAlwaysSolo()
|
|
{
|
|
OrderedDrawStream stream = StreamOf(
|
|
MakeCommand(0),
|
|
MakeCommand(1, detailCategory: 1),
|
|
MakeCommand(2));
|
|
|
|
List<WbDrawDispatcher.OrderedMergeRun> runs =
|
|
WbDrawDispatcher.BuildOrderedMergeRuns(stream);
|
|
|
|
Assert.Equal(
|
|
[
|
|
new WbDrawDispatcher.OrderedMergeRun(0, 1),
|
|
new WbDrawDispatcher.OrderedMergeRun(1, 1),
|
|
new WbDrawDispatcher.OrderedMergeRun(2, 1),
|
|
],
|
|
runs);
|
|
}
|
|
|
|
[Fact]
|
|
public void BuildOrderedMergeRuns_ThrowsNotSupportedForAPortalPunchCommand()
|
|
{
|
|
OrderedDrawStream stream = StreamOf(
|
|
MakeCommand(0, stage: WalkDrawStage.Terrain),
|
|
MakeCommand(1, stage: WalkDrawStage.PortalPunch));
|
|
|
|
Assert.Throws<NotSupportedException>(
|
|
() => WbDrawDispatcher.BuildOrderedMergeRuns(stream));
|
|
}
|
|
|
|
[Fact]
|
|
public void BuildOrderedMergeRuns_EveryCommandBelongsToExactlyOneRunInOrderWithNoGaps()
|
|
{
|
|
OrderedDrawStream stream = StreamOf(
|
|
MakeCommand(0, stage: WalkDrawStage.Terrain, translucency: TranslucencyKind.Opaque, cullMode: CullMode.None),
|
|
MakeCommand(1, stage: WalkDrawStage.Terrain, translucency: TranslucencyKind.Opaque, cullMode: CullMode.None),
|
|
MakeCommand(2, stage: WalkDrawStage.Terrain, translucency: TranslucencyKind.AlphaBlend, cullMode: CullMode.None),
|
|
MakeCommand(3, stage: WalkDrawStage.Terrain, translucency: TranslucencyKind.AlphaBlend, cullMode: CullMode.Clockwise),
|
|
MakeCommand(4, stage: WalkDrawStage.CellStatic, translucency: TranslucencyKind.AlphaBlend, cullMode: CullMode.Clockwise),
|
|
MakeCommand(5, stage: WalkDrawStage.CellStatic, translucency: TranslucencyKind.AlphaBlend, cullMode: CullMode.Clockwise, detailCategory: 1),
|
|
MakeCommand(6, stage: WalkDrawStage.CellStatic, translucency: TranslucencyKind.AlphaBlend, cullMode: CullMode.Clockwise));
|
|
|
|
List<WbDrawDispatcher.OrderedMergeRun> runs =
|
|
WbDrawDispatcher.BuildOrderedMergeRuns(stream);
|
|
|
|
int coveredThrough = 0;
|
|
int totalCommands = 0;
|
|
foreach (WbDrawDispatcher.OrderedMergeRun run in runs)
|
|
{
|
|
Assert.Equal(coveredThrough, run.FirstCommand);
|
|
Assert.True(run.CommandCount > 0);
|
|
coveredThrough = run.FirstCommand + run.CommandCount;
|
|
totalCommands += run.CommandCount;
|
|
}
|
|
Assert.Equal(stream.Count, coveredThrough);
|
|
Assert.Equal(stream.Count, totalCommands);
|
|
}
|
|
|
|
// ── SubmitOrderedStream — recorded RHI calls against RecordingGpuDevice ─
|
|
|
|
[Fact]
|
|
public void SubmitOrderedStream_AlternatingStateCommandsRecordOneDrawEachInOrder()
|
|
{
|
|
using var fx = new DispatcherFixture();
|
|
using DrawScope draw = fx.BeginDraw();
|
|
|
|
OrderedDrawStream stream = StreamOf(
|
|
MakeCommand(0, translucency: TranslucencyKind.Opaque),
|
|
MakeCommand(1, translucency: TranslucencyKind.AlphaBlend),
|
|
MakeCommand(2, translucency: TranslucencyKind.Opaque),
|
|
MakeCommand(3, translucency: TranslucencyKind.AlphaBlend));
|
|
|
|
fx.Dispatcher.SubmitOrderedStream(draw.Frame, draw.Pass, stream, Matrix4x4.Identity);
|
|
|
|
List<(int Start, int Count)> ranges = DecodeDrawRanges(fx.Device);
|
|
Assert.Equal([(0, 1), (1, 1), (2, 1), (3, 1)], ranges);
|
|
}
|
|
|
|
[Fact]
|
|
public void SubmitOrderedStream_MergesAdjacentSameStateCommandsIntoOneMultiDrawIndirect()
|
|
{
|
|
using var fx = new DispatcherFixture();
|
|
using DrawScope draw = fx.BeginDraw();
|
|
|
|
OrderedDrawStream stream = StreamOf(
|
|
MakeCommand(0), MakeCommand(1), MakeCommand(2));
|
|
|
|
fx.Dispatcher.SubmitOrderedStream(draw.Frame, draw.Pass, stream, Matrix4x4.Identity);
|
|
|
|
List<(int Start, int Count)> ranges = DecodeDrawRanges(fx.Device);
|
|
Assert.Equal([(0, 3)], ranges);
|
|
}
|
|
|
|
[Fact]
|
|
public void SubmitOrderedStream_CullModeChangeRecordsSeparateCullCallsAndSplitsTheDraw()
|
|
{
|
|
using var fx = new DispatcherFixture();
|
|
using DrawScope draw = fx.BeginDraw();
|
|
|
|
OrderedDrawStream stream = StreamOf(
|
|
MakeCommand(0, cullMode: CullMode.None),
|
|
MakeCommand(1, cullMode: CullMode.None),
|
|
MakeCommand(2, cullMode: CullMode.Clockwise));
|
|
|
|
fx.Dispatcher.SubmitOrderedStream(draw.Frame, draw.Pass, stream, Matrix4x4.Identity);
|
|
|
|
Assert.Equal([(0, 2), (2, 1)], DecodeDrawRanges(fx.Device));
|
|
|
|
List<GpuCullMode> cullCalls =
|
|
[.. fx.Device.Calls.OfType<GpuRecordedCullMode>().Select(c => c.CullMode)];
|
|
// ApplyCullModeRhi: CullMode.None -> GpuCullMode.None, CullMode.Clockwise -> GpuCullMode.Front.
|
|
Assert.Equal([GpuCullMode.None, GpuCullMode.Front], cullCalls);
|
|
}
|
|
|
|
[Fact]
|
|
public void SubmitOrderedStream_StageChangeSplitsTheDrawEvenWithIdenticalMaterialState()
|
|
{
|
|
using var fx = new DispatcherFixture();
|
|
using DrawScope draw = fx.BeginDraw();
|
|
|
|
OrderedDrawStream stream = StreamOf(
|
|
MakeCommand(0, stage: WalkDrawStage.Terrain),
|
|
MakeCommand(1, stage: WalkDrawStage.CellStatic));
|
|
|
|
fx.Dispatcher.SubmitOrderedStream(draw.Frame, draw.Pass, stream, Matrix4x4.Identity);
|
|
|
|
Assert.Equal([(0, 1), (1, 1)], DecodeDrawRanges(fx.Device));
|
|
}
|
|
|
|
[Fact]
|
|
public void SubmitOrderedStream_ADetailCategoryCommandRecordsItsOwnSoloDraw()
|
|
{
|
|
using var fx = new DispatcherFixture();
|
|
using DrawScope draw = fx.BeginDraw();
|
|
|
|
OrderedDrawStream stream = StreamOf(
|
|
MakeCommand(0),
|
|
MakeCommand(1, detailCategory: 1),
|
|
MakeCommand(2));
|
|
|
|
fx.Dispatcher.SubmitOrderedStream(draw.Frame, draw.Pass, stream, Matrix4x4.Identity);
|
|
|
|
Assert.Equal([(0, 1), (1, 1), (2, 1)], DecodeDrawRanges(fx.Device));
|
|
}
|
|
|
|
[Fact]
|
|
public void SubmitOrderedStream_OpaqueRunUsesRenderPassZeroAndAlphaBlendRunUsesRenderPassOne()
|
|
{
|
|
using var fx = new DispatcherFixture();
|
|
using DrawScope draw = fx.BeginDraw();
|
|
|
|
OrderedDrawStream stream = StreamOf(
|
|
MakeCommand(0, translucency: TranslucencyKind.Opaque),
|
|
MakeCommand(1, translucency: TranslucencyKind.AlphaBlend));
|
|
|
|
fx.Dispatcher.SubmitOrderedStream(draw.Frame, draw.Pass, stream, Matrix4x4.Identity);
|
|
|
|
List<(GpuPushConstants Constants, int Start, int Count)> runs = DecodeRuns(fx.Device);
|
|
Assert.Equal(2, runs.Count);
|
|
Assert.Equal(0, runs[0].Constants.RenderPass);
|
|
Assert.Equal(1, runs[1].Constants.RenderPass);
|
|
}
|
|
|
|
[Fact]
|
|
public void SubmitOrderedStream_ThrowsNotSupportedForAPortalPunchCommandBeforeAnyDraw()
|
|
{
|
|
using var fx = new DispatcherFixture();
|
|
using DrawScope draw = fx.BeginDraw();
|
|
|
|
OrderedDrawStream stream = StreamOf(
|
|
MakeCommand(0, stage: WalkDrawStage.PortalPunch));
|
|
|
|
Assert.Throws<NotSupportedException>(
|
|
() => fx.Dispatcher.SubmitOrderedStream(
|
|
draw.Frame, draw.Pass, stream, Matrix4x4.Identity));
|
|
|
|
Assert.Empty(fx.Device.Calls.OfType<GpuRecordedMultiDrawIndirect>());
|
|
Assert.Empty(fx.Device.Calls.OfType<GpuRecordedStorageBind>());
|
|
}
|
|
|
|
[Fact]
|
|
public void SubmitOrderedStream_EmptyStreamRecordsNoDraws()
|
|
{
|
|
using var fx = new DispatcherFixture();
|
|
using DrawScope draw = fx.BeginDraw();
|
|
|
|
fx.Dispatcher.SubmitOrderedStream(
|
|
draw.Frame, draw.Pass, new OrderedDrawStream(), Matrix4x4.Identity);
|
|
|
|
Assert.Empty(fx.Device.Calls.OfType<GpuRecordedMultiDrawIndirect>());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Fail-loud invariant: whatever the state pattern, the recorded
|
|
/// MultiDrawIndirect calls' DrawCounts always sum to the stream's Count —
|
|
/// no command is ever silently skipped, and none is drawn twice.
|
|
/// </summary>
|
|
[Fact]
|
|
public void SubmitOrderedStream_TotalRecordedDrawCountAlwaysEqualsTheStreamCount()
|
|
{
|
|
using var fx = new DispatcherFixture();
|
|
using DrawScope draw = fx.BeginDraw();
|
|
|
|
var stream = new OrderedDrawStream();
|
|
var stages = new[] { WalkDrawStage.Terrain, WalkDrawStage.CellStatic, WalkDrawStage.BuildingShell };
|
|
var blends = new[]
|
|
{
|
|
TranslucencyKind.Opaque, TranslucencyKind.AlphaBlend,
|
|
TranslucencyKind.Additive, TranslucencyKind.InvAlpha,
|
|
};
|
|
var culls = new[] { CullMode.None, CullMode.Clockwise, CullMode.CounterClockwise };
|
|
const int commandCount = 11;
|
|
for (int i = 0; i < commandCount; i++)
|
|
{
|
|
stream.Append(MakeCommand(
|
|
i,
|
|
stage: stages[i % stages.Length],
|
|
translucency: blends[i % blends.Length],
|
|
cullMode: culls[i % culls.Length],
|
|
detailCategory: i == 5 ? 1u : 0u));
|
|
}
|
|
|
|
fx.Dispatcher.SubmitOrderedStream(draw.Frame, draw.Pass, stream, Matrix4x4.Identity);
|
|
|
|
List<(int Start, int Count)> ranges = DecodeDrawRanges(fx.Device);
|
|
int sum = ranges.Sum(r => r.Count);
|
|
Assert.Equal(commandCount, sum);
|
|
|
|
int coveredThrough = 0;
|
|
foreach ((int start, int count) in ranges)
|
|
{
|
|
Assert.Equal(coveredThrough, start);
|
|
coveredThrough += count;
|
|
}
|
|
Assert.Equal(commandCount, coveredThrough);
|
|
}
|
|
|
|
// ── Decode helpers ──────────────────────────────────────────────────────
|
|
|
|
private static List<(int Start, int Count)> DecodeDrawRanges(RecordingGpuDevice device) =>
|
|
[.. DecodeRuns(device).Select(r => (r.Start, r.Count))];
|
|
|
|
private static List<(GpuPushConstants Constants, int Start, int Count)> DecodeRuns(
|
|
RecordingGpuDevice device)
|
|
{
|
|
GpuPushConstants? lastConstants = null;
|
|
uint? commandBase = null;
|
|
var result = new List<(GpuPushConstants, int, int)>();
|
|
foreach (var call in device.Calls)
|
|
{
|
|
if (call is GpuRecordedPushConstants pc)
|
|
{
|
|
lastConstants = pc.Constants;
|
|
}
|
|
else if (call is GpuRecordedMultiDrawIndirect mdi)
|
|
{
|
|
Assert.Equal((uint)WbDrawDispatcher.DrawCommandStride, mdi.StrideBytes);
|
|
commandBase ??= mdi.OffsetBytes;
|
|
int start = (int)((mdi.OffsetBytes - commandBase.Value) / mdi.StrideBytes);
|
|
Assert.NotNull(lastConstants);
|
|
result.Add((lastConstants!.Value, start, (int)mdi.DrawCount));
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// ── Fixture: a real WbDrawDispatcher against RecordingGpuDevice ─────────
|
|
|
|
private readonly struct DrawScope : IDisposable
|
|
{
|
|
private readonly IDisposable _publication;
|
|
private readonly IGpuPassEncoder _pass;
|
|
|
|
public DrawScope(IGpuFrame frame, IGpuPassEncoder pass, IDisposable publication)
|
|
{
|
|
Frame = frame;
|
|
_pass = pass;
|
|
_publication = publication;
|
|
}
|
|
|
|
public IGpuFrame Frame { get; }
|
|
|
|
public IGpuPassEncoder Pass => _pass;
|
|
|
|
public void Dispose()
|
|
{
|
|
_publication.Dispose();
|
|
_pass.Dispose();
|
|
}
|
|
}
|
|
|
|
private sealed class DispatcherFixture : IDisposable
|
|
{
|
|
private readonly WbMeshAdapter _meshAdapter;
|
|
private readonly TextureCache _textures;
|
|
|
|
public DispatcherFixture()
|
|
{
|
|
Device = new RecordingGpuDevice();
|
|
FrameLifetime = new GpuDeviceFrameLifetime(Device);
|
|
Scope = new VulkanWorldPassScope(sampleCount: 1);
|
|
_textures = new TextureCache(Device, new NoopDatReaderWriter());
|
|
_meshAdapter = new WbMeshAdapter(
|
|
Device,
|
|
new NoopDatReaderWriter(),
|
|
new NullPreparedAssetSource(),
|
|
NullLogger<WbMeshAdapter>.Instance,
|
|
Device.Retirement);
|
|
var entitySpawnAdapter = new EntitySpawnAdapter(
|
|
_textures,
|
|
_ => throw new NotSupportedException(
|
|
"Not exercised by SubmitOrderedStream tests."));
|
|
|
|
Dispatcher = new WbDrawDispatcher(
|
|
Device,
|
|
FrameLifetime,
|
|
Scope,
|
|
_textures,
|
|
_meshAdapter,
|
|
entitySpawnAdapter,
|
|
new EntityClassificationCache(),
|
|
new AcDream.Core.Rendering.TranslucencyFadeManager());
|
|
}
|
|
|
|
public RecordingGpuDevice Device { get; }
|
|
|
|
public GpuDeviceFrameLifetime FrameLifetime { get; }
|
|
|
|
public VulkanWorldPassScope Scope { get; }
|
|
|
|
public WbDrawDispatcher Dispatcher { get; }
|
|
|
|
/// <summary>Opens a frame and a backbuffer pass, publishes it on
|
|
/// <see cref="Scope"/>, then clears the recorded calls so a test only
|
|
/// sees what its own <c>SubmitOrderedStream</c> call produced.</summary>
|
|
public DrawScope BeginDraw()
|
|
{
|
|
FrameLifetime.BeginFrame();
|
|
IGpuFrame frame = FrameLifetime.CurrentFrame!;
|
|
IGpuPassEncoder pass = frame.BeginPass(
|
|
GpuPassDescription.BackbufferClear(
|
|
"fw2-ordered-stream-test", Vector4.Zero, sampleCount: 1));
|
|
IDisposable publication = Scope.Publish(pass);
|
|
Device.Clear();
|
|
return new DrawScope(frame, pass, publication);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Dispatcher.Dispose();
|
|
_meshAdapter.Dispose();
|
|
_textures.Dispose();
|
|
Device.Dispose();
|
|
}
|
|
}
|
|
|
|
private sealed class NullPreparedAssetSource : IPreparedAssetSource
|
|
{
|
|
public PreparedAssetSourceStats Stats => default;
|
|
|
|
public CacheStats DecodedTextureCacheStats => default;
|
|
|
|
public PreparedAssetPresence Probe(
|
|
AcDream.Content.Pak.PakAssetType type,
|
|
uint sourceFileId) =>
|
|
PreparedAssetPresence.Missing;
|
|
|
|
public PreparedAssetReadResult Read(
|
|
in PreparedAssetRequest request,
|
|
CancellationToken cancellationToken = default) =>
|
|
PreparedAssetReadResult.Missing;
|
|
|
|
public void Dispose()
|
|
{
|
|
}
|
|
}
|
|
|
|
private sealed class NoopDatReaderWriter : IDatReaderWriter
|
|
{
|
|
private readonly StubDatabase _portal = new();
|
|
private readonly StubDatabase _highRes = new();
|
|
private readonly StubDatabase _language = new();
|
|
private readonly StubDatabase _cell = new();
|
|
|
|
public string SourceDirectory => string.Empty;
|
|
|
|
public IDatDatabase Portal => _portal;
|
|
|
|
public IDatDatabase Cell => _cell;
|
|
|
|
public ReadOnlyDictionary<uint, IDatDatabase> CellRegions { get; } =
|
|
new(new Dictionary<uint, IDatDatabase>());
|
|
|
|
public IDatDatabase HighRes => _highRes;
|
|
|
|
public IDatDatabase Language => _language;
|
|
|
|
public IDatDatabase Local => _language;
|
|
|
|
public ReadOnlyDictionary<uint, uint> RegionFileMap { get; } =
|
|
new(new Dictionary<uint, uint>());
|
|
|
|
public int PortalIteration => 0;
|
|
|
|
public int CellIteration => 0;
|
|
|
|
public int HighResIteration => 0;
|
|
|
|
public int LanguageIteration => 0;
|
|
|
|
public bool TryGetFileBytes(
|
|
uint regionId,
|
|
uint fileId,
|
|
ref byte[] bytes,
|
|
out int bytesRead)
|
|
{
|
|
bytesRead = 0;
|
|
return false;
|
|
}
|
|
|
|
public IEnumerable<uint> GetAllIdsOfType<T>() where T : IDBObj =>
|
|
Array.Empty<uint>();
|
|
|
|
public IEnumerable<IDatReaderWriter.IdResolution> ResolveId(uint id) =>
|
|
Array.Empty<IDatReaderWriter.IdResolution>();
|
|
|
|
public bool TrySave<T>(T obj, int iteration = 0) where T : IDBObj =>
|
|
throw new NotSupportedException();
|
|
|
|
public bool TrySave<T>(
|
|
uint regionId,
|
|
T obj,
|
|
int iteration = 0) where T : IDBObj =>
|
|
throw new NotSupportedException();
|
|
|
|
[return: MaybeNull]
|
|
public T Get<T>(uint fileId) where T : IDBObj => default;
|
|
|
|
public bool TryGet<T>(
|
|
uint fileId,
|
|
[MaybeNullWhen(false)] out T value) where T : IDBObj
|
|
{
|
|
value = default;
|
|
return false;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
}
|
|
|
|
private sealed class StubDatabase : IDatDatabase
|
|
{
|
|
public DatDatabase Db => throw new NotSupportedException();
|
|
|
|
public int Iteration => 0;
|
|
|
|
public IEnumerable<uint> GetAllIdsOfType<T>() where T : IDBObj =>
|
|
Array.Empty<uint>();
|
|
|
|
public bool TryGet<T>(
|
|
uint fileId,
|
|
[MaybeNullWhen(false)] out T value) where T : IDBObj
|
|
{
|
|
value = default;
|
|
return false;
|
|
}
|
|
|
|
public bool TryGetFileBytes(
|
|
uint fileId,
|
|
[MaybeNullWhen(false)] out byte[] value)
|
|
{
|
|
value = null;
|
|
return false;
|
|
}
|
|
|
|
public bool TryGetFileBytes(
|
|
uint fileId,
|
|
ref byte[] bytes,
|
|
out int bytesRead)
|
|
{
|
|
bytesRead = 0;
|
|
return false;
|
|
}
|
|
|
|
public bool TrySave<T>(T obj, int iteration = 0) where T : IDBObj =>
|
|
throw new NotSupportedException();
|
|
|
|
public void Dispose()
|
|
{
|
|
}
|
|
}
|
|
}
|
|
}
|