using System.Numerics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using AcDream.App.Rendering; using AcDream.App.Rendering.Gpu; using AcDream.App.Rendering.Gpu.Vk; using AcDream.App.Rendering.Wb; using AcDream.App.Tests.Rendering.Gpu; using AcDream.Content; using Chorizite.Core.Render.Enums; using Microsoft.Extensions.Logging.Abstractions; using CullMode = DatReaderWriter.Enums.CullMode; namespace AcDream.App.Tests.Rendering; /// /// S4-c2 final EnvCell production-dispatch pins: exact retained per-batch mask, /// one token per (cell,list), separate list-filtered replay sources, immediate /// detail turns, interleaving, and warmed allocation behavior. /// public sealed class EnvCellAlphaDrawSourceTests { /// /// Canonical F4180104 surface 08000BFF's pure Base1ClipMap mask 0x08 /// reaches CLIP while alpha-family 0x02 reaches ALPHA. Mutation check: /// hardcoding MaskAlphaFamily in the production batch classifier makes /// the first assertion report Alpha instead of Clip. [Fact] public void RetainedPerBatchMask_RoutesClipAndAlphaExactly() { var clip = new ObjectRenderBatch { IsTransparent = true, RetailSurfaceMask = 0x08 }; var alpha = new ObjectRenderBatch { IsTransparent = true, RetailSurfaceMask = 0x02 }; Assert.Equal( EnvCellTransparentRoute.Clip, EnvCellRenderer.RouteTransparentBatch(clip, detailSurfaceActive: false)); Assert.Equal( EnvCellTransparentRoute.Alpha, EnvCellRenderer.RouteTransparentBatch(alpha, detailSurfaceActive: false)); } /// /// The real walk leaf executes the preceding opaque pass, dispatch, and /// queue drain. The opaque subset draws at the turn; CLIP and ALPHA do not. /// Each delayed list then replays exactly its own subset. [Fact] public void WholeLeaf_MixedCellDrawsOpaqueAtTurnThenClipAndAlphaAtDrain() { using var fixture = new ProductionEnvCellFixture( detailSurfaceActive: false, BatchSpec.Opaque, BatchSpec.ClipDds, BatchSpec.Alpha); fixture.Queue.BeginFrame(); fixture.Leaf.DrawCellShell(ProductionEnvCellFixture.CellId); GpuRecordedMultiDrawIndirect turnDraw = Assert.Single( fixture.Device.Calls.OfType()); Assert.Equal(1u, turnDraw.DrawCount); Assert.Equal(["envcell-opaque"], DrawPipelineNames(fixture.Device)); Assert.Equal(1, fixture.Queue.ClipCount); Assert.Equal(1, fixture.Queue.AlphaCount); Assert.False(Assert.Single(ClipEntries(fixture.Queue)).OverrideClipmap); fixture.Queue.EndFrame(); GpuRecordedMultiDrawIndirect[] draws = [.. fixture.Device.Calls.OfType()]; Assert.Equal(3, draws.Length); Assert.All(draws, static draw => Assert.Equal(1u, draw.DrawCount)); Assert.Equal( ["envcell-opaque", "envcell-clip", "envcell-alpha"], DrawPipelineNames(fixture.Device)); } [Theory] [InlineData(0, "envcell-opaque", 0f)] [InlineData(1, "envcell-alpha", 0f)] [InlineData(2, "envcell-additive", 0f)] [InlineData(3, "envcell-clip", 200f / 255f)] [InlineData(4, "envcell-clip", 100f / 255f)] public void DetailOn_EveryEnvCellFamilyDrawsOnceInPlaceWithAuthoredOpacity( int specIndex, string expectedPipeline, float expectedReference) { BatchSpec spec = specIndex switch { 0 => BatchSpec.Opaque, 1 => BatchSpec.Alpha, 2 => BatchSpec.Additive, 3 => BatchSpec.ClipDds, 4 => BatchSpec.ClipPaletted, _ => throw new ArgumentOutOfRangeException(nameof(specIndex)), }; using var fixture = new ProductionEnvCellFixture( detailSurfaceActive: true, spec); fixture.Queue.BeginFrame(); fixture.Leaf.DrawCellShell(ProductionEnvCellFixture.CellId); Assert.Single(fixture.Device.Calls.OfType()); Assert.Equal( [expectedPipeline], DrawPipelineNames(fixture.Device)); Assert.All( DrawPushConstants(fixture.Device), constants => Assert.Equal(expectedReference, constants.ParamB)); GpuPushConstants armed = Assert.Single( DrawPushConstants(fixture.Device), constants => constants.ParamA != 0f); Assert.NotEqual(0u, armed.TextureIndexA); Assert.Equal(0, fixture.Queue.PendingCount); GpuRecordedStorageBind batchBind = fixture.Device.Calls .OfType() .Last(call => call.Binding == GpuBindingModel.StorageBatches); ReadOnlySpan gpuBatches = MemoryMarshal.Cast( fixture.Device.RingBytes.Slice((int)batchBind.OffsetBytes, (int)batchBind.SizeBytes)); Assert.Equal(0.25f, Assert.Single(gpuBatches.ToArray()).SurfaceOpacity); fixture.Queue.EndFrame(); Assert.Single(fixture.Device.Calls.OfType()); } /// Two cell tokens from the SAME , /// with an unrelated source's entry appended between them, must still /// produce TWO separate single-cell draw calls around the interposed /// entry — 's "only adjacent /// same-source entries batch" invariant (the queue never groups across /// another entry, which is what keeps compositing order exact). Mutation /// check: an implementation that draws every prepared cell id in one /// call regardless of the requested (first, count) range would /// print both cell ids together on EACH of the two /// DrawPreparedAlphaBatch invocations instead of once each — the /// exact sequence assertion below fails against that mutation. [Fact] public void ParticleAppendedBetweenTwoCellTokens_KeepsItsPositionInTheCombinedDrain() { var log = new List(); var clipSource = Source(EnvCellTransparentRoute.Clip, log); var cellSource = Source(EnvCellTransparentRoute.Alpha, log); var particleSource = new RecordingSource("particle", log); var queue = new RetailAlphaQueue(); queue.BeginFrame(); RetailPViewPassExecutor.DispatchTransparentCellShell( 0x100u, EnvCellTransparentRoute.Alpha, false, queue, clipSource, cellSource, (_, _, _) => throw new InvalidOperationException()); Assert.True(queue.TryAppend(RetailAlphaList.Alpha, particleSource, 7, false)); RetailPViewPassExecutor.DispatchTransparentCellShell( 0x200u, EnvCellTransparentRoute.Alpha, false, queue, clipSource, cellSource, (_, _, _) => throw new InvalidOperationException()); queue.EndFrame(); Assert.Equal(new[] { "Alpha:00000100", "particle:7", "Alpha:00000200" }, log); } [Fact] public void ProductionWholeLeaf_WarmedScanSubmitRhiAndFilteredReplayDoNotAllocate() { using var fixture = new ProductionEnvCellFixture( detailSurfaceActive: false, BatchSpec.Opaque, BatchSpec.ClipDds, BatchSpec.Alpha, ringCapacityBytes: 64 * 1024 * 1024); fixture.Device.Clear(); fixture.Device.RecordingEnabled = false; long allocated = ZeroAllocationProbe.MeasureWarmed( fixture.RunWholeLeafFrame, batchSize: 256, warmupBatches: 2, samples: 4); Assert.Equal(0, allocated); } [Theory] [InlineData(false, 200f / 255f)] [InlineData(true, 100f / 255f)] public void WholeLeaf_ClipDrainBindsExactStateAndTextureClassReference( bool paletted, float expectedReference) { BatchSpec clip = paletted ? BatchSpec.ClipPaletted : BatchSpec.ClipDds; using var fixture = new ProductionEnvCellFixture( detailSurfaceActive: false, clip); fixture.Queue.BeginFrame(); fixture.Leaf.DrawCellShell(ProductionEnvCellFixture.CellId); Assert.Empty(fixture.Device.Calls.OfType()); Assert.False(Assert.Single(ClipEntries(fixture.Queue)).OverrideClipmap); fixture.Queue.EndFrame(); Assert.Equal(["envcell-clip"], DrawPipelineNames(fixture.Device)); Assert.Equal( expectedReference, Assert.Single(DrawPushConstants(fixture.Device)).ParamB); GpuPipelineDescription clipPipeline = fixture.Device.CreatedPipelines .Single(static pipeline => pipeline.Description.Name == "envcell-clip") .Description; Assert.Equal(GpuBlendMode.PremultipliedAlpha, clipPipeline.Blend); Assert.True(clipPipeline.Depth.Test); Assert.True(clipPipeline.Depth.Write); Assert.Equal(WorldDepthContract.WorldCompare, clipPipeline.Depth.Compare); Assert.False(clipPipeline.AlphaToCoverage); GpuPipelineDescription alphaPipeline = fixture.Device.CreatedPipelines .Single(static pipeline => pipeline.Description.Name == "envcell-alpha") .Description; Assert.Equal(GpuBlendMode.StraightAlpha, alphaPipeline.Blend); Assert.True(alphaPipeline.Depth.Test); Assert.False(alphaPipeline.Depth.Write); } [Fact] public void WholeLeaf_PositiveStippleClipMaskUsesClipPipelineAndDdsReference() { using var fixture = new ProductionEnvCellFixture( detailSurfaceActive: false, BatchSpec.ClipPositiveStippleDds); fixture.Queue.BeginFrame(); fixture.Leaf.DrawCellShell(ProductionEnvCellFixture.CellId); Assert.Empty(fixture.Device.Calls.OfType()); Assert.Equal(1, fixture.Queue.ClipCount); Assert.Equal(0, fixture.Queue.AlphaCount); Assert.False(Assert.Single(ClipEntries(fixture.Queue)).OverrideClipmap); fixture.Queue.EndFrame(); Assert.Equal(["envcell-clip"], DrawPipelineNames(fixture.Device)); Assert.Equal( 200f / 255f, Assert.Single(DrawPushConstants(fixture.Device)).ParamB); } /// /// Vulkan has no fixed-function alpha test. The shader's discard-on-less /// spelling is exactly GREATER_EQUAL: equality survives. Changing either /// comparison to <= (strict GREATER) fails this named source+manifest pin. /// [Fact] public void ClipShaders_UseGreaterEqualForThePerRangeReference() { string root = RepositoryRoot(); string modern = File.ReadAllText(Path.Combine( root, "src", "AcDream.App", "Rendering", "Shaders", "mesh_modern.frag")); string atmospheric = File.ReadAllText(Path.Combine( root, "src", "AcDream.App", "Rendering", "Shaders", "mesh_atmospheric.frag")); foreach (string shader in new[] { modern, atmospheric }) { Assert.Contains( "if (detailActive ? alpha < alphaCutoff : color.a < alphaCutoff)", shader, StringComparison.Ordinal); Assert.DoesNotContain("alpha <= alphaCutoff", shader, StringComparison.Ordinal); Assert.DoesNotContain("color.a <= alphaCutoff", shader, StringComparison.Ordinal); Assert.Contains("isRetailClipReference(uParamB) ? uParamB : 0.05", shader, StringComparison.Ordinal); Assert.Contains("abs(value - (100.0 / 255.0)) < 0.000001", shader, StringComparison.Ordinal); Assert.Contains("abs(value - (200.0 / 255.0)) < 0.000001", shader, StringComparison.Ordinal); Assert.DoesNotContain("uParamB > 0.0", shader, StringComparison.Ordinal); Assert.DoesNotContain("value - 0.5", shader, StringComparison.Ordinal); } } [Theory] [InlineData(0)] // Flush [InlineData(1)] // EndFrame [InlineData(2)] // AbortFrame public void RejectedEnvCellStorm_RollsBackPayloadAndStillResetsFirstUseSource(int completion) { const int retainedGeometricBound = 4096; int rejectedResetCount = 0; int rejectedDrawCount = 0; var accepted = new RetailPViewPassExecutor.EnvCellAlphaDrawSource( static (_, _, _) => { }, EnvCellTransparentRoute.Clip); var rejected = new RetailPViewPassExecutor.EnvCellAlphaDrawSource( (_, _, _) => rejectedDrawCount++, EnvCellTransparentRoute.Clip, () => rejectedResetCount++); var unusedAlpha = new RetailPViewPassExecutor.EnvCellAlphaDrawSource( static (_, _, _) => { }, EnvCellTransparentRoute.Alpha); var queue = new RetailAlphaQueue(); RetailPViewPassExecutor.RenderImmediateEnvCellRoute neverImmediate = static (_, _, _) => throw new InvalidOperationException(); queue.BeginFrame(); for (int i = 0; i < RetailAlphaQueue.ListCapacity; i++) { RetailPViewPassExecutor.DispatchTransparentCellShell( (uint)i, EnvCellTransparentRoute.Clip, detailSurfaceActive: false, queue, accepted, unusedAlpha, neverImmediate); } for (int i = 0; i < RetailAlphaQueue.ListCapacity * 3; i++) { RetailPViewPassExecutor.DispatchTransparentCellShell( 0xF4180104u, EnvCellTransparentRoute.Clip, detailSurfaceActive: false, queue, rejected, unusedAlpha, neverImmediate); } Assert.Equal(RetailAlphaQueue.ListCapacity, queue.ClipCount); Assert.Equal(RetailAlphaQueue.ListCapacity, accepted.PendingCount); Assert.Equal(0, rejected.PendingCount); Assert.InRange(accepted.PendingCapacity, RetailAlphaQueue.ListCapacity, retainedGeometricBound); Assert.InRange(rejected.PendingCapacity, 0, retainedGeometricBound); switch (completion) { case 0: queue.Flush(RetailAlphaFlushSite.DrawBuilding, 0f); queue.AbortFrame(); break; case 1: queue.EndFrame(); break; case 2: queue.AbortFrame(); break; default: throw new ArgumentOutOfRangeException(nameof(completion)); } Assert.Equal(1, rejectedResetCount); Assert.Equal(0, rejectedDrawCount); Assert.Equal(0, rejected.PendingCount); Assert.InRange(accepted.PendingCapacity, 0, retainedGeometricBound); Assert.InRange(accepted.PreparedCapacity, 0, retainedGeometricBound); Assert.InRange(accepted.DrawCapacity, 0, retainedGeometricBound); Assert.InRange(rejected.PendingCapacity, 0, retainedGeometricBound); Assert.InRange(rejected.PreparedCapacity, 0, retainedGeometricBound); Assert.InRange(rejected.DrawCapacity, 0, retainedGeometricBound); } private static RetailPViewPassExecutor.EnvCellAlphaDrawSource Source( EnvCellTransparentRoute expectedRoute, List log) => new( (cells, route, detail) => { Assert.Equal(expectedRoute, route); Assert.False(detail); for (int i = 0; i < cells.Count; i++) log.Add($"{route}:{cells[i]:X8}"); }, expectedRoute); private static List ClipEntries(RetailAlphaQueue queue) => (List)typeof(RetailAlphaQueue) .GetField("_clip", BindingFlags.NonPublic | BindingFlags.Instance)! .GetValue(queue)!; private static string[] DrawPipelineNames(RecordingGpuDevice device) { var names = new List(); IReadOnlyList calls = device.Calls; for (int i = 0; i < calls.Count; i++) { if (calls[i] is not GpuRecordedMultiDrawIndirect) continue; for (int prior = i - 1; prior >= 0; prior--) { if (calls[prior] is GpuRecordedPipelineBind bind) { names.Add(bind.PipelineName); break; } } } return [.. names]; } private static GpuPushConstants[] DrawPushConstants(RecordingGpuDevice device) { var constants = new List(); IReadOnlyList calls = device.Calls; for (int i = 0; i < calls.Count; i++) { if (calls[i] is not GpuRecordedMultiDrawIndirect) continue; for (int prior = i - 1; prior >= 0; prior--) { if (calls[prior] is GpuRecordedPushConstants push) { constants.Add(push.Constants); break; } } } return [.. constants]; } private static string RepositoryRoot() { DirectoryInfo? cursor = new(AppContext.BaseDirectory); while (cursor is not null && !File.Exists(Path.Combine(cursor.FullName, "AcDream.slnx"))) cursor = cursor.Parent; return cursor?.FullName ?? throw new DirectoryNotFoundException("Could not locate AcDream.slnx."); } private readonly record struct BatchSpec( byte Mask, bool IsTransparent, AcDream.Core.Meshing.TranslucencyKind Translucency, uint PaletteId) { internal static BatchSpec Opaque { get; } = new( 0x00, IsTransparent: false, AcDream.Core.Meshing.TranslucencyKind.Opaque, PaletteId: 0); internal static BatchSpec ClipDds { get; } = new( RetailAlphaMeshRouter.MaskClipMap, IsTransparent: true, AcDream.Core.Meshing.TranslucencyKind.ClipMap, PaletteId: 0); internal static BatchSpec ClipPaletted { get; } = new( RetailAlphaMeshRouter.MaskClipMap, IsTransparent: true, AcDream.Core.Meshing.TranslucencyKind.ClipMap, PaletteId: 0x04000001u); internal static BatchSpec ClipPositiveStippleDds { get; } = new( RetailAlphaMeshRouter.MaskClipMap | RetailAlphaMeshRouter.MaskPositiveStipple, IsTransparent: true, AcDream.Core.Meshing.TranslucencyKind.ClipMap, PaletteId: 0); internal static BatchSpec Alpha { get; } = new( RetailAlphaMeshRouter.MaskAlphaFamily, IsTransparent: true, AcDream.Core.Meshing.TranslucencyKind.AlphaBlend, PaletteId: 0); internal static BatchSpec Additive { get; } = new( RetailAlphaMeshRouter.MaskAlphaFamily, IsTransparent: true, AcDream.Core.Meshing.TranslucencyKind.Additive, PaletteId: 0); } /// /// Recording-GPU fixture for the production EnvCell scan, dispatch and /// filtered replay path. The two retained masks enter through the real /// Content upload boundary, not through hand-built App batches. /// private sealed class ProductionEnvCellFixture : IDisposable { public const uint CellId = 0xF4180104u; private const ulong MeshId = 0x2_F4180104UL; private readonly GpuDeviceFrameLifetime _frames; private readonly VulkanWorldPassScope _scope; private readonly ObjectMeshManager _meshManager; private readonly IGpuPassEncoder _pass; private readonly IDisposable _publication; public ProductionEnvCellFixture( bool detailSurfaceActive, BatchSpec firstBatch, BatchSpec secondBatch = default, BatchSpec thirdBatch = default, int ringCapacityBytes = 8 * 1024 * 1024) { Device = new RecordingGpuDevice(ringCapacityBytes); _frames = new GpuDeviceFrameLifetime(Device); _scope = new VulkanWorldPassScope(sampleCount: 1); _meshManager = new ObjectMeshManager( new VulkanMeshPipelineDevice(Device.Retirement), Device, new NullPreparedAssetSource(), NullLogger.Instance); var mesh = new ObjectMeshData { ObjectId = MeshId, Vertices = [ new VertexPositionNormalTexture { Position = new Vector3(0, 0, 0) }, new VertexPositionNormalTexture { Position = new Vector3(1, 0, 0) }, new VertexPositionNormalTexture { Position = new Vector3(0, 1, 0) }, ], }; BatchSpec[] specs = secondBatch == default ? [firstBatch] : thirdBatch == default ? [firstBatch, secondBatch] : [firstBatch, secondBatch, thirdBatch]; var batches = new List(specs.Length); for (int i = 0; i < specs.Length; i++) { BatchSpec spec = specs[i]; batches.Add(new TextureBatchData { Key = new TextureKey { SurfaceId = 0x08000BFFu + (uint)i, PaletteId = spec.PaletteId, }, TextureData = new byte[8 * 8 * 4], Indices = [0, 1, 2], IsTransparent = spec.IsTransparent, IsAdditive = spec.Translucency == AcDream.Core.Meshing.TranslucencyKind.Additive, Translucency = spec.Translucency, SurfaceOpacity = 0.25f, RetailSurfaceMask = spec.Mask, CullMode = CullMode.Clockwise, IsCellShell = true, SourceSurfaceIndex = i, }); } mesh.TextureBatches[(8, 8, TextureFormat.RGBA8)] = batches; Assert.NotNull(_meshManager.UploadMeshData(mesh)); Renderer = new EnvCellRenderer( Device, _frames, _scope, _meshManager, new WbFrustum(), detailSurfaceActive ? new TerrainAtlas.RetailDetailTextureBinding( new GpuTextureSlot(99), Tiling: 2f, SurfaceTextureId: 1, RenderSurfaceId: 2, Width: 4, Height: 4) : default, detailSurfaceActive ? DetailOn : DetailOff); typeof(EnvCellRenderer) .GetField("_activeSnapshot", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)! .SetValue(Renderer, new EnvCellVisibilitySnapshot { BatchedByCell = new Dictionary>> { [CellId] = new Dictionary> { [MeshId] = [ new InstanceData { Transform = Matrix4x4.Identity, CellId = CellId, }, ], }, }, }); ((HashSet)typeof(EnvCellRenderer) .GetField("_transparentCellIds", BindingFlags.NonPublic | BindingFlags.Instance)! .GetValue(Renderer)!) .Add(CellId); Queue = new RetailAlphaQueue(); ClipSource = new RetailPViewPassExecutor.EnvCellAlphaDrawSource( Renderer.RenderTransparentOrdered, EnvCellTransparentRoute.Clip); AlphaSource = new RetailPViewPassExecutor.EnvCellAlphaDrawSource( Renderer.RenderTransparentOrdered, EnvCellTransparentRoute.Alpha); var passes = new RetailPViewPassExecutor( new NullWorldPassSurface(), NullRenderFrameGlState.Instance, ClipFrame.NoClip(), terrain: null, Renderer, (WbDrawDispatcher)RuntimeHelpers.GetUninitializedObject(typeof(WbDrawDispatcher)), sky: null, particles: null, particleRenderer: null, portalDepthMask: null, Queue, (WorldRenderDiagnostics)RuntimeHelpers.GetUninitializedObject(typeof(WorldRenderDiagnostics)), (TerrainDrawDiagnosticsController)RuntimeHelpers.GetUninitializedObject( typeof(TerrainDrawDiagnosticsController))); Leaf = new WalkProductionLeafRenderer( passes, new RetailPViewFrameInput(), new ClipFrameAssembly(), static () => { }, static () => { }, static () => 0); _frames.BeginFrame(); IGpuFrame frame = _frames.CurrentFrame!; _pass = frame.BeginPass( GpuPassDescription.BackbufferClear( "s4-c2-envcell-production", Vector4.Zero, sampleCount: 1)); _publication = _scope.Publish(_pass); Renderer.BeginFrame(frameSlot: 0); Device.Clear(); } public RecordingGpuDevice Device { get; } public EnvCellRenderer Renderer { get; } public RetailAlphaQueue Queue { get; } public RetailPViewPassExecutor.EnvCellAlphaDrawSource ClipSource { get; } public RetailPViewPassExecutor.EnvCellAlphaDrawSource AlphaSource { get; } public WalkProductionLeafRenderer Leaf { get; } public void RunWholeLeafFrame() { Queue.BeginFrame(); Leaf.DrawCellShell(CellId); Queue.EndFrame(); } private static bool DetailOn() => true; private static bool DetailOff() => false; public void Dispose() { _publication.Dispose(); _pass.Dispose(); Renderer.Dispose(); _meshManager.Dispose(); Device.Dispose(); } } private sealed class NullWorldPassSurface : IWorldPassSurface { public void PrepareClipFrame() { } public void EnableClipDistances() { } public void DisableClipDistances() { } public void ClearInteriorDepth() => throw new InvalidOperationException(); } 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 RecordingSource(string name, List log) : IRetailAlphaDrawSource { private int[] _prepared = []; public void PrepareAlphaDraws(ReadOnlySpan tokens) => _prepared = tokens.ToArray(); public void DrawPreparedAlphaBatch(int firstPreparedDraw, int drawCount) { for (int i = 0; i < drawCount; i++) log.Add($"{name}:{_prepared[firstPreparedDraw + i]}"); } public void ResetAlphaSubmissions() { } } }