using System.Numerics; using AcDream.App.Rendering; using AcDream.App.Rendering.Scene; using AcDream.App.Rendering.Scene.Arch; using AcDream.Core.World; namespace AcDream.App.Tests.Rendering; public sealed class RenderScenePViewFrameProductTests { private const uint Landblock = 0xA9B4FFFF; private const uint Cell = 0xA9B40170; private static readonly RenderSceneGeneration Generation = RenderSceneGeneration.FromRaw(1); [Fact] public void Builder_PreservesPViewRoutesAndExcludesShellsAndWithdrawnOwners() { using var scene = new ArchRenderScene(Generation); RenderProjectionRecord outdoor = Record( 0x0100_0000_0000_0001, RenderProjectionClass.OutdoorStatic); RenderProjectionRecord hidden = Record( 0x0100_0000_0000_0002, RenderProjectionClass.OutdoorStatic, flags: RenderProjectionFlags.Hidden | RenderProjectionFlags.SpatiallyResident); RenderProjectionRecord withdrawn = Record( 0x0100_0000_0000_0003, RenderProjectionClass.OutdoorStatic, flags: RenderProjectionFlags.Draw); RenderProjectionRecord shell = Record( 0x0200_0000_0000_0004, RenderProjectionClass.IndoorCellStatic, parentCell: Cell); RenderProjectionRecord cellStatic = Record( 0x0100_0000_0000_0005, RenderProjectionClass.IndoorCellStatic, parentCell: Cell); RenderProjectionRecord outdoorDynamic = Record( 0x0300_0000_0000_0006, RenderProjectionClass.LiveDynamicRoot); RenderProjectionRecord cellDynamic = Record( 0x0300_0000_0000_0007, RenderProjectionClass.LiveDynamicRoot, parentCell: Cell); RenderProjectionRecord[] records = [ outdoor, hidden, withdrawn, shell, cellStatic, outdoorDynamic, cellDynamic, ]; var deltas = new RenderProjectionDelta[records.Length]; for (int index = 0; index < records.Length; index++) { deltas[index] = RenderProjectionDelta.Register( Generation, (ulong)index + 1, records[index]); } scene.Apply(deltas); PortalVisibilityFrame portal = Portal(Cell); ClipFrameAssembly clip = FullScreenClip(Cell); ViewconeCuller viewcone = ViewconeCuller.Build(clip, Matrix4x4.Identity); var exchange = new RenderFrameExchange(); var builder = new RenderScenePViewFrameBuilder(); RenderSceneDigest digest = scene.BuildDigest(new RenderSceneDigestBuffer()); var input = new RenderScenePViewBuildInput( scene.OpenQuery(), digest, portal, clip, viewcone, [], [Cell], EmptyCellSource.Instance, RootIsOutdoor: true); builder.Build(exchange, frameSequence: 1, in input); RenderFrameView view = exchange.BorrowLatest(Generation, 1); try { Assert.Equal( [outdoor.Id, hidden.Id], view.OutdoorStaticCandidates.ToArray() .Select(static item => item.Id)); Assert.Equal( [cellStatic.Id], view.CellStaticCandidates.ToArray() .Select(static item => item.Id)); Assert.Equal( [outdoorDynamic.Id, cellDynamic.Id], view.DynamicCandidates.ToArray() .Select(static item => item.Id)); Assert.Equal(5, view.Transforms.Length); Assert.Equal(5, view.RouteCandidates.Length); Assert.Equal(3, view.RouteRanges.Length); Assert.DoesNotContain( view.RouteCandidates.ToArray(), item => item.Id == withdrawn.Id || item.Id == shell.Id); Assert.Equal( new RenderFrameDiagnosticCounts( OutdoorStaticCandidates: 2, CellStaticCandidates: 1, DynamicCandidates: 2, TransformCount: 5, OpaqueClassificationCount: 0, AlphaClassificationCount: 0, LightSetCount: 0, SelectionPartCount: 0, RouteCandidateCount: 5), view.DiagnosticCounts); Assert.Same(portal, view.PortalFrame); Assert.Same(clip, view.ClipAssembly); Assert.Equal(digest, view.SourceDigest); } finally { exchange.Release(in view); } } [Fact] public void Controller_MatchesCurrentPViewThenNamesAWithdrawnCandidate() { WorldEntity outdoor = Entity(10, serverGuid: 0, parentCell: null); WorldEntity cellStatic = Entity(11, serverGuid: 0, parentCell: Cell); WorldEntity outdoorDynamic = Entity(1_000_012, 0x8000_0012, parentCell: null); WorldEntity cellDynamic = Entity(1_000_013, 0x8000_0013, parentCell: Cell); WorldEntity[] entities = [outdoor, cellStatic, outdoorDynamic, cellDynamic]; var oracle = new CurrentRenderSceneOracle(); var partition = new InteriorEntityPartition.Result(); InteriorEntityPartition.Partition( partition, [Cell], [Entry(entities)], oracle); oracle.BeginPViewFrame(); oracle.ObservePViewBucket( CurrentRenderPViewRoute.LandscapeOutdoorStatic, 0, 0, [outdoor]); oracle.ObservePViewBucket( CurrentRenderPViewRoute.CellStatic, 0, 0, [cellStatic]); oracle.ObservePViewBucket( CurrentRenderPViewRoute.DynamicLast, 0, 0, [outdoorDynamic, cellDynamic]); oracle.CompletePViewFrame(); using var shadow = new RenderSceneShadowRuntime(Generation); foreach (WorldEntity entity in entities) { RenderProjectionRecord record = Project(entity); shadow.Journal.Register(in record); } shadow.DrainUpdateBoundary(); var logs = new List(); var controller = new RenderScenePViewFrameProductController( shadow, oracle, logs.Add); PortalVisibilityFrame portal = Portal(Cell); ClipFrameAssembly clip = FullScreenClip(Cell); ViewconeCuller viewcone = ViewconeCuller.Build(clip, Matrix4x4.Identity); controller.BuildAndCompare( portal, clip, viewcone, [], [Cell], EmptyCellSource.Instance, rootIsOutdoor: true); RenderFrameProductComparisonSnapshot matched = controller.Snapshot; Assert.True(matched.Enabled); Assert.Equal(1uL, matched.ComparisonCount); Assert.Equal(1uL, matched.SuccessfulComparisonCount); Assert.Equal(4, matched.ExpectedCandidateCount); Assert.Equal(4, matched.ActualCandidateCount); Assert.Equal(0, matched.MismatchCount); Assert.Null(matched.FirstMismatch); Assert.Equal(matched.ExpectedDigest, matched.ActualDigest); Assert.Empty(logs); RenderProjectionId withdrawnId = LiveRenderProjectionJournal.ProjectionId(cellDynamic.Id); shadow.Journal.Unregister( withdrawnId, RenderOwnerIncarnation.FromRaw(cellDynamic.Id)); shadow.DrainUpdateBoundary(); controller.BuildAndCompare( portal, clip, viewcone, [], [Cell], EmptyCellSource.Instance, rootIsOutdoor: true); RenderFrameProductComparisonSnapshot mismatch = controller.Snapshot; Assert.Equal(2uL, mismatch.ComparisonCount); Assert.Equal(1uL, mismatch.SuccessfulComparisonCount); Assert.Equal(1, mismatch.MismatchCount); Assert.Contains("field=count expected=4 actual=3", mismatch.FirstMismatch); Assert.Single(logs); } private static RenderProjectionRecord Project(WorldEntity entity) { bool dynamic = entity.ServerGuid != 0; RenderProjectionId id = dynamic ? LiveRenderProjectionJournal.ProjectionId(entity.Id) : StaticRenderProjectionJournal.StaticEntityId( Landblock, entity.Id); RenderProjectionClass projectionClass = dynamic ? RenderProjectionClass.LiveDynamicRoot : entity.ParentCellId is uint parentCell && InteriorEntityPartition.IsIndoorCellId(parentCell) ? RenderProjectionClass.IndoorCellStatic : RenderProjectionClass.OutdoorStatic; return RenderProjectionRecordFactory.ProjectEntity( id, projectionClass, RenderOwnerIncarnation.FromRaw(entity.Id), Landblock, entity.ParentCellId ?? Landblock, entity, spatiallyVisible: true); } private static RenderProjectionRecord Record( ulong id, RenderProjectionClass projectionClass, uint? parentCell = null, RenderProjectionFlags flags = RenderProjectionFlags.Draw | RenderProjectionFlags.SpatiallyResident) { uint fullCellId = parentCell ?? Landblock; Matrix4x4 transform = Matrix4x4.Identity; return new RenderProjectionRecord( RenderProjectionId.FromRaw(id), projectionClass, RenderOwnerIncarnation.FromRaw(id), new RenderTransform(transform), new PreviousRenderTransform(transform), new RenderMeshSet(RenderAssetHandle.FromRaw(id), 1, 1), new RenderMaterialVariant(0, 0, 1), new RenderSpatialResidency( RenderSpatialBucket.FromRaw(fullCellId), Landblock, fullCellId), new RenderWorldBounds( new Vector3(-1), new Vector3(1)), flags, default, new RenderSortKey(id), RenderDirtyMask.All, new RenderSourceMetadata( LocalEntityId: (uint)id, ServerGuid: projectionClass is RenderProjectionClass.LiveDynamicRoot ? (uint)id : 0, SourceId: 1, ParentCellId: parentCell ?? 0, EffectCellId: 0, BuildingShellAnchorCellId: 0, TransformFingerprint: default, GeometryFingerprint: default, AppearanceFingerprint: default)); } private static WorldEntity Entity( uint id, uint serverGuid, uint? parentCell) => new() { Id = id, ServerGuid = serverGuid, SourceGfxObjOrSetupId = 0x0200_0001, Position = Vector3.Zero, Rotation = Quaternion.Identity, MeshRefs = [ new MeshRef(0x0100_0001, Matrix4x4.Identity), ], ParentCellId = parentCell, }; private static PortalVisibilityFrame Portal(uint cellId) { var frame = new PortalVisibilityFrame(); frame.OrderedVisibleCells.Add(cellId); return frame; } private static ClipFrameAssembly FullScreenClip(uint cellId) { var slice = new ClipViewSlice( Slot: 0, NdcAabb: new Vector4(-1, -1, 1, 1), Planes: []); var assembly = new ClipFrameAssembly(); assembly.SetOutsideViewSlices([slice]); assembly.CellIdToViewSlices[cellId] = [slice]; return assembly; } private static ( uint LandblockId, Vector3 AabbMin, Vector3 AabbMax, IReadOnlyList Entities, IReadOnlyDictionary? AnimatedById) Entry(IReadOnlyList entities) => ( Landblock, Vector3.Zero, Vector3.One, entities, null); private sealed class EmptyCellSource : IRetailPViewCellSource { public static EmptyCellSource Instance { get; } = new(); private EmptyCellSource() { } public LoadedCell? Find(uint cellId) => null; } }