Each of these was temporary apparatus added to chase one bug, and each was supposed to be deleted in the commit that fixed it. Fourteen closed issues later they were still here: #337's support/wire-mesh trio, #171's sticky timeline, #119's viewer and entity dumps, #113's phantom probe, and a dozen more. 3,493 lines removed; the client now reads 144 environment variables instead of 161, and 47 temporary probes remain instead of 64. This is not only tidying. Every probe leaves a branch on its hot path when unset, several re-read the environment per call rather than caching, and the volume buries the diagnostics that are actually load-bearing. It is also a headless correctness matter: HeadlessStaticStateAudit reflects over PhysicsDiagnostics' flags to refuse a multi-session host when any is set, and cannot see probes that live outside that owner. Four files went entirely — WalkMissDiagnostic.cs, CollisionMeshWireframe.cs and two test files whose only subject was a deleted probe. TransitionTypes.SetContactPlane also sheds its CallerMemberName / CallerLineNumber parameters, which existed solely for #337's cpSrc= attribution and carried the instruction to strip them with the probe family; no call site passed them, so no behavior changes. F2's collision overlay survives and reverts to its proxy-cylinder form, which is what removing the ACDREAM_WIRE_MESH upgrade means. LaunchOptionsDocumentationTests earned its keep here: it refused the deletion until docs/launch-options.md moved the 17 rows into Retired and the frozen direct-read counts came down (PhysicsEngine.cs to zero, TransitionTypes.cs 3 to 2). The documentation could not drift during a cleanup this wide. The 14 probes that name no owning issue are deliberately NOT deleted. Nothing records when they became safe to remove, and guessing is how a future investigation loses apparatus it needed; #435 stays open for their attribution. Full hermetic suite 15,321 passed / 0 failed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
663 lines
25 KiB
C#
663 lines
25 KiB
C#
using System.Numerics;
|
|
using System.Reflection;
|
|
using AcDream.App.Composition;
|
|
using AcDream.App.Rendering;
|
|
using AcDream.App.Rendering.Scene;
|
|
using AcDream.App.Tests.Architecture;
|
|
using AcDream.Core.World;
|
|
|
|
namespace AcDream.App.Tests.Rendering;
|
|
|
|
public sealed class RetailPViewPassExecutorTests
|
|
{
|
|
[Fact]
|
|
public void DrawInside_outdoor_executes_the_real_typed_sequence_without_interior_clear()
|
|
{
|
|
var renderer = new RetailPViewRenderer();
|
|
using var executor = new RecordingExecutor();
|
|
LoadedCell root = OutdoorCellNode.Build(0xA9B40000u);
|
|
|
|
renderer.DrawInside(Frame(root), executor);
|
|
|
|
Assert.Equal(
|
|
[
|
|
"begin",
|
|
"assemble",
|
|
"prepare-clip:3",
|
|
"indoor-routing",
|
|
"prepare-cells",
|
|
"diagnostics",
|
|
"terrain-clip",
|
|
"clear-routing",
|
|
"landscape-early",
|
|
"terrain-clip",
|
|
"clear-routing",
|
|
"landscape-late",
|
|
"landscape-alpha",
|
|
"indoor-routing",
|
|
"indoor-routing",
|
|
"exit-mask",
|
|
"indoor-routing",
|
|
"opaque-shells",
|
|
],
|
|
executor.Operations);
|
|
}
|
|
|
|
[Fact]
|
|
public void DrawInside_interior_exit_flushes_before_clear_and_resets_borrowed_result()
|
|
{
|
|
var renderer = new RetailPViewRenderer();
|
|
using var executor = new RecordingExecutor();
|
|
LoadedCell outdoor = OutdoorCellNode.Build(0xA9B40000u);
|
|
RetailPViewFrameResult borrowed = renderer.DrawInside(Frame(outdoor), executor);
|
|
Assert.Contains(outdoor.CellId, borrowed.DrawableCells);
|
|
|
|
executor.Operations.Clear();
|
|
LoadedCell interior = InteriorWithExit(0xA9B40100u);
|
|
RetailPViewFrameResult reused = renderer.DrawInside(Frame(interior), executor);
|
|
|
|
Assert.Same(borrowed, reused);
|
|
Assert.DoesNotContain(outdoor.CellId, reused.DrawableCells);
|
|
Assert.Contains(interior.CellId, reused.DrawableCells);
|
|
AssertAppearsInOrder(
|
|
string.Join('|', executor.Operations),
|
|
"landscape-early",
|
|
"landscape-late",
|
|
"unattached-particles",
|
|
"landscape-alpha",
|
|
"interior-depth-clear",
|
|
"indoor-routing",
|
|
"exit-mask");
|
|
}
|
|
|
|
[Fact]
|
|
public void DrawInside_interior_without_an_outside_slice_skips_landscape_and_depth_clear()
|
|
{
|
|
var renderer = new RetailPViewRenderer();
|
|
using var executor = new RecordingExecutor();
|
|
var root = new LoadedCell
|
|
{
|
|
CellId = 0xA9B40100u,
|
|
WorldTransform = Matrix4x4.Identity,
|
|
InverseWorldTransform = Matrix4x4.Identity,
|
|
};
|
|
|
|
renderer.DrawInside(Frame(root), executor);
|
|
|
|
Assert.DoesNotContain("terrain-clip", executor.Operations);
|
|
Assert.DoesNotContain("landscape-early", executor.Operations);
|
|
Assert.DoesNotContain("landscape-late", executor.Operations);
|
|
Assert.DoesNotContain("landscape-alpha", executor.Operations);
|
|
Assert.DoesNotContain("interior-depth-clear", executor.Operations);
|
|
}
|
|
|
|
[Fact]
|
|
public void Particle_classifications_reset_before_an_empty_following_frame()
|
|
{
|
|
var classifications = new RetailPViewParticleClassifications();
|
|
classifications.ReplaceOutdoor([Entity(7u)]);
|
|
classifications.Visible.Add(8u);
|
|
classifications.Dynamics.Add(9u);
|
|
|
|
classifications.BeginFrame();
|
|
|
|
Assert.Empty(classifications.Outdoor);
|
|
Assert.Empty(classifications.Visible);
|
|
Assert.Empty(classifications.Dynamics);
|
|
}
|
|
|
|
[Fact]
|
|
public void DrawInside_real_fixtures_reach_entity_particle_and_transparent_shell_operations()
|
|
{
|
|
var renderer = new RetailPViewRenderer();
|
|
using var executor = new RecordingExecutor { HasTransparentShells = true };
|
|
LoadedCell interior = InteriorWithExit(0xA9B40100u);
|
|
WorldEntity cellStatic = Entity(10u, parentCellId: interior.CellId);
|
|
WorldEntity dynamic = Entity(
|
|
11u,
|
|
serverGuid: 0x80000011u,
|
|
parentCellId: interior.CellId);
|
|
|
|
renderer.DrawInside(Frame(interior, [cellStatic, dynamic]), executor);
|
|
|
|
Assert.Contains("opaque-shells", executor.Operations);
|
|
Assert.Contains("transparent-shells-ordered", executor.Operations);
|
|
Assert.Contains("entity-bucket", executor.Operations);
|
|
Assert.Contains("cell-particles", executor.Operations);
|
|
Assert.Contains("dynamics-particles", executor.Operations);
|
|
}
|
|
|
|
[Fact]
|
|
public void DrawInside_referee_records_the_exact_routed_entity_buckets()
|
|
{
|
|
var oracle = new CurrentRenderSceneOracle();
|
|
var renderer = new RetailPViewRenderer(oracle);
|
|
using var executor = new RecordingExecutor();
|
|
LoadedCell interior = InteriorWithExit(0xA9B40100u);
|
|
WorldEntity cellStatic = Entity(20u, parentCellId: interior.CellId);
|
|
WorldEntity dynamic = Entity(
|
|
21u,
|
|
serverGuid: 0x80000021u,
|
|
parentCellId: interior.CellId);
|
|
|
|
renderer.DrawInside(Frame(interior, [cellStatic, dynamic]), executor);
|
|
|
|
Assert.Equal(1uL, oracle.Snapshot.CompletedPViewFrameSequence);
|
|
Assert.Equal(2, oracle.Snapshot.PViewCandidateCount);
|
|
Assert.Collection(
|
|
oracle.PViewCandidates,
|
|
candidate =>
|
|
{
|
|
Assert.Equal(CurrentRenderPViewRoute.CellStatic, candidate.Route);
|
|
Assert.Equal(cellStatic.Id, candidate.Projection.EntityId);
|
|
},
|
|
candidate =>
|
|
{
|
|
Assert.Equal(CurrentRenderPViewRoute.DynamicLast, candidate.Route);
|
|
Assert.Equal(dynamic.Id, candidate.Projection.EntityId);
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void DrawInside_production_product_supplies_the_ordered_entity_routes()
|
|
{
|
|
LoadedCell interior = InteriorWithExit(0xA9B40100u);
|
|
WorldEntity cellStatic = Entity(
|
|
30u,
|
|
parentCellId: interior.CellId);
|
|
WorldEntity dynamic = Entity(
|
|
31u,
|
|
serverGuid: 0x80000031u,
|
|
parentCellId: interior.CellId);
|
|
uint landblockId = interior.CellId & 0xFFFF0000u;
|
|
var oracle = new CurrentRenderSceneOracle();
|
|
using var shadow = new RenderSceneShadowRuntime(
|
|
RenderSceneGeneration.FromRaw(1));
|
|
RenderProjectionRecord staticRecord =
|
|
RenderProjectionRecordFactory.ProjectEntity(
|
|
StaticRenderProjectionJournal.StaticEntityId(
|
|
landblockId,
|
|
cellStatic.Id),
|
|
RenderProjectionClass.IndoorCellStatic,
|
|
RenderOwnerIncarnation.FromRaw(cellStatic.Id),
|
|
landblockId,
|
|
interior.CellId,
|
|
cellStatic,
|
|
spatiallyVisible: true);
|
|
RenderProjectionRecord dynamicRecord =
|
|
RenderProjectionRecordFactory.ProjectEntity(
|
|
LiveRenderProjectionJournal.ProjectionId(dynamic.Id),
|
|
RenderProjectionClass.LiveDynamicRoot,
|
|
RenderOwnerIncarnation.FromRaw(dynamic.Id),
|
|
landblockId,
|
|
interior.CellId,
|
|
dynamic,
|
|
spatiallyVisible: true);
|
|
shadow.Journal.Register(in staticRecord);
|
|
shadow.Journal.Register(in dynamicRecord);
|
|
shadow.DrainUpdateBoundary();
|
|
var product = new RenderScenePViewFrameProductController(
|
|
shadow,
|
|
oracle);
|
|
var renderer = new RetailPViewRenderer(oracle, product);
|
|
using var executor = new RecordingExecutor();
|
|
|
|
renderer.DrawInside(
|
|
Frame(interior, [cellStatic, dynamic]),
|
|
executor);
|
|
|
|
AssertAppearsInOrder(
|
|
string.Join('|', executor.Operations),
|
|
"entity-frame-begin",
|
|
"entity-route:CellStatic:0:00000000",
|
|
"entity-route:DynamicLast:0:00000000",
|
|
"entity-frame-complete");
|
|
Assert.Equal(0, product.Snapshot.MismatchCount);
|
|
Assert.Equal(
|
|
product.Snapshot.ExpectedDigest,
|
|
product.Snapshot.ActualDigest);
|
|
}
|
|
|
|
[Fact]
|
|
public void DrawInside_production_product_uses_retained_routes_without_legacy_partition_when_diagnostics_are_disabled()
|
|
{
|
|
LoadedCell interior = InteriorWithExit(0xA9B40100u);
|
|
WorldEntity dynamic = Entity(
|
|
32u,
|
|
serverGuid: 0x80000032u,
|
|
parentCellId: interior.CellId);
|
|
uint landblockId = interior.CellId & 0xFFFF0000u;
|
|
using var shadow = new RenderSceneShadowRuntime(
|
|
RenderSceneGeneration.FromRaw(1));
|
|
RenderProjectionRecord dynamicRecord =
|
|
RenderProjectionRecordFactory.ProjectEntity(
|
|
LiveRenderProjectionJournal.ProjectionId(dynamic.Id),
|
|
RenderProjectionClass.LiveDynamicRoot,
|
|
RenderOwnerIncarnation.FromRaw(dynamic.Id),
|
|
landblockId,
|
|
interior.CellId,
|
|
dynamic,
|
|
spatiallyVisible: true);
|
|
shadow.Journal.Register(in dynamicRecord);
|
|
shadow.DrainUpdateBoundary();
|
|
var product = new RenderScenePViewFrameProductController(shadow);
|
|
var renderer = new RetailPViewRenderer(null, product);
|
|
using var executor = new RecordingExecutor();
|
|
|
|
renderer.DrawInside(Frame(interior, [dynamic]), executor);
|
|
|
|
AssertAppearsInOrder(
|
|
string.Join('|', executor.Operations),
|
|
"entity-frame-begin",
|
|
"entity-route:DynamicLast:0:00000000",
|
|
"entity-frame-complete");
|
|
Assert.Equal(0uL, product.Snapshot.ComparisonCount);
|
|
Assert.Equal(1, product.Snapshot.ActualCandidateCount);
|
|
}
|
|
|
|
[Fact]
|
|
public void DrawInside_interior_root_executes_the_nearby_building_look_in_punch()
|
|
{
|
|
var renderer = new RetailPViewRenderer();
|
|
using var executor = new RecordingExecutor();
|
|
LoadedCell root = InteriorWithExit(0xA9B40100u);
|
|
root.BuildingId = 1u;
|
|
LoadedCell[] building = NearbyTwoCellBuilding();
|
|
|
|
renderer.DrawInside(
|
|
Frame(
|
|
root,
|
|
nearbyBuildingCells: building,
|
|
additionalCells: building),
|
|
executor);
|
|
|
|
Assert.Contains("look-in-punch", executor.Operations);
|
|
}
|
|
|
|
[Fact]
|
|
public void Extracted_contracts_retain_no_window_callbacks_or_visibility_owner()
|
|
{
|
|
Assert.DoesNotContain(
|
|
typeof(RetailPViewFrameInput).GetProperties(),
|
|
property => typeof(Delegate).IsAssignableFrom(property.PropertyType));
|
|
|
|
FieldInfo[] fields = typeof(RetailPViewPassExecutor).GetFields(
|
|
BindingFlags.Instance | BindingFlags.NonPublic);
|
|
Assert.DoesNotContain(fields, field => field.FieldType == typeof(GameWindow));
|
|
Assert.DoesNotContain(fields, field => field.FieldType == typeof(CellVisibility));
|
|
Assert.DoesNotContain(fields, field => field.FieldType == typeof(RetailPViewFrameInput));
|
|
Assert.DoesNotContain(fields, field => field.FieldType == typeof(RetailPViewFrameResult));
|
|
Assert.DoesNotContain(fields, field => field.FieldType == typeof(ClipFrameAssembly));
|
|
Assert.DoesNotContain(
|
|
fields,
|
|
field => typeof(Delegate).IsAssignableFrom(field.FieldType));
|
|
}
|
|
|
|
[Fact]
|
|
public void Concrete_executor_forwards_frame_reset_and_brackets_terrain_diagnostics()
|
|
{
|
|
MethodInfo begin = typeof(RetailPViewPassExecutor).GetMethod(
|
|
nameof(RetailPViewPassExecutor.BeginFrame))!;
|
|
IReadOnlyList<CompiledCall> beginCalls = CompiledCallGraph.Read(begin);
|
|
Assert.True(
|
|
CompiledCallGraph.IndexOf(
|
|
beginCalls,
|
|
typeof(RetailPViewParticleClassifications),
|
|
nameof(RetailPViewParticleClassifications.BeginFrame)) >= 0);
|
|
|
|
MethodInfo landscape = typeof(RetailPViewPassExecutor).GetMethod(
|
|
nameof(RetailPViewPassExecutor.DrawLandscapeSlice))!;
|
|
IReadOnlyList<CompiledCall> landscapeCalls = CompiledCallGraph.Read(landscape);
|
|
int diagnosticsBegin = RequiredCallIndex(
|
|
landscapeCalls,
|
|
typeof(TerrainDrawDiagnosticsController),
|
|
nameof(TerrainDrawDiagnosticsController.Begin));
|
|
int terrainDraw = RequiredCallIndex(
|
|
landscapeCalls,
|
|
typeof(TerrainModernRenderer),
|
|
nameof(TerrainModernRenderer.Draw));
|
|
int diagnosticsComplete = RequiredCallIndex(
|
|
landscapeCalls,
|
|
typeof(TerrainDrawDiagnosticsController),
|
|
nameof(TerrainDrawDiagnosticsController.Complete));
|
|
|
|
Assert.True(diagnosticsBegin < terrainDraw);
|
|
Assert.True(terrainDraw < diagnosticsComplete);
|
|
}
|
|
|
|
[Fact]
|
|
public void GameWindow_composes_one_executor_instead_of_a_draw_callback_bag()
|
|
{
|
|
MethodInfo compose = typeof(FrameRootCompositionPhase).GetMethod(
|
|
"ComposeCore",
|
|
BindingFlags.Instance | BindingFlags.NonPublic)!;
|
|
IReadOnlyList<CompiledCall> calls = CompiledCallGraph.Read(compose);
|
|
|
|
int executor = RequiredCallIndex(
|
|
calls,
|
|
typeof(RetailPViewPassExecutor),
|
|
".ctor");
|
|
int renderer = RequiredCallIndex(
|
|
calls,
|
|
typeof(WorldScenePViewRenderer),
|
|
".ctor");
|
|
|
|
Assert.True(executor < renderer);
|
|
Assert.Single(
|
|
calls,
|
|
call => call.Target.DeclaringType == typeof(RetailPViewPassExecutor)
|
|
&& call.Target.Name == ".ctor");
|
|
Assert.Single(
|
|
calls,
|
|
call => call.Target.DeclaringType == typeof(WorldScenePViewRenderer)
|
|
&& call.Target.Name == ".ctor");
|
|
}
|
|
|
|
private static RetailPViewFrameInput Frame(
|
|
LoadedCell root,
|
|
IReadOnlyList<WorldEntity>? entities = null,
|
|
IReadOnlyList<LoadedCell>? nearbyBuildingCells = null,
|
|
IReadOnlyList<LoadedCell>? additionalCells = null)
|
|
{
|
|
var cells = new Dictionary<uint, LoadedCell> { [root.CellId] = root };
|
|
if (additionalCells is not null)
|
|
{
|
|
foreach (LoadedCell cell in additionalCells)
|
|
cells[cell.CellId] = cell;
|
|
}
|
|
|
|
var entries = new List<(
|
|
uint LandblockId,
|
|
Vector3 AabbMin,
|
|
Vector3 AabbMax,
|
|
IReadOnlyList<WorldEntity> Entities,
|
|
IReadOnlyDictionary<uint, WorldEntity>? AnimatedById)>();
|
|
if (entities is not null)
|
|
{
|
|
entries.Add((
|
|
root.CellId & 0xFFFF0000u,
|
|
Vector3.Zero,
|
|
Vector3.Zero,
|
|
entities,
|
|
null));
|
|
}
|
|
|
|
return new RetailPViewFrameInput().Reset(
|
|
root,
|
|
nearbyBuildingCells,
|
|
Vector3.Zero,
|
|
TestCamera.ViewProjection,
|
|
new DictionaryCellSource(cells),
|
|
new TestCamera(),
|
|
Vector3.Zero,
|
|
frustum: null,
|
|
playerLandblockId: root.CellId & 0xFFFF0000u,
|
|
animatedEntityIds: null,
|
|
renderCenterLbX: 0,
|
|
renderCenterLbY: 0,
|
|
renderRadius: 1,
|
|
landblockEntries: entries,
|
|
renderSky: true,
|
|
renderWeather: true,
|
|
dayFraction: 0f,
|
|
activeDayGroup: null,
|
|
skyKeyframe: default,
|
|
environOverrideActive: false,
|
|
viewerCellId: root.CellId,
|
|
playerCellId: root.CellId,
|
|
playerViewPosition: Vector3.Zero,
|
|
cameraView: TestCamera.ViewMatrix,
|
|
cameraCellResolution: CameraCellResolution.None);
|
|
}
|
|
|
|
private static LoadedCell InteriorWithExit(uint cellId)
|
|
{
|
|
var cell = new LoadedCell
|
|
{
|
|
CellId = cellId,
|
|
WorldTransform = Matrix4x4.Identity,
|
|
InverseWorldTransform = Matrix4x4.Identity,
|
|
Portals = [new CellPortalInfo(0xFFFF, 0, 0, 0)],
|
|
};
|
|
cell.PortalPolygons.Add(
|
|
[
|
|
new Vector3(-1f, -1f, -2f),
|
|
new Vector3(1f, -1f, -2f),
|
|
new Vector3(1f, 1f, -2f),
|
|
new Vector3(-1f, 1f, -2f),
|
|
]);
|
|
return cell;
|
|
}
|
|
|
|
private static LoadedCell[] NearbyTwoCellBuilding()
|
|
{
|
|
const uint vestibuleId = 0xA9B40170u;
|
|
const uint roomId = 0xA9B40171u;
|
|
var vestibule = new LoadedCell
|
|
{
|
|
CellId = vestibuleId,
|
|
BuildingId = 2u,
|
|
WorldTransform = Matrix4x4.Identity,
|
|
InverseWorldTransform = Matrix4x4.Identity,
|
|
Portals =
|
|
[
|
|
new CellPortalInfo(0xFFFF, 0, 0, 0),
|
|
new CellPortalInfo(0x0171, 1, 0, 0),
|
|
],
|
|
ClipPlanes =
|
|
[
|
|
new PortalClipPlane
|
|
{
|
|
Normal = new Vector3(0, 0, 1),
|
|
D = 3f,
|
|
InsideSide = 1,
|
|
},
|
|
],
|
|
};
|
|
vestibule.PortalPolygons.Add(
|
|
[
|
|
new Vector3(-0.5f, -0.5f, -2f),
|
|
new Vector3(0.5f, -0.5f, -2f),
|
|
new Vector3(0.5f, 0.5f, -2f),
|
|
new Vector3(-0.5f, 0.5f, -2f),
|
|
]);
|
|
vestibule.PortalPolygons.Add(
|
|
[
|
|
new Vector3(-0.6f, -0.6f, -4f),
|
|
new Vector3(0.6f, -0.6f, -4f),
|
|
new Vector3(0.6f, 0.6f, -4f),
|
|
new Vector3(-0.6f, 0.6f, -4f),
|
|
]);
|
|
|
|
var room = new LoadedCell
|
|
{
|
|
CellId = roomId,
|
|
BuildingId = 2u,
|
|
WorldTransform = Matrix4x4.Identity,
|
|
InverseWorldTransform = Matrix4x4.Identity,
|
|
Portals = [new CellPortalInfo(0x0170, 0, 0, 1)],
|
|
};
|
|
room.PortalPolygons.Add(
|
|
[
|
|
new Vector3(-0.6f, -0.6f, -4f),
|
|
new Vector3(0.6f, -0.6f, -4f),
|
|
new Vector3(0.6f, 0.6f, -4f),
|
|
new Vector3(-0.6f, 0.6f, -4f),
|
|
]);
|
|
return [vestibule, room];
|
|
}
|
|
|
|
private static WorldEntity Entity(
|
|
uint id,
|
|
uint serverGuid = 0,
|
|
uint? parentCellId = null) => new()
|
|
{
|
|
Id = id,
|
|
ServerGuid = serverGuid,
|
|
SourceGfxObjOrSetupId = 0,
|
|
Position = Vector3.Zero,
|
|
Rotation = Quaternion.Identity,
|
|
MeshRefs = [new MeshRef(1u, Matrix4x4.Identity)],
|
|
ParentCellId = parentCellId,
|
|
};
|
|
|
|
private static void AssertAppearsInOrder(string source, params string[] needles)
|
|
{
|
|
int cursor = -1;
|
|
foreach (string needle in needles)
|
|
{
|
|
int next = source.IndexOf(needle, cursor + 1, StringComparison.Ordinal);
|
|
Assert.True(next > cursor, $"Missing or out-of-order fragment: {needle}");
|
|
cursor = next;
|
|
}
|
|
}
|
|
|
|
private static int RequiredCallIndex(
|
|
IReadOnlyList<CompiledCall> calls,
|
|
Type declaringType,
|
|
string methodName)
|
|
{
|
|
int index = CompiledCallGraph.IndexOf(calls, declaringType, methodName);
|
|
Assert.True(index >= 0, $"Missing compiled call: {declaringType.Name}.{methodName}");
|
|
return index;
|
|
}
|
|
|
|
private sealed class DictionaryCellSource(
|
|
IReadOnlyDictionary<uint, LoadedCell> cells) : IRetailPViewCellSource
|
|
{
|
|
public LoadedCell? Find(uint cellId) =>
|
|
cells.TryGetValue(cellId, out LoadedCell? cell) ? cell : null;
|
|
}
|
|
|
|
private sealed class TestCamera : ICamera
|
|
{
|
|
public static Matrix4x4 ViewMatrix { get; } = Matrix4x4.CreateLookAt(
|
|
Vector3.Zero,
|
|
new Vector3(0, 0, -1),
|
|
Vector3.UnitY);
|
|
|
|
public static Matrix4x4 ViewProjection { get; } = ViewMatrix
|
|
* Matrix4x4.CreatePerspectiveFieldOfView(1.2f, 1f, 0.1f, 1000f);
|
|
|
|
public Matrix4x4 View => ViewMatrix;
|
|
public Matrix4x4 Projection { get; } =
|
|
Matrix4x4.CreatePerspectiveFieldOfView(1.2f, 1f, 0.1f, 1000f);
|
|
public float Aspect { get; set; } = 1f;
|
|
}
|
|
|
|
private sealed class RecordingExecutor :
|
|
IRetailPViewPassExecutor,
|
|
IRenderFrameEntityPassExecutor,
|
|
IDisposable
|
|
{
|
|
public void AbortFrame() => Operations.Add("abort");
|
|
|
|
private readonly ClipFrame _clipFrame = ClipFrame.NoClip();
|
|
|
|
public List<string> Operations { get; } = [];
|
|
public bool HasTransparentShells { get; init; }
|
|
|
|
public void BeginFrame() => Operations.Add("begin");
|
|
public ClipFrameAssembly AssembleClipFrame(
|
|
PortalVisibilityFrame portalFrame,
|
|
ClipFrameAssembly reuseAssembly)
|
|
{
|
|
Operations.Add("assemble");
|
|
return ClipFrameAssembler.Assemble(_clipFrame, portalFrame, reuseAssembly);
|
|
}
|
|
|
|
public void PrepareClipFrame(int terrainUploadCount) =>
|
|
Operations.Add($"prepare-clip:{terrainUploadCount}");
|
|
|
|
public void SetTerrainClip(ReadOnlySpan<Vector4> planes) =>
|
|
Operations.Add("terrain-clip");
|
|
|
|
public void ClearClipRouting() => Operations.Add("clear-routing");
|
|
public void UseIndoorMembershipOnlyRouting() => Operations.Add("indoor-routing");
|
|
public void PrepareCellBatches(RetailPViewFrameInput frame, HashSet<uint> visibleCellIds) =>
|
|
Operations.Add("prepare-cells");
|
|
public void DrawOpaqueCellShells(HashSet<uint> cellIds) => Operations.Add("opaque-shells");
|
|
public bool CellHasTransparentShell(uint cellId) => HasTransparentShells;
|
|
public void DrawTransparentCellShells(HashSet<uint> cellIds) => Operations.Add("transparent-shells");
|
|
public void DrawTransparentCellShellsOrdered(IReadOnlyList<uint> cellIds) =>
|
|
Operations.Add("transparent-shells-ordered");
|
|
public void DrawEntityBucket(
|
|
RetailPViewFrameInput frame,
|
|
IReadOnlyList<WorldEntity> entities,
|
|
HashSet<uint>? visibleCellIds) => Operations.Add("entity-bucket");
|
|
public void EmitClipRouteProbe(
|
|
ClipFrameAssembly clipAssembly,
|
|
ClipViewSlice slice,
|
|
int sliceIndex) => Operations.Add("clip-probe");
|
|
public void DrawLandscapeSlice(
|
|
RetailPViewFrameInput frame,
|
|
RetailPViewLandscapeSliceContext context)
|
|
{
|
|
Operations.Add("landscape-early");
|
|
if (context.EntityDraw is RenderFrameEntityDrawRequest request)
|
|
{
|
|
RenderFrameView view = request.View;
|
|
DrawEntityRoute(
|
|
frame.Camera,
|
|
in view,
|
|
request.Route,
|
|
request.RouteIndex,
|
|
request.CellId,
|
|
request.TupleLandblockId);
|
|
}
|
|
}
|
|
|
|
public void DrawLandscapeSliceLate(
|
|
RetailPViewFrameInput frame,
|
|
RetailPViewLandscapeLateSliceContext context)
|
|
{
|
|
Operations.Add("landscape-late");
|
|
if (context.EntityDraw is RenderFrameEntityDrawRequest request)
|
|
{
|
|
RenderFrameView view = request.View;
|
|
DrawEntityRoute(
|
|
frame.Camera,
|
|
in view,
|
|
request.Route,
|
|
request.RouteIndex,
|
|
request.CellId,
|
|
request.TupleLandblockId);
|
|
}
|
|
}
|
|
public void ClearInteriorDepth() => Operations.Add("interior-depth-clear");
|
|
public void DrawExitPortalMask(RetailPViewFrameInput frame, RetailPViewCellSliceContext context) => Operations.Add("exit-mask");
|
|
public void DrawLookInPortalPunch(RetailPViewFrameInput frame, RetailPViewCellSliceContext context) => Operations.Add("look-in-punch");
|
|
public void DrawUnattachedSceneParticles(RetailPViewFrameInput frame) => Operations.Add("unattached-particles");
|
|
public void FlushLandscapeAlpha() => Operations.Add("landscape-alpha");
|
|
public void DrawCellParticles(RetailPViewFrameInput frame, RetailPViewCellSliceContext context) => Operations.Add("cell-particles");
|
|
public void DrawDynamicsParticles(
|
|
RetailPViewFrameInput frame,
|
|
IReadOnlySet<uint> ownerIds) =>
|
|
Operations.Add("dynamics-particles");
|
|
public void EmitDiagnostics(RetailPViewFrameInput frame, RetailPViewFrameResult result) => Operations.Add("diagnostics");
|
|
|
|
public void BeginEntityFrame(in RenderFrameView view) =>
|
|
Operations.Add("entity-frame-begin");
|
|
|
|
public bool DrawEntityRoute(
|
|
ICamera camera,
|
|
in RenderFrameView view,
|
|
RenderFrameCandidateRoute route,
|
|
int routeIndex,
|
|
uint cellId,
|
|
uint tupleLandblockId)
|
|
{
|
|
Operations.Add(
|
|
$"entity-route:{route}:{routeIndex}:{cellId:X8}");
|
|
return true;
|
|
}
|
|
|
|
public void CompleteEntityFrame(in RenderFrameView view) =>
|
|
Operations.Add("entity-frame-complete");
|
|
|
|
public void AbortEntityFrame() =>
|
|
Operations.Add("entity-frame-abort");
|
|
|
|
public void Dispose() => _clipFrame.Dispose();
|
|
}
|
|
}
|