Skip to main content

pfx_bake/plate/
codec.rs

1const MANTISSA: i32 = 9;
2const BIAS: i32 = 15;
3const MAX_EXPONENT: i32 = 31;
4pub const RGB9E5_MAX: f32 = 65408.0;
5pub const DEPTH_STEPS: f32 = 65535.0;
6pub const SNORM16: f32 = 32767.0;
7
8fn floor_log2(value: f32) -> i32 {
9    let bits = value.to_bits();
10    let exponent = ((bits >> 23) & 0xff) as i32;
11    if exponent == 0 { -127 } else { exponent - 127 }
12}
13
14pub fn rgb9e5(color: [f32; 3]) -> u32 {
15    let clamped = color.map(|value| {
16        if value.is_finite() {
17            value.clamp(0.0, RGB9E5_MAX)
18        } else if value == f32::INFINITY {
19            RGB9E5_MAX
20        } else {
21            0.0
22        }
23    });
24    let largest = clamped[0].max(clamped[1]).max(clamped[2]);
25    if largest <= 0.0 {
26        return 0;
27    }
28    let mut exponent = floor_log2(largest).max(-BIAS - 1) + 1 + BIAS;
29    let mut scale = 2f32.powi(exponent - BIAS - MANTISSA);
30    if (largest / scale + 0.5).floor() as i32 == 1 << MANTISSA {
31        scale *= 2.0;
32        exponent += 1;
33    }
34    let exponent = exponent.min(MAX_EXPONENT) as u32;
35    let [r, g, b] = clamped.map(|value| ((value / scale + 0.5).floor() as u32).min(511));
36    r | (g << 9) | (b << 18) | (exponent << 27)
37}
38
39pub fn from_rgb9e5(packed: u32) -> [f32; 3] {
40    let exponent = (packed >> 27) as i32;
41    let scale = 2f32.powi(exponent - BIAS - MANTISSA);
42    [packed & 511, (packed >> 9) & 511, (packed >> 18) & 511].map(|m| m as f32 * scale)
43}
44
45fn sign(value: f32) -> f32 {
46    if value >= 0.0 { 1.0 } else { -1.0 }
47}
48
49pub fn oct16(normal: [f32; 3]) -> u32 {
50    let length = normal.iter().map(|v| v * v).sum::<f32>().sqrt();
51    if length <= 1e-12 || !length.is_finite() {
52        return 0;
53    }
54    let n = normal.map(|v| v / length);
55    let l1 = n[0].abs() + n[1].abs() + n[2].abs();
56    let mut x = n[0] / l1;
57    let mut y = n[1] / l1;
58    if n[2] < 0.0 {
59        let (ox, oy) = (x, y);
60        x = (1.0 - oy.abs()) * sign(ox);
61        y = (1.0 - ox.abs()) * sign(oy);
62    }
63    let pack = |v: f32| ((v.clamp(-1.0, 1.0) * SNORM16).round() as i16) as u16 as u32;
64    pack(x) | (pack(y) << 16)
65}
66
67pub fn from_oct16(packed: u32) -> [f32; 3] {
68    let unpack = |bits: u32| (bits as u16 as i16) as f32 / SNORM16;
69    let x = unpack(packed & 0xffff).max(-1.0);
70    let y = unpack(packed >> 16).max(-1.0);
71    let z = 1.0 - x.abs() - y.abs();
72    let (x, y) = if z < 0.0 {
73        ((1.0 - y.abs()) * sign(x), (1.0 - x.abs()) * sign(y))
74    } else {
75        (x, y)
76    };
77    let length = (x * x + y * y + z * z).sqrt();
78    [x / length, y / length, z / length]
79}
80
81pub fn inverse_depth(depth: f32, near: f32) -> u16 {
82    if depth <= 0.0 || !depth.is_finite() {
83        return 0;
84    }
85    (near / depth * DEPTH_STEPS).round().clamp(1.0, DEPTH_STEPS) as u16
86}
87
88pub fn from_inverse_depth(code: u16, near: f32) -> Option<f32> {
89    (code != 0).then(|| near * DEPTH_STEPS / f32::from(code))
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95
96    fn relative(a: f32, b: f32) -> f32 {
97        (a - b).abs() / b.abs().max(1e-30)
98    }
99
100    #[test]
101    fn rgb9e5_round_trips_within_its_mantissa_and_clamps_the_rest() {
102        assert_eq!(rgb9e5([0.0; 3]), 0);
103        assert_eq!(from_rgb9e5(0), [0.0; 3]);
104        assert_eq!(from_rgb9e5(rgb9e5([1.0, 0.5, 0.25])), [1.0, 0.5, 0.25]);
105        assert_eq!(from_rgb9e5(rgb9e5([-1.0, f32::NAN, 2.0]))[..2], [0.0, 0.0]);
106        assert_eq!(from_rgb9e5(rgb9e5([1e9, 0.0, 0.0]))[0], RGB9E5_MAX);
107        let mut state = 0x9e3779b9u32;
108        for _ in 0..20000 {
109            let mut next = || {
110                state ^= state << 13;
111                state ^= state >> 17;
112                state ^= state << 5;
113                (state as f32 / u32::MAX as f32) * 2f32.powi((state % 24) as i32 - 12)
114            };
115            let color = [next(), next(), next()];
116            let back = from_rgb9e5(rgb9e5(color));
117            let largest = color[0].max(color[1]).max(color[2]);
118            for k in 0..3 {
119                assert!(
120                    (back[k] - color[k]).abs() <= largest / 256.0 + 1e-9,
121                    "{color:?} came back as {back:?}"
122                );
123            }
124            assert!(relative(back[0].max(back[1]).max(back[2]), largest) <= 1.0 / 512.0 + 1e-6);
125        }
126    }
127
128    #[test]
129    fn rgb9e5_matches_the_gpu_layout() {
130        let one = rgb9e5([1.0, 0.0, 0.0]);
131        assert_eq!(one & 511, 256);
132        assert_eq!(one >> 27, 16);
133        let blue = rgb9e5([0.0, 0.0, 1.0]);
134        assert_eq!((blue >> 18) & 511, 256);
135    }
136
137    #[test]
138    fn oct16_round_trips_every_direction_within_a_hundredth_of_a_degree() {
139        let mut worst: f32 = 0.0;
140        for i in 0..64 {
141            for j in 0..128 {
142                let theta = std::f32::consts::PI * (i as f32 + 0.5) / 64.0;
143                let phi = std::f32::consts::TAU * j as f32 / 128.0;
144                let n = [
145                    theta.sin() * phi.cos(),
146                    theta.cos(),
147                    theta.sin() * phi.sin(),
148                ];
149                let back = from_oct16(oct16(n));
150                let a = n.map(f64::from);
151                let b = back.map(f64::from);
152                let cross = [
153                    a[1] * b[2] - a[2] * b[1],
154                    a[2] * b[0] - a[0] * b[2],
155                    a[0] * b[1] - a[1] * b[0],
156                ];
157                let sin = cross.iter().map(|v| v * v).sum::<f64>().sqrt();
158                worst = worst.max(sin.asin().to_degrees() as f32);
159            }
160        }
161        assert!(worst < 0.01, "{worst}");
162        assert_eq!(from_oct16(oct16([0.0, 0.0, -1.0])), [0.0, 0.0, -1.0]);
163        assert_eq!(oct16([0.0; 3]), 0);
164    }
165
166    #[test]
167    fn inverse_depth_keeps_its_step_and_marks_the_sky() {
168        assert_eq!(inverse_depth(0.0, 0.5), 0);
169        assert_eq!(inverse_depth(f32::INFINITY, 0.5), 0);
170        assert_eq!(from_inverse_depth(0, 0.5), None);
171        assert_eq!(inverse_depth(0.5, 0.5), 65535);
172        assert_eq!(inverse_depth(0.4, 0.5), 65535);
173        for depth in [0.5f32, 1.0, 3.0, 10.0] {
174            let back = from_inverse_depth(inverse_depth(depth, 0.5), 0.5).unwrap();
175            let step = depth * depth / (0.5 * DEPTH_STEPS);
176            assert!((back - depth).abs() <= step * 0.5 + 1e-6, "{depth} {back}");
177        }
178    }
179}