Skip to main content

concinnity_core/render/uniforms/
view.rs

1//! The camera blocks: what the forward pass and the G-buffer pre-pass need to
2//! place a fragment, and the per-draw model pair the pre-pass differentiates for
3//! motion vectors.
4
5/// Per-frame view-projection uniforms, uploaded once per frame and shared across
6/// every draw in it. `view` is the standalone view matrix the vertex shader uses
7/// to compute view-space depth for cascade selection in the fragment shader.
8///
9/// The `.slang` source declares the same bytes with its own spelling
10/// (`view_mat`, and `cam_x`/`cam_y`/`cam_z` in place of
11/// `cam_pos`), which is why the two are checked as byte ranges rather than by
12/// name.
13#[derive(Copy, Clone, bytemuck::NoUninit)]
14#[repr(C)]
15pub struct ViewUniforms {
16    /// Combined view-projection matrix (column-major).
17    pub vp: [[f32; 4]; 4],
18    /// Camera view matrix (column-major). Used to compute view-space depth
19    /// in the vertex shader for shadow cascade selection.
20    pub view: [[f32; 4]; 4],
21    /// Elapsed seconds, available to shaders for animation.
22    pub elapsed: f32,
23    /// 1.0 when a screen-space / ray-traced reflection resolve composites this
24    /// frame, else 0.0. The forward fragment shader uses it to yield the sharp
25    /// specular for glossy surfaces to that resolve (whose miss-fallback samples
26    /// the same probe set), so a glossy surface does not show both the
27    /// parallax-approximate forward probe reflection and the exact resolved one.
28    pub reflections_enabled: f32,
29    /// World-space camera position (packed_float3 in the MSL contract, alignment 4).
30    pub cam_pos: [f32; 3],
31    /// Number of mip levels in the bound IBL prefilter cubemap. 0 means
32    /// "no EnvironmentMap bound": the fragment shader uses this as the IBL
33    /// enable flag and falls back to a flat ambient placeholder.
34    pub prefilter_mip_count: f32,
35    /// 1.0 while the unlit view mode is active: shade_surface returns the base
36    /// color before lighting. Occupies what was pad space, so the offsets in
37    /// the user-shader binding contract are unchanged.
38    pub shade_mode: f32,
39    /// End-padding: the shader rounds the block up to a multiple of float4x4's
40    /// 16-byte alignment, so the upload rounds explicitly to match.
41    pub _end_pad: f32,
42    /// Rows of the rotation that takes a world-space direction into the
43    /// environment cubemaps' baked frame: the inverse of the sky's current
44    /// rotation (`SkyOrientation::sample_rows`), identity when the world
45    /// declares no `SkyRotation`. Every cubemap sample helper applies it, so
46    /// the sky, the ambient fill and the glossy reflections turn together.
47    /// One `float4` per row; `w` is unused.
48    pub sky_rot: [[f32; 4]; 3],
49}
50
51/// Per-frame view inputs to the unified G-buffer pre-pass. The jittered current
52/// VP drives the rasterised position (matching the main pass); `view` takes the
53/// normal + position into view space (where SSR / SSAO / SSGI / RT work); the
54/// un-jittered cur/prev VPs derive a jitter-free motion vector. Matches `GbView`
55/// in `shaders/gbuffer_prepass.slang`. 256 bytes (four float4x4, all naturally
56/// 16-aligned, no padding).
57#[derive(Copy, Clone, bytemuck::NoUninit)]
58#[repr(C)]
59pub struct GBufferView {
60    /// View-projection with this frame's TAA jitter applied.
61    pub jittered_vp: [[f32; 4]; 4],
62    /// This frame's unjittered view-projection.
63    pub cur_vp: [[f32; 4]; 4],
64    /// The previous frame's unjittered view-projection.
65    pub prev_vp: [[f32; 4]; 4],
66    /// View matrix, column-major.
67    pub view: [[f32; 4]; 4],
68}
69
70#[cfg(test)]
71mod tests {
72    use super::*;
73    use core::mem::{offset_of, size_of};
74
75    // Every offset here is a byte position the main pass reads through.
76    // `sky_rot` lands past the block's own end padding, so nothing before it
77    // moved.
78    #[test]
79    fn view_uniforms_layout_matches_msl() {
80        assert_eq!(size_of::<ViewUniforms>(), 208);
81        assert_eq!(offset_of!(ViewUniforms, vp), 0);
82        assert_eq!(offset_of!(ViewUniforms, view), 64);
83        assert_eq!(offset_of!(ViewUniforms, elapsed), 128);
84        assert_eq!(offset_of!(ViewUniforms, reflections_enabled), 132);
85        assert_eq!(offset_of!(ViewUniforms, cam_pos), 136);
86        assert_eq!(offset_of!(ViewUniforms, prefilter_mip_count), 148);
87        assert_eq!(offset_of!(ViewUniforms, shade_mode), 152);
88        assert_eq!(offset_of!(ViewUniforms, _end_pad), 156);
89        // A float4 array is 16-byte aligned on every target.
90        assert_eq!(offset_of!(ViewUniforms, sky_rot), 160);
91        assert_eq!(size_of::<ViewUniforms>() % 16, 0);
92    }
93
94    // Four float4x4s, all naturally aligned.
95    #[test]
96    fn gbuffer_view_layout_matches_the_shader() {
97        assert_eq!(size_of::<GBufferView>(), 256);
98        assert_eq!(offset_of!(GBufferView, jittered_vp), 0);
99        assert_eq!(offset_of!(GBufferView, cur_vp), 64);
100        assert_eq!(offset_of!(GBufferView, prev_vp), 128);
101        assert_eq!(offset_of!(GBufferView, view), 192);
102    }
103}