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}