From 03a108ffa594eb9176f4c6f1ad6eac5c3cca63f9 Mon Sep 17 00:00:00 2001 From: Erik Date: Sat, 5 Sep 2026 01:54:42 +0200 Subject: [PATCH] fix(rendering): restore classic transparent batching Restore the pre-c4 classic immediate path for transparent commands without active building detail: always use the shared alpha pipeline and coalesce adjacent same-cull commands across TranslucencyKind boundaries. Keep detail-active material selection, detail arm/reset, and inner cull-run splitting unchanged. Required mutations and first discriminating failures (all restored): 1. Non-detail arm restored to PipelineForBlend: ClassicImmediate_NonDetailUsesAlphaBlendWhenDetailIsDisabledOrUnavailable first reported InvAlpha/detail-disabled at line 1258; expected wb-mesh-alpha-1x, actual wb-mesh-inverse-1x. 2. Translucency equality split restored with the alpha arm intact: ClassicImmediate_NonDetailCrossBlendRunCoalescesIntoOnePhysicalDraw failed at line 1279; Assert.Single saw 3 physical MDI records. 3. Detail-active command forced onto AlphaBlend: ClassicImmediate_DetailActiveUsesExactMaterialPipelineAndArm failed at line 1303; expected wb-mesh-raw-additive-depth-write-1x, actual wb-mesh-alpha-1x. --- .../Rendering/Wb/WbDrawDispatcher.Rhi.cs | 4 +- .../Walk/WalkStaticStreamPopulatorTests.cs | 179 +++++++++++++++++- 2 files changed, 176 insertions(+), 7 deletions(-) diff --git a/src/AcDream.App/Rendering/Wb/WbDrawDispatcher.Rhi.cs b/src/AcDream.App/Rendering/Wb/WbDrawDispatcher.Rhi.cs index b4711b83..f5294586 100644 --- a/src/AcDream.App/Rendering/Wb/WbDrawDispatcher.Rhi.cs +++ b/src/AcDream.App/Rendering/Wb/WbDrawDispatcher.Rhi.cs @@ -725,7 +725,6 @@ public sealed unsafe partial class WbDrawDispatcher int end = command + _transparentDrawCount; while (command < end) { - TranslucencyKind blend = _groupInputScratch[command].Translucency; RetailSetSurfaceMaterialState materialState = _groupInputScratch[command].MaterialState; bool hasDetail = detailEnabled @@ -734,7 +733,6 @@ public sealed unsafe partial class WbDrawDispatcher usedDetailCategories); int runEnd = command + 1; while (runEnd < end - && _groupInputScratch[runEnd].Translucency == blend && !hasDetail && (!detailEnabled || !CommandContainsDetailCategory( @@ -748,7 +746,7 @@ public sealed unsafe partial class WbDrawDispatcher encoder, hasDetail ? PipelineForMaterial(pipelines, materialState, pipelines.Opaque) - : PipelineForBlend(pipelines, blend), + : pipelines.AlphaBlend, mesh); if (hasDetail) ArmBuildingDetail(ref pushConstants, materialState); diff --git a/tests/AcDream.App.Tests/Rendering/Walk/WalkStaticStreamPopulatorTests.cs b/tests/AcDream.App.Tests/Rendering/Walk/WalkStaticStreamPopulatorTests.cs index 863a3500..64faeb61 100644 --- a/tests/AcDream.App.Tests/Rendering/Walk/WalkStaticStreamPopulatorTests.cs +++ b/tests/AcDream.App.Tests/Rendering/Walk/WalkStaticStreamPopulatorTests.cs @@ -1231,6 +1231,117 @@ public sealed class WalkStaticStreamPopulatorTests call => call.Constants.TextureIndexA == 99u && call.Constants.ParamA == 2f); } + [Theory] + [InlineData(SurfaceType.Additive, true, false)] + [InlineData(SurfaceType.InvAlpha, true, false)] + [InlineData(SurfaceType.Additive, false, true)] + [InlineData(SurfaceType.InvAlpha, false, true)] + public void ClassicImmediate_NonDetailUsesAlphaBlendWhenDetailIsDisabledOrUnavailable( + SurfaceType surfaceType, + bool detailAvailable, + bool detailEnabled) + { + using var fx = new DispatcherFixture( + detailAvailable: detailAvailable, + detailEnabled: detailEnabled); + using DrawScope draw = fx.BeginDraw(); + + ExecuteClassicGroups( + fx, + AlphaInstanceGroup( + detailCategory: 1u, + surfaceType: surfaceType, + sortDistance: 10f)); + + (string Pipeline, GpuPushConstants Constants, GpuRecordedMultiDrawIndirect Draw) transcript = + Assert.Single(ClassicDrawTranscript(fx.Device)); + Assert.Equal("wb-mesh-alpha-1x", transcript.Pipeline); + Assert.Equal(1u, transcript.Draw.DrawCount); + Assert.Equal(0u, transcript.Constants.TextureIndexA); + Assert.Equal(0f, transcript.Constants.ParamA); + Assert.Equal(0f, transcript.Constants.ParamB); + } + + [Fact] + public void ClassicImmediate_NonDetailCrossBlendRunCoalescesIntoOnePhysicalDraw() + { + using var fx = new DispatcherFixture( + detailAvailable: true, + detailEnabled: false); + using DrawScope draw = fx.BeginDraw(); + + ExecuteClassicGroups( + fx, + AlphaInstanceGroup(1u, SurfaceType.Alpha, sortDistance: 30f), + AlphaInstanceGroup(1u, SurfaceType.Additive, sortDistance: 20f), + AlphaInstanceGroup(1u, SurfaceType.InvAlpha, sortDistance: 10f)); + + (string Pipeline, GpuPushConstants Constants, GpuRecordedMultiDrawIndirect Draw) transcript = + Assert.Single(ClassicDrawTranscript(fx.Device)); + Assert.Equal("wb-mesh-alpha-1x", transcript.Pipeline); + Assert.Equal(3u, transcript.Draw.DrawCount); + Assert.Equal(0f, transcript.Constants.ParamA); + } + + [Fact] + public void ClassicImmediate_DetailActiveUsesExactMaterialPipelineAndArm() + { + using var fx = new DispatcherFixture( + detailAvailable: true, + detailEnabled: true); + using DrawScope draw = fx.BeginDraw(); + + ExecuteClassicGroups( + fx, + AlphaInstanceGroup( + 1u, + SurfaceType.Additive | SurfaceType.Base1ClipMap, + sortDistance: 10f)); + + (string Pipeline, GpuPushConstants Constants, GpuRecordedMultiDrawIndirect Draw) transcript = + Assert.Single(ClassicDrawTranscript(fx.Device)); + Assert.Equal("wb-mesh-raw-additive-depth-write-1x", transcript.Pipeline); + Assert.Equal(1u, transcript.Draw.DrawCount); + Assert.Equal(99u, transcript.Constants.TextureIndexA); + Assert.Equal(2f, transcript.Constants.ParamA); + Assert.Equal(200f / 255f, transcript.Constants.ParamB); + Assert.NotEqual( + 0, + transcript.Constants.RenderPass & RetailDetailTextureContract.NoFogRenderPassFlag); + } + + [Fact] + public void ClassicImmediate_NonDetailDetailNonDetailKeepsBothBoundariesAndDoesNotLeak() + { + using var fx = new DispatcherFixture( + detailAvailable: true, + detailEnabled: true); + using DrawScope draw = fx.BeginDraw(); + + ExecuteClassicGroups( + fx, + AlphaInstanceGroup(0u, SurfaceType.Alpha, sortDistance: 30f), + AlphaInstanceGroup( + 1u, + SurfaceType.Additive | SurfaceType.Base1ClipMap, + sortDistance: 20f), + AlphaInstanceGroup(0u, SurfaceType.InvAlpha, sortDistance: 10f)); + + var transcript = ClassicDrawTranscript(fx.Device); + Assert.Equal(3, transcript.Count); + Assert.Equal( + ["wb-mesh-alpha-1x", "wb-mesh-raw-additive-depth-write-1x", "wb-mesh-alpha-1x"], + transcript.Select(entry => entry.Pipeline)); + Assert.All(transcript, entry => Assert.Equal(1u, entry.Draw.DrawCount)); + Assert.Equal(0f, transcript[0].Constants.ParamA); + Assert.Equal(2f, transcript[1].Constants.ParamA); + Assert.Equal(99u, transcript[1].Constants.TextureIndexA); + Assert.Equal(200f / 255f, transcript[1].Constants.ParamB); + Assert.Equal(0f, transcript[2].Constants.ParamA); + Assert.Equal(0u, transcript[2].Constants.TextureIndexA); + Assert.Equal(0f, transcript[2].Constants.ParamB); + } + [Theory] [InlineData(SurfaceType.Alpha, false, "wb-mesh-alpha-1x", GpuBlendMode.StraightAlpha, 0f, true)] [InlineData(SurfaceType.Alpha | SurfaceType.Additive, false, "wb-mesh-additive-1x", GpuBlendMode.Additive, 0f, false)] @@ -1415,7 +1526,10 @@ public sealed class WalkStaticStreamPopulatorTests IsOpaque: false, LocalSortCenter: Vector3.Zero); - private static WbDrawDispatcher.InstanceGroup AlphaInstanceGroup(uint detailCategory) + private static WbDrawDispatcher.InstanceGroup AlphaInstanceGroup( + uint detailCategory, + SurfaceType surfaceType = SurfaceType.Alpha, + float sortDistance = 0f) { var group = new WbDrawDispatcher.InstanceGroup { @@ -1424,13 +1538,13 @@ public sealed class WalkStaticStreamPopulatorTests IndexCount = 3, TextureSlot = new GpuTextureSlot(1), TextureLayer = 0, - Translucency = TranslucencyKind.AlphaBlend, + Translucency = TranslucencyKindExtensions.FromSurfaceType(surfaceType), MaterialState = RetailSetSurfaceMaterialState.Resolve( - SurfaceType.Alpha, + surfaceType, texturePresent: true, textureHasPalette: false), }; - group.Matrices.Add(Matrix4x4.Identity); + group.Matrices.Add(Matrix4x4.CreateTranslation(0f, 0f, sortDistance)); group.SubmissionOrders.Add(0); group.Slots.Add(0); group.LightSets.Add(WbDrawDispatcher.InstanceLightSet.Disabled); @@ -1441,6 +1555,63 @@ public sealed class WalkStaticStreamPopulatorTests return group; } + private static void ExecuteClassicGroups( + DispatcherFixture fixture, + params WbDrawDispatcher.InstanceGroup[] groups) + { + fixture.Dispatcher.BeginFrame(frameSlot: 0); + MethodInfo execute = typeof(WbDrawDispatcher).GetMethod( + "ExecuteClassifiedGroups", + BindingFlags.Instance | BindingFlags.NonPublic)!; + execute.Invoke( + fixture.Dispatcher, + [ + Matrix4x4.Identity, + Vector3.Zero, + 0u, + groups, + WbDrawDispatcher.EntitySet.All, + groups.Length, + groups.Length, + false, + false, + ]); + } + + private static List<( + string Pipeline, + GpuPushConstants Constants, + GpuRecordedMultiDrawIndirect Draw)> ClassicDrawTranscript( + RecordingGpuDevice device) + { + var transcript = new List<( + string Pipeline, + GpuPushConstants Constants, + GpuRecordedMultiDrawIndirect Draw)>(); + IReadOnlyList calls = device.Calls; + for (int i = 0; i < calls.Count; i++) + { + if (calls[i] is not GpuRecordedMultiDrawIndirect draw) + continue; + + string? pipeline = null; + GpuPushConstants? constants = null; + for (int prior = i - 1; prior >= 0 && (pipeline is null || constants is null); prior--) + { + if (pipeline is null && calls[prior] is GpuRecordedPipelineBind bind) + pipeline = bind.PipelineName; + if (constants is null && calls[prior] is GpuRecordedPushConstants push) + constants = push.Constants; + } + + Assert.NotNull(pipeline); + Assert.NotNull(constants); + transcript.Add((pipeline!, constants!.Value, draw)); + } + + return transcript; + } + private static ObjectMeshData PreparedTriangle(uint objectId, uint surfaceId) => new() { ObjectId = objectId,