using System.Collections.Immutable; namespace AcDream.App.Rendering.Gpu; /// Vertex attribute component layout. Only the formats acdream's meshes actually use. internal enum GpuVertexFormat { Float1, Float2, Float3, Float4, /// Four unsigned bytes scaled to [0,1] floats — a shader vec4 input. UByte4Normalized, /// /// Four unsigned bytes delivered as INTEGERS — a shader uvec4 input. /// /// Distinct from in kind, not just in scaling: /// GL requires glVertexAttribIPointer for an integer shader input and /// leaves the value undefined if it arrives through the float path, and Vulkan /// needs the format named as R8G8B8A8_UINT rather than _UNORM. /// /// Added at slice V4d, which found `terrain_modern.vert` declares locations 2–5 /// as uvec4 and feeds them with glVertexAttribIPointer. Those /// packed bytes carry terrain-type, road and split-direction codes that drive /// every blend decision, so normalising them would not be an approximation — /// it would be garbage. /// UByte4UInt, } /// One vertex attribute, matching a layout(location = N) in declaration. internal readonly record struct GpuVertexAttribute( uint Location, GpuVertexFormat Format, uint OffsetBytes); /// Interleaved vertex layout for a single bound vertex buffer. internal sealed record GpuVertexLayout(uint StrideBytes, ImmutableArray Attributes) { /// /// The world mesh vertex shared by mesh_modern, EnvCells, and terrain: /// position, normal, texcoord — 32 bytes, matching the format /// ObjectMeshManager packs into GlobalMeshBuffer. /// public static GpuVertexLayout WorldMesh { get; } = new( StrideBytes: 32, [ new GpuVertexAttribute(0, GpuVertexFormat.Float3, 0), new GpuVertexAttribute(1, GpuVertexFormat.Float3, 12), new GpuVertexAttribute(2, GpuVertexFormat.Float2, 24), ]); /// Empty layout for pipelines whose vertices come entirely from storage buffers. public static GpuVertexLayout None { get; } = new(0, []); } /// /// Names one GLSL shader pair. The backend resolves it: the GL backend loads /// Rendering/Shaders/{Name}.vert and .frag and compiles at startup; /// the Vulkan backend loads the committed Rendering/Shaders/spv/{Name}.vert.spv /// and .frag.spv produced by tools/compile-shaders.ps1. One source /// of truth (the GLSL), two consumption paths. /// internal readonly record struct GpuShaderSet(string Name); /// Depth-buffer behaviour baked into a pipeline. /// Whether depth testing is enabled at all. /// Default depth-write state; overridable per draw via dynamic state. /// Comparison function when testing is enabled. internal readonly record struct GpuDepthState(bool Test, bool Write, GpuCompareOp Compare) { /// Standard opaque geometry: test and write, nearer wins. public static GpuDepthState OpaqueDefault { get; } = new(Test: true, Write: true, GpuCompareOp.LessOrEqual); /// Translucent geometry: test against existing depth but do not occlude later draws. public static GpuDepthState TranslucentDefault { get; } = new(Test: true, Write: false, GpuCompareOp.LessOrEqual); /// Sky and 2-D overlays: depth is irrelevant. public static GpuDepthState Disabled { get; } = new(Test: false, Write: false, GpuCompareOp.Always); } /// /// Everything a draw needs beyond its buffers: the shader pair and all fixed /// state. Vulkan bakes this into one VkPipeline at startup, which is why /// runtime shader compilation and driver state revalidation both disappear. /// /// Equality is NOT part of this contract — pipelines are created explicitly and /// held by their renderer. is the identity used by debug /// tooling and by the backend's own pipeline cache key. /// internal sealed record GpuPipelineDescription { /// Stable identifier, e.g. "mesh-opaque". Surfaced to RenderDoc and validation layers. public required string Name { get; init; } /// The GLSL pair this pipeline draws with. public required GpuShaderSet Shaders { get; init; } /// Vertex input layout; for buffer-fed geometry. public required GpuVertexLayout VertexLayout { get; init; } public GpuPrimitiveTopology Topology { get; init; } = GpuPrimitiveTopology.TriangleList; public GpuBlendMode Blend { get; init; } = GpuBlendMode.None; public GpuDepthState Depth { get; init; } = GpuDepthState.OpaqueDefault; /// Default cull mode; overridable per draw via . public GpuCullMode Cull { get; init; } = GpuCullMode.Back; /// /// Winding treated as front-facing, expressed in GL convention. The Vulkan /// backend inverts it internally to compensate for its negative viewport /// height; no renderer performs that flip itself. /// public GpuFrontFace FrontFace { get; init; } = GpuFrontFace.CounterClockwise; /// /// Alpha-to-coverage for foliage. Only meaningful when the pass is /// multisampled; the backend ignores it at one sample. /// public bool AlphaToCoverage { get; init; } /// Whether the pipeline writes colour at all. False for depth/stencil-only prepasses. public bool ColorWrite { get; init; } = true; /// /// Format of the colour attachment this pipeline renders into. /// /// Vulkan's dynamic rendering bakes the attachment format into the pipeline: /// VkPipelineRenderingCreateInfo has to name it at creation, and a /// pipeline whose declared format differs from the attachment it is used /// with is undefined. GL has no equivalent — a framebuffer carries its own /// attachment formats and a program is bound to whatever is attached — so /// the GL backend ignores this field entirely. /// /// The default is the offscreen render-target format, which the Vulkan /// backend maps to the swapchain's B8G8R8A8_UNORM /// (VulkanTextureFormatMapping.CanonicalColorAttachmentFormat) so /// that backbuffer and offscreen pipelines really do agree. That mapping is /// what made this field necessary and is why the default preserves it: slice /// V6c had to hard-code one format for every pipeline because the contract /// could not express the question, and recorded the gap rather than hiding /// it. Naming the format here is the reviewed fix, in the same shape as /// (V4c) and /// (V4d). /// public GpuTextureFormat ColorFormat { get; init; } = GpuTextureFormat.Rgba8UnormRenderTarget; /// Sample count of the passes this pipeline is used in. Must match the pass. public int SampleCount { get; init; } = 1; }