Skip to main content

concinnity_core/render/metal/
uniforms.rs

1//! repr(C) uniform structs only the Metal frame encoder and its passes bind.
2//! Each layout must match the corresponding struct in an `.metal` shader under
3//! `metal/shaders/`.
4//!
5//! Blocks whose shader counterpart is a single-source `.slang` declaration are
6//! declared once for every backend in `crate::render::uniforms`; what is left here is
7//! what only this backend binds. Their layouts are checked by `shader_layout` in
8//! concinnity-device, which reads the expected offsets out of slangc's
9//! reflection per target. The hand-written asserts below stay alongside that
10//! check for the blocks this backend alone binds.
11
12/// Per-draw-call model matrix pushed at buffer(2) before each draw.
13#[derive(Copy, Clone, bytemuck::NoUninit)]
14#[repr(C)]
15pub struct ModelUniforms {
16    /// Model-to-world matrix (column-major).
17    pub model: [[f32; 4]; 4],
18}
19
20/// Per-frame inputs to the GPU-driven cull, pushed inline at buffer(2) of the
21/// encoder both cull dispatches share. Layout (208 bytes) must match the
22/// `METAL_BINDINGS` `CullParams` in `cull.slang`, which `shader_layout` reflects;
23/// the encode kernel reads none of it and takes [`EncodeParams`] instead.
24#[derive(Copy, Clone, bytemuck::NoUninit)]
25#[repr(C)]
26pub struct CullUniforms {
27    /// The six frustum planes (left/right/bottom/top/near/far), each
28    /// `[normal.x, normal.y, normal.z, d]`, extracted CPU-side and already
29    /// normalised so the kernel's plane test matches `gfx::frustum` exactly.
30    pub planes: [[f32; 4]; 6],
31    /// World-space camera position in `xyz`; `w` is unused. A whole lane
32    /// because the shader-side `float3` is 16 bytes on Metal.
33    pub cam_pos: [f32; 4],
34    /// Previous frame's un-jittered view-projection. The kernel projects each
35    /// AABB through this so the NDC depths line up with the Hi-Z values the
36    /// previous frame's main pass produced.
37    pub prev_view_proj: [[f32; 4]; 4],
38    /// Hi-Z mip-0 dimensions in texels. `[1.0, 1.0]` when no Hi-Z is bound.
39    pub hiz_size: [f32; 2],
40    /// Mip levels in the bound Hi-Z texture.
41    pub hiz_mip_count: u32,
42    /// `0` skips the Hi-Z occlusion test (first frame / after a resize, before
43    /// a valid pyramid exists); `1` runs it.
44    pub hiz_enabled: u32,
45    /// Number of valid `DrawObject` records; kernel threads past it return.
46    pub object_count: u32,
47    /// Unified-cull index where the folded skinned records begin (= static +
48    /// instances). Equals `object_count` when no skinned mesh is folded. Read
49    /// by the host when it fills [`EncodeParams`], not by the decision kernel.
50    pub skinned_base: u32,
51    /// Status-slot base for the GPU-driven shadow cull: cascade `c` writes its
52    /// outcomes at `cascade_base + tid` (= `c * object_count`). The main cull
53    /// leaves it 0.
54    pub cascade_base: u32,
55    /// How many shader-bucket ICBs this dispatch's argument buffer carries.
56    /// The main cull passes the world's bucket count; single-stream dispatches
57    /// (shadow, mirror) pass 1.
58    pub bucket_count: u32,
59}
60
61/// Parameters of the Metal ICB encode kernel, pushed inline at buffer(7) after
62/// the decision dispatch. Layout (32 bytes) must match `EncodeParams` in
63/// `cull_encode.metal`.
64#[derive(Copy, Clone, bytemuck::NoUninit)]
65#[repr(C)]
66pub struct EncodeParams {
67    /// Records per region: the slot grid is `region_count * object_count`.
68    pub object_count: u32,
69    /// Regions in the target ICB: 1 for the main, phase-2 and mirror culls,
70    /// one per cascade for the shadow cull.
71    pub region_count: u32,
72    /// Bit `r` set means region `r` is encoded this dispatch; a clear bit
73    /// leaves that region's commands untouched.
74    pub region_mask: u32,
75    /// Record index where the folded skinned tail begins; those records draw
76    /// through the skinned index buffer.
77    pub skinned_base: u32,
78    /// How many shader-bucket ICBs the argument buffer carries.
79    pub bucket_count: u32,
80    /// The `cull_status` value that encodes a draw (`CullStatus::DRAWN` for
81    /// every dispatch but phase 2, which encodes `CullStatus::REDRAW`).
82    pub draw_status: u32,
83    /// Padding to a 16-byte multiple.
84    pub _pad: [u32; 2],
85}
86
87/// Per-frame uniforms for the TAA velocity pre-pass at buffer(0). Layout must
88/// match `VelUniforms` in `pipeline.rs`'s velocity MSL.
89#[derive(Copy, Clone)]
90#[repr(C)]
91pub struct VelocityUniforms {
92    /// Jittered current view-projection: drives the rasterised position so
93    /// the pre-pass covers exactly the same pixels as the main pass.
94    pub jittered_vp: [[f32; 4]; 4],
95    /// Un-jittered current view-projection: keeps the stored motion vector
96    /// free of the sub-pixel projection jitter.
97    pub cur_vp: [[f32; 4]; 4],
98    /// Un-jittered previous-frame view-projection.
99    pub prev_vp: [[f32; 4]; 4],
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105    use core::mem::{offset_of, size_of};
106
107    #[test]
108    fn cull_uniforms_layout_matches_the_shader() {
109        // `CullParams` under METAL_BINDINGS in cull.slang: float4 planes[6], a
110        // float4 camera lane, a float4x4 at 112, a float2 and six uints.
111        assert_eq!(size_of::<CullUniforms>(), 208);
112        assert_eq!(offset_of!(CullUniforms, planes), 0);
113        assert_eq!(offset_of!(CullUniforms, cam_pos), 96);
114        assert_eq!(offset_of!(CullUniforms, prev_view_proj), 112);
115        assert_eq!(offset_of!(CullUniforms, hiz_size), 176);
116        assert_eq!(offset_of!(CullUniforms, hiz_mip_count), 184);
117        assert_eq!(offset_of!(CullUniforms, hiz_enabled), 188);
118        assert_eq!(offset_of!(CullUniforms, object_count), 192);
119        assert_eq!(offset_of!(CullUniforms, skinned_base), 196);
120        assert_eq!(offset_of!(CullUniforms, cascade_base), 200);
121        assert_eq!(offset_of!(CullUniforms, bucket_count), 204);
122        assert_eq!(size_of::<CullUniforms>() % 16, 0);
123    }
124
125    #[test]
126    fn encode_params_layout_matches_msl() {
127        // `EncodeParams` in cull_encode.metal: eight tightly packed uints.
128        assert_eq!(size_of::<EncodeParams>(), 32);
129        assert_eq!(offset_of!(EncodeParams, object_count), 0);
130        assert_eq!(offset_of!(EncodeParams, region_count), 4);
131        assert_eq!(offset_of!(EncodeParams, region_mask), 8);
132        assert_eq!(offset_of!(EncodeParams, skinned_base), 12);
133        assert_eq!(offset_of!(EncodeParams, bucket_count), 16);
134        assert_eq!(offset_of!(EncodeParams, draw_status), 20);
135        assert_eq!(offset_of!(EncodeParams, _pad), 24);
136    }
137
138    #[test]
139    fn velocity_uniforms_layout_matches_msl() {
140        // MSL `VelUniforms` in velocity.metal: three float4x4.
141        assert_eq!(size_of::<VelocityUniforms>(), 192);
142        assert_eq!(offset_of!(VelocityUniforms, jittered_vp), 0);
143        assert_eq!(offset_of!(VelocityUniforms, cur_vp), 64);
144        assert_eq!(offset_of!(VelocityUniforms, prev_vp), 128);
145    }
146}