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
|
|
@ -132,6 +132,12 @@ internal enum GpuBlendMode
|
|||
/// <summary>Additive: <c>SrcAlpha, One</c>.</summary>
|
||||
Additive,
|
||||
|
||||
/// <summary>Retail raw Additive: <c>One, One</c>.</summary>
|
||||
RawAdditive,
|
||||
|
||||
/// <summary>Retail inverse-additive: <c>OneMinusSrcAlpha, One</c>.</summary>
|
||||
InverseAdditive,
|
||||
|
||||
/// <summary>
|
||||
/// Retail's inverse-alpha translucency: <c>OneMinusSrcAlpha, SrcAlpha</c>.
|
||||
///
|
||||
|
|
|
|||
|
|
@ -176,6 +176,8 @@ internal static class VulkanViewportMapping
|
|||
GpuBlendMode.StraightAlpha => (BlendFactor.SrcAlpha, BlendFactor.OneMinusSrcAlpha),
|
||||
GpuBlendMode.PremultipliedAlpha => (BlendFactor.One, BlendFactor.OneMinusSrcAlpha),
|
||||
GpuBlendMode.Additive => (BlendFactor.SrcAlpha, BlendFactor.One),
|
||||
GpuBlendMode.RawAdditive => (BlendFactor.One, BlendFactor.One),
|
||||
GpuBlendMode.InverseAdditive => (BlendFactor.OneMinusSrcAlpha, BlendFactor.One),
|
||||
// Retail's third mode, found at slice V4c in WbDrawDispatcher.ApplyRetailBlend.
|
||||
GpuBlendMode.InverseAlpha => (BlendFactor.OneMinusSrcAlpha, BlendFactor.SrcAlpha),
|
||||
GpuBlendMode.None => (BlendFactor.One, BlendFactor.Zero),
|
||||
|
|
|
|||
|
|
@ -34,6 +34,8 @@ namespace AcDream.App.Rendering;
|
|||
/// </summary>
|
||||
internal static class RetailDetailTextureContract
|
||||
{
|
||||
internal const int NoFogRenderPassFlag = 0x200;
|
||||
|
||||
internal static bool ShouldRender(
|
||||
bool settingEnabled,
|
||||
TerrainAtlas.RetailDetailTextureBinding binding) =>
|
||||
|
|
|
|||
|
|
@ -128,9 +128,12 @@ void main() {
|
|||
alpha = color.a * vOpacityMultiplier;
|
||||
}
|
||||
|
||||
if (detailActive ? alpha < alphaCutoff : color.a < alphaCutoff)
|
||||
if (detailActive
|
||||
? isRetailClipReference(uParamB) && alpha < uParamB
|
||||
: color.a < alphaCutoff)
|
||||
discard;
|
||||
|
||||
rgb = applyFog(rgb, vWorldPos);
|
||||
if (!detailActive || (uRenderPass & 0x200) == 0)
|
||||
rgb = applyFog(rgb, vWorldPos);
|
||||
FragColor = vec4(rgb, alpha);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ in flat uint vDetailCategory;
|
|||
// discard alpha >= 0.95 (already drawn opaque) and
|
||||
// alpha < 0.05 (skip empty fragments — large
|
||||
// transparent overdraw cost otherwise)
|
||||
// bit 0x200 = detail-active raw Additive; bypass fixed-function fog
|
||||
uniform int uRenderPass;
|
||||
uniform int uLightDebug; // #176 stripe hunt (see mesh_modern.vert) — mode 3 handled here
|
||||
// S4-c2 F3: only retail's exact pure-ClipMap alpha-test references are
|
||||
|
|
@ -120,9 +121,12 @@ void main() {
|
|||
|
||||
// '<' spells D3DCMP_GREATEREQUAL: equality passes. Detail-active CLIP
|
||||
// tests retail's final stage-1 alpha; detail-off keeps base-alpha logic.
|
||||
if (detailActive ? alpha < alphaCutoff : color.a < alphaCutoff)
|
||||
if (detailActive
|
||||
? isRetailClipReference(uParamB) && alpha < uParamB
|
||||
: color.a < alphaCutoff)
|
||||
discard;
|
||||
|
||||
rgb = applyFog(rgb, vWorldPos);
|
||||
if (!detailActive || (uRenderPass & 0x200) == 0)
|
||||
rgb = applyFog(rgb, vWorldPos);
|
||||
FragColor = vec4(rgb, alpha);
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
|
|
@ -220,7 +220,7 @@
|
|||
},
|
||||
{
|
||||
"stage": "frag",
|
||||
"sourceSha256": "5eb058e92ab27f1f6c245401e05f78a278fe00696b5c77f36ae33f8428f83bf3",
|
||||
"sourceSha256": "c95e372c557a8a4b252ecf824ad049fe9611b0a1e49b0e6d18938d373d9c6794",
|
||||
"compiled": true
|
||||
}
|
||||
]
|
||||
|
|
@ -236,7 +236,7 @@
|
|||
},
|
||||
{
|
||||
"stage": "frag",
|
||||
"sourceSha256": "396c382af73505bc2cac7a2db5878a63166730e47096a94630c35ad8376fe565",
|
||||
"sourceSha256": "e145f32cbd50edcbc5e3196a49f0e2b05548bc0199ce13139e01c443794473b0",
|
||||
"compiled": true
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ using System.Numerics;
|
|||
using System.Runtime.InteropServices;
|
||||
using AcDream.App.Rendering.Gpu;
|
||||
using AcDream.Core.Lighting;
|
||||
using AcDream.Core.Meshing;
|
||||
using DatReaderWriter.Enums;
|
||||
|
||||
namespace AcDream.App.Rendering.Wb;
|
||||
|
|
@ -28,6 +29,9 @@ public sealed unsafe partial class EnvCellRenderer
|
|||
private IGpuPipeline? _alphaPipeline;
|
||||
private IGpuPipeline? _clipPipeline;
|
||||
private IGpuPipeline? _additivePipeline;
|
||||
private IGpuPipeline? _rawAdditivePipeline;
|
||||
private IGpuPipeline? _inversePipeline;
|
||||
private IGpuPipeline? _inverseAdditivePipeline;
|
||||
private readonly TerrainAtlas.RetailDetailTextureBinding _environmentDetail;
|
||||
private readonly Func<bool> _buildingDetailEnabled;
|
||||
|
||||
|
|
@ -48,7 +52,7 @@ public sealed unsafe partial class EnvCellRenderer
|
|||
|
||||
/// <summary>
|
||||
/// The RHI arm's constructor. It also completes <c>Initialize</c>'s job: the
|
||||
/// four pipelines ARE this renderer's program, so there is no second step
|
||||
/// SetSurface pipeline family IS this renderer's program, so there is no second step
|
||||
/// and no <c>Shader</c> to hand in.
|
||||
/// </summary>
|
||||
internal EnvCellRenderer(
|
||||
|
|
@ -80,6 +84,12 @@ public sealed unsafe partial class EnvCellRenderer
|
|||
scope.SampleCount);
|
||||
_additivePipeline = CreateShellPipeline(
|
||||
device, "envcell-additive", GpuBlendMode.Additive, depthWrite: false, scope.SampleCount);
|
||||
_rawAdditivePipeline = CreateShellPipeline(
|
||||
device, "envcell-raw-additive", GpuBlendMode.RawAdditive, depthWrite: false, scope.SampleCount);
|
||||
_inversePipeline = CreateShellPipeline(
|
||||
device, "envcell-inverse", GpuBlendMode.InverseAlpha, depthWrite: false, scope.SampleCount);
|
||||
_inverseAdditivePipeline = CreateShellPipeline(
|
||||
device, "envcell-inverse-additive", GpuBlendMode.InverseAdditive, depthWrite: false, scope.SampleCount);
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
|
|
@ -272,7 +282,9 @@ public sealed unsafe partial class EnvCellRenderer
|
|||
: isAdditive
|
||||
? _additivePipeline!
|
||||
: _alphaPipeline!;
|
||||
if (renderPass == WbRenderPass.Transparent)
|
||||
if (detailEnabled)
|
||||
rangeBasePipeline = PipelineForMaterial(drawRange.MaterialState);
|
||||
if (renderPass == WbRenderPass.Transparent || detailEnabled)
|
||||
{
|
||||
// Blend state is the pipeline's; switching variants mid-pass has
|
||||
// to re-establish the mesh, which is vertex-array state.
|
||||
|
|
@ -289,10 +301,14 @@ public sealed unsafe partial class EnvCellRenderer
|
|||
pushConstants.RenderPass = isAdditive
|
||||
? (int)renderPass | 0x100
|
||||
: (int)renderPass;
|
||||
if (detailEnabled && !drawRange.MaterialState.FogEnabled)
|
||||
pushConstants.RenderPass |= RetailDetailTextureContract.NoFogRenderPassFlag;
|
||||
pushConstants.DrawIdOffset = drawRange.FirstCommand;
|
||||
pushConstants.ParamB = isClip
|
||||
? groupIndex >= ClipPalettedGroupBase ? 100f / 255f : 200f / 255f
|
||||
: 0f;
|
||||
pushConstants.ParamB = detailEnabled
|
||||
? drawRange.MaterialState.AlphaTestReference
|
||||
: isClip
|
||||
? groupIndex >= ClipPalettedGroupBase ? 100f / 255f : 200f / 255f
|
||||
: 0f;
|
||||
pushConstants.TextureIndexA = detailEnabled
|
||||
? _environmentDetail.TextureSlot.Index
|
||||
: 0u;
|
||||
|
|
@ -311,9 +327,23 @@ public sealed unsafe partial class EnvCellRenderer
|
|||
pushConstants.TextureIndexA = 0;
|
||||
pushConstants.ParamA = 0f;
|
||||
pushConstants.ParamB = 0f;
|
||||
pushConstants.RenderPass &= ~RetailDetailTextureContract.NoFogRenderPassFlag;
|
||||
encoder.SetPushConstants(in pushConstants);
|
||||
}
|
||||
|
||||
private IGpuPipeline PipelineForMaterial(RetailSetSurfaceMaterialState material) =>
|
||||
material.Blend switch
|
||||
{
|
||||
RetailSetSurfaceBlend.Opaque => _opaquePipeline!,
|
||||
RetailSetSurfaceBlend.StraightAlpha => _alphaPipeline!,
|
||||
RetailSetSurfaceBlend.AlphaAdditive => _additivePipeline!,
|
||||
RetailSetSurfaceBlend.Additive => _rawAdditivePipeline!,
|
||||
RetailSetSurfaceBlend.InverseAlpha => _inversePipeline!,
|
||||
RetailSetSurfaceBlend.InverseAdditive => _inverseAdditivePipeline!,
|
||||
RetailSetSurfaceBlend.Clip => _clipPipeline!,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(material), material, "Unknown SetSurface blend."),
|
||||
};
|
||||
|
||||
private void BindPipelineWithMesh(
|
||||
IGpuPassEncoder encoder,
|
||||
IGpuPipeline pipeline,
|
||||
|
|
@ -429,5 +459,11 @@ public sealed unsafe partial class EnvCellRenderer
|
|||
_clipPipeline = null;
|
||||
_additivePipeline?.Dispose();
|
||||
_additivePipeline = null;
|
||||
_rawAdditivePipeline?.Dispose();
|
||||
_rawAdditivePipeline = null;
|
||||
_inversePipeline?.Dispose();
|
||||
_inversePipeline = null;
|
||||
_inverseAdditivePipeline?.Dispose();
|
||||
_inverseAdditivePipeline = null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ using System.Numerics;
|
|||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AcDream.Core.Meshing;
|
||||
using DatReaderWriter.Enums;
|
||||
|
||||
namespace AcDream.App.Rendering.Wb;
|
||||
|
|
@ -162,7 +163,11 @@ public sealed partial class EnvCellRenderer :
|
|||
private readonly List<MdiDrawRange> _mdiDrawRanges = new();
|
||||
|
||||
private readonly record struct DrawCallRange(int First, int Count);
|
||||
internal readonly record struct MdiDrawRange(int GroupIndex, int FirstCommand, int CommandCount);
|
||||
internal readonly record struct MdiDrawRange(
|
||||
int GroupIndex,
|
||||
int FirstCommand,
|
||||
int CommandCount,
|
||||
RetailSetSurfaceMaterialState MaterialState);
|
||||
// Unfiltered rendering is retained for diagnostic callers only. Build its
|
||||
// global grouping lazily instead of duplicating every prepared gameplay
|
||||
// instance in both a per-cell and global tree each frame.
|
||||
|
|
@ -883,7 +888,7 @@ public sealed partial class EnvCellRenderer :
|
|||
EnvCellTransparentRoute transparentRoute,
|
||||
bool detailSurfaceActive)
|
||||
{
|
||||
// WB EnvCellRenderManager.cs:400: the RHI arm's four pipelines are built
|
||||
// WB EnvCellRenderManager.cs:400: the RHI arm's SetSurface pipelines are built
|
||||
// at construction (see EnvCellRenderer.Rhi.cs), so _initialized alone
|
||||
// answers whether this renderer is ready to draw.
|
||||
if (!_initialized) return;
|
||||
|
|
@ -1304,7 +1309,6 @@ public sealed partial class EnvCellRenderer :
|
|||
int groupIndex = _activeCullGroups[activeIndex];
|
||||
List<(ObjectRenderBatch batch, int instanceCount, int instanceOffset)> group =
|
||||
_batchesByCullGroup[groupIndex];
|
||||
int firstCommand = cmdIndex;
|
||||
foreach (var item in group)
|
||||
{
|
||||
_modernBatches[cmdIndex] = new ModernBatchData
|
||||
|
|
@ -1330,22 +1334,14 @@ public sealed partial class EnvCellRenderer :
|
|||
BaseVertex = (int)item.batch.BaseVertex,
|
||||
BaseInstance = (uint)item.instanceOffset,
|
||||
};
|
||||
AppendMdiDrawRange(
|
||||
_mdiDrawRanges,
|
||||
groupIndex,
|
||||
cmdIndex,
|
||||
1,
|
||||
item.batch.MaterialState);
|
||||
cmdIndex++;
|
||||
}
|
||||
|
||||
int commandCount = cmdIndex - firstCommand;
|
||||
if (commandCount == 0)
|
||||
continue;
|
||||
|
||||
// Adjacent cells frequently resolve to the same cull/blend
|
||||
// state. Their commands are already contiguous and remain in
|
||||
// strict cell order, so one MDI call can cover the complete run
|
||||
// without changing alpha compositing or gl_DrawID indexing.
|
||||
AppendMdiDrawRange(
|
||||
_mdiDrawRanges,
|
||||
groupIndex,
|
||||
firstCommand,
|
||||
commandCount);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1400,7 +1396,8 @@ public sealed partial class EnvCellRenderer :
|
|||
List<MdiDrawRange> ranges,
|
||||
int groupIndex,
|
||||
int firstCommand,
|
||||
int commandCount)
|
||||
int commandCount,
|
||||
RetailSetSurfaceMaterialState materialState)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(ranges);
|
||||
if (commandCount <= 0)
|
||||
|
|
@ -1410,6 +1407,7 @@ public sealed partial class EnvCellRenderer :
|
|||
{
|
||||
MdiDrawRange previous = ranges[^1];
|
||||
if (previous.GroupIndex == groupIndex
|
||||
&& previous.MaterialState == materialState
|
||||
&& previous.FirstCommand + previous.CommandCount == firstCommand)
|
||||
{
|
||||
ranges[^1] = previous with
|
||||
|
|
@ -1420,7 +1418,7 @@ public sealed partial class EnvCellRenderer :
|
|||
}
|
||||
}
|
||||
|
||||
ranges.Add(new MdiDrawRange(groupIndex, firstCommand, commandCount));
|
||||
ranges.Add(new MdiDrawRange(groupIndex, firstCommand, commandCount, materialState));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ internal readonly record struct GroupKey(
|
|||
GpuTextureSlot TextureSlot,
|
||||
uint TextureLayer,
|
||||
TranslucencyKind Translucency,
|
||||
RetailSetSurfaceMaterialState MaterialState,
|
||||
uint FoliageFlags,
|
||||
float SurfaceOpacity = 1f,
|
||||
CullMode CullMode = CullMode.CounterClockwise);
|
||||
|
|
|
|||
|
|
@ -120,6 +120,8 @@ namespace AcDream.App.Rendering.Wb
|
|||
public TextureKey Key { get; set; }
|
||||
public DatReaderWriter.Enums.CullMode CullMode { get; set; }
|
||||
public AcDream.Core.Meshing.TranslucencyKind Translucency { get; set; }
|
||||
public AcDream.Core.Meshing.RetailSetSurfaceMaterialState MaterialState { get; set; }
|
||||
= AcDream.Core.Meshing.RetailSetSurfaceMaterialState.Opaque;
|
||||
public bool IsTransparent { get; set; }
|
||||
public bool IsAdditive { get; set; }
|
||||
public bool HasWrappingUVs { get; set; }
|
||||
|
|
@ -2262,6 +2264,7 @@ namespace AcDream.App.Rendering.Wb
|
|||
TextureFormat = format.Format,
|
||||
RetailSurfaceMask = batch.RetailSurfaceMask,
|
||||
Translucency = batch.Translucency,
|
||||
MaterialState = batch.MaterialState,
|
||||
IsTransparent = batch.IsTransparent,
|
||||
IsAdditive = batch.IsAdditive,
|
||||
HasWrappingUVs = batch.HasWrappingUVs,
|
||||
|
|
|
|||
|
|
@ -488,7 +488,14 @@ public sealed unsafe partial class WbDrawDispatcher
|
|||
if (global is null)
|
||||
return;
|
||||
|
||||
MeshPipelineSet pipelines = PipelinesFor(encoder);
|
||||
IGpuFrame frame = _orderedFrame
|
||||
?? throw new InvalidOperationException(
|
||||
"DrawOrderedRange has no frame to bind clip-region/"
|
||||
+ "scene-lighting sections against — PrepareOrderedStream must run first.");
|
||||
MeshPipelineSet pipelines = PipelinesFor(
|
||||
encoder,
|
||||
frame,
|
||||
out DirectionalShadowFrameBinding shadowBinding);
|
||||
var pushConstants = new GpuPushConstants
|
||||
{
|
||||
ViewProjection = _orderedViewProjection,
|
||||
|
|
@ -503,11 +510,6 @@ public sealed unsafe partial class WbDrawDispatcher
|
|||
};
|
||||
|
||||
{
|
||||
IGpuFrame frame = _orderedFrame
|
||||
?? throw new InvalidOperationException(
|
||||
"DrawOrderedRange has no frame to bind clip-region/"
|
||||
+ "scene-lighting sections against — PrepareOrderedStream must run first.");
|
||||
|
||||
// Bind the SECTIONS on EVERY range call — never latch them
|
||||
// across calls. Between ordered ranges the walk's leaf draws run
|
||||
// (terrain, cell shells, sky, punch fans) and RetailAlphaQueue
|
||||
|
|
@ -521,6 +523,7 @@ public sealed unsafe partial class WbDrawDispatcher
|
|||
// DrawPreparedAlphaBatchRhi does for the same reason.
|
||||
BindPipelineWithMesh(encoder, pipelines.Opaque, global);
|
||||
encoder.SetPushConstants(in pushConstants);
|
||||
BindDirectionalShadowReceiver(encoder, in shadowBinding);
|
||||
BindSection(encoder, GpuBindingModel.StorageInstances, _orderedInstances);
|
||||
BindSection(encoder, GpuBindingModel.StorageBatches, _orderedBatches);
|
||||
BindSection(encoder, GpuBindingModel.StorageClipSlots, _orderedClipSlots);
|
||||
|
|
@ -567,11 +570,16 @@ public sealed unsafe partial class WbDrawDispatcher
|
|||
ValidateMergeRun(_orderedStream, run);
|
||||
|
||||
PipelineBucket bucket = BucketFor(_orderedStream.Keys[run.FirstCommand].Translucency);
|
||||
IGpuPipeline pipeline = PipelineForBucket(pipelines, bucket);
|
||||
GroupKey key = _orderedStream.Keys[run.FirstCommand];
|
||||
bool hasDetail = detailEnabled
|
||||
&& _orderedStream.DetailCategories[run.FirstCommand] != 0u;
|
||||
IGpuPipeline bucketPipeline = PipelineForBucket(pipelines, bucket);
|
||||
IGpuPipeline pipeline = hasDetail
|
||||
? PipelineForMaterial(pipelines, key.MaterialState, bucketPipeline)
|
||||
: bucketPipeline;
|
||||
pushConstants.RenderPass = bucket == PipelineBucket.Opaque ? 0 : 1;
|
||||
if (detailEnabled
|
||||
&& _orderedStream.DetailCategories[run.FirstCommand] != 0u)
|
||||
ArmBuildingDetail(ref pushConstants);
|
||||
if (hasDetail)
|
||||
ArmBuildingDetail(ref pushConstants, key.MaterialState);
|
||||
else
|
||||
ClearDetailPushConstants(ref pushConstants);
|
||||
|
||||
|
|
|
|||
|
|
@ -52,7 +52,9 @@ public sealed unsafe partial class WbDrawDispatcher
|
|||
IGpuPipeline OpaqueAlphaToCoverage,
|
||||
IGpuPipeline AlphaBlend,
|
||||
IGpuPipeline AlphaAdditive,
|
||||
IGpuPipeline AlphaInverse);
|
||||
IGpuPipeline RawAdditive,
|
||||
IGpuPipeline AlphaInverse,
|
||||
IGpuPipeline InverseAdditive);
|
||||
|
||||
private MeshPipelineSet? _backbufferPipelines;
|
||||
private MeshPipelineSet? _offscreenPipelines;
|
||||
|
|
@ -227,7 +229,7 @@ public sealed unsafe partial class WbDrawDispatcher
|
|||
bool usesRenderPackShaderAbi = false)
|
||||
{
|
||||
string suffix = samples > 1 ? string.Empty : "-1x";
|
||||
var created = new List<IGpuPipeline>(5);
|
||||
var created = new List<IGpuPipeline>(7);
|
||||
try
|
||||
{
|
||||
return new MeshPipelineSet(
|
||||
|
|
@ -252,10 +254,20 @@ public sealed unsafe partial class WbDrawDispatcher
|
|||
shaders: baseShaders,
|
||||
shaderName: baseShaderName,
|
||||
usesRenderPackShaderAbi: usesRenderPackShaderAbi)),
|
||||
Track(CreateMeshPipeline(
|
||||
device, $"{namePrefix}-raw-additive{suffix}", GpuBlendMode.RawAdditive, false, false, samples,
|
||||
shaders: baseShaders,
|
||||
shaderName: baseShaderName,
|
||||
usesRenderPackShaderAbi: usesRenderPackShaderAbi)),
|
||||
Track(CreateMeshPipeline(
|
||||
device, $"{namePrefix}-inverse{suffix}", GpuBlendMode.InverseAlpha, false, false, samples,
|
||||
shaders: baseShaders,
|
||||
shaderName: baseShaderName,
|
||||
usesRenderPackShaderAbi: usesRenderPackShaderAbi)),
|
||||
Track(CreateMeshPipeline(
|
||||
device, $"{namePrefix}-inverse-additive{suffix}", GpuBlendMode.InverseAdditive, false, false, samples,
|
||||
shaders: baseShaders,
|
||||
shaderName: baseShaderName,
|
||||
usesRenderPackShaderAbi: usesRenderPackShaderAbi)));
|
||||
}
|
||||
catch
|
||||
|
|
@ -370,9 +382,12 @@ public sealed unsafe partial class WbDrawDispatcher
|
|||
encoder,
|
||||
frame,
|
||||
out DirectionalShadowFrameBinding shadowBinding);
|
||||
IGpuPipeline opaquePipeline = AlphaToCoverage
|
||||
? pipelines.OpaqueAlphaToCoverage
|
||||
: pipelines.Opaque;
|
||||
BindPipelineWithMesh(
|
||||
encoder,
|
||||
AlphaToCoverage ? pipelines.OpaqueAlphaToCoverage : pipelines.Opaque,
|
||||
opaquePipeline,
|
||||
mesh);
|
||||
encoder.SetPushConstants(in pushConstants);
|
||||
BindDirectionalShadowReceiver(encoder, in shadowBinding);
|
||||
|
|
@ -436,7 +451,8 @@ public sealed unsafe partial class WbDrawDispatcher
|
|||
using (BeginRhiTimer(encoder, diag, OpaqueTimerScope))
|
||||
{
|
||||
DrawDetailAwareRangeRhi(
|
||||
encoder, ref pushConstants, commandBuffer, commandBase,
|
||||
encoder, mesh, pipelines, opaquePipeline,
|
||||
ref pushConstants, commandBuffer, commandBase,
|
||||
0, _opaqueDrawCount, usedDetailCategories, detailEnabled);
|
||||
}
|
||||
}
|
||||
|
|
@ -634,7 +650,10 @@ public sealed unsafe partial class WbDrawDispatcher
|
|||
/// (M3c) precisely because detail-eligible content no longer reaches it.
|
||||
/// </summary>
|
||||
private void DrawImmediateAlphaInstanceRhi(
|
||||
GlobalMeshBuffer mesh, TranslucencyKind blend, Matrix4x4 viewProjection)
|
||||
GlobalMeshBuffer mesh,
|
||||
TranslucencyKind blend,
|
||||
RetailSetSurfaceMaterialState materialState,
|
||||
Matrix4x4 viewProjection)
|
||||
{
|
||||
if (_alphaCommands.Buffer is null)
|
||||
return;
|
||||
|
|
@ -644,9 +663,12 @@ public sealed unsafe partial class WbDrawDispatcher
|
|||
GpuPushConstants pushConstants = BindAlphaDrawState(
|
||||
encoder, frame, mesh, viewProjection, out MeshPipelineSet pipelines);
|
||||
|
||||
BindPipelineWithMesh(encoder, PipelineForBlend(pipelines, blend), mesh);
|
||||
BindPipelineWithMesh(
|
||||
encoder,
|
||||
PipelineForMaterial(pipelines, materialState, pipelines.Opaque),
|
||||
mesh);
|
||||
encoder.SetPushConstants(in pushConstants);
|
||||
ArmBuildingDetail(ref pushConstants);
|
||||
ArmBuildingDetail(ref pushConstants, materialState);
|
||||
DrawIndirectRangeRhi(
|
||||
encoder,
|
||||
ref pushConstants,
|
||||
|
|
@ -673,6 +695,8 @@ public sealed unsafe partial class WbDrawDispatcher
|
|||
while (command < end)
|
||||
{
|
||||
TranslucencyKind blend = _groupInputScratch[command].Translucency;
|
||||
RetailSetSurfaceMaterialState materialState =
|
||||
_groupInputScratch[command].MaterialState;
|
||||
bool hasDetail = detailEnabled
|
||||
&& CommandContainsDetailCategory(
|
||||
_indirectCommands[command],
|
||||
|
|
@ -691,10 +715,12 @@ public sealed unsafe partial class WbDrawDispatcher
|
|||
|
||||
BindPipelineWithMesh(
|
||||
encoder,
|
||||
PipelineForBlend(pipelines, blend),
|
||||
hasDetail
|
||||
? PipelineForMaterial(pipelines, materialState, pipelines.Opaque)
|
||||
: PipelineForBlend(pipelines, blend),
|
||||
mesh);
|
||||
if (hasDetail)
|
||||
ArmBuildingDetail(ref pushConstants);
|
||||
ArmBuildingDetail(ref pushConstants, materialState);
|
||||
else
|
||||
ClearDetailPushConstants(ref pushConstants);
|
||||
DrawIndirectRangeRhi(
|
||||
|
|
@ -712,6 +738,9 @@ public sealed unsafe partial class WbDrawDispatcher
|
|||
|
||||
private void DrawDetailAwareRangeRhi(
|
||||
IGpuPassEncoder encoder,
|
||||
GlobalMeshBuffer mesh,
|
||||
MeshPipelineSet pipelines,
|
||||
IGpuPipeline opaquePipeline,
|
||||
ref GpuPushConstants pushConstants,
|
||||
IGpuBuffer commandBuffer,
|
||||
uint commandBase,
|
||||
|
|
@ -738,9 +767,20 @@ public sealed unsafe partial class WbDrawDispatcher
|
|||
}
|
||||
|
||||
if (hasDetail)
|
||||
ArmBuildingDetail(ref pushConstants);
|
||||
{
|
||||
RetailSetSurfaceMaterialState materialState =
|
||||
_groupInputScratch[command].MaterialState;
|
||||
BindPipelineWithMesh(
|
||||
encoder,
|
||||
PipelineForMaterial(pipelines, materialState, opaquePipeline),
|
||||
mesh);
|
||||
ArmBuildingDetail(ref pushConstants, materialState);
|
||||
}
|
||||
else
|
||||
{
|
||||
BindPipelineWithMesh(encoder, opaquePipeline, mesh);
|
||||
ClearDetailPushConstants(ref pushConstants);
|
||||
}
|
||||
DrawIndirectRangeRhi(
|
||||
encoder,
|
||||
ref pushConstants,
|
||||
|
|
@ -754,10 +794,17 @@ public sealed unsafe partial class WbDrawDispatcher
|
|||
encoder.SetPushConstants(in pushConstants);
|
||||
}
|
||||
|
||||
private void ArmBuildingDetail(ref GpuPushConstants pushConstants)
|
||||
private void ArmBuildingDetail(
|
||||
ref GpuPushConstants pushConstants,
|
||||
RetailSetSurfaceMaterialState materialState)
|
||||
{
|
||||
pushConstants.TextureIndexA = _buildingDetail.TextureSlot.Index;
|
||||
pushConstants.ParamA = _buildingDetail.Tiling;
|
||||
pushConstants.ParamB = materialState.AlphaTestReference;
|
||||
if (materialState.FogEnabled)
|
||||
pushConstants.RenderPass &= ~RetailDetailTextureContract.NoFogRenderPassFlag;
|
||||
else
|
||||
pushConstants.RenderPass |= RetailDetailTextureContract.NoFogRenderPassFlag;
|
||||
}
|
||||
|
||||
private static void ClearDetailPushConstants(
|
||||
|
|
@ -766,6 +813,7 @@ public sealed unsafe partial class WbDrawDispatcher
|
|||
pushConstants.TextureIndexA = 0;
|
||||
pushConstants.ParamA = 0f;
|
||||
pushConstants.ParamB = 0f;
|
||||
pushConstants.RenderPass &= ~RetailDetailTextureContract.NoFogRenderPassFlag;
|
||||
}
|
||||
|
||||
private static IGpuPipeline PipelineForBlend(MeshPipelineSet pipelines, TranslucencyKind blend) =>
|
||||
|
|
@ -776,6 +824,23 @@ public sealed unsafe partial class WbDrawDispatcher
|
|||
_ => pipelines.AlphaBlend,
|
||||
};
|
||||
|
||||
private static IGpuPipeline PipelineForMaterial(
|
||||
MeshPipelineSet pipelines,
|
||||
RetailSetSurfaceMaterialState material,
|
||||
IGpuPipeline opaquePipeline) => material.Blend switch
|
||||
{
|
||||
RetailSetSurfaceBlend.Opaque => opaquePipeline,
|
||||
RetailSetSurfaceBlend.StraightAlpha => pipelines.AlphaBlend,
|
||||
RetailSetSurfaceBlend.AlphaAdditive => pipelines.AlphaAdditive,
|
||||
RetailSetSurfaceBlend.Additive => pipelines.RawAdditive,
|
||||
RetailSetSurfaceBlend.InverseAlpha => pipelines.AlphaInverse,
|
||||
RetailSetSurfaceBlend.InverseAdditive => pipelines.InverseAdditive,
|
||||
// AP-240: ordinary Gfx/building pure Clip stays on its selected
|
||||
// opaque/A2C pipeline; exact final-X ParamB still reaches shader.
|
||||
RetailSetSurfaceBlend.Clip => opaquePipeline,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(material), material, "Unknown SetSurface blend."),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Reads cull modes from <paramref name="cullModes"/> when the caller
|
||||
/// supplies one, or from the shared <see cref="_drawCullModes"/> scratch
|
||||
|
|
@ -1102,7 +1167,9 @@ public sealed unsafe partial class WbDrawDispatcher
|
|||
pipelines.OpaqueAlphaToCoverage.Dispose();
|
||||
pipelines.AlphaBlend.Dispose();
|
||||
pipelines.AlphaAdditive.Dispose();
|
||||
pipelines.RawAdditive.Dispose();
|
||||
pipelines.AlphaInverse.Dispose();
|
||||
pipelines.InverseAdditive.Dispose();
|
||||
}
|
||||
|
||||
private sealed class NullRhiTimerScope : IDisposable
|
||||
|
|
|
|||
|
|
@ -240,6 +240,7 @@ public sealed partial class WbDrawDispatcher
|
|||
key = new GroupKey(
|
||||
batch.FirstIndex, (int)batch.BaseVertex,
|
||||
batch.IndexCount, texture.Slot, texture.Layer, translucency,
|
||||
MaterialState: batch.MaterialState,
|
||||
FoliageFlags: foliageFlags,
|
||||
SurfaceOpacity: batch.SurfaceOpacity,
|
||||
CullMode: batch.CullMode);
|
||||
|
|
|
|||
|
|
@ -2050,6 +2050,7 @@ public sealed partial class WbDrawDispatcher : IDisposable
|
|||
TextureIndex: g.TextureSlot.Index,
|
||||
TextureLayer: g.TextureLayer,
|
||||
Translucency: g.Translucency,
|
||||
MaterialState: g.MaterialState,
|
||||
SurfaceOpacity: g.SurfaceOpacity,
|
||||
CullMode: g.CullMode,
|
||||
FoliageFlags: g.FoliageFlags);
|
||||
|
|
@ -2123,6 +2124,7 @@ public sealed partial class WbDrawDispatcher : IDisposable
|
|||
g.TextureSlot,
|
||||
g.TextureLayer,
|
||||
g.Translucency,
|
||||
g.MaterialState,
|
||||
g.FoliageFlags,
|
||||
g.SurfaceOpacity,
|
||||
g.CullMode);
|
||||
|
|
@ -2144,6 +2146,7 @@ public sealed partial class WbDrawDispatcher : IDisposable
|
|||
TextureSlot = key.TextureSlot,
|
||||
TextureLayer = key.TextureLayer,
|
||||
Translucency = key.Translucency,
|
||||
MaterialState = key.MaterialState,
|
||||
CullMode = key.CullMode,
|
||||
FoliageFlags = key.FoliageFlags,
|
||||
SurfaceOpacity = key.SurfaceOpacity,
|
||||
|
|
@ -2641,7 +2644,11 @@ public sealed partial class WbDrawDispatcher : IDisposable
|
|||
EnsureDeferredAlphaCapacity(1);
|
||||
WriteDeferredAlphaEntrySlot(0, in entry);
|
||||
PrepareRhiAlphaSections(1);
|
||||
DrawImmediateAlphaInstanceRhi(global, entry.Key.Translucency, viewProjection);
|
||||
DrawImmediateAlphaInstanceRhi(
|
||||
global,
|
||||
entry.Key.Translucency,
|
||||
entry.Key.MaterialState,
|
||||
viewProjection);
|
||||
}
|
||||
|
||||
private void DrawPreparedAlphaBatch(int firstPreparedDraw, int drawCount)
|
||||
|
|
@ -3437,6 +3444,7 @@ public sealed partial class WbDrawDispatcher : IDisposable
|
|||
uint TextureIndex,
|
||||
uint TextureLayer,
|
||||
TranslucencyKind Translucency,
|
||||
RetailSetSurfaceMaterialState MaterialState,
|
||||
float SurfaceOpacity = 1f,
|
||||
CullMode CullMode = CullMode.CounterClockwise,
|
||||
// Campaign VM VM6: FoliageWindClassification.CutoutFoliageFlag /
|
||||
|
|
@ -3675,6 +3683,8 @@ public sealed partial class WbDrawDispatcher : IDisposable
|
|||
AcDream.App.Rendering.Gpu.GpuTextureSlot.Unassigned;
|
||||
public uint TextureLayer; // Layer in either the pooled composite array or WB shared atlas.
|
||||
public TranslucencyKind Translucency;
|
||||
public RetailSetSurfaceMaterialState MaterialState =
|
||||
RetailSetSurfaceMaterialState.Opaque;
|
||||
public float SurfaceOpacity = 1f;
|
||||
public CullMode CullMode;
|
||||
public int FirstInstance; // offset into the shared instance VBO (in instances, not bytes)
|
||||
|
|
|
|||
|
|
@ -396,6 +396,7 @@ public sealed class MeshExtractor {
|
|||
bool isSolid = RetailUntexturedSurfacePolicy.IsUntextured(surface.Type);
|
||||
bool isClipMap = surface.Type.HasFlag(SurfaceType.Base1ClipMap);
|
||||
uint paletteId = 0;
|
||||
bool texturePresent = false;
|
||||
bool isDxt3or5 = false;
|
||||
bool textureDataIsCached = false;
|
||||
DatReaderWriter.Enums.PixelFormat? sourceFormat = null;
|
||||
|
|
@ -410,6 +411,7 @@ public sealed class MeshExtractor {
|
|||
textureDataIsCached = true;
|
||||
}
|
||||
else if (_dats.Portal.TryGet<SurfaceTexture>(surface.OrigTextureId, out var surfaceTexture)) {
|
||||
texturePresent = true;
|
||||
var renderSurfaceId = surfaceTexture.Textures.First();
|
||||
if (!_dats.Portal.TryGet<RenderSurface>(renderSurfaceId, out var renderSurface)) {
|
||||
// check highres
|
||||
|
|
@ -603,6 +605,10 @@ public sealed class MeshExtractor {
|
|||
TranslucencyKindExtensions.OpacityFromSurfaceTranslucency(
|
||||
surface.Type,
|
||||
surface.Translucency),
|
||||
MaterialState = RetailSetSurfaceMaterialState.Resolve(
|
||||
surface.Type,
|
||||
texturePresent,
|
||||
paletteId != 0),
|
||||
IsTransparent = isTransparent,
|
||||
IsAdditive = isAdditive
|
||||
};
|
||||
|
|
@ -900,6 +906,7 @@ public sealed class MeshExtractor {
|
|||
bool isSolid = RetailUntexturedSurfacePolicy.IsUntextured(surface.Type);
|
||||
bool isClipMap = surface.Type.HasFlag(SurfaceType.Base1ClipMap);
|
||||
uint paletteId = 0;
|
||||
bool texturePresent = false;
|
||||
bool isDxt3or5 = false;
|
||||
bool textureDataIsCached = false;
|
||||
DatReaderWriter.Enums.PixelFormat? sourceFormat = null;
|
||||
|
|
@ -914,6 +921,7 @@ public sealed class MeshExtractor {
|
|||
textureDataIsCached = true;
|
||||
}
|
||||
else if (_dats.Portal.TryGet<SurfaceTexture>(surface.OrigTextureId, out var surfaceTexture)) {
|
||||
texturePresent = true;
|
||||
var renderSurfaceId = surfaceTexture.Textures.First();
|
||||
if (!_dats.Portal.TryGet<RenderSurface>(renderSurfaceId, out var renderSurface)) {
|
||||
if (!_dats.HighRes.TryGet<RenderSurface>(renderSurfaceId, out var hrRenderSurface)) {
|
||||
|
|
@ -1080,6 +1088,10 @@ public sealed class MeshExtractor {
|
|||
TranslucencyKindExtensions.OpacityFromSurfaceTranslucency(
|
||||
surface.Type,
|
||||
surface.Translucency),
|
||||
MaterialState = RetailSetSurfaceMaterialState.Resolve(
|
||||
surface.Type,
|
||||
texturePresent,
|
||||
paletteId != 0),
|
||||
IsTransparent = isTransparent,
|
||||
IsAdditive = isAdditive,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -181,6 +181,13 @@ public class TextureBatchData {
|
|||
/// </summary>
|
||||
public float SurfaceOpacity { get; set; } = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// Exact retail SetSurface blend/alpha-test/fog state resolved during
|
||||
/// extraction from the source Surface and texture palette class.
|
||||
/// </summary>
|
||||
public RetailSetSurfaceMaterialState MaterialState { get; set; } =
|
||||
RetailSetSurfaceMaterialState.Opaque;
|
||||
|
||||
/// <summary>
|
||||
/// Retail source surface-array index this subset was constructed from
|
||||
/// (contract §3.6) — the CellStruct subset/material OWNER, not the
|
||||
|
|
|
|||
|
|
@ -228,9 +228,13 @@ public static class ObjectMeshDataSerializer {
|
|||
w.Write(batch.RetailSurfaceMask);
|
||||
w.Write(batch.RawSurfaceType);
|
||||
w.Write(batch.IsCellShell);
|
||||
// OVERHAUL S5-c4: recipe 9 appends authored material opacity. Recipe
|
||||
// OVERHAUL S5-c4: recipe 9 appended authored material opacity. Recipe
|
||||
// 10 appends the extraction-resolved SetSurface state byte. Recipe
|
||||
// identity rejects older payloads before this serializer is entered.
|
||||
w.Write(batch.SurfaceOpacity);
|
||||
// S5-c4 fix round 1: recipe 10 appends the immutable resolved
|
||||
// SetSurface state as one validated deterministic byte.
|
||||
w.Write(batch.MaterialState.ToPackedByte());
|
||||
}
|
||||
|
||||
private static TextureBatchData ReadTextureBatchData(
|
||||
|
|
@ -256,6 +260,9 @@ public static class ObjectMeshDataSerializer {
|
|||
batch.RawSurfaceType = r.ReadUInt32();
|
||||
batch.IsCellShell = r.ReadBoolean();
|
||||
batch.SurfaceOpacity = r.ReadSingle();
|
||||
batch.MaterialState =
|
||||
AcDream.Core.Meshing.RetailSetSurfaceMaterialState.FromPackedByte(
|
||||
r.ReadByte());
|
||||
return batch;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -45,9 +45,11 @@ public static class PakFormat {
|
|||
/// (Surface.Type & (BASE1_IMAGE|BASE1_CLIPMAP)) != 0 applied after
|
||||
/// surface resolution. Version 9 appends authored surface opacity to
|
||||
/// every prepared mesh texture batch. The binary format remains version
|
||||
/// 2, but every prepared render record must be regenerated.
|
||||
/// 2, but every prepared render record must be regenerated. Version 10
|
||||
/// appends the exact resolved SetSurface blend, alpha-test, and fog state
|
||||
/// to every prepared texture batch; recipe 9 cannot be read as recipe 10.
|
||||
/// </summary>
|
||||
public const uint CurrentBakeToolVersion = 9;
|
||||
public const uint CurrentBakeToolVersion = 10;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -38,6 +38,135 @@ public enum TranslucencyKind
|
|||
InvAlpha = 4,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exact framebuffer blend selected by retail's
|
||||
/// <c>D3DPolyRender::SetSurface</c>. This is deliberately separate from
|
||||
/// <see cref="TranslucencyKind"/>, which remains the existing queue-membership
|
||||
/// classification and cannot distinguish the additive factor pairs.
|
||||
/// </summary>
|
||||
public enum RetailSetSurfaceBlend : byte
|
||||
{
|
||||
Opaque = 0,
|
||||
StraightAlpha = 1,
|
||||
AlphaAdditive = 2,
|
||||
Additive = 3,
|
||||
InverseAlpha = 4,
|
||||
InverseAdditive = 5,
|
||||
Clip = 6,
|
||||
}
|
||||
|
||||
/// <summary>The exact final-alpha comparison selected by SetSurface.</summary>
|
||||
public enum RetailSetSurfaceAlphaTest : byte
|
||||
{
|
||||
Disabled = 0,
|
||||
Paletted = 1,
|
||||
Dds = 2,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Immutable resolved SetSurface state prepared from the source Surface and
|
||||
/// its actual texture/palette class. Renderers consume this value directly;
|
||||
/// they never reconstruct it from queue kind or texture identity.
|
||||
/// </summary>
|
||||
public readonly record struct RetailSetSurfaceMaterialState(
|
||||
RetailSetSurfaceBlend Blend,
|
||||
RetailSetSurfaceAlphaTest AlphaTest,
|
||||
bool FogEnabled)
|
||||
{
|
||||
public static RetailSetSurfaceMaterialState Opaque { get; } = new(
|
||||
RetailSetSurfaceBlend.Opaque,
|
||||
RetailSetSurfaceAlphaTest.Disabled,
|
||||
FogEnabled: true);
|
||||
|
||||
public bool AlphaTestEnabled => AlphaTest != RetailSetSurfaceAlphaTest.Disabled;
|
||||
|
||||
public float AlphaTestReference => AlphaTest switch
|
||||
{
|
||||
RetailSetSurfaceAlphaTest.Disabled => 0f,
|
||||
RetailSetSurfaceAlphaTest.Paletted => 100f / 255f,
|
||||
RetailSetSurfaceAlphaTest.Dds => 200f / 255f,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(AlphaTest), AlphaTest, null),
|
||||
};
|
||||
|
||||
/// <summary>One deterministic recipe byte; bits 6-7 remain reserved.</summary>
|
||||
public byte ToPackedByte()
|
||||
{
|
||||
if ((uint)Blend > (uint)RetailSetSurfaceBlend.Clip)
|
||||
throw new InvalidOperationException($"Unknown SetSurface blend {Blend}.");
|
||||
if ((uint)AlphaTest > (uint)RetailSetSurfaceAlphaTest.Dds)
|
||||
throw new InvalidOperationException($"Unknown SetSurface alpha test {AlphaTest}.");
|
||||
return (byte)((byte)Blend | ((byte)AlphaTest << 3) | (FogEnabled ? 0 : 0x20));
|
||||
}
|
||||
|
||||
public static RetailSetSurfaceMaterialState FromPackedByte(byte value)
|
||||
{
|
||||
if ((value & 0xC0) != 0)
|
||||
throw new InvalidDataException($"Reserved SetSurface state bits are set: 0x{value:X2}.");
|
||||
var blend = (RetailSetSurfaceBlend)(value & 0x07);
|
||||
var alphaTest = (RetailSetSurfaceAlphaTest)((value >> 3) & 0x03);
|
||||
if ((uint)blend > (uint)RetailSetSurfaceBlend.Clip
|
||||
|| (uint)alphaTest > (uint)RetailSetSurfaceAlphaTest.Dds)
|
||||
{
|
||||
throw new InvalidDataException($"Invalid SetSurface state byte: 0x{value:X2}.");
|
||||
}
|
||||
return new RetailSetSurfaceMaterialState(
|
||||
blend,
|
||||
alphaTest,
|
||||
FogEnabled: (value & 0x20) == 0);
|
||||
}
|
||||
|
||||
public static RetailSetSurfaceMaterialState Resolve(
|
||||
SurfaceType type,
|
||||
bool texturePresent,
|
||||
bool textureHasPalette)
|
||||
{
|
||||
bool additive = (type & SurfaceType.Additive) != 0;
|
||||
bool alpha = (type & SurfaceType.Alpha) != 0;
|
||||
bool inverse = (type & SurfaceType.InvAlpha) != 0;
|
||||
bool clip = (type & SurfaceType.Base1ClipMap) != 0;
|
||||
bool translucent = (type & SurfaceType.Translucent) != 0;
|
||||
|
||||
RetailSetSurfaceBlend blend = alpha
|
||||
? additive
|
||||
? RetailSetSurfaceBlend.AlphaAdditive
|
||||
: RetailSetSurfaceBlend.StraightAlpha
|
||||
: inverse
|
||||
? additive
|
||||
? RetailSetSurfaceBlend.InverseAdditive
|
||||
: RetailSetSurfaceBlend.InverseAlpha
|
||||
: additive
|
||||
? RetailSetSurfaceBlend.Additive
|
||||
: RetailSetSurfaceBlend.Opaque;
|
||||
|
||||
RetailSetSurfaceAlphaTest alphaTest = RetailSetSurfaceAlphaTest.Disabled;
|
||||
if (clip)
|
||||
{
|
||||
alphaTest = texturePresent && textureHasPalette
|
||||
? RetailSetSurfaceAlphaTest.Paletted
|
||||
: RetailSetSurfaceAlphaTest.Dds;
|
||||
if (blend == RetailSetSurfaceBlend.Opaque)
|
||||
blend = RetailSetSurfaceBlend.Clip;
|
||||
}
|
||||
|
||||
// Retail's later Translucent arm forces every ClipMap combination and
|
||||
// the otherwise-opaque/straight cases to straight alpha, disabling the
|
||||
// ClipMap test. Established additive/inverse families remain intact.
|
||||
if (translucent
|
||||
&& (clip
|
||||
|| blend is RetailSetSurfaceBlend.Opaque
|
||||
or RetailSetSurfaceBlend.StraightAlpha))
|
||||
{
|
||||
blend = RetailSetSurfaceBlend.StraightAlpha;
|
||||
alphaTest = RetailSetSurfaceAlphaTest.Disabled;
|
||||
}
|
||||
|
||||
return new RetailSetSurfaceMaterialState(
|
||||
blend,
|
||||
alphaTest,
|
||||
FogEnabled: !additive);
|
||||
}
|
||||
}
|
||||
|
||||
public static class TranslucencyKindExtensions
|
||||
{
|
||||
// Translucent override comes FIRST, then the existing priority chain:
|
||||
|
|
|
|||
|
|
@ -61,6 +61,10 @@ public static class ContentMigrationCatalog
|
|||
8,
|
||||
9,
|
||||
"authored surface opacity in prepared mesh batches"),
|
||||
[10] = FullRebuild(
|
||||
9,
|
||||
10,
|
||||
"exact resolved SetSurface state in prepared mesh batches"),
|
||||
};
|
||||
|
||||
public static ContentMigrationPlan Resolve(uint fromRecipeVersion, uint targetRecipeVersion)
|
||||
|
|
|
|||
|
|
@ -39,9 +39,10 @@ public sealed class LauncherInstallRecordStore
|
|||
// with retail's authored DrawingBSP view sphere. Version 8 (OH2/S1) keeps
|
||||
// pak format 2 and regenerates every CellStruct/EnvCell render record
|
||||
// with retail's exact surface-array-index subset construction. Version 9
|
||||
// carries authored surface opacity in prepared mesh batches. Each is a
|
||||
// mandatory full rebuild from its predecessor recipe.
|
||||
public const uint CurrentBakeToolVersion = 9;
|
||||
// carries authored surface opacity in prepared mesh batches. Version 10
|
||||
// adds exact resolved SetSurface state. Each is a mandatory full rebuild
|
||||
// from its predecessor recipe.
|
||||
public const uint CurrentBakeToolVersion = 10;
|
||||
|
||||
private static readonly JsonSerializerOptions SerializerOptions = new()
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue