acdream/src/AcDream.App/Rendering/Gpu/GpuPushConstants.cs
Erik 75664805f8 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.
2026-09-05 02:03:13 +02:00

86 lines
3.5 KiB
C#

using System.Numerics;
using System.Runtime.InteropServices;
namespace AcDream.App.Rendering.Gpu;
/// <summary>
/// The single push-constant block shared by every acdream pipeline — 96 of the
/// 128 bytes Vulkan guarantees, leaving 32 bytes of headroom for later slices.
///
/// One shared block (rather than a per-shader block) is what lets every world
/// pipeline share ONE pipeline layout, so switching pipelines mid-pass does not
/// invalidate bound descriptor sets or push constants. Shaders declare only the
/// fields they read; unused fields cost nothing.
///
/// The GL backend maps each field to the correspondingly named uniform and skips
/// any the program does not declare (location -1). The Vulkan backend writes the
/// struct verbatim with one <c>vkCmdPushConstants</c>.
///
/// Layout is asserted by <c>GpuContractTests</c>; changing it is a contract change
/// that must land together with the matching edit to the shared GLSL preamble.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 4)]
internal struct GpuPushConstants
{
/// <summary>
/// GLSL <c>uViewProjection</c>. acdream's cameras build this with
/// <c>Matrix4x4.CreatePerspectiveFieldOfView</c>, whose NDC z range is [0,1] —
/// already Vulkan's convention (see <c>PortalProjection.cs</c>). No projection
/// rework is needed for the migration, and Vulkan gains the half of the depth
/// range GL was discarding.
/// </summary>
public Matrix4x4 ViewProjection;
/// <summary>
/// GLSL <c>uDrawIDOffset</c>. Issue #52: the draw index resets to 0 at the
/// start of each multi-draw-indirect call, so a pass that begins partway into
/// the batch array must offset its lookup. Vulkan's <c>gl_DrawID</c> resets
/// identically per <c>vkCmdDrawIndexedIndirect</c>, so the pattern carries over
/// unchanged.
/// </summary>
public int DrawIdOffset;
/// <summary>GLSL <c>uLightingMode</c>: 0 = object (plain Lambert + sun), 1 = EnvCell (half-Lambert wrap, no sun).</summary>
public int LightingMode;
/// <summary>GLSL <c>uRenderPass</c>: 0 = opaque, 1 = translucent.</summary>
public int RenderPass;
/// <summary>GLSL <c>uLightDebug</c>: #176 stripe-hunt isolation modes; 0 = off.</summary>
public int LightDebug;
/// <summary>
/// GLSL <c>uTextureIndexA</c>. Primary texture-table slot for pipelines whose
/// texture is per-pass rather than per-batch — currently the terrain atlas.
/// </summary>
public uint TextureIndexA;
/// <summary>GLSL <c>uTextureIndexB</c>. Secondary per-pass slot; terrain
/// uses it for the alpha-mask array, while shared-pose world passes
/// carry the absolute transform-prefix instance count.</summary>
public uint TextureIndexB;
/// <summary>
/// GLSL <c>uParamA</c>. Spare scalar, claimed at slice V6e by
/// <c>particle_mesh</c> as the array layer its per-pass texture is sampled
/// from — a value the shader converted to float anyway.
/// </summary>
public float ParamA;
/// <summary>GLSL <c>uParamB</c>. Spare scalar; unclaimed at V0.</summary>
public float ParamB;
/// <summary>Neutral defaults: identity transform, opaque object lighting, no debug mode.</summary>
public static GpuPushConstants Default => new()
{
ViewProjection = Matrix4x4.Identity,
DrawIdOffset = 0,
LightingMode = 0,
RenderPass = 0,
LightDebug = 0,
TextureIndexA = 0,
TextureIndexB = 0,
ParamA = 0f,
ParamB = 0f,
};
}