Skip to main content

oxide_engine/light/
uniform.rs

1//! GPU-aligned light structures for uniform/storage buffer bindings.
2
3use bytemuck::{Pod, Zeroable};
4
5use super::{AmbientLight, DirectionalLight};
6
7pub const MAX_DIRECTIONAL_LIGHTS: usize = 4;
8pub const MAX_POINT_LIGHTS: usize = 4096;
9
10#[repr(C, align(16))]
11#[derive(Copy, Clone, Debug, Pod, Zeroable)]
12pub struct GpuDirectionalLight {
13    pub direction: [f32; 4],
14    pub color_intensity: [f32; 4],
15}
16
17impl From<&DirectionalLight> for GpuDirectionalLight {
18    fn from(light: &DirectionalLight) -> Self {
19        Self {
20            direction: [light.direction.x, light.direction.y, light.direction.z, 0.0],
21            color_intensity: [light.color.x, light.color.y, light.color.z, light.intensity],
22        }
23    }
24}
25
26/// Storage-buffer-friendly point light with explicit 16-byte alignment.
27#[repr(C, align(16))]
28#[derive(Copy, Clone, Debug, Pod, Zeroable)]
29pub struct GpuPointLight {
30    pub position_radius: [f32; 4],
31    pub color_intensity: [f32; 4],
32    pub _padding: [f32; 4],
33}
34
35impl GpuPointLight {
36    pub fn from_values(
37        position: glam::Vec3,
38        color: glam::Vec3,
39        intensity: f32,
40        radius: f32,
41    ) -> Self {
42        Self {
43            position_radius: [position.x, position.y, position.z, radius],
44            color_intensity: [color.x, color.y, color.z, intensity],
45            _padding: [0.0; 4],
46        }
47    }
48}
49
50#[repr(C, align(16))]
51#[derive(Copy, Clone, Debug, Pod, Zeroable)]
52pub struct LightUniform {
53    pub ambient_color_intensity: [f32; 4],
54    pub directional_count: u32,
55    pub point_count: u32,
56    pub _padding: [u32; 2],
57    pub directional_lights: [GpuDirectionalLight; MAX_DIRECTIONAL_LIGHTS],
58}
59
60impl Default for LightUniform {
61    fn default() -> Self {
62        Self {
63            ambient_color_intensity: [0.2, 0.2, 0.2, 0.2],
64            directional_count: 0,
65            point_count: 0,
66            _padding: [0; 2],
67            directional_lights: [GpuDirectionalLight::zeroed(); MAX_DIRECTIONAL_LIGHTS],
68        }
69    }
70}
71
72impl LightUniform {
73    pub fn new(
74        ambient: Option<&AmbientLight>,
75        directional_lights: &[&DirectionalLight],
76        point_count: usize,
77    ) -> Self {
78        let mut uniform = Self::default();
79
80        if let Some(ambient) = ambient {
81            uniform.ambient_color_intensity = [
82                ambient.color.x,
83                ambient.color.y,
84                ambient.color.z,
85                ambient.intensity,
86            ];
87        }
88
89        uniform.directional_count =
90            (directional_lights.len() as u32).min(MAX_DIRECTIONAL_LIGHTS as u32);
91        for (i, light) in directional_lights
92            .iter()
93            .take(MAX_DIRECTIONAL_LIGHTS)
94            .enumerate()
95        {
96            uniform.directional_lights[i] = GpuDirectionalLight::from(*light);
97        }
98
99        uniform.point_count = (point_count as u32).min(MAX_POINT_LIGHTS as u32);
100        uniform
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    fn assert_pod<T: Pod>() {}
109
110    #[test]
111    fn storage_and_uniform_alignment_contracts() {
112        assert_pod::<GpuDirectionalLight>();
113        assert_pod::<GpuPointLight>();
114        assert_pod::<LightUniform>();
115
116        assert_eq!(std::mem::size_of::<GpuDirectionalLight>() % 16, 0);
117        assert_eq!(std::mem::size_of::<GpuPointLight>() % 16, 0);
118        assert_eq!(std::mem::size_of::<LightUniform>() % 16, 0);
119        assert_eq!(std::mem::size_of::<GpuPointLight>(), 48);
120
121        assert_eq!(std::mem::align_of::<GpuDirectionalLight>(), 16);
122        assert_eq!(std::mem::align_of::<GpuPointLight>(), 16);
123        assert_eq!(std::mem::align_of::<LightUniform>(), 16);
124    }
125}