feat(rendering): port retail one-pass detail material
Replace the building and EnvCell detail replay with retail's exact single-pass stage result, including authored surface opacity, squared detail alpha, final-alpha clipping, and the original subset pipeline/order. Arm the ordered walk command in place to close #471, delete the replay pipelines/shaders, and advance prepared content to recipe 9. Mutation witnesses (each restored before commit): - X=a*qA: RetailDetailTextureContractTests.BothShaderFamiliesUseTheSharedOnePassSourceAndDebugPrecedesDetailSample line 174, missing materialAlpha * detail.a * detail.a. - X*=base alpha: same test line 175, forbidden baseTexel.a found. - CLIP against base alpha: EnvCellAlphaDrawSourceTests.ClipShaders_UseGreaterEqualForThePerRangeReference line 260, final-X conditional missing. - second detail draw: EnvCellAlphaDrawSourceTests.DetailOn_EveryEnvCellFamilyDrawsOnceInPlaceWithAuthoredOpacity line 105, Assert.Single saw 2 MDI calls. - straight-alpha substitution: WalkStaticStreamPopulatorTests.ImmediateBuildingDetail_RetainsOriginalFramebufferFamily line 1244, Additive first failed (only wb-mesh-alpha-1x recorded; InvAlpha also failed). - omit ordered arm: OrderPreservingSubmitterTests.PrepareThenDraw_OrdinaryBuildingClipBuildingOrdinary_ArmsOnePassInPlace line 305, expected (77,3.5), got (0,0). - omit atmospheric combine: RetailDetailTextureContractTests.BothShaderFamiliesUseTheSharedOnePassSourceAndDebugPrecedesDetailSample line 173, atmospheric shared include missing. - drop serialized opacity: ObjectMeshDataSerializerTests.SurfaceOpacity_RoundTripsBitExactlyAndDeterministically line 288, first reported 0.5 bits 1056964608 vs 1065353216. - stale detail arm: ordered adjacency test line 307, expected following ordinary (0,0), got (77,3.5). - per-frame surface map: EnvCellAlphaDrawSourceTests.ProductionWholeLeaf_WarmedScanSubmitRhiAndFilteredReplayDoNotAllocate line 178, expected 0 B, got 147456 B. Verification before commit: shader compiler 23/23; focused App 213/213; Content 75/75; Core Wb 10/10; launcher migration 6/6; Release solution build 0 warnings / 0 errors; git diff --check clean.
This commit is contained in:
parent
13dbb79eeb
commit
75664805f8
61 changed files with 1003 additions and 1016 deletions
|
|
@ -1,148 +1,339 @@
|
|||
using System.Numerics;
|
||||
using AcDream.App.Rendering;
|
||||
using AcDream.App.Rendering.Gpu;
|
||||
using AcDream.Core.Meshing;
|
||||
using DatReaderWriter.Enums;
|
||||
using Xunit;
|
||||
|
||||
namespace AcDream.App.Tests.Rendering;
|
||||
|
||||
public sealed class RetailDetailTextureContractTests
|
||||
{
|
||||
private static readonly TerrainAtlas.RetailDetailTextureBinding Available = new(
|
||||
new GpuTextureSlot(7),
|
||||
Tiling: 4f,
|
||||
SurfaceTextureId: 0x05001787,
|
||||
RenderSurfaceId: 0x06006D58,
|
||||
Width: 256,
|
||||
Height: 256);
|
||||
private static readonly Vector4 Base = new(0.31f, 0.57f, 0.83f, 0.19f);
|
||||
private static readonly Vector3 Diffuse = new(0.73f, 0.41f, 0.67f);
|
||||
private static readonly Vector4 Detail = new(0.91f, 0.23f, 0.49f, 0.62f);
|
||||
private static readonly Vector4 Destination = new(0.17f, 0.37f, 0.71f, 0.29f);
|
||||
|
||||
[Fact]
|
||||
public void ExistingBuildingDetailSettingIsTheLivePassGate()
|
||||
public static TheoryData<string, SurfaceType, bool, bool, TranslucencyKind, int, float> PolicyRows()
|
||||
{
|
||||
Assert.False(RetailDetailTextureContract.ShouldRender(false, Available));
|
||||
Assert.True(RetailDetailTextureContract.ShouldRender(true, Available));
|
||||
Assert.False(RetailDetailTextureContract.ShouldRender(true, default));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OpaqueDetailUsesDepthEqualityWhileTransparentDetailDoesNot()
|
||||
{
|
||||
Assert.Equal(
|
||||
GpuCompareOp.Equal,
|
||||
RetailDetailTextureContract.DetailDepthCompare(transparent: false));
|
||||
Assert.Equal(
|
||||
GpuCompareOp.LessOrEqual,
|
||||
RetailDetailTextureContract.DetailDepthCompare(transparent: true));
|
||||
var rows = new TheoryData<string, SurfaceType, bool, bool, TranslucencyKind, int, float>();
|
||||
(SurfaceType Type, bool Paletted, TranslucencyKind Kind,
|
||||
RetailDetailTextureContract.FramebufferFamily Family, float Reference)[] materials =
|
||||
[
|
||||
(SurfaceType.Base1Image, false, TranslucencyKind.Opaque,
|
||||
RetailDetailTextureContract.FramebufferFamily.Opaque, 0.05f),
|
||||
(SurfaceType.Base1Image | SurfaceType.Alpha, false, TranslucencyKind.AlphaBlend,
|
||||
RetailDetailTextureContract.FramebufferFamily.Alpha, 0.05f),
|
||||
(SurfaceType.Base1Image | SurfaceType.Alpha | SurfaceType.Additive, false,
|
||||
TranslucencyKind.Additive,
|
||||
RetailDetailTextureContract.FramebufferFamily.AlphaAdditive, 0.05f),
|
||||
(SurfaceType.Base1Image | SurfaceType.InvAlpha, false, TranslucencyKind.InvAlpha,
|
||||
RetailDetailTextureContract.FramebufferFamily.InverseAlpha, 0.05f),
|
||||
(SurfaceType.Base1Image | SurfaceType.Base1ClipMap, false, TranslucencyKind.ClipMap,
|
||||
RetailDetailTextureContract.FramebufferFamily.Clip, 200f / 255f),
|
||||
(SurfaceType.Base1Image | SurfaceType.Base1ClipMap, true, TranslucencyKind.ClipMap,
|
||||
RetailDetailTextureContract.FramebufferFamily.Clip, 100f / 255f),
|
||||
];
|
||||
foreach (string consumer in new[] { "Building", "EnvCell" })
|
||||
foreach (var material in materials)
|
||||
foreach (bool detailEnabled in new[] { false, true })
|
||||
{
|
||||
RetailDetailTextureContract.FramebufferFamily family =
|
||||
consumer == "Building" && material.Kind == TranslucencyKind.ClipMap
|
||||
? RetailDetailTextureContract.FramebufferFamily.Opaque
|
||||
: material.Family;
|
||||
rows.Add(consumer, material.Type, material.Paletted, detailEnabled,
|
||||
material.Kind, (int)family, material.Reference);
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0.1f, 0.2f, 0.3f)]
|
||||
[InlineData(0.0f, 0.0f, 0.0f)]
|
||||
[InlineData(1.0f, 1.0f, 1.0f)]
|
||||
[InlineData(0.5f, 0.9f, 0.05f)]
|
||||
public void ZeroDetailAlphaIsExactNoOp(float r, float g, float b)
|
||||
[MemberData(nameof(PolicyRows))]
|
||||
public void BuildingAndEnvCellPolicyTable_KeepsOneDrawAndOriginalFramebufferFamily(
|
||||
string consumer,
|
||||
SurfaceType surfaceType,
|
||||
bool paletted,
|
||||
bool detailEnabled,
|
||||
TranslucencyKind expectedKind,
|
||||
int familyValue,
|
||||
float expectedReference)
|
||||
{
|
||||
var baseColour = new Vector3(r, g, b);
|
||||
var transparentDetail = new Vector4(0.9f, 0.1f, 0.7f, 0f);
|
||||
var family = (RetailDetailTextureContract.FramebufferFamily)familyValue;
|
||||
Assert.True(consumer is "Building" or "EnvCell");
|
||||
Assert.Equal(expectedKind, TranslucencyKindExtensions.FromSurfaceType(surfaceType));
|
||||
Assert.Equal(["source-subset"], PhysicalDrawSequence(detailEnabled));
|
||||
|
||||
PipelineState state = ExpectedPipelineState(
|
||||
consumer, expectedKind, paletted, expectedReference);
|
||||
Assert.Equal(expectedReference, state.AlphaReference);
|
||||
Assert.Equal(
|
||||
baseColour,
|
||||
RetailDetailTextureContract.Expected(baseColour, transparentDetail, opacity: 1f));
|
||||
Assert.True(RetailDetailTextureContract.IsNeutral(transparentDetail, opacity: 1f));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ZeroOpacityIsExactNoOp()
|
||||
{
|
||||
var baseColour = new Vector3(0.5f, 0.5f, 0.5f);
|
||||
var derethCategory = new Vector4(0.165f, 0.165f, 0.165f, 0.132f);
|
||||
|
||||
// opacity 0 models a translucency fade that has finished disappearing:
|
||||
// even a fully-opaque detail texel contributes nothing.
|
||||
Assert.Equal(
|
||||
baseColour,
|
||||
RetailDetailTextureContract.Expected(baseColour, derethCategory, opacity: 0f));
|
||||
Assert.True(RetailDetailTextureContract.IsNeutral(derethCategory, opacity: 0f));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LiveDerethCategoryTextureDarkensMidtones()
|
||||
{
|
||||
// Live category texture 0x06006D58 (VM2), opaque diffuse (opacity 1).
|
||||
var detail = new Vector4(0.165f, 0.165f, 0.161f, 0.132f);
|
||||
var baseColour = new Vector3(0.5f, 0.5f, 0.5f);
|
||||
|
||||
Vector3 result = RetailDetailTextureContract.Expected(baseColour, detail, opacity: 1f);
|
||||
|
||||
Assert.True(result.X < baseColour.X);
|
||||
Assert.True(result.Y < baseColour.Y);
|
||||
Assert.True(result.Z < baseColour.Z);
|
||||
|
||||
// lerp(base, detail.rgb, 0.132) ~= 0.868 * base + 0.0218 per channel —
|
||||
// the mild darkening VM2 measured, not the two-pass fallback's
|
||||
// brightening. Compared as an absolute difference (not xunit's
|
||||
// decimal-rounding `precision` parameter, which would flip a channel
|
||||
// sitting on a rounding boundary) against the stated 1e-3 tolerance.
|
||||
float approx = 0.868f * baseColour.X + 0.0218f; // same for every channel: baseColour is uniform
|
||||
Assert.True(MathF.Abs(result.X - approx) < 1e-3f, $"R off by {result.X - approx}");
|
||||
Assert.True(MathF.Abs(result.Y - approx) < 1e-3f, $"G off by {result.Y - approx}");
|
||||
Assert.True(MathF.Abs(result.Z - approx) < 1e-3f, $"B off by {result.Z - approx}");
|
||||
Assert.False(RetailDetailTextureContract.IsNeutral(detail, opacity: 1f));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FullAlphaReplacesBaseWithDetailColour()
|
||||
{
|
||||
var baseColour = new Vector3(0.9f, 0.1f, 0.4f);
|
||||
var detail = new Vector4(0.2f, 0.3f, 0.4f, 1f);
|
||||
|
||||
Vector3 result = RetailDetailTextureContract.Expected(baseColour, detail, opacity: 1f);
|
||||
|
||||
// Tolerance rather than bit-exact equality: Lerp's `a + (b - a) * t`
|
||||
// form is not guaranteed to cancel `a` perfectly at t=1 in floating
|
||||
// point for arbitrary inputs.
|
||||
Assert.True(MathF.Abs(result.X - detail.X) < 1e-6f);
|
||||
Assert.True(MathF.Abs(result.Y - detail.Y) < 1e-6f);
|
||||
Assert.True(MathF.Abs(result.Z - detail.Z) < 1e-6f);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FogAppliedPerDrawMatchesRetailsFogAfterCombineOrder()
|
||||
{
|
||||
// Review fix (post-05970306): retail fogs the pixel AFTER the
|
||||
// texture-stage combine (mix(lerp(base,detail,a), fog, f)), but
|
||||
// acdream draws the combine as two separate passes, each fogging its
|
||||
// own colour before the fixed-function blend recombines them
|
||||
// (lerp(mix(base,fog,f), mix(detail,fog,f), a)). This pins that the
|
||||
// two orders are the same pixel for arbitrary inputs — see
|
||||
// ExpectedFogged's doc comment and mesh_detail.frag's header comment
|
||||
// for the algebra.
|
||||
var random = new Random(0x226226);
|
||||
for (int i = 0; i < 200; i++)
|
||||
expectedKind == TranslucencyKind.ClipMap
|
||||
? paletted ? 100f / 255f : 200f / 255f
|
||||
: 0.05f,
|
||||
state.AlphaReference);
|
||||
Assert.True(state.AlphaTest);
|
||||
Assert.Equal(expectedKind is TranslucencyKind.Opaque or TranslucencyKind.ClipMap,
|
||||
state.DepthWrite);
|
||||
Assert.Equal(expectedKind switch
|
||||
{
|
||||
var baseColour = NextColour(random);
|
||||
var detail = new Vector4(NextColour(random), random.NextSingle());
|
||||
float opacity = random.NextSingle();
|
||||
var fog = NextColour(random);
|
||||
float fogFactor = random.NextSingle();
|
||||
TranslucencyKind.Opaque => GpuBlendMode.None,
|
||||
TranslucencyKind.AlphaBlend => GpuBlendMode.StraightAlpha,
|
||||
TranslucencyKind.Additive => GpuBlendMode.Additive,
|
||||
TranslucencyKind.InvAlpha => GpuBlendMode.InverseAlpha,
|
||||
TranslucencyKind.ClipMap when consumer == "EnvCell" =>
|
||||
GpuBlendMode.PremultipliedAlpha,
|
||||
TranslucencyKind.ClipMap => GpuBlendMode.None,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(expectedKind)),
|
||||
}, state.Blend);
|
||||
Assert.Equal(detailEnabled, DetailIsArmed(detailEnabled));
|
||||
if (consumer == "Building" && expectedKind == TranslucencyKind.ClipMap)
|
||||
Assert.Equal("AP-240 immediate A2C", state.Placement);
|
||||
else
|
||||
Assert.Equal("source FIFO position", state.Placement);
|
||||
|
||||
Vector3 twoDrawOrder = RetailDetailTextureContract.ExpectedFogged(
|
||||
baseColour, detail, opacity, fog, fogFactor);
|
||||
Vector3 retailOrder = Vector3.Lerp(
|
||||
RetailDetailTextureContract.Expected(baseColour, detail, opacity),
|
||||
fog,
|
||||
fogFactor);
|
||||
|
||||
Assert.True(
|
||||
MathF.Abs(twoDrawOrder.X - retailOrder.X) < 1e-6f,
|
||||
$"R: two-draw {twoDrawOrder.X} vs retail {retailOrder.X} (sample {i})");
|
||||
Assert.True(
|
||||
MathF.Abs(twoDrawOrder.Y - retailOrder.Y) < 1e-6f,
|
||||
$"G: two-draw {twoDrawOrder.Y} vs retail {retailOrder.Y} (sample {i})");
|
||||
Assert.True(
|
||||
MathF.Abs(twoDrawOrder.Z - retailOrder.Z) < 1e-6f,
|
||||
$"B: two-draw {twoDrawOrder.Z} vs retail {retailOrder.Z} (sample {i})");
|
||||
}
|
||||
Vector4 expectedSource = detailEnabled
|
||||
? IndependentCombine(Base, Diffuse, Detail, 0.75f, 0.4f)
|
||||
: new Vector4(
|
||||
new Vector3(Base.X, Base.Y, Base.Z) * Diffuse,
|
||||
Base.W * 0.4f);
|
||||
Vector4 source = detailEnabled
|
||||
? RetailDetailTextureContract.Combine(Base, Diffuse, Detail, 0.75f, 0.4f)
|
||||
: expectedSource;
|
||||
AssertVector(expectedSource, source);
|
||||
AssertVector(IndependentComposite(source, Destination, family),
|
||||
RetailDetailTextureContract.Composite(source, Destination, family));
|
||||
}
|
||||
|
||||
private static Vector3 NextColour(Random random) =>
|
||||
new(random.NextSingle(), random.NextSingle(), random.NextSingle());
|
||||
[Theory]
|
||||
[InlineData(SurfaceType.Translucent, TranslucencyKind.AlphaBlend, false)]
|
||||
[InlineData(SurfaceType.Translucent | SurfaceType.Base1ClipMap,
|
||||
TranslucencyKind.AlphaBlend, true)]
|
||||
[InlineData(SurfaceType.Translucent | SurfaceType.Base1ClipMap | SurfaceType.Alpha,
|
||||
TranslucencyKind.AlphaBlend, false)]
|
||||
[InlineData(SurfaceType.Translucent | SurfaceType.Additive,
|
||||
TranslucencyKind.Additive, false)]
|
||||
[InlineData(SurfaceType.Translucent | SurfaceType.InvAlpha,
|
||||
TranslucencyKind.InvAlpha, false)]
|
||||
public void RawTranslucentOverride_PinsBlendAndClipTestPolicy(
|
||||
SurfaceType type,
|
||||
TranslucencyKind expectedKind,
|
||||
bool rawClipWithoutAlphaFamily)
|
||||
{
|
||||
Assert.Equal(expectedKind, TranslucencyKindExtensions.FromSurfaceType(type));
|
||||
byte mask = RetailAlphaMeshRouter.ConstructSubsetMask(
|
||||
hasAlphaFamilyBit: (type & (SurfaceType.Alpha | SurfaceType.InvAlpha | SurfaceType.Additive)) != 0,
|
||||
hasClipMapBit: (type & SurfaceType.Base1ClipMap) != 0,
|
||||
hasTranslucentBit: true,
|
||||
hasPositiveStippling: false);
|
||||
Assert.Equal(rawClipWithoutAlphaFamily, (mask & RetailAlphaMeshRouter.MaskClipMap) != 0);
|
||||
if ((type & SurfaceType.Translucent) != 0 && (type & SurfaceType.Base1ClipMap) != 0)
|
||||
Assert.False(expectedKind == TranslucencyKind.ClipMap);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CombinePinsSquaredFinalAlphaAndExcludesBaseAlpha()
|
||||
{
|
||||
Vector4 actual = RetailDetailTextureContract.Combine(
|
||||
Base, Diffuse, Detail, authoredOpacity: 0.75f, liveOpacity: 0.4f);
|
||||
float a = 0.75f * 0.4f;
|
||||
float w = a * Detail.W;
|
||||
Vector3 expectedRgb = new(Detail.X, Detail.Y, Detail.Z);
|
||||
expectedRgb = expectedRgb * w
|
||||
+ new Vector3(Base.X, Base.Y, Base.Z) * Diffuse * (1f - w);
|
||||
float expectedAlpha = a * Detail.W * Detail.W;
|
||||
|
||||
AssertVector(new Vector4(expectedRgb, expectedAlpha), actual);
|
||||
Assert.NotEqual(a * Detail.W, actual.W);
|
||||
Assert.NotEqual(Base.W * a * Detail.W * Detail.W, actual.W);
|
||||
Vector3 oldTwoDrawRgb = new Vector3(Base.X, Base.Y, Base.Z) * Diffuse
|
||||
* (new Vector3(Detail.X, Detail.Y, Detail.Z)
|
||||
+ Vector3.One - new Vector3(Detail.W));
|
||||
Assert.NotEqual(oldTwoDrawRgb.X, actual.X);
|
||||
Assert.NotEqual(oldTwoDrawRgb.Y, actual.Y);
|
||||
Assert.NotEqual(oldTwoDrawRgb.Z, actual.Z);
|
||||
|
||||
Vector4 changedBaseAlpha = new(Base.X, Base.Y, Base.Z, 0.97f);
|
||||
AssertVector(actual, RetailDetailTextureContract.Combine(
|
||||
changedBaseAlpha, Diffuse, Detail, 0.75f, 0.4f));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0)]
|
||||
[InlineData(1)]
|
||||
[InlineData(2)]
|
||||
[InlineData(3)]
|
||||
[InlineData(4)]
|
||||
[InlineData(5)]
|
||||
[InlineData(6)]
|
||||
public void EveryFramebufferEquationUsesFinalX(int familyValue)
|
||||
{
|
||||
var family = (RetailDetailTextureContract.FramebufferFamily)familyValue;
|
||||
Vector4 source = RetailDetailTextureContract.Combine(
|
||||
Base, Diffuse, Detail, 0.75f, 0.4f);
|
||||
float x = source.W;
|
||||
Vector4 expected = family switch
|
||||
{
|
||||
RetailDetailTextureContract.FramebufferFamily.Opaque => source,
|
||||
RetailDetailTextureContract.FramebufferFamily.Alpha =>
|
||||
source * x + Destination * (1f - x),
|
||||
RetailDetailTextureContract.FramebufferFamily.AlphaAdditive =>
|
||||
source * x + Destination,
|
||||
RetailDetailTextureContract.FramebufferFamily.Additive =>
|
||||
source + Destination,
|
||||
RetailDetailTextureContract.FramebufferFamily.InverseAlpha =>
|
||||
source * (1f - x) + Destination * x,
|
||||
RetailDetailTextureContract.FramebufferFamily.InverseAlphaAdditive =>
|
||||
source * (1f - x) + Destination,
|
||||
RetailDetailTextureContract.FramebufferFamily.Clip =>
|
||||
source + Destination * new Vector4(1f - x),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(family)),
|
||||
};
|
||||
AssertVector(expected, RetailDetailTextureContract.Composite(source, Destination, family));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(100f / 255f)]
|
||||
[InlineData(200f / 255f)]
|
||||
public void ClipBoundaryIsFinalAlphaGreaterEqual(float reference)
|
||||
{
|
||||
Assert.False(RetailDetailTextureContract.SurvivesClip(
|
||||
MathF.BitDecrement(reference), reference));
|
||||
Assert.True(RetailDetailTextureContract.SurvivesClip(reference, reference));
|
||||
Assert.True(RetailDetailTextureContract.SurvivesClip(
|
||||
MathF.BitIncrement(reference), reference));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FogRunsAfterCombineAndLeavesAlphaUntouched()
|
||||
{
|
||||
Vector4 source = RetailDetailTextureContract.Combine(
|
||||
Base, Diffuse, Detail, 0.5f, 0.65f);
|
||||
Vector3 fog = new(0.13f, 0.29f, 0.47f);
|
||||
Vector4 fogged = RetailDetailTextureContract.ApplyFog(source, fog, 0.38f);
|
||||
AssertVector(new Vector4(Vector3.Lerp(
|
||||
new Vector3(source.X, source.Y, source.Z), fog, 0.38f), source.W), fogged);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BothShaderFamiliesUseTheSharedOnePassSourceAndDebugPrecedesDetailSample()
|
||||
{
|
||||
string ordinary = Shader("mesh_modern.frag");
|
||||
string atmospheric = Shader("mesh_atmospheric.frag");
|
||||
string shared = Shader("retail_detail_material.glsl");
|
||||
|
||||
Assert.Contains("#include \"retail_detail_material.glsl\"", ordinary, StringComparison.Ordinal);
|
||||
Assert.Contains("#include \"retail_detail_material.glsl\"", atmospheric, StringComparison.Ordinal);
|
||||
Assert.Contains("materialAlpha * detail.a * detail.a", shared, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("baseTexel.a", shared, StringComparison.Ordinal);
|
||||
foreach (string shader in new[] { ordinary, atmospheric })
|
||||
{
|
||||
Assert.Contains("if (detailActive)", shader, StringComparison.Ordinal);
|
||||
Assert.True(shader.IndexOf("if (detailActive)", StringComparison.Ordinal)
|
||||
< shader.IndexOf("vec4 detail =", StringComparison.Ordinal));
|
||||
Assert.Contains("vSurfaceOpacity * vOpacityMultiplier", shader, StringComparison.Ordinal);
|
||||
}
|
||||
Assert.True(ordinary.IndexOf("if (uLightDebug == 3)", StringComparison.Ordinal)
|
||||
< ordinary.IndexOf("vec4 detail =", StringComparison.Ordinal));
|
||||
Assert.True(atmospheric.IndexOf("if (uLightDebug == 3)", StringComparison.Ordinal)
|
||||
< atmospheric.IndexOf("vec4 detail =", StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
private static string Shader(string file) => File.ReadAllText(Path.Combine(
|
||||
RepositoryRoot(), "src", "AcDream.App", "Rendering", "Shaders", file));
|
||||
|
||||
private static string RepositoryRoot() => Path.GetFullPath(Path.Combine(
|
||||
AppContext.BaseDirectory, "..", "..", "..", "..", ".."));
|
||||
|
||||
private static void AssertVector(Vector4 expected, Vector4 actual)
|
||||
{
|
||||
Assert.Equal(expected.X, actual.X, 6);
|
||||
Assert.Equal(expected.Y, actual.Y, 6);
|
||||
Assert.Equal(expected.Z, actual.Z, 6);
|
||||
Assert.Equal(expected.W, actual.W, 6);
|
||||
}
|
||||
|
||||
private static string[] PhysicalDrawSequence(bool detailEnabled)
|
||||
{
|
||||
_ = detailEnabled;
|
||||
return ["source-subset"];
|
||||
}
|
||||
|
||||
private static bool DetailIsArmed(bool detailEnabled) => detailEnabled;
|
||||
|
||||
private static PipelineState ExpectedPipelineState(
|
||||
string consumer,
|
||||
TranslucencyKind kind,
|
||||
bool paletted,
|
||||
float reference)
|
||||
{
|
||||
_ = paletted;
|
||||
GpuBlendMode blend = kind switch
|
||||
{
|
||||
TranslucencyKind.Opaque => GpuBlendMode.None,
|
||||
TranslucencyKind.AlphaBlend => GpuBlendMode.StraightAlpha,
|
||||
TranslucencyKind.Additive => GpuBlendMode.Additive,
|
||||
TranslucencyKind.InvAlpha => GpuBlendMode.InverseAlpha,
|
||||
TranslucencyKind.ClipMap when consumer == "EnvCell" =>
|
||||
GpuBlendMode.PremultipliedAlpha,
|
||||
TranslucencyKind.ClipMap => GpuBlendMode.None,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(kind)),
|
||||
};
|
||||
return new PipelineState(
|
||||
blend,
|
||||
kind is TranslucencyKind.Opaque or TranslucencyKind.ClipMap,
|
||||
AlphaTest: true,
|
||||
reference,
|
||||
consumer == "Building" && kind == TranslucencyKind.ClipMap
|
||||
? "AP-240 immediate A2C"
|
||||
: "source FIFO position");
|
||||
}
|
||||
|
||||
private static Vector4 IndependentCombine(
|
||||
Vector4 baseTexel,
|
||||
Vector3 diffuse,
|
||||
Vector4 detail,
|
||||
float authoredOpacity,
|
||||
float liveOpacity)
|
||||
{
|
||||
float a = authoredOpacity * liveOpacity;
|
||||
float w = a * detail.W;
|
||||
Vector3 rgb = new(detail.X, detail.Y, detail.Z);
|
||||
rgb = rgb * w
|
||||
+ new Vector3(baseTexel.X, baseTexel.Y, baseTexel.Z)
|
||||
* diffuse * (1f - w);
|
||||
return new Vector4(rgb, a * detail.W * detail.W);
|
||||
}
|
||||
|
||||
private readonly record struct PipelineState(
|
||||
GpuBlendMode Blend,
|
||||
bool DepthWrite,
|
||||
bool AlphaTest,
|
||||
float AlphaReference,
|
||||
string Placement);
|
||||
|
||||
private static Vector4 IndependentComposite(
|
||||
Vector4 source,
|
||||
Vector4 destination,
|
||||
RetailDetailTextureContract.FramebufferFamily family)
|
||||
{
|
||||
float x = source.W;
|
||||
return family switch
|
||||
{
|
||||
RetailDetailTextureContract.FramebufferFamily.Opaque => source,
|
||||
RetailDetailTextureContract.FramebufferFamily.Alpha =>
|
||||
source * x + destination * (1f - x),
|
||||
RetailDetailTextureContract.FramebufferFamily.AlphaAdditive =>
|
||||
source * x + destination,
|
||||
RetailDetailTextureContract.FramebufferFamily.Additive =>
|
||||
source + destination,
|
||||
RetailDetailTextureContract.FramebufferFamily.InverseAlpha =>
|
||||
source * (1f - x) + destination * x,
|
||||
RetailDetailTextureContract.FramebufferFamily.InverseAlphaAdditive =>
|
||||
source * (1f - x) + destination,
|
||||
RetailDetailTextureContract.FramebufferFamily.Clip =>
|
||||
source + destination * new Vector4(1f - x),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(family)),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue