"""Single source of the 3D mesh vertex-buffer binding layouts (vertex stream split, D5).
Every mesh pipeline declares its vertex input from the constants below; no pass
hand-rolls stride/attribute tables. The streams (one GPU buffer each, uploaded
by ``MeshRegistry`` from a :class:`~simvx.graphics.types.VertexStreams`):
====== ======================================== ====== =================
binding contents stride shader locations
====== ======================================== ====== =================
0 position float3 12 B 0
1 normal float3 + uv float2 20 B 1, 2
2 tangent float4 + colour unorm8x4 28 B 3, 4, 5
+ uv2 float2 (optional)
3 joints uint16x4 + weights float4 24 B 3, 4
(skinned only)
====== ======================================== ====== =================
Locations 0/1/2 are the stable ShaderMaterial user ABI. The extras stream's
locations 3/4/5 overlap the skin stream's 3/4: no pipeline consumes both today
(skinned pipelines do not read tangent/colour/uv2); a combined variant is a
future decision, not a latent bug.
Depth-only passes (shadow, point shadow, depth prepass, pick) declare
:data:`POSITION_ONLY_BINDINGS` and bind just the position buffer, so their
vertex fetch touches 12 bytes per vertex instead of 32.
"""
import vulkan as vk
from ..gpu.pipeline import VertexBinding
from ..types import EXTRAS_DTYPE, SHADING_DTYPE, SKIN_DTYPE
__all__ = [
"POSITION_BINDING",
"SHADING_BINDING",
"SHADING_UV_BINDING",
"EXTRAS_BINDING",
"SKIN_BINDING",
"MESH_BINDINGS",
"SKINNED_MESH_BINDINGS",
"POSITION_ONLY_BINDINGS",
"POSITION_UV_BINDINGS",
]
# Binding 0: position float3. Bound alone by every depth-only pass.
POSITION_BINDING: VertexBinding = (
0,
12,
((0, vk.VK_FORMAT_R32G32B32_SFLOAT, 0),), # location 0: position
)
# Binding 1: normal float3 + uv float2.
SHADING_BINDING: VertexBinding = (
1,
SHADING_DTYPE.itemsize,
(
(1, vk.VK_FORMAT_R32G32B32_SFLOAT, 0), # location 1: normal
(2, vk.VK_FORMAT_R32G32_SFLOAT, 12), # location 2: uv
),
)
# Binding 1 variant: just the UV attribute (location 2) from the shading stream,
# skipping the normal (location 1). Used by the alpha-tested (cutout) shadow
# casters (RM-G11), which need position + UV but not the normal; declaring the
# normal attribute the shader never reads trips a Vulkan "attribute not consumed"
# validation warning.
SHADING_UV_BINDING: VertexBinding = (
1,
SHADING_DTYPE.itemsize,
((2, vk.VK_FORMAT_R32G32_SFLOAT, 12),), # location 2: uv (normal at 0 is skipped)
)
# Binding 2 (optional): tangent float4 + colour unorm8x4 + uv2 float2.
# Present only when the mesh carries any of them; no built-in pipeline
# consumes these locations yet (tangent normal mapping lands in RM-A13).
EXTRAS_BINDING: VertexBinding = (
2,
EXTRAS_DTYPE.itemsize,
(
(3, vk.VK_FORMAT_R32G32B32A32_SFLOAT, 0), # location 3: tangent
(4, vk.VK_FORMAT_R8G8B8A8_UNORM, 16), # location 4: colour
(5, vk.VK_FORMAT_R32G32_SFLOAT, 20), # location 5: uv2
),
)
# Binding 3 (skinned only): joints uint16x4 + weights float4, packing unchanged.
SKIN_BINDING: VertexBinding = (
3,
SKIN_DTYPE.itemsize,
(
(3, vk.VK_FORMAT_R16G16B16A16_UINT, 0), # location 3: joints
(4, vk.VK_FORMAT_R32G32B32A32_SFLOAT, 8), # location 4: weights
),
)
# Forward mesh pipelines (opaque / double-sided / transparent), velocity,
# outline, and ShaderMaterial pipelines: locations 0/1/2 from streams 0+1.
MESH_BINDINGS: tuple[VertexBinding, ...] = (POSITION_BINDING, SHADING_BINDING)
# Skinned forward pipeline: streams 0+1 plus the skin stream at binding 3
# (binding 2 stays unbound; the layout is sparse by design).
SKINNED_MESH_BINDINGS: tuple[VertexBinding, ...] = (POSITION_BINDING, SHADING_BINDING, SKIN_BINDING)
# Depth-only passes: shadow, point shadow, depth prepass, pick.
POSITION_ONLY_BINDINGS: tuple[VertexBinding, ...] = (POSITION_BINDING,)
# Alpha-tested (cutout) shadow casters (RM-G11): position + UV, no normal.
POSITION_UV_BINDINGS: tuple[VertexBinding, ...] = (POSITION_BINDING, SHADING_UV_BINDING)