1pub const PATTERNS: u32 = 256;
2pub const DIMENSIONS: u32 = 4;
3pub const MIN_SAMPLES: u32 = 256;
4pub const MAX_SAMPLES: u32 = 8192;
5pub const TABLE_WIDTH: u32 = 1024;
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8pub enum Sampler {
9 Pcg,
10 Sobol { samples: u32 },
11}
12
13impl Default for Sampler {
14 fn default() -> Self {
15 Sampler::Sobol {
16 samples: MIN_SAMPLES,
17 }
18 }
19}
20
21impl Sampler {
22 pub fn sequence(self) -> Option<u32> {
23 if std::env::var("PFX_TRACE_SAMPLER").is_ok_and(|value| value == "pcg") {
24 return None;
25 }
26 match self {
27 Sampler::Pcg => None,
28 Sampler::Sobol { samples } => Some(sequence_size(samples)),
29 }
30 }
31}
32
33pub fn sequence_size(samples: u32) -> u32 {
34 samples
35 .max(1)
36 .checked_next_power_of_two()
37 .unwrap_or(MAX_SAMPLES)
38 .clamp(MIN_SAMPLES, MAX_SAMPLES)
39}
40
41const XORS: [[u32; 32]; 4] = [
42 [0; 32],
43 [
44 0x00000000, 0x00000001, 0x00000001, 0x00000007, 0x00000001, 0x00000013, 0x00000015,
45 0x0000007f, 0x00000001, 0x00000103, 0x00000105, 0x0000070f, 0x00000111, 0x00001333,
46 0x00001555, 0x00007fff, 0x00000001, 0x00010003, 0x00010005, 0x0007000f, 0x00010011,
47 0x00130033, 0x00150055, 0x007f00ff, 0x00010101, 0x01030303, 0x01050505, 0x070f0f0f,
48 0x01111111, 0x13333333, 0x15555555, 0x7fffffff,
49 ],
50 [
51 0x00000000, 0x00000001, 0x00000003, 0x00000001, 0x00000005, 0x0000001f, 0x0000002b,
52 0x0000003d, 0x00000011, 0x00000133, 0x00000377, 0x00000199, 0x00000445, 0x00001ccf,
53 0x00002ddb, 0x0000366d, 0x00000101, 0x00010303, 0x00030707, 0x00010909, 0x00051515,
54 0x001f3f3f, 0x002b6b6b, 0x003dbdbd, 0x00101011, 0x01303033, 0x03707077, 0x01909099,
55 0x04515145, 0x1cf3f3cf, 0x2db6b6db, 0x36dbdb6d,
56 ],
57 [
58 0x00000000, 0x00000001, 0x00000000, 0x00000003, 0x0000000d, 0x0000000c, 0x00000005,
59 0x0000004f, 0x00000014, 0x000000e7, 0x00000329, 0x0000039c, 0x00000011, 0x00001033,
60 0x00000044, 0x000030bb, 0x0000d1cd, 0x0000c2ec, 0x00005415, 0x0004fc3f, 0x00015054,
61 0x000e5c97, 0x0032e5b9, 0x0039725c, 0x00000101, 0x01000303, 0x00000404, 0x03000b0b,
62 0x0d001d1d, 0x0c002c2c, 0x05004545, 0x4f00cfcf,
63 ],
64];
65
66fn hash_hp_uint(mut i: u32) -> u32 {
67 i ^= i >> 16;
68 i = i.wrapping_mul(0x21f0aaad);
69 i ^= i >> 15;
70 i = i.wrapping_mul(0xd35a2d97);
71 i ^= i >> 15;
72 i ^ 0xe6fe3beb
73}
74
75fn hash_hp_seeded_uint(i: u32, mut seed: u32) -> u32 {
76 seed ^= seed << 19;
77 hash_hp_uint(i ^ seed)
78}
79
80fn hash_hp_float(i: u32) -> f32 {
81 hash_hp_uint(i) as f32 * (1.0 / 4_294_967_808.0)
82}
83
84pub fn generate_4d(points: &mut [[f32; 4]], seed: u32) {
85 let size = points.len();
86 if size == 0 {
87 return;
88 }
89 let mut rng = hash_hp_seeded_uint(seed, 0x44605a73);
90 let mut next = || {
91 let value = hash_hp_float(rng);
92 rng = rng.wrapping_add(1);
93 value
94 };
95 points[0] = [next(), next(), next(), next()];
96 let mut log_n = 0;
97 let mut n = 1;
98 while n < size {
99 let strata = (n * 2) as f32;
100 let mut i = 0;
101 while i < n && n + i < size {
102 for axis in 0..4 {
103 let occupied = (points[i ^ XORS[axis][log_n] as usize][axis] * strata) as u32;
104 points[n + i][axis] = ((occupied ^ 1) as f32 + next()) / strata;
105 }
106 i += 1;
107 }
108 log_n += 1;
109 n *= 2;
110 }
111}
112
113pub fn table(sequence: u32) -> Vec<[f32; 4]> {
114 let length = sequence as usize;
115 let mut points = vec![[0.0; 4]; length * PATTERNS as usize];
116 let threads = std::thread::available_parallelism().map_or(1, usize::from);
117 let per = (PATTERNS as usize).div_ceil(threads) * length;
118 std::thread::scope(|scope| {
119 for (chunk, block) in points.chunks_mut(per).enumerate() {
120 scope.spawn(move || {
121 for (j, pattern) in block.chunks_mut(length).enumerate() {
122 generate_4d(pattern, (chunk * per / length + j) as u32);
123 }
124 });
125 }
126 });
127 points
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133
134 #[test]
135 fn the_table_is_the_same_bytes_every_time() {
136 let a = table(MIN_SAMPLES);
137 let b = table(MIN_SAMPLES);
138 assert_eq!(a.len(), (MIN_SAMPLES * PATTERNS) as usize);
139 assert!(a.iter().flatten().all(|v| (0.0..1.0).contains(v)));
140 assert_eq!(a, b);
141 let mut single = vec![[0.0; 4]; MIN_SAMPLES as usize];
142 generate_4d(&mut single, 37);
143 assert_eq!(
144 &a[37 * MIN_SAMPLES as usize..38 * MIN_SAMPLES as usize],
145 &single[..]
146 );
147 }
148
149 #[test]
150 fn every_power_of_two_prefix_is_stratified_in_each_axis() {
151 let mut points = vec![[0.0; 4]; 1024];
152 generate_4d(&mut points, 5);
153 for n in [2usize, 16, 128, 1024] {
154 for axis in 0..4 {
155 let mut seen = vec![false; n];
156 for point in &points[..n] {
157 seen[(point[axis] * n as f32) as usize] = true;
158 }
159 assert!(seen.iter().all(|s| *s), "{n} points, axis {axis}");
160 }
161 }
162 }
163
164 #[test]
165 fn the_sequence_follows_the_sample_count() {
166 assert_eq!(sequence_size(1), MIN_SAMPLES);
167 assert_eq!(sequence_size(300), 512);
168 assert_eq!(sequence_size(4096), 4096);
169 assert_eq!(sequence_size(u32::MAX), MAX_SAMPLES);
170 assert_eq!(Sampler::default().sequence(), Some(MIN_SAMPLES));
171 assert_eq!(Sampler::Pcg.sequence(), None);
172 }
173}