concinnity_core/build/environment_map/
mod.rs1pub mod bake;
40pub mod schedule;
41
42use crate::decode::{ByteReader, checked_product};
43use alloc::format;
44use alloc::string::String;
45use alloc::vec::Vec;
46
47pub use bake::{
48 CubeBake, DEFAULT_IRRADIANCE_PHI_SAMPLES, DEFAULT_IRRADIANCE_THETA_SAMPLES, FaceRow,
49 compute_irradiance, compute_prefilter, face_rows, prefilter_mip0, prefilter_roughness,
50};
51pub use schedule::{RowScheduler, Serial};
52
53pub(crate) const ENVMAP_PAYLOAD_MAGIC: u32 = u32::from_le_bytes(*b"ENVM");
54pub(crate) const ENVMAP_FORMAT_RGBA32F: u32 = 0;
55pub(crate) const ENVMAP_PAYLOAD_HEADER_BYTES: usize = 24;
56
57pub fn max_mip_count(face_size: u32) -> u32 {
61 let mut mips = 0u32;
62 let mut s = face_size;
63 while s >= 4 {
64 mips += 1;
65 s /= 2;
66 }
67 mips
68}
69
70pub fn serialise_payload(
74 irradiance_face: u32,
75 prefilter_face: u32,
76 prefilter_mips: u32,
77 irradiance: &[Vec<f32>; 6],
78 prefilter: &[[Vec<f32>; 6]],
79) -> Vec<u8> {
80 debug_assert_eq!(prefilter.len(), prefilter_mips as usize);
81 let mut total = ENVMAP_PAYLOAD_HEADER_BYTES + 6 * (irradiance_face as usize).pow(2) * 4 * 4;
82 for mip in 0..prefilter_mips {
83 let s = (prefilter_face >> mip) as usize;
84 total += 6 * s * s * 4 * 4;
85 }
86 let mut buf = Vec::with_capacity(total);
87 buf.extend_from_slice(&ENVMAP_PAYLOAD_MAGIC.to_le_bytes());
88 buf.extend_from_slice(&ENVMAP_FORMAT_RGBA32F.to_le_bytes());
89 buf.extend_from_slice(&irradiance_face.to_le_bytes());
90 buf.extend_from_slice(&prefilter_face.to_le_bytes());
91 buf.extend_from_slice(&prefilter_mips.to_le_bytes());
92 buf.extend_from_slice(&0u32.to_le_bytes()); for face in irradiance {
94 buf.extend_from_slice(bytemuck::cast_slice::<f32, u8>(face));
95 }
96 for mip in prefilter {
97 for face in mip {
98 buf.extend_from_slice(bytemuck::cast_slice::<f32, u8>(face));
99 }
100 }
101 buf
102}
103
104#[derive(Debug)]
107pub struct EnvMapView<'a> {
108 pub irradiance_face: u32,
110 pub prefilter_face: u32,
112 pub irradiance_bytes: &'a [u8],
114 pub prefilter_mip_bytes: Vec<&'a [u8]>,
116}
117
118fn cube_face_bytes(label: &str, edge: u32) -> Result<usize, String> {
124 checked_product(label, &[6, edge as usize, edge as usize, 4, 4])
125}
126
127pub fn deserialise(bytes: &[u8]) -> Result<EnvMapView<'_>, String> {
129 let mut r = ByteReader::open_payload(
130 bytes,
131 ENVMAP_PAYLOAD_MAGIC,
132 ENVMAP_PAYLOAD_HEADER_BYTES,
133 "envmap",
134 )?;
135 let format = r.u32()?;
136 if format != ENVMAP_FORMAT_RGBA32F {
137 return Err(format!("envmap format_id {} unsupported", format));
138 }
139 let irradiance_face = r.u32()?;
140 let prefilter_face = r.u32()?;
141 let prefilter_mips = r.u32()?;
142 if prefilter_mips == 0 || prefilter_mips > 12 {
143 return Err(format!(
144 "envmap prefilter_mips {} out of range",
145 prefilter_mips
146 ));
147 }
148 r.seek(ENVMAP_PAYLOAD_HEADER_BYTES)?;
151 let irradiance_bytes = r.take(cube_face_bytes("envmap irradiance", irradiance_face)?)?;
152 let mut prefilter_mip_bytes = Vec::with_capacity(prefilter_mips as usize);
153 for mip in 0..prefilter_mips {
154 let edge = prefilter_face >> mip;
155 let mip_size = cube_face_bytes("envmap prefilter mip", edge)?;
156 prefilter_mip_bytes.push(r.take(mip_size)?);
157 }
158 Ok(EnvMapView {
159 irradiance_face,
160 prefilter_face,
161 irradiance_bytes,
162 prefilter_mip_bytes,
163 })
164}
165
166#[cfg(test)]
169mod tests {
170 use super::*;
171
172 fn solid_cube(face_size: u32, color: [f32; 3]) -> [Vec<f32>; 6] {
173 let f = face_size as usize;
174 core::array::from_fn(|_| {
175 let mut face = Vec::with_capacity(f * f * 4);
176 for _ in 0..f * f {
177 face.extend_from_slice(&[color[0], color[1], color[2], 1.0]);
178 }
179 face
180 })
181 }
182
183 #[test]
184 fn payload_round_trip() {
185 let source = solid_cube(8, [0.6, 0.4, 0.2]);
186 let irr = compute_irradiance(&source, 8, 4, 32, 8);
187 let prefilter = compute_prefilter(&source, 8, 2, 16, 0.0, false);
188 let blob = serialise_payload(4, 8, 2, &irr, &prefilter);
189 let view = deserialise(&blob).expect("deserialise");
190 assert_eq!(view.irradiance_face, 4);
191 assert_eq!(view.prefilter_face, 8);
192 assert_eq!(view.prefilter_mip_bytes.len(), 2);
193 assert_eq!(view.irradiance_bytes.len(), 6 * 4 * 4 * 4 * 4);
194 assert_eq!(view.prefilter_mip_bytes[0].len(), 6 * 8 * 8 * 4 * 4);
195 assert_eq!(view.prefilter_mip_bytes[1].len(), 6 * 4 * 4 * 4 * 4);
196 }
197
198 #[test]
199 fn max_mip_count_clamps_at_four_pixels() {
200 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); }
205
206 fn header_only(irradiance_face: u32, prefilter_face: u32, prefilter_mips: u32) -> Vec<u8> {
208 let mut buf = ENVMAP_PAYLOAD_MAGIC.to_le_bytes().to_vec();
209 buf.extend_from_slice(&ENVMAP_FORMAT_RGBA32F.to_le_bytes());
210 buf.extend_from_slice(&irradiance_face.to_le_bytes());
211 buf.extend_from_slice(&prefilter_face.to_le_bytes());
212 buf.extend_from_slice(&prefilter_mips.to_le_bytes());
213 buf.extend_from_slice(&0u32.to_le_bytes());
214 buf
215 }
216
217 #[test]
218 fn rejects_a_payload_shorter_than_the_header() {
219 let full = header_only(4, 8, 2);
220 for len in 0..ENVMAP_PAYLOAD_HEADER_BYTES {
221 assert!(deserialise(&full[..len]).is_err(), "len {} decoded", len);
222 }
223 }
224
225 #[test]
226 fn rejects_a_bad_magic() {
227 let mut bytes = header_only(4, 8, 2);
228 bytes[..4].copy_from_slice(&0xDEAD_BEEFu32.to_le_bytes());
229 assert!(deserialise(&bytes).is_err());
230 }
231
232 #[test]
233 fn rejects_a_payload_truncated_in_the_irradiance_section() {
234 let mut bytes = header_only(4, 8, 2);
235 bytes.extend(core::iter::repeat_n(0u8, 6 * 4 * 4 * 4 * 4 - 8));
236 let err = deserialise(&bytes).unwrap_err();
237 assert!(err.contains("unexpected end"), "{}", err);
238 }
239
240 #[test]
241 fn rejects_a_payload_truncated_in_a_prefilter_mip() {
242 let mut bytes = header_only(4, 8, 2);
243 bytes.extend(core::iter::repeat_n(0u8, 6 * 4 * 4 * 4 * 4));
244 bytes.extend(core::iter::repeat_n(0u8, 6 * 8 * 8 * 4 * 4));
245 let err = deserialise(&bytes).unwrap_err();
247 assert!(err.contains("unexpected end"), "{}", err);
248 }
249
250 #[test]
254 fn rejects_an_irradiance_face_that_overflows_its_footprint() {
255 let bytes = header_only(u32::MAX, 8, 2);
256 let err = deserialise(&bytes).unwrap_err();
257 assert!(err.contains("overflow"), "{}", err);
258 }
259
260 #[test]
261 fn rejects_a_prefilter_face_that_overflows_its_footprint() {
262 let mut bytes = header_only(4, u32::MAX, 2);
263 bytes.extend(core::iter::repeat_n(0u8, 6 * 4 * 4 * 4 * 4));
264 let err = deserialise(&bytes).unwrap_err();
265 assert!(err.contains("overflow"), "{}", err);
266 }
267
268 #[test]
269 fn rejects_out_of_range_mip_counts() {
270 assert!(deserialise(&header_only(4, 8, 0)).is_err());
271 assert!(deserialise(&header_only(4, 8, 13)).is_err());
272 }
273}