concinnity_core/render/vulkan/uniforms.rs
1//! repr(C) uniform / push-constant structs only the Vulkan frame encoders bind
2//! (std140 / std430 / push-constant layouts). Each is mirrored field-for-field
3//! in a `.glsl`/`.vert`/`.frag`/`.comp` shader under `vulkan/shaders/`, or in
4//! the one `.slang` block Vulkan alone declares.
5//!
6//! Blocks whose shader counterpart is a single-source `.slang` declaration are
7//! declared once for every backend in `crate::render::uniforms`; what is left here is
8//! what only this backend binds. Their layouts are checked by `shader_layout` in
9//! concinnity-device, which reads the expected offsets out of slangc's
10//! reflection per target. The hand-written asserts below are for the families
11//! whose shaders are still per backend -- the cull kernel, the skinning and
12//! morph kernels, the raymarch SDF templates, the legacy per-draw main and
13//! velocity passes, and Metal's water.
14
15use crate::components::sdf_volume::SDF_PARAMS_LEN;
16
17/// Byte size of the auto-exposure push-constant range. Pins the struct size to
18/// what the pipeline layout declares.
19pub const AUTO_EXPOSURE_PUSH_BYTES: u32 = 16;
20
21/// The main-pass push constant (std430): the model matrix, roughness/metallic
22/// with two pads, then tint and emissive vec3s each followed by a pad (112 B
23/// total). Matches ModelUniforms(64) + MaterialUniforms(48) packed together.
24#[derive(Copy, Clone)]
25#[repr(C)]
26pub struct MainPush {
27 /// Model matrix, column-major.
28 pub model: [[f32; 4]; 4],
29 /// Perceptual roughness in `[0, 1]`.
30 pub roughness: f32,
31 /// Metalness in `[0, 1]`.
32 pub metallic: f32,
33 /// Padding so the field layout matches the shader-side struct.
34 pub _mpad0: f32,
35 /// Padding so the field layout matches the shader-side struct.
36 pub _mpad1: f32,
37 /// Linear RGB base-colour tint.
38 pub tint: [f32; 3],
39 /// Padding so the field layout matches the shader-side struct.
40 pub _mpad2: f32,
41 /// Linear RGB emissive radiance.
42 pub emissive: [f32; 3],
43 /// Padding so the field layout matches the shader-side struct.
44 pub _mpad3: f32,
45}
46
47/// The GPU-cull push constant (cull.comp, std430): six already-normalised frustum
48/// planes (xyz = normal, w = d), the camera position sharing its 16-byte slot with
49/// the build-time object count, then the shader-bucket routing (120 B total).
50#[derive(Copy, Clone)]
51#[repr(C)]
52pub struct CullParams {
53 /// Frustum planes, each `(normal.xyz, d)`.
54 pub planes: [[f32; 4]; 6],
55 /// World-space camera position.
56 pub cam_pos: [f32; 3],
57 /// Draw records the kernel iterates.
58 pub object_count: u32,
59 /// Shader-bucket command regions in the indirect buffer. The kernel writes
60 /// every record's slot in all `bucket_count` regions (a draw in the record's
61 /// own bucket, a no-op everywhere else); region `b` starts at command
62 /// `b * bucket_stride`. `bucket_count = 1` degenerates to a single region.
63 pub bucket_count: u32,
64 /// `u32` slots one bucket occupies in the output list.
65 pub bucket_stride: u32,
66}
67
68/// Cull-side Hi-Z uniforms (cull.comp, std140, 80 bytes): the previous frame's
69/// un-jittered view-projection, the Hi-Z mip-0 dimensions, the mip count, and an
70/// enable flag. Mirrors the Metal / DirectX CullUniforms tail.
71#[derive(Copy, Clone)]
72#[repr(C)]
73pub struct CullHizParams {
74 /// Previous frame's un-jittered view-projection. Projects each AABB into the
75 /// depth space the Hi-Z pyramid was reduced from (`M * v`).
76 pub prev_view_proj: [[f32; 4]; 4],
77 /// Hi-Z mip-0 dimensions (in texels).
78 pub hiz_size: [f32; 2],
79 /// How many mip levels live in the bound texture.
80 pub hiz_mip_count: u32,
81 /// 0 skips the Hi-Z test entirely (first frame / after a resize, before a
82 /// valid pyramid exists).
83 pub hiz_enabled: u32,
84}
85
86/// The G-buffer pre-pass push constant (shared GLSL `PushBlock`): cur_model then
87/// prev_model (two column-major mat4) then roughness, plus a trailing pad to
88/// 16-byte alignment. The motion vector reads cur/prev model; the fragment reads
89/// roughness.
90#[derive(Copy, Clone)]
91#[repr(C)]
92pub struct GbModelPush {
93 /// This frame's model matrix, column-major.
94 pub cur_model: [[f32; 4]; 4],
95 /// The previous frame's model matrix, for velocity.
96 pub prev_model: [[f32; 4]; 4],
97 /// Perceptual roughness in `[0, 1]`.
98 pub roughness: f32,
99 /// Padding so the field layout matches the shader-side struct.
100 pub _pad: [f32; 3],
101}
102
103/// Byte size of the G-buffer pre-pass push-constant range (cur_model 64 +
104/// prev_model 64 + roughness 4 + 12 pad). Pins the struct size.
105pub const GBUFFER_PREPASS_PUSH_BYTES: u32 = 144;
106
107/// The raymarch pass per-frame view UBO (raymarch_helpers.glsl
108/// `RaymarchViewBlock`, std140, 160 bytes). Mirrors the DirectX / Metal
109/// `RaymarchView`.
110#[derive(Copy, Clone)]
111#[repr(C)]
112pub struct RaymarchView {
113 /// View-projection matrix, column-major.
114 pub vp: [[f32; 4]; 4],
115 /// Inverse view-projection matrix, column-major.
116 pub inv_vp: [[f32; 4]; 4],
117 /// World-space camera position.
118 pub cam_pos: [f32; 3],
119 /// Padding so the field layout matches the shader-side struct.
120 pub _pad0: f32,
121 /// Render-target size in pixels.
122 pub viewport: [f32; 2],
123 /// Seconds since the world started.
124 pub time: f32,
125 /// IBL cubemap mip count; 0 when there is no IBL.
126 pub prefilter_mip_count: f32,
127}
128
129/// The per-volume SDF raymarch UBO (`SdfVolumeBlock`, std140, 176 bytes). Mirrors
130/// the DirectX `RaymarchVolumeUniforms`.
131#[derive(Copy, Clone)]
132#[repr(C)]
133pub struct RaymarchVolumeUniforms {
134 /// World-space centre.
135 pub centre: [f32; 3],
136 /// Padding so the field layout matches the shader-side struct.
137 pub _pad0: f32,
138 /// Half-extents from the centre, in world units.
139 pub extent: [f32; 3],
140 /// Padding so the field layout matches the shader-side struct.
141 pub _pad1: f32,
142 /// Cone-tracing footprint growth per unit of march distance.
143 pub cone_ratio: f32,
144 /// Furthest world distance the march travels.
145 pub max_distance: f32,
146 /// Ray-march step cap.
147 pub max_steps: i32,
148 /// Non-zero when the volume samples the shadow maps.
149 pub receive_shadows: i32,
150 /// The volume's authored SDF parameters.
151 pub params: [f32; SDF_PARAMS_LEN],
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157 use core::mem::{offset_of, size_of};
158
159 // MainPush must match the `PushBlock` push constant in the main-pass shaders
160 // (std430): the model matrix, roughness/metallic with two pads, then tint and
161 // emissive vec3s each followed by a pad (112 B total).
162 #[test]
163 fn main_push_layout_matches_glsl() {
164 assert_eq!(size_of::<MainPush>(), 112);
165 assert_eq!(offset_of!(MainPush, model), 0);
166 assert_eq!(offset_of!(MainPush, roughness), 64);
167 assert_eq!(offset_of!(MainPush, metallic), 68);
168 assert_eq!(offset_of!(MainPush, _mpad0), 72);
169 assert_eq!(offset_of!(MainPush, _mpad1), 76);
170 assert_eq!(offset_of!(MainPush, tint), 80);
171 assert_eq!(offset_of!(MainPush, _mpad2), 92);
172 assert_eq!(offset_of!(MainPush, emissive), 96);
173 assert_eq!(offset_of!(MainPush, _mpad3), 108);
174 }
175
176 // CullParams must match the `CullParams` push-constant block in cull.comp
177 // (std430): six frustum planes, then cam_pos sharing its 16-byte slot with
178 // object_count (112 B total).
179 #[test]
180 fn cull_params_layout_matches_glsl() {
181 assert_eq!(size_of::<CullParams>(), 120);
182 assert_eq!(offset_of!(CullParams, planes), 0);
183 assert_eq!(offset_of!(CullParams, cam_pos), 96);
184 assert_eq!(offset_of!(CullParams, object_count), 108);
185 assert_eq!(offset_of!(CullParams, bucket_count), 112);
186 assert_eq!(offset_of!(CullParams, bucket_stride), 116);
187 }
188
189 // std140 CullHizParams in cull.comp: mat4 (64) + vec2 (8, 8-aligned) + two
190 // uints. Total 80 bytes, tightly packed after the mat4.
191 #[test]
192 fn cull_hiz_params_layout_matches_glsl() {
193 assert_eq!(size_of::<CullHizParams>(), 80);
194 assert_eq!(offset_of!(CullHizParams, prev_view_proj), 0);
195 assert_eq!(offset_of!(CullHizParams, hiz_size), 64);
196 assert_eq!(offset_of!(CullHizParams, hiz_mip_count), 72);
197 assert_eq!(offset_of!(CullHizParams, hiz_enabled), 76);
198 }
199
200 // The GLSL `RaymarchViewBlock` std140 layout is 160 bytes.
201 #[test]
202 fn raymarch_view_layout_matches_glsl() {
203 assert_eq!(size_of::<RaymarchView>(), 160);
204 assert_eq!(offset_of!(RaymarchView, vp), 0);
205 assert_eq!(offset_of!(RaymarchView, inv_vp), 64);
206 assert_eq!(offset_of!(RaymarchView, cam_pos), 128);
207 assert_eq!(offset_of!(RaymarchView, viewport), 144);
208 assert_eq!(offset_of!(RaymarchView, time), 152);
209 assert_eq!(offset_of!(RaymarchView, prefilter_mip_count), 156);
210 }
211
212 // The GLSL `SdfVolumeBlock` std140 layout is 176 bytes.
213 #[test]
214 fn sdf_volume_uniforms_layout_matches_glsl() {
215 assert_eq!(size_of::<RaymarchVolumeUniforms>(), 176);
216 assert_eq!(offset_of!(RaymarchVolumeUniforms, centre), 0);
217 assert_eq!(offset_of!(RaymarchVolumeUniforms, extent), 16);
218 assert_eq!(offset_of!(RaymarchVolumeUniforms, cone_ratio), 32);
219 assert_eq!(offset_of!(RaymarchVolumeUniforms, max_distance), 36);
220 assert_eq!(offset_of!(RaymarchVolumeUniforms, max_steps), 40);
221 assert_eq!(offset_of!(RaymarchVolumeUniforms, receive_shadows), 44);
222 assert_eq!(offset_of!(RaymarchVolumeUniforms, params), 48);
223 }
224}