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}