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