Fix alpha-tested blend depth writes

Add paired depth-write variants for all five blended SetSurface families in ordinary and atmospheric Wb pipeline sets at both sample counts and in EnvCell. Select them only from the carried AlphaTestEnabled state, retain pure-Clip and Translucent override behavior, and cover disposal plus partial-construction rollback.

Correct the three stale depth oracles and the AP register count/temporary AP-232 overclaim.

Required restored mutations and first discriminating failures:

1. Wb StraightAlpha+Clip collapsed to depth-off: WalkStaticStreamPopulatorTests.ImmediateBuildingDetail_UsesExactResolvedSetSurfaceState failed at line 1278; expected wb-mesh-alpha-depth-write-1x, binds were wb-mesh-alpha-1x.

2. EnvCell raw Additive+Clip collapsed to depth-off: EnvCellAlphaDrawSourceTests.DetailOn_EveryEnvCellFamilyDrawsOnceInPlaceWithAuthoredOpacity failed at line 132; expected envcell-raw-additive-depth-write, actual envcell-raw-additive.

3. EnvCell non-Clip raw Additive forced depth-on: the same production transcript failed at line 132; first row expected envcell-raw-additive, actual envcell-raw-additive-depth-write.

4. Late Translucent|Clip override forced alpha-test/depth-on: the same production transcript failed at line 132; expected envcell-alpha, actual envcell-alpha-depth-write.
This commit is contained in:
Erik 2026-09-05 01:23:49 +02:00
parent 15ed57a1e7
commit d5cfd1c916
8 changed files with 285 additions and 82 deletions

View file

@ -95,16 +95,16 @@ public sealed class EnvCellAlphaDrawSourceTests
{ SurfaceType.Translucent | SurfaceType.InvAlpha, false, "envcell-inverse", GpuBlendMode.InverseAlpha, 0f, true },
{ SurfaceType.Base1Image | SurfaceType.Base1ClipMap, false, "envcell-clip", GpuBlendMode.PremultipliedAlpha, 200f / 255f, true },
{ SurfaceType.Base1Image | SurfaceType.Base1ClipMap, true, "envcell-clip", GpuBlendMode.PremultipliedAlpha, 100f / 255f, true },
{ SurfaceType.Base1Image | SurfaceType.Alpha | SurfaceType.Base1ClipMap, false, "envcell-alpha", GpuBlendMode.StraightAlpha, 200f / 255f, true },
{ SurfaceType.Base1Image | SurfaceType.Alpha | SurfaceType.Base1ClipMap, true, "envcell-alpha", GpuBlendMode.StraightAlpha, 100f / 255f, true },
{ SurfaceType.Base1Image | SurfaceType.Alpha | SurfaceType.Additive | SurfaceType.Base1ClipMap, false, "envcell-additive", GpuBlendMode.Additive, 200f / 255f, false },
{ SurfaceType.Base1Image | SurfaceType.Alpha | SurfaceType.Additive | SurfaceType.Base1ClipMap, true, "envcell-additive", GpuBlendMode.Additive, 100f / 255f, false },
{ SurfaceType.Base1Image | SurfaceType.Additive | SurfaceType.Base1ClipMap, false, "envcell-raw-additive", GpuBlendMode.RawAdditive, 200f / 255f, false },
{ SurfaceType.Base1Image | SurfaceType.Additive | SurfaceType.Base1ClipMap, true, "envcell-raw-additive", GpuBlendMode.RawAdditive, 100f / 255f, false },
{ SurfaceType.Base1Image | SurfaceType.InvAlpha | SurfaceType.Base1ClipMap, false, "envcell-inverse", GpuBlendMode.InverseAlpha, 200f / 255f, true },
{ SurfaceType.Base1Image | SurfaceType.InvAlpha | SurfaceType.Base1ClipMap, true, "envcell-inverse", GpuBlendMode.InverseAlpha, 100f / 255f, true },
{ SurfaceType.Base1Image | SurfaceType.InvAlpha | SurfaceType.Additive | SurfaceType.Base1ClipMap, false, "envcell-inverse-additive", GpuBlendMode.InverseAdditive, 200f / 255f, false },
{ SurfaceType.Base1Image | SurfaceType.InvAlpha | SurfaceType.Additive | SurfaceType.Base1ClipMap, true, "envcell-inverse-additive", GpuBlendMode.InverseAdditive, 100f / 255f, false },
{ SurfaceType.Base1Image | SurfaceType.Alpha | SurfaceType.Base1ClipMap, false, "envcell-alpha-depth-write", GpuBlendMode.StraightAlpha, 200f / 255f, true },
{ SurfaceType.Base1Image | SurfaceType.Alpha | SurfaceType.Base1ClipMap, true, "envcell-alpha-depth-write", GpuBlendMode.StraightAlpha, 100f / 255f, true },
{ SurfaceType.Base1Image | SurfaceType.Alpha | SurfaceType.Additive | SurfaceType.Base1ClipMap, false, "envcell-additive-depth-write", GpuBlendMode.Additive, 200f / 255f, false },
{ SurfaceType.Base1Image | SurfaceType.Alpha | SurfaceType.Additive | SurfaceType.Base1ClipMap, true, "envcell-additive-depth-write", GpuBlendMode.Additive, 100f / 255f, false },
{ SurfaceType.Base1Image | SurfaceType.Additive | SurfaceType.Base1ClipMap, false, "envcell-raw-additive-depth-write", GpuBlendMode.RawAdditive, 200f / 255f, false },
{ SurfaceType.Base1Image | SurfaceType.Additive | SurfaceType.Base1ClipMap, true, "envcell-raw-additive-depth-write", GpuBlendMode.RawAdditive, 100f / 255f, false },
{ SurfaceType.Base1Image | SurfaceType.InvAlpha | SurfaceType.Base1ClipMap, false, "envcell-inverse-depth-write", GpuBlendMode.InverseAlpha, 200f / 255f, true },
{ SurfaceType.Base1Image | SurfaceType.InvAlpha | SurfaceType.Base1ClipMap, true, "envcell-inverse-depth-write", GpuBlendMode.InverseAlpha, 100f / 255f, true },
{ SurfaceType.Base1Image | SurfaceType.InvAlpha | SurfaceType.Additive | SurfaceType.Base1ClipMap, false, "envcell-inverse-additive-depth-write", GpuBlendMode.InverseAdditive, 200f / 255f, false },
{ SurfaceType.Base1Image | SurfaceType.InvAlpha | SurfaceType.Additive | SurfaceType.Base1ClipMap, true, "envcell-inverse-additive-depth-write", GpuBlendMode.InverseAdditive, 100f / 255f, false },
{ SurfaceType.Translucent | SurfaceType.Base1ClipMap | SurfaceType.Additive, false, "envcell-alpha", GpuBlendMode.StraightAlpha, 0f, false },
{ SurfaceType.Translucent | SurfaceType.Base1ClipMap | SurfaceType.Additive, true, "envcell-alpha", GpuBlendMode.StraightAlpha, 0f, false },
};
@ -150,7 +150,7 @@ public sealed class EnvCellAlphaDrawSourceTests
Assert.Equal(expectedBlend, selected.Blend);
Assert.True(selected.Depth.Test);
Assert.Equal(
resolved.Blend is RetailSetSurfaceBlend.Opaque or RetailSetSurfaceBlend.Clip,
resolved.Blend == RetailSetSurfaceBlend.Opaque || resolved.AlphaTestEnabled,
selected.Depth.Write);
Assert.Equal(WorldDepthContract.WorldCompare, selected.Depth.Compare);
Assert.Equal(0, fixture.Queue.PendingCount);

View file

@ -321,7 +321,7 @@ public sealed class RetailDetailTextureContractTests
};
return new PipelineState(
exactBlend,
resolved.Blend is RetailSetSurfaceBlend.Opaque or RetailSetSurfaceBlend.Clip,
resolved.Blend == RetailSetSurfaceBlend.Opaque || resolved.AlphaTestEnabled,
resolved.AlphaTestEnabled,
resolved.AlphaTestReference,
resolved.FogEnabled,

View file

@ -380,16 +380,16 @@ public sealed class OrderPreservingSubmitterTests
atmospheric: true);
using DrawScope draw = fx.BeginDraw();
OrderedDrawStream stream = StreamOf(
MakeCommand(0),
MakeCommand(0, translucency: TranslucencyKind.Additive),
MakeCommand(
1,
translucency: TranslucencyKind.Additive,
detailCategory: 1,
materialState: RetailSetSurfaceMaterialState.Resolve(
SurfaceType.Additive,
SurfaceType.Additive | SurfaceType.Base1ClipMap,
texturePresent: true,
textureHasPalette: false)),
MakeCommand(2));
MakeCommand(2, translucency: TranslucencyKind.Additive));
PrepareAndDrawWhole(fx.Dispatcher, draw, stream);
@ -397,16 +397,24 @@ public sealed class OrderPreservingSubmitterTests
Assert.Equal([(0, 1), (1, 1), (2, 1)],
runs.Select(run => (run.Start, run.Count)).ToList());
Assert.Equal((0u, 0f, 0f, 0), DetailState(runs[0].Constants));
Assert.Equal((77u, 3.5f, 0f, RetailDetailTextureContract.NoFogRenderPassFlag),
Assert.Equal((77u, 3.5f, 200f / 255f, RetailDetailTextureContract.NoFogRenderPassFlag),
DetailState(runs[1].Constants));
Assert.Equal((0u, 0f, 0f, 0), DetailState(runs[2].Constants));
Assert.Contains(fx.Device.Calls.OfType<GpuRecordedPipelineBind>(),
call => call.PipelineName == "wb-mesh-atmospheric-raw-additive-1x");
call => call.PipelineName == "wb-mesh-atmospheric-raw-additive-depth-write-1x");
Assert.Contains(fx.Device.Calls.OfType<GpuRecordedPipelineBind>(),
call => call.PipelineName == "wb-mesh-atmospheric-additive-1x");
GpuPipelineDescription selected = fx.Device.CreatedPipelines
.Single(pipeline => pipeline.Description.Name == "wb-mesh-atmospheric-raw-additive-1x")
.Single(pipeline => pipeline.Description.Name == "wb-mesh-atmospheric-raw-additive-depth-write-1x")
.Description;
Assert.Equal("mesh_atmospheric", selected.Shaders.Name);
Assert.Equal(GpuBlendMode.RawAdditive, selected.Blend);
Assert.True(selected.Depth.Write);
GpuPipelineDescription adjacent = fx.Device.CreatedPipelines
.Single(pipeline => pipeline.Description.Name == "wb-mesh-atmospheric-additive-1x")
.Description;
Assert.Equal(GpuBlendMode.Additive, adjacent.Blend);
Assert.False(adjacent.Depth.Write);
}
private static (uint Slot, float Tiling, float Reference, int NoFog) DetailState(
@ -414,6 +422,68 @@ public sealed class OrderPreservingSubmitterTests
(constants.TextureIndexA, constants.ParamA, constants.ParamB,
constants.RenderPass & RetailDetailTextureContract.NoFogRenderPassFlag);
[Theory]
[InlineData(false, "wb-mesh")]
[InlineData(true, "wb-mesh-atmospheric")]
public void SetSurfacePipelineFamilies_CarryDepthWritePairsAcrossBothSampleCountsAndDispose(
bool atmospheric,
string prefix)
{
var fx = new DispatcherFixture(atmospheric: atmospheric, sampleCount: 4);
var families = new (string Name, GpuBlendMode Blend)[]
{
("alpha", GpuBlendMode.StraightAlpha),
("additive", GpuBlendMode.Additive),
("raw-additive", GpuBlendMode.RawAdditive),
("inverse", GpuBlendMode.InverseAlpha),
("inverse-additive", GpuBlendMode.InverseAdditive),
};
var retained = new List<RecordingGpuPipeline>();
foreach ((string name, GpuBlendMode blend) in families)
{
foreach ((string suffix, int samples) in new[] { (string.Empty, 4), ("-1x", 1) })
{
RecordingGpuPipeline depthOff = fx.Device.CreatedPipelines.Single(
pipeline => pipeline.Description.Name == $"{prefix}-{name}{suffix}");
RecordingGpuPipeline depthOn = fx.Device.CreatedPipelines.Single(
pipeline => pipeline.Description.Name == $"{prefix}-{name}-depth-write{suffix}");
retained.Add(depthOff);
retained.Add(depthOn);
Assert.Equal(blend, depthOff.Description.Blend);
Assert.Equal(blend, depthOn.Description.Blend);
Assert.Equal(samples, depthOff.Description.SampleCount);
Assert.Equal(samples, depthOn.Description.SampleCount);
Assert.True(depthOff.Description.Depth.Test);
Assert.True(depthOn.Description.Depth.Test);
Assert.False(depthOff.Description.Depth.Write);
Assert.True(depthOn.Description.Depth.Write);
Assert.Equal(WorldDepthContract.WorldCompare, depthOff.Description.Depth.Compare);
Assert.Equal(WorldDepthContract.WorldCompare, depthOn.Description.Depth.Compare);
}
}
fx.Dispose();
Assert.All(retained, static pipeline => Assert.True(pipeline.IsDisposed));
}
[Fact]
public void SetSurfacePipelineConstructionFailureRollsBackBothSampleCountOwners()
{
using var device = new RecordingGpuDevice();
device.PipelineFailure = description =>
description.Name == "wb-mesh-inverse-depth-write-1x"
? new InvalidOperationException("injected pipeline failure")
: null;
Assert.Throws<InvalidOperationException>(() =>
new DispatcherFixture(sampleCount: 4, device: device));
Assert.Equal(21, device.CreatedPipelines.Count);
Assert.All(device.CreatedPipelines, static pipeline => Assert.True(pipeline.IsDisposed));
}
[Fact]
public void PrepareThenDraw_OpaqueRunUsesRenderPassZeroAndAlphaBlendRunUsesRenderPassOne()
{
@ -707,11 +777,13 @@ public sealed class OrderPreservingSubmitterTests
public DispatcherFixture(
bool detailAvailable = false,
bool detailEnabled = false,
bool atmospheric = false)
bool atmospheric = false,
int sampleCount = 1,
RecordingGpuDevice? device = null)
{
Device = new RecordingGpuDevice();
Device = device ?? new RecordingGpuDevice();
FrameLifetime = new GpuDeviceFrameLifetime(Device);
Scope = new VulkanWorldPassScope(sampleCount: 1);
Scope = new VulkanWorldPassScope(sampleCount);
_textures = new TextureCache(Device, new NoopDatReaderWriter());
_meshAdapter = new WbMeshAdapter(
Device,
@ -743,7 +815,7 @@ public sealed class OrderPreservingSubmitterTests
var source = new BindableAtmosphericSource();
WbDrawDispatcher.DirectionalShadowReceiverPipelineState candidate =
Assert.IsType<WbDrawDispatcher.DirectionalShadowReceiverPipelineState>(
Dispatcher.PrepareDirectionalShadowReceiver(source, sampleCount: 1));
Dispatcher.PrepareDirectionalShadowReceiver(source, sampleCount));
Assert.Null(Dispatcher.SwapDirectionalShadowReceiver(candidate));
}
Atmospheric = atmospheric;
@ -772,7 +844,7 @@ public sealed class OrderPreservingSubmitterTests
? DirectionalShadowReceiverPolicy.AtmosphericWorldPassName
: "fw2-ordered-stream-test",
Vector4.Zero,
sampleCount: 1));
Scope.SampleCount));
IDisposable publication = Scope.Publish(pass);
Device.Clear();
return new DrawScope(frame, pass, publication);

View file

@ -1240,16 +1240,16 @@ public sealed class WalkStaticStreamPopulatorTests
[InlineData(SurfaceType.Translucent, false, "wb-mesh-alpha-1x", GpuBlendMode.StraightAlpha, 0f, true)]
[InlineData(SurfaceType.Translucent | SurfaceType.Additive, false, "wb-mesh-raw-additive-1x", GpuBlendMode.RawAdditive, 0f, false)]
[InlineData(SurfaceType.Translucent | SurfaceType.InvAlpha, false, "wb-mesh-inverse-1x", GpuBlendMode.InverseAlpha, 0f, true)]
[InlineData(SurfaceType.Alpha | SurfaceType.Base1ClipMap, false, "wb-mesh-alpha-1x", GpuBlendMode.StraightAlpha, 200f / 255f, true)]
[InlineData(SurfaceType.Alpha | SurfaceType.Base1ClipMap, true, "wb-mesh-alpha-1x", GpuBlendMode.StraightAlpha, 100f / 255f, true)]
[InlineData(SurfaceType.Alpha | SurfaceType.Additive | SurfaceType.Base1ClipMap, false, "wb-mesh-additive-1x", GpuBlendMode.Additive, 200f / 255f, false)]
[InlineData(SurfaceType.Alpha | SurfaceType.Additive | SurfaceType.Base1ClipMap, true, "wb-mesh-additive-1x", GpuBlendMode.Additive, 100f / 255f, false)]
[InlineData(SurfaceType.Additive | SurfaceType.Base1ClipMap, false, "wb-mesh-raw-additive-1x", GpuBlendMode.RawAdditive, 200f / 255f, false)]
[InlineData(SurfaceType.Additive | SurfaceType.Base1ClipMap, true, "wb-mesh-raw-additive-1x", GpuBlendMode.RawAdditive, 100f / 255f, false)]
[InlineData(SurfaceType.InvAlpha | SurfaceType.Base1ClipMap, false, "wb-mesh-inverse-1x", GpuBlendMode.InverseAlpha, 200f / 255f, true)]
[InlineData(SurfaceType.InvAlpha | SurfaceType.Base1ClipMap, true, "wb-mesh-inverse-1x", GpuBlendMode.InverseAlpha, 100f / 255f, true)]
[InlineData(SurfaceType.InvAlpha | SurfaceType.Additive | SurfaceType.Base1ClipMap, false, "wb-mesh-inverse-additive-1x", GpuBlendMode.InverseAdditive, 200f / 255f, false)]
[InlineData(SurfaceType.InvAlpha | SurfaceType.Additive | SurfaceType.Base1ClipMap, true, "wb-mesh-inverse-additive-1x", GpuBlendMode.InverseAdditive, 100f / 255f, false)]
[InlineData(SurfaceType.Alpha | SurfaceType.Base1ClipMap, false, "wb-mesh-alpha-depth-write-1x", GpuBlendMode.StraightAlpha, 200f / 255f, true)]
[InlineData(SurfaceType.Alpha | SurfaceType.Base1ClipMap, true, "wb-mesh-alpha-depth-write-1x", GpuBlendMode.StraightAlpha, 100f / 255f, true)]
[InlineData(SurfaceType.Alpha | SurfaceType.Additive | SurfaceType.Base1ClipMap, false, "wb-mesh-additive-depth-write-1x", GpuBlendMode.Additive, 200f / 255f, false)]
[InlineData(SurfaceType.Alpha | SurfaceType.Additive | SurfaceType.Base1ClipMap, true, "wb-mesh-additive-depth-write-1x", GpuBlendMode.Additive, 100f / 255f, false)]
[InlineData(SurfaceType.Additive | SurfaceType.Base1ClipMap, false, "wb-mesh-raw-additive-depth-write-1x", GpuBlendMode.RawAdditive, 200f / 255f, false)]
[InlineData(SurfaceType.Additive | SurfaceType.Base1ClipMap, true, "wb-mesh-raw-additive-depth-write-1x", GpuBlendMode.RawAdditive, 100f / 255f, false)]
[InlineData(SurfaceType.InvAlpha | SurfaceType.Base1ClipMap, false, "wb-mesh-inverse-depth-write-1x", GpuBlendMode.InverseAlpha, 200f / 255f, true)]
[InlineData(SurfaceType.InvAlpha | SurfaceType.Base1ClipMap, true, "wb-mesh-inverse-depth-write-1x", GpuBlendMode.InverseAlpha, 100f / 255f, true)]
[InlineData(SurfaceType.InvAlpha | SurfaceType.Additive | SurfaceType.Base1ClipMap, false, "wb-mesh-inverse-additive-depth-write-1x", GpuBlendMode.InverseAdditive, 200f / 255f, false)]
[InlineData(SurfaceType.InvAlpha | SurfaceType.Additive | SurfaceType.Base1ClipMap, true, "wb-mesh-inverse-additive-depth-write-1x", GpuBlendMode.InverseAdditive, 100f / 255f, false)]
[InlineData(SurfaceType.Translucent | SurfaceType.Base1ClipMap | SurfaceType.Additive,
true, "wb-mesh-alpha-1x", GpuBlendMode.StraightAlpha, 0f, false)]
public void ImmediateBuildingDetail_UsesExactResolvedSetSurfaceState(
@ -1294,7 +1294,7 @@ public sealed class WalkStaticStreamPopulatorTests
Assert.Equal(expectedBlend, selected.Blend);
Assert.True(selected.Depth.Test);
Assert.Equal(
resolved.Blend is RetailSetSurfaceBlend.Opaque or RetailSetSurfaceBlend.Clip,
resolved.Blend == RetailSetSurfaceBlend.Opaque || resolved.AlphaTestEnabled,
selected.Depth.Write);
Assert.Equal(WorldDepthContract.WorldCompare, selected.Depth.Compare);
Vector4 source = RetailDetailTextureContract.Combine(

View file

@ -252,11 +252,51 @@ public class EnvCellRendererTests
meshManager,
new WbFrustum());
Assert.Equal(7, device.CreatedPipelines.Count);
Assert.Equal(12, device.CreatedPipelines.Count);
Assert.DoesNotContain(device.CreatedPipelines,
pipeline => pipeline.Description.Name.Contains("detail", StringComparison.Ordinal));
}
[Fact]
public void SetSurfacePipelineConstructionFailureDisposesEveryCompletedVariant()
{
using var device = new RecordingGpuDevice();
using var meshManager = CreateMeshManager(device);
device.PipelineFailure = description =>
description.Name == "envcell-inverse-depth-write"
? new InvalidOperationException("injected pipeline failure")
: null;
Assert.Throws<InvalidOperationException>(() => new EnvCellRenderer(
device,
new GpuDeviceFrameLifetime(device),
new VulkanWorldPassScope(sampleCount: 1),
meshManager,
new WbFrustum()));
Assert.Equal(9, device.CreatedPipelines.Count);
Assert.All(device.CreatedPipelines, static pipeline => Assert.True(pipeline.IsDisposed));
}
[Fact]
public void DisposeReleasesEverySetSurfacePipelineVariant()
{
using var device = new RecordingGpuDevice();
using var meshManager = CreateMeshManager(device);
var renderer = new EnvCellRenderer(
device,
new GpuDeviceFrameLifetime(device),
new VulkanWorldPassScope(sampleCount: 1),
meshManager,
new WbFrustum());
RecordingGpuPipeline[] pipelines = device.CreatedPipelines.ToArray();
renderer.Dispose();
Assert.Equal(12, pipelines.Length);
Assert.All(pipelines, static pipeline => Assert.True(pipeline.IsDisposed));
}
[Fact]
public void OrderedMdiRanges_CoalesceAdjacentCellsWithIdenticalState()
{