concinnity_core/bake/environment_map/
mod.rs1pub mod bake;
40pub mod schedule;
41pub mod source;
42pub mod stars;
43
44use crate::decode::{ByteReader, checked_product};
45use alloc::format;
46use alloc::string::String;
47use alloc::vec::Vec;
48
49pub use bake::{
50 CubeBake, DEFAULT_IRRADIANCE_PHI_SAMPLES, DEFAULT_IRRADIANCE_THETA_SAMPLES, FaceRow,
51 compute_irradiance, compute_prefilter, face_rows, prefilter_mip0, prefilter_roughness,
52};
53pub use schedule::{RowScheduler, Serial};
54
55pub(crate) const ENVMAP_PAYLOAD_MAGIC: u32 = u32::from_le_bytes(*b"ENVM");
56pub(crate) const ENVMAP_FORMAT_RGBA32F: u32 = 0;
57pub(crate) const ENVMAP_PAYLOAD_HEADER_BYTES: usize = 24;
58
59pub fn check_sizes(map: &crate::components::EnvironmentMap) -> Result<(), String> {
65 let prefilter_face = map.prefilter_face_size;
66 if !(16..=1024).contains(&prefilter_face) || !prefilter_face.is_power_of_two() {
67 return Err(format!(
68 "EnvironmentMap prefilter_face_size {} must be a power of two in 16..=1024",
69 prefilter_face
70 ));
71 }
72 let irradiance_face = map.irradiance_face_size;
73 if !(8..=128).contains(&irradiance_face) || !irradiance_face.is_power_of_two() {
74 return Err(format!(
75 "EnvironmentMap irradiance_face_size {} must be a power of two in 8..=128",
76 irradiance_face
77 ));
78 }
79 if !map.prefilter_clamp.is_finite() || map.prefilter_clamp < 0.0 {
80 return Err(format!(
81 "EnvironmentMap prefilter_clamp {} must be a finite value >= 0 (0 disables it)",
82 map.prefilter_clamp
83 ));
84 }
85 Ok(())
86}
87
88pub const fn max_mip_count(face_size: u32) -> u32 {
92 let mut mips = 0u32;
93 let mut s = face_size;
94 while s >= 4 {
95 mips += 1;
96 s /= 2;
97 }
98 mips
99}
100
101pub fn serialise_payload(
105 irradiance_face: u32,
106 prefilter_face: u32,
107 prefilter_mips: u32,
108 irradiance: &[Vec<f32>; 6],
109 prefilter: &[[Vec<f32>; 6]],
110) -> Vec<u8> {
111 debug_assert_eq!(prefilter.len(), prefilter_mips as usize);
112 let mut total = ENVMAP_PAYLOAD_HEADER_BYTES + 6 * (irradiance_face as usize).pow(2) * 4 * 4;
113 for mip in 0..prefilter_mips {
114 let s = (prefilter_face >> mip) as usize;
115 total += 6 * s * s * 4 * 4;
116 }
117 let mut buf = Vec::with_capacity(total);
118 buf.extend_from_slice(&ENVMAP_PAYLOAD_MAGIC.to_le_bytes());
119 buf.extend_from_slice(&ENVMAP_FORMAT_RGBA32F.to_le_bytes());
120 buf.extend_from_slice(&irradiance_face.to_le_bytes());
121 buf.extend_from_slice(&prefilter_face.to_le_bytes());
122 buf.extend_from_slice(&prefilter_mips.to_le_bytes());
123 buf.extend_from_slice(&0u32.to_le_bytes()); for face in irradiance {
125 buf.extend_from_slice(bytemuck::cast_slice::<f32, u8>(face));
126 }
127 for mip in prefilter {
128 for face in mip {
129 buf.extend_from_slice(bytemuck::cast_slice::<f32, u8>(face));
130 }
131 }
132 buf
133}
134
135#[derive(Debug)]
138pub struct EnvMapView<'a> {
139 pub irradiance_face: u32,
141 pub prefilter_face: u32,
143 pub irradiance_bytes: &'a [u8],
145 pub prefilter_mip_bytes: Vec<&'a [u8]>,
147}
148
149fn cube_face_bytes(label: &str, edge: u32) -> Result<usize, String> {
155 checked_product(label, &[6, edge as usize, edge as usize, 4, 4])
156}
157
158pub fn deserialise(bytes: &[u8]) -> Result<EnvMapView<'_>, String> {
160 let mut r = ByteReader::open_payload(
161 bytes,
162 ENVMAP_PAYLOAD_MAGIC,
163 ENVMAP_PAYLOAD_HEADER_BYTES,
164 "envmap",
165 )?;
166 let format = r.u32()?;
167 if format != ENVMAP_FORMAT_RGBA32F {
168 return Err(format!("envmap format_id {} unsupported", format));
169 }
170 let irradiance_face = r.u32()?;
171 let prefilter_face = r.u32()?;
172 let prefilter_mips = r.u32()?;
173 if prefilter_mips == 0 || prefilter_mips > 12 {
174 return Err(format!(
175 "envmap prefilter_mips {} out of range",
176 prefilter_mips
177 ));
178 }
179 r.seek(ENVMAP_PAYLOAD_HEADER_BYTES)?;
182 let irradiance_bytes = r.take(cube_face_bytes("envmap irradiance", irradiance_face)?)?;
183 let mut prefilter_mip_bytes = Vec::with_capacity(prefilter_mips as usize);
184 for mip in 0..prefilter_mips {
185 let edge = prefilter_face >> mip;
186 let mip_size = cube_face_bytes("envmap prefilter mip", edge)?;
187 prefilter_mip_bytes.push(r.take(mip_size)?);
188 }
189 Ok(EnvMapView {
190 irradiance_face,
191 prefilter_face,
192 irradiance_bytes,
193 prefilter_mip_bytes,
194 })
195}
196
197#[cfg(test)]
200mod tests {
201 use super::*;
202
203 fn solid_cube(face_size: u32, color: [f32; 3]) -> [Vec<f32>; 6] {
204 let f = face_size as usize;
205 core::array::from_fn(|_| {
206 let mut face = Vec::with_capacity(f * f * 4);
207 for _ in 0..f * f {
208 face.extend_from_slice(&[color[0], color[1], color[2], 1.0]);
209 }
210 face
211 })
212 }
213
214 #[test]
215 fn payload_round_trip() {
216 let source = solid_cube(8, [0.6, 0.4, 0.2]);
217 let irr = compute_irradiance(&source, 8, 4, 32, 8);
218 let prefilter = compute_prefilter(&source, 8, 2, 16, 0.0, false);
219 let blob = serialise_payload(4, 8, 2, &irr, &prefilter);
220 let view = deserialise(&blob).expect("deserialise");
221 assert_eq!(view.irradiance_face, 4);
222 assert_eq!(view.prefilter_face, 8);
223 assert_eq!(view.prefilter_mip_bytes.len(), 2);
224 assert_eq!(view.irradiance_bytes.len(), 6 * 4 * 4 * 4 * 4);
225 assert_eq!(view.prefilter_mip_bytes[0].len(), 6 * 8 * 8 * 4 * 4);
226 assert_eq!(view.prefilter_mip_bytes[1].len(), 6 * 4 * 4 * 4 * 4);
227 }
228
229 #[test]
230 fn max_mip_count_clamps_at_four_pixels() {
231 assert_eq!(max_mip_count(256), 7); assert_eq!(max_mip_count(16), 3); assert_eq!(max_mip_count(8), 2); assert_eq!(max_mip_count(4), 1); }
236
237 fn header_only(irradiance_face: u32, prefilter_face: u32, prefilter_mips: u32) -> Vec<u8> {
239 let mut buf = ENVMAP_PAYLOAD_MAGIC.to_le_bytes().to_vec();
240 buf.extend_from_slice(&ENVMAP_FORMAT_RGBA32F.to_le_bytes());
241 buf.extend_from_slice(&irradiance_face.to_le_bytes());
242 buf.extend_from_slice(&prefilter_face.to_le_bytes());
243 buf.extend_from_slice(&prefilter_mips.to_le_bytes());
244 buf.extend_from_slice(&0u32.to_le_bytes());
245 buf
246 }
247
248 #[test]
249 fn rejects_a_payload_shorter_than_the_header() {
250 let full = header_only(4, 8, 2);
251 for len in 0..ENVMAP_PAYLOAD_HEADER_BYTES {
252 assert!(deserialise(&full[..len]).is_err(), "len {} decoded", len);
253 }
254 }
255
256 #[test]
257 fn rejects_a_bad_magic() {
258 let mut bytes = header_only(4, 8, 2);
259 bytes[..4].copy_from_slice(&0xDEAD_BEEFu32.to_le_bytes());
260 assert!(deserialise(&bytes).is_err());
261 }
262
263 #[test]
264 fn rejects_a_payload_truncated_in_the_irradiance_section() {
265 let mut bytes = header_only(4, 8, 2);
266 bytes.extend(core::iter::repeat_n(0u8, 6 * 4 * 4 * 4 * 4 - 8));
267 let err = deserialise(&bytes).unwrap_err();
268 assert!(err.contains("unexpected end"), "{}", err);
269 }
270
271 #[test]
272 fn rejects_a_payload_truncated_in_a_prefilter_mip() {
273 let mut bytes = header_only(4, 8, 2);
274 bytes.extend(core::iter::repeat_n(0u8, 6 * 4 * 4 * 4 * 4));
275 bytes.extend(core::iter::repeat_n(0u8, 6 * 8 * 8 * 4 * 4));
276 let err = deserialise(&bytes).unwrap_err();
278 assert!(err.contains("unexpected end"), "{}", err);
279 }
280
281 #[test]
285 fn rejects_an_irradiance_face_that_overflows_its_footprint() {
286 let bytes = header_only(u32::MAX, 8, 2);
287 let err = deserialise(&bytes).unwrap_err();
288 assert!(err.contains("overflow"), "{}", err);
289 }
290
291 #[test]
292 fn rejects_a_prefilter_face_that_overflows_its_footprint() {
293 let mut bytes = header_only(4, u32::MAX, 2);
294 bytes.extend(core::iter::repeat_n(0u8, 6 * 4 * 4 * 4 * 4));
295 let err = deserialise(&bytes).unwrap_err();
296 assert!(err.contains("overflow"), "{}", err);
297 }
298
299 #[test]
300 fn rejects_out_of_range_mip_counts() {
301 assert!(deserialise(&header_only(4, 8, 0)).is_err());
302 assert!(deserialise(&header_only(4, 8, 13)).is_err());
303 }
304}