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.
This commit is contained in:
Erik 2026-09-05 01:54:42 +02:00
parent d5cfd1c916
commit 03a108ffa5
2 changed files with 176 additions and 7 deletions

View file

@ -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);

View file

@ -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<GpuRecordedCall> 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,