fix(rendering): carry retail SetSurface state to detail draws
Resolve exact SetSurface blend, alpha-test, and fog state once during extraction and preserve it through recipe-10 prepared payloads, Wb/EnvCell command data, Vulkan pipelines, push constants, and both ordinary/atmospheric one-pass shaders. Preserve AP-240 Wb pure-Clip immediate opaque/A2C while EnvCell uses retail premultiplied Clip; detail-off routing remains unchanged. Correct AP-232 and register the remaining detail-off state divergence. Mutation first failures (all restored): - raw Add->SRCALPHA/ONE: WalkStaticStreamPopulatorTests.ImmediateBuildingDetail_UsesExactResolvedSetSurfaceState line 1278, expected wb-mesh-raw-additive-1x. - inverse-add->alpha-add: same test line 1278, expected wb-mesh-inverse-additive-1x. - remove Env inverse: EnvCellAlphaDrawSourceTests.DetailOn_EveryEnvCellFamilyDrawsOnceInPlaceWithAuthoredOpacity line 132, expected envcell-inverse. - raw IsAdditive precedence: same test line 132, Translucent|Clip|Additive expected envcell-alpha. - Wb paletted ParamB=0: OrderPreservingSubmitterTests line 333, expected 0.392156869. - Wb DDS ParamB=0.05: same test line 333, expected 0.784313738. - disable Alpha+Clip test: WalkStaticStreamPopulatorTests line 1286, expected 0.784313738. - always fog raw Add: same test line 1287, expected no-fog true. - disable fog non-Add: same test line 1287, expected no-fog false. - X=a*qA: RetailDetailTextureContractTests line 261, shared squared-alpha substring absent. - X includes base alpha: same test line 262, forbidden baseTexel.a present. - CLIP uses 0.05: EnvCellAlphaDrawSourceTests.ClipShaders_UseGreaterEqualForThePerRangeReference line 367. - second detail draw: EnvCell detail-on line 131, collection contained 2 draws. - straight-alpha substitute: Wb immediate line 1278, expected wb-mesh-additive-1x. - omit ordered detail arm: OrderPreservingSubmitterTests line 318, expected (77,3.5), got (0,0). - omit atmospheric combine: RetailDetailTextureContractTests line 260, shared include absent. - drop serialized opacity: ObjectMeshDataSerializerTests line 292, expected opacity bits, got 1.0. - stale detail arm: atmospheric adjacency line 402, expected slot 0, got 77. - per-frame surface map: EnvCell warmed allocation line 285, expected 0 B, got 204800 B.
This commit is contained in:
parent
75664805f8
commit
15ed57a1e7
44 changed files with 1154 additions and 187 deletions
|
|
@ -32,7 +32,8 @@ public sealed class OrderPreservingSubmitterTests
|
|||
WalkDrawStage stage = WalkDrawStage.Terrain,
|
||||
TranslucencyKind translucency = TranslucencyKind.Opaque,
|
||||
CullMode cullMode = CullMode.CounterClockwise,
|
||||
uint detailCategory = 0) =>
|
||||
uint detailCategory = 0,
|
||||
RetailSetSurfaceMaterialState? materialState = null) =>
|
||||
new(
|
||||
Key: new GroupKey(
|
||||
FirstIndex: (uint)index * 3,
|
||||
|
|
@ -41,6 +42,7 @@ public sealed class OrderPreservingSubmitterTests
|
|||
TextureSlot: new GpuTextureSlot((uint)index),
|
||||
TextureLayer: 0,
|
||||
Translucency: translucency,
|
||||
MaterialState: materialState ?? RetailSetSurfaceMaterialState.Opaque,
|
||||
FoliageFlags: 0,
|
||||
CullMode: cullMode),
|
||||
Transform: Matrix4x4.CreateTranslation(index, index * 2, index * 3),
|
||||
|
|
@ -284,8 +286,12 @@ public sealed class OrderPreservingSubmitterTests
|
|||
Assert.Equal([(0, 1), (1, 1)], DecodeDrawRanges(fx.Device));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PrepareThenDraw_OrdinaryBuildingClipBuildingOrdinary_ArmsOnePassInPlace()
|
||||
[Theory]
|
||||
[InlineData(false, 200f / 255f)]
|
||||
[InlineData(true, 100f / 255f)]
|
||||
public void PrepareThenDraw_OrdinaryBuildingClipBuildingOrdinary_ArmsOnePassInPlace(
|
||||
bool paletted,
|
||||
float expectedReference)
|
||||
{
|
||||
using var fx = new DispatcherFixture(detailAvailable: true, detailEnabled: true);
|
||||
using DrawScope draw = fx.BeginDraw();
|
||||
|
|
@ -293,7 +299,14 @@ public sealed class OrderPreservingSubmitterTests
|
|||
OrderedDrawStream stream = StreamOf(
|
||||
MakeCommand(0),
|
||||
MakeCommand(1, detailCategory: 1),
|
||||
MakeCommand(2, translucency: TranslucencyKind.ClipMap, detailCategory: 1),
|
||||
MakeCommand(
|
||||
2,
|
||||
translucency: TranslucencyKind.ClipMap,
|
||||
detailCategory: 1,
|
||||
materialState: RetailSetSurfaceMaterialState.Resolve(
|
||||
SurfaceType.Base1ClipMap,
|
||||
texturePresent: true,
|
||||
textureHasPalette: paletted)),
|
||||
MakeCommand(3));
|
||||
|
||||
PrepareAndDrawWhole(fx.Dispatcher, draw, stream);
|
||||
|
|
@ -317,8 +330,90 @@ public sealed class OrderPreservingSubmitterTests
|
|||
.Select(call => call.PipelineName)
|
||||
.ToArray());
|
||||
Assert.Equal(4, fx.Device.Calls.OfType<GpuRecordedMultiDrawIndirect>().Count());
|
||||
Assert.Equal(expectedReference, runs[2].Constants.ParamB);
|
||||
GpuPipelineDescription a2c = fx.Device.CreatedPipelines
|
||||
.Single(pipeline => pipeline.Description.Name == "wb-mesh-opaque-a2c-1x")
|
||||
.Description;
|
||||
Assert.Equal(GpuBlendMode.None, a2c.Blend);
|
||||
Assert.True(a2c.AlphaToCoverage);
|
||||
Assert.True(a2c.Depth.Test);
|
||||
Assert.True(a2c.Depth.Write);
|
||||
Assert.Equal(WorldDepthContract.WorldCompare, a2c.Depth.Compare);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PrepareThenDraw_PureClipBuildingDetailOffRetainsAp240A2cAndNeutralDetailState()
|
||||
{
|
||||
using var fx = new DispatcherFixture(detailAvailable: true, detailEnabled: false);
|
||||
using DrawScope draw = fx.BeginDraw();
|
||||
OrderedDrawStream stream = StreamOf(
|
||||
MakeCommand(0),
|
||||
MakeCommand(
|
||||
1,
|
||||
translucency: TranslucencyKind.ClipMap,
|
||||
detailCategory: 1,
|
||||
materialState: RetailSetSurfaceMaterialState.Resolve(
|
||||
SurfaceType.Base1ClipMap,
|
||||
texturePresent: true,
|
||||
textureHasPalette: true)),
|
||||
MakeCommand(2));
|
||||
|
||||
PrepareAndDrawWhole(fx.Dispatcher, draw, stream);
|
||||
|
||||
List<(GpuPushConstants Constants, int Start, int Count)> runs = DecodeRuns(fx.Device);
|
||||
Assert.Equal([(0, 1), (1, 1), (2, 1)],
|
||||
runs.Select(run => (run.Start, run.Count)).ToList());
|
||||
Assert.Equal(0u, runs[1].Constants.TextureIndexA);
|
||||
Assert.Equal(0f, runs[1].Constants.ParamA);
|
||||
Assert.Equal(0f, runs[1].Constants.ParamB);
|
||||
Assert.Equal(0, runs[1].Constants.RenderPass & RetailDetailTextureContract.NoFogRenderPassFlag);
|
||||
Assert.Contains(fx.Device.Calls.OfType<GpuRecordedPipelineBind>(),
|
||||
call => call.PipelineName == "wb-mesh-opaque-a2c-1x");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AtmosphericReceiver_AdjacentOrdinaryDetailOrdinaryCommandsDoNotLeakExactState()
|
||||
{
|
||||
using var fx = new DispatcherFixture(
|
||||
detailAvailable: true,
|
||||
detailEnabled: true,
|
||||
atmospheric: true);
|
||||
using DrawScope draw = fx.BeginDraw();
|
||||
OrderedDrawStream stream = StreamOf(
|
||||
MakeCommand(0),
|
||||
MakeCommand(
|
||||
1,
|
||||
translucency: TranslucencyKind.Additive,
|
||||
detailCategory: 1,
|
||||
materialState: RetailSetSurfaceMaterialState.Resolve(
|
||||
SurfaceType.Additive,
|
||||
texturePresent: true,
|
||||
textureHasPalette: false)),
|
||||
MakeCommand(2));
|
||||
|
||||
PrepareAndDrawWhole(fx.Dispatcher, draw, stream);
|
||||
|
||||
List<(GpuPushConstants Constants, int Start, int Count)> runs = DecodeRuns(fx.Device);
|
||||
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),
|
||||
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");
|
||||
GpuPipelineDescription selected = fx.Device.CreatedPipelines
|
||||
.Single(pipeline => pipeline.Description.Name == "wb-mesh-atmospheric-raw-additive-1x")
|
||||
.Description;
|
||||
Assert.Equal("mesh_atmospheric", selected.Shaders.Name);
|
||||
Assert.Equal(GpuBlendMode.RawAdditive, selected.Blend);
|
||||
}
|
||||
|
||||
private static (uint Slot, float Tiling, float Reference, int NoFog) DetailState(
|
||||
GpuPushConstants constants) =>
|
||||
(constants.TextureIndexA, constants.ParamA, constants.ParamB,
|
||||
constants.RenderPass & RetailDetailTextureContract.NoFogRenderPassFlag);
|
||||
|
||||
[Fact]
|
||||
public void PrepareThenDraw_OpaqueRunUsesRenderPassZeroAndAlphaBlendRunUsesRenderPassOne()
|
||||
{
|
||||
|
|
@ -609,7 +704,10 @@ public sealed class OrderPreservingSubmitterTests
|
|||
private readonly WbMeshAdapter _meshAdapter;
|
||||
private readonly TextureCache _textures;
|
||||
|
||||
public DispatcherFixture(bool detailAvailable = false, bool detailEnabled = false)
|
||||
public DispatcherFixture(
|
||||
bool detailAvailable = false,
|
||||
bool detailEnabled = false,
|
||||
bool atmospheric = false)
|
||||
{
|
||||
Device = new RecordingGpuDevice();
|
||||
FrameLifetime = new GpuDeviceFrameLifetime(Device);
|
||||
|
|
@ -640,6 +738,15 @@ public sealed class OrderPreservingSubmitterTests
|
|||
new GpuTextureSlot(77), 3.5f, 0x05000001, 0x08000001, 16, 16)
|
||||
: default,
|
||||
buildingDetailEnabled: () => detailEnabled);
|
||||
if (atmospheric)
|
||||
{
|
||||
var source = new BindableAtmosphericSource();
|
||||
WbDrawDispatcher.DirectionalShadowReceiverPipelineState candidate =
|
||||
Assert.IsType<WbDrawDispatcher.DirectionalShadowReceiverPipelineState>(
|
||||
Dispatcher.PrepareDirectionalShadowReceiver(source, sampleCount: 1));
|
||||
Assert.Null(Dispatcher.SwapDirectionalShadowReceiver(candidate));
|
||||
}
|
||||
Atmospheric = atmospheric;
|
||||
}
|
||||
|
||||
public RecordingGpuDevice Device { get; }
|
||||
|
|
@ -650,6 +757,8 @@ public sealed class OrderPreservingSubmitterTests
|
|||
|
||||
public WbDrawDispatcher Dispatcher { get; }
|
||||
|
||||
public bool Atmospheric { get; }
|
||||
|
||||
/// <summary>Opens a frame and a backbuffer pass, publishes it on
|
||||
/// <see cref="Scope"/>, then clears the recorded calls so a test only
|
||||
/// sees what its own <c>SubmitOrderedStream</c> call produced.</summary>
|
||||
|
|
@ -659,7 +768,11 @@ public sealed class OrderPreservingSubmitterTests
|
|||
IGpuFrame frame = FrameLifetime.CurrentFrame!;
|
||||
IGpuPassEncoder pass = frame.BeginPass(
|
||||
GpuPassDescription.BackbufferClear(
|
||||
"fw2-ordered-stream-test", Vector4.Zero, sampleCount: 1));
|
||||
Atmospheric
|
||||
? DirectionalShadowReceiverPolicy.AtmosphericWorldPassName
|
||||
: "fw2-ordered-stream-test",
|
||||
Vector4.Zero,
|
||||
sampleCount: 1));
|
||||
IDisposable publication = Scope.Publish(pass);
|
||||
Device.Clear();
|
||||
return new DrawScope(frame, pass, publication);
|
||||
|
|
@ -674,6 +787,30 @@ public sealed class OrderPreservingSubmitterTests
|
|||
}
|
||||
}
|
||||
|
||||
private sealed class BindableAtmosphericSource : IDirectionalShadowReceiverSource
|
||||
{
|
||||
public DirectionalShadowPipelineShaders PipelineShaders =>
|
||||
DirectionalShadowPipelineShaders.Local;
|
||||
|
||||
public bool TryGetCurrentFrameBinding(
|
||||
IGpuFrame frame,
|
||||
out DirectionalShadowFrameBinding binding)
|
||||
{
|
||||
GpuRingAllocation allocation = frame.AllocateRing(
|
||||
checked((int)DirectionalShadowUniforms.SizeInBytes),
|
||||
GpuRingUsage.Uniform);
|
||||
binding = new DirectionalShadowFrameBinding(
|
||||
frame.Serial,
|
||||
Enabled: false,
|
||||
allocation.Buffer,
|
||||
allocation.OffsetBytes,
|
||||
DirectionalShadowUniforms.SizeInBytes,
|
||||
GpuTextureSlot.Unassigned,
|
||||
CascadeCount: 0);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class NullPreparedAssetSource : IPreparedAssetSource
|
||||
{
|
||||
public PreparedAssetSourceStats Stats => default;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue