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", "outstage-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); Assert.Contains("phantom-objects", 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 beginCalls = CompiledCallGraph.Read(begin); Assert.True( CompiledCallGraph.IndexOf( beginCalls, typeof(RetailPViewParticleClassifications), nameof(RetailPViewParticleClassifications.BeginFrame)) >= 0); MethodInfo landscape = typeof(RetailPViewPassExecutor).GetMethod( nameof(RetailPViewPassExecutor.DrawLandscapeSlice))!; IReadOnlyList 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 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? entities = null, IReadOnlyList? nearbyBuildingCells = null, IReadOnlyList? additionalCells = null) { var cells = new Dictionary { [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 Entities, IReadOnlyDictionary? 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 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 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 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 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 visibleCellIds) => Operations.Add("prepare-cells"); public void DrawOpaqueCellShells(HashSet cellIds) => Operations.Add("opaque-shells"); public bool CellHasTransparentShell(uint cellId) => HasTransparentShells; public void DrawTransparentCellShells(HashSet cellIds) => Operations.Add("transparent-shells"); public void DrawTransparentCellShellsOrdered(IReadOnlyList cellIds) => Operations.Add("transparent-shells-ordered"); public void DrawEntityBucket( RetailPViewFrameInput frame, IReadOnlyList entities, HashSet? visibleCellIds) => Operations.Add("entity-bucket"); public void EmitClipRouteProbe( ClipFrameAssembly clipAssembly, ClipViewSlice slice, int sliceIndex) => Operations.Add("clip-probe"); public void EmitOutStageOwner( WorldEntity entity, Vector3 sphereCenter, float sphereRadius, int sliceIndex, bool passed) => Operations.Add("outstage-owner"); public void EmitOutStageRouting( int sliceIndex, IReadOnlyList entities, ViewconeCuller viewcone) => Operations.Add("outstage-routing"); public void EmitPhantomObjects(uint cellId, int survivorCount) => Operations.Add("phantom-objects"); 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 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(); } }