1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
//! Wasm-safe, thread-free "trace these pixels x samples" tracer over [`FrameScene`].
//!
//! The shared core both the desktop's threaded `renderer::gpu::hybrid::cpu_trace_range` and a
//! browser's per-Worker CPU tracer call.
//!
//! No threads, no [`std::time::Instant`], no filesystem access: every function here can
//! run inside a wasm32 Web Worker as well as a native thread.
//!
//! # Determinism and partitioning
//!
//! A pixel's sample stream is a pure function of `(pixel, sample_num)` alone (see
//! [`trace_pixels_interleaved`]'s own doc), so ANY partition of a frame's pixels across
//! however many callers -- one thread, one Worker, any `first_pixel`/`stride` choice --
//! produces per-pixel bit-identical summed radiance, as long as every pixel is covered
//! by exactly one partition. [`scatter_interleaved`] adds a partition's results back
//! into a full-frame buffer using the same `first_pixel`/`stride` convention, so summing
//! several partitions' scatters reproduces a single unpartitioned trace exactly.
use Vec3;
use crate::;
use FrameScene;
/// One `(pixel, sample_num)` sample, traced through the real
/// [`trace_spectral_ray_with_finish_soa`] -- never a reimplementation of the estimator,
/// only of the per-sample seed/jitter construction around it.
///
/// Takes the batch's `plane_soa` arena (built once by the caller) rather than
/// rebuilding it from `scene.planes` on every one of the many calls a batch makes.
///
/// `rot` is `pixel`'s [`PixelRotations`], computed once per pixel by the caller: it is a
/// pure function of `pixel` alone, so passing the precomputed value to
/// [`sample_draws`] yields the same draws as recomputing it for every sample.
/// Traces one interleaved partition of the frame's pixels and returns one SUMMED
/// [`Vec3`] per owned pixel.
///
/// Sample indices `[sample_offset, sample_offset + spp)` are traced for pixels
/// `first_pixel, first_pixel + stride, first_pixel + 2 * stride, …` (stopping once a
/// pixel index would reach `scene.width * scene.height`), in that same ascending
/// order. A non-finite sample is dropped but still counted, like the GPU reduction it
/// merges with (see [`add_finite_sample`]).
///
/// Because a pixel's sample stream depends only on `(pixel, sample_num)` -- never on
/// `first_pixel`, `stride`, or which caller happens to own that pixel -- any partition
/// of a frame's pixels into disjoint interleaved sets, traced independently and
/// recombined with [`scatter_interleaved`], reproduces the exact same per-pixel sums a
/// single unpartitioned call over the whole frame would produce. That is what lets a
/// desktop thread pool and a browser's Web Worker pool call this same function with
/// different `first_pixel`/`stride` splits and still agree bit-for-bit with each other
/// and with a single-threaded reference trace.
/// [`trace_pixels_interleaved`] for a stone with tools.
///
/// `scene.planes` stay the polyhedron and `tools` are subtracted from it; `plane_soa` is
/// the arena built from `scene.planes` alone. `FrameScene` carries no tools field of its
/// own because it is constructed by struct literal in the desktop, worker and web
/// crates, so the tools ride beside it here instead; the `fluorescence` of the material
/// rides beside the tools the same way. With empty `tools` and an empty `fluorescence`
/// the sums are [`trace_pixels_interleaved`]'s, bit for bit. A fluorescent frame, or a stone
/// with more tools than the GPU kernels trace, never reaches the GPU (see
/// `gpu_backend::scene_routes_to_gpu_with_tools`), so this CPU path is the one that traces it;
/// a concave stone within the limit is also traced by the GPU
/// (`GpuBackend::try_accumulate_geom`), with this function as its reference.
/// Adds one partition's `sums` back into the full-frame buffer `dst`.
///
/// `sums` must be in the same `first_pixel, first_pixel + stride, …` order
/// [`trace_pixels_interleaved`] produced them in -- this scatters them into `dst` at
/// those same pixel indices. ADDS rather than overwrites, matching every other
/// accumulation buffer in this crate (see `renderer::gpu::hybrid`'s "Merge convention"
/// doc): summing several partitions' scatters into the same zero-initialised buffer
/// reproduces a single unpartitioned trace's sums exactly.
///
/// # Panics
///
/// Panics if `sums` has more entries than `dst` has pixels starting at `first_pixel`
/// and stepping by `stride` -- i.e. if it was not produced by a
/// [`trace_pixels_interleaved`] call against a `dst`-shaped frame with the same
/// `first_pixel`/`stride`.