fix(rendering): preserve opaque building coverage under detail MSAA (#473)
This commit is contained in:
parent
d35ed1aece
commit
bf23673f3d
7 changed files with 387 additions and 46 deletions
|
|
@ -1,6 +1,7 @@
|
|||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Numerics;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using AcDream.App.Rendering;
|
||||
using AcDream.App.Rendering.Gpu;
|
||||
|
|
@ -288,58 +289,201 @@ public sealed class OrderPreservingSubmitterTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(false, 200f / 255f)]
|
||||
[InlineData(true, 100f / 255f)]
|
||||
public void PrepareThenDraw_OrdinaryBuildingClipBuildingOrdinary_ArmsOnePassInPlace(
|
||||
bool paletted,
|
||||
float expectedReference)
|
||||
[InlineData(false, 1)]
|
||||
[InlineData(false, 4)]
|
||||
[InlineData(true, 1)]
|
||||
[InlineData(true, 4)]
|
||||
public void PrepareThenDraw_OrdinaryBuildingClipBuildingOrdinary_ArmsOnePassWithoutOpaqueCoverage(
|
||||
bool atmospheric,
|
||||
int sampleCount)
|
||||
{
|
||||
using var fx = new DispatcherFixture(detailAvailable: true, detailEnabled: true);
|
||||
foreach (bool paletted in new[] { false, true })
|
||||
{
|
||||
using var fx = new DispatcherFixture(
|
||||
detailAvailable: true,
|
||||
detailEnabled: true,
|
||||
atmospheric: atmospheric,
|
||||
sampleCount: sampleCount);
|
||||
using DrawScope draw = fx.BeginDraw();
|
||||
|
||||
OrderedDrawStream stream = StreamOf(
|
||||
MakeCommand(0),
|
||||
MakeCommand(1, 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);
|
||||
|
||||
string prefix = atmospheric ? "wb-mesh-atmospheric" : "wb-mesh";
|
||||
string suffix = sampleCount == 1 ? "-1x" : string.Empty;
|
||||
string opaque = $"{prefix}-opaque{suffix}";
|
||||
string opaqueA2c = $"{prefix}-opaque-a2c{suffix}";
|
||||
List<(GpuPushConstants Constants, int Start, int Count)> runs = DecodeRuns(fx.Device);
|
||||
Assert.Equal([(0, 1), (1, 1), (2, 1), (3, 1)],
|
||||
runs.Select(run => (run.Start, run.Count)).ToList());
|
||||
Assert.Equal((0u, 0f, 0f), DetailFields(runs[0].Constants));
|
||||
Assert.Equal((77u, 3.5f, 0f), DetailFields(runs[1].Constants));
|
||||
Assert.Equal(
|
||||
(77u, 3.5f, paletted ? 100f / 255f : 200f / 255f),
|
||||
DetailFields(runs[2].Constants));
|
||||
Assert.Equal((0u, 0f, 0f), DetailFields(runs[3].Constants));
|
||||
Assert.Equal(
|
||||
[opaque, opaqueA2c, opaque, opaqueA2c, opaqueA2c],
|
||||
fx.Device.Calls.OfType<GpuRecordedPipelineBind>()
|
||||
.Select(call => call.PipelineName)
|
||||
.ToArray());
|
||||
Assert.Equal(4, fx.Device.Calls.OfType<GpuRecordedMultiDrawIndirect>().Count());
|
||||
|
||||
AssertOpaquePipeline(fx.Device, opaque, atmospheric, sampleCount, alphaToCoverage: false);
|
||||
AssertOpaquePipeline(fx.Device, opaqueA2c, atmospheric, sampleCount, alphaToCoverage: true);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(false, 1)]
|
||||
[InlineData(false, 4)]
|
||||
[InlineData(true, 1)]
|
||||
[InlineData(true, 4)]
|
||||
public void ClassicGrouped_OrdinaryBuildingClipBuildingOrdinary_ArmsOnePassWithoutOpaqueCoverage(
|
||||
bool atmospheric,
|
||||
int sampleCount)
|
||||
{
|
||||
using var fx = new DispatcherFixture(
|
||||
detailAvailable: true,
|
||||
detailEnabled: true,
|
||||
atmospheric: atmospheric,
|
||||
sampleCount: sampleCount);
|
||||
using DrawScope draw = fx.BeginDraw();
|
||||
|
||||
OrderedDrawStream stream = StreamOf(
|
||||
MakeCommand(0),
|
||||
MakeCommand(1, detailCategory: 1),
|
||||
MakeCommand(
|
||||
ExecuteClassicGroups(
|
||||
fx,
|
||||
MakeClassicGroup(0, detailCategory: 0),
|
||||
MakeClassicGroup(1, detailCategory: 1),
|
||||
MakeClassicGroup(
|
||||
2,
|
||||
translucency: TranslucencyKind.ClipMap,
|
||||
detailCategory: 1,
|
||||
translucency: TranslucencyKind.ClipMap,
|
||||
materialState: RetailSetSurfaceMaterialState.Resolve(
|
||||
SurfaceType.Base1ClipMap,
|
||||
texturePresent: true,
|
||||
textureHasPalette: paletted)),
|
||||
MakeCommand(3));
|
||||
textureHasPalette: false)),
|
||||
MakeClassicGroup(3, detailCategory: 0));
|
||||
|
||||
string prefix = atmospheric ? "wb-mesh-atmospheric" : "wb-mesh";
|
||||
string suffix = sampleCount == 1 ? "-1x" : string.Empty;
|
||||
string opaque = $"{prefix}-opaque{suffix}";
|
||||
string opaqueA2c = $"{prefix}-opaque-a2c{suffix}";
|
||||
var transcript = DecodePipelineRuns(fx.Device);
|
||||
Assert.Equal([opaqueA2c, opaque, opaqueA2c, opaqueA2c],
|
||||
transcript.Select(entry => entry.Pipeline));
|
||||
Assert.All(transcript, entry => Assert.Equal(1u, entry.Draw.DrawCount));
|
||||
Assert.Equal((0u, 0f, 0f), DetailFields(transcript[0].Constants));
|
||||
Assert.Equal((77u, 3.5f, 0f), DetailFields(transcript[1].Constants));
|
||||
Assert.Equal((77u, 3.5f, 200f / 255f), DetailFields(transcript[2].Constants));
|
||||
Assert.Equal((0u, 0f, 0f), DetailFields(transcript[3].Constants));
|
||||
AssertOpaquePipeline(fx.Device, opaque, atmospheric, sampleCount, alphaToCoverage: false);
|
||||
AssertOpaquePipeline(fx.Device, opaqueA2c, atmospheric, sampleCount, alphaToCoverage: true);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(false, 1)]
|
||||
[InlineData(false, 4)]
|
||||
[InlineData(true, 1)]
|
||||
[InlineData(true, 4)]
|
||||
public void ClassicGrouped_MixedOrdinaryAndBuildingInstances_UsesNonCoverageForWholeCommand(
|
||||
bool atmospheric,
|
||||
int sampleCount)
|
||||
{
|
||||
using var fx = new DispatcherFixture(
|
||||
detailAvailable: true,
|
||||
detailEnabled: true,
|
||||
atmospheric: atmospheric,
|
||||
sampleCount: sampleCount);
|
||||
using DrawScope draw = fx.BeginDraw();
|
||||
WbDrawDispatcher.InstanceGroup mixed = MakeClassicGroup(0, detailCategory: 0);
|
||||
AppendClassicInstance(mixed, index: 1, detailCategory: 1);
|
||||
|
||||
ExecuteClassicGroups(fx, mixed);
|
||||
|
||||
string prefix = atmospheric ? "wb-mesh-atmospheric" : "wb-mesh";
|
||||
string suffix = sampleCount == 1 ? "-1x" : string.Empty;
|
||||
var entry = Assert.Single(DecodePipelineRuns(fx.Device));
|
||||
Assert.Equal($"{prefix}-opaque{suffix}", entry.Pipeline);
|
||||
Assert.Equal(1u, entry.Draw.DrawCount);
|
||||
Assert.Equal((77u, 3.5f, 0f), DetailFields(entry.Constants));
|
||||
Assert.Equal([0u, 1u], mixed.DetailCategories);
|
||||
Assert.Equal(2, mixed.Matrices.Count);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(false, true, true)]
|
||||
[InlineData(true, false, true)]
|
||||
[InlineData(true, true, false)]
|
||||
public void PrepareThenDraw_DetailUnavailableDisabledOrQualityA2cFalsePreservesSelection(
|
||||
bool detailAvailable,
|
||||
bool detailEnabled,
|
||||
bool alphaToCoverage)
|
||||
{
|
||||
using var fx = new DispatcherFixture(
|
||||
detailAvailable: detailAvailable,
|
||||
detailEnabled: detailEnabled,
|
||||
sampleCount: 4,
|
||||
alphaToCoverage: alphaToCoverage);
|
||||
using DrawScope draw = fx.BeginDraw();
|
||||
OrderedDrawStream stream = StreamOf(
|
||||
MakeCommand(0),
|
||||
MakeCommand(1, detailCategory: 1));
|
||||
|
||||
PrepareAndDrawWhole(fx.Dispatcher, draw, stream);
|
||||
|
||||
List<(GpuPushConstants Constants, int Start, int Count)> runs = DecodeRuns(fx.Device);
|
||||
Assert.Equal([(0, 1), (1, 1), (2, 1), (3, 1)],
|
||||
runs.Select(run => (run.Start, run.Count)).ToList());
|
||||
Assert.Equal((0u, 0f), (runs[0].Constants.TextureIndexA, runs[0].Constants.ParamA));
|
||||
Assert.Equal((77u, 3.5f), (runs[1].Constants.TextureIndexA, runs[1].Constants.ParamA));
|
||||
Assert.Equal((77u, 3.5f), (runs[2].Constants.TextureIndexA, runs[2].Constants.ParamA));
|
||||
Assert.Equal((0u, 0f), (runs[3].Constants.TextureIndexA, runs[3].Constants.ParamA));
|
||||
bool detailActive = detailAvailable && detailEnabled;
|
||||
string selected = alphaToCoverage ? "wb-mesh-opaque-a2c" : "wb-mesh-opaque";
|
||||
Assert.Equal(
|
||||
[
|
||||
"wb-mesh-opaque-1x",
|
||||
"wb-mesh-opaque-a2c-1x",
|
||||
"wb-mesh-opaque-a2c-1x",
|
||||
"wb-mesh-opaque-a2c-1x",
|
||||
"wb-mesh-opaque-a2c-1x",
|
||||
],
|
||||
["wb-mesh-opaque", selected, detailActive ? "wb-mesh-opaque" : selected],
|
||||
fx.Device.Calls.OfType<GpuRecordedPipelineBind>()
|
||||
.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);
|
||||
.Select(call => call.PipelineName));
|
||||
List<(GpuPushConstants Constants, int Start, int Count)> runs = DecodeRuns(fx.Device);
|
||||
Assert.Equal([(0, 1), (1, 1)], runs.Select(run => (run.Start, run.Count)));
|
||||
Assert.Equal((0u, 0f, 0f), DetailFields(runs[0].Constants));
|
||||
Assert.Equal(
|
||||
detailActive ? (77u, 3.5f, 0f) : (0u, 0f, 0f),
|
||||
DetailFields(runs[1].Constants));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(false, true, true)]
|
||||
[InlineData(true, false, true)]
|
||||
[InlineData(true, true, false)]
|
||||
public void ClassicGrouped_DetailUnavailableDisabledOrQualityA2cFalsePreservesSelection(
|
||||
bool detailAvailable,
|
||||
bool detailEnabled,
|
||||
bool alphaToCoverage)
|
||||
{
|
||||
using var fx = new DispatcherFixture(
|
||||
detailAvailable: detailAvailable,
|
||||
detailEnabled: detailEnabled,
|
||||
sampleCount: 4,
|
||||
alphaToCoverage: alphaToCoverage);
|
||||
using DrawScope draw = fx.BeginDraw();
|
||||
|
||||
ExecuteClassicGroups(fx, MakeClassicGroup(0, detailCategory: 1));
|
||||
|
||||
bool detailActive = detailAvailable && detailEnabled;
|
||||
var entry = Assert.Single(DecodePipelineRuns(fx.Device));
|
||||
Assert.Equal(alphaToCoverage && !detailActive
|
||||
? "wb-mesh-opaque-a2c"
|
||||
: "wb-mesh-opaque", entry.Pipeline);
|
||||
Assert.Equal(1u, entry.Draw.DrawCount);
|
||||
Assert.Equal(
|
||||
detailActive ? (77u, 3.5f, 0f) : (0u, 0f, 0f),
|
||||
DetailFields(entry.Constants));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -832,6 +976,130 @@ public sealed class OrderPreservingSubmitterTests
|
|||
private static List<(int Start, int Count)> DecodeDrawRanges(RecordingGpuDevice device) =>
|
||||
[.. DecodeRuns(device).Select(r => (r.Start, r.Count))];
|
||||
|
||||
private static (uint TextureIndexA, float ParamA, float ParamB) DetailFields(
|
||||
GpuPushConstants constants) =>
|
||||
(constants.TextureIndexA, constants.ParamA, constants.ParamB);
|
||||
|
||||
private static void AssertOpaquePipeline(
|
||||
RecordingGpuDevice device,
|
||||
string name,
|
||||
bool atmospheric,
|
||||
int sampleCount,
|
||||
bool alphaToCoverage)
|
||||
{
|
||||
GpuPipelineDescription description = device.CreatedPipelines
|
||||
.Single(pipeline => pipeline.Description.Name == name)
|
||||
.Description;
|
||||
Assert.Equal(atmospheric ? "mesh_atmospheric" : "mesh_modern", description.Shaders.Name);
|
||||
Assert.Equal(GpuBlendMode.None, description.Blend);
|
||||
Assert.Equal(alphaToCoverage, description.AlphaToCoverage);
|
||||
Assert.Equal(sampleCount, description.SampleCount);
|
||||
Assert.True(description.Depth.Test);
|
||||
Assert.True(description.Depth.Write);
|
||||
Assert.Equal(WorldDepthContract.WorldCompare, description.Depth.Compare);
|
||||
}
|
||||
|
||||
private static WbDrawDispatcher.InstanceGroup MakeClassicGroup(
|
||||
int index,
|
||||
uint detailCategory,
|
||||
TranslucencyKind translucency = TranslucencyKind.Opaque,
|
||||
RetailSetSurfaceMaterialState? materialState = null)
|
||||
{
|
||||
var group = new WbDrawDispatcher.InstanceGroup
|
||||
{
|
||||
FirstIndex = (uint)index * 3,
|
||||
BaseVertex = index * 4,
|
||||
IndexCount = 3,
|
||||
TextureSlot = new GpuTextureSlot((uint)index),
|
||||
TextureLayer = 0,
|
||||
Translucency = translucency,
|
||||
MaterialState = materialState ?? RetailSetSurfaceMaterialState.Opaque,
|
||||
SurfaceOpacity = 1f,
|
||||
CullMode = CullMode.CounterClockwise,
|
||||
FoliageFlags = 0,
|
||||
};
|
||||
group.Matrices.Add(Matrix4x4.CreateTranslation(index, index * 2, index * 3));
|
||||
group.SubmissionOrders.Add(index);
|
||||
group.Slots.Add(0);
|
||||
group.LightSets.Add(WbDrawDispatcher.InstanceLightSet.Disabled);
|
||||
group.IndoorFlags.Add(0);
|
||||
group.DetailCategories.Add(detailCategory);
|
||||
group.Opacities.Add(1f);
|
||||
group.SelectionLighting.Add(Vector2.Zero);
|
||||
return group;
|
||||
}
|
||||
|
||||
private static void AppendClassicInstance(
|
||||
WbDrawDispatcher.InstanceGroup group,
|
||||
int index,
|
||||
uint detailCategory)
|
||||
{
|
||||
group.Matrices.Add(Matrix4x4.CreateTranslation(index, index * 2, index * 3));
|
||||
group.SubmissionOrders.Add(index);
|
||||
group.Slots.Add(0);
|
||||
group.LightSets.Add(WbDrawDispatcher.InstanceLightSet.Disabled);
|
||||
group.IndoorFlags.Add(0);
|
||||
group.DetailCategories.Add(detailCategory);
|
||||
group.Opacities.Add(1f);
|
||||
group.SelectionLighting.Add(Vector2.Zero);
|
||||
}
|
||||
|
||||
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)> DecodePipelineRuns(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 List<(GpuPushConstants Constants, int Start, int Count)> DecodeRuns(
|
||||
RecordingGpuDevice device)
|
||||
{
|
||||
|
|
@ -892,6 +1160,7 @@ public sealed class OrderPreservingSubmitterTests
|
|||
bool atmospheric = false,
|
||||
bool receiverBindingAvailable = true,
|
||||
int sampleCount = 1,
|
||||
bool alphaToCoverage = true,
|
||||
RecordingGpuDevice? device = null)
|
||||
{
|
||||
Device = device ?? new RecordingGpuDevice();
|
||||
|
|
@ -923,6 +1192,7 @@ public sealed class OrderPreservingSubmitterTests
|
|||
new GpuTextureSlot(77), 3.5f, 0x05000001, 0x08000001, 16, 16)
|
||||
: default,
|
||||
buildingDetailEnabled: () => detailEnabled);
|
||||
Dispatcher.AlphaToCoverage = alphaToCoverage;
|
||||
if (atmospheric)
|
||||
{
|
||||
var source = new BindableAtmosphericSource(receiverBindingAvailable);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue