==> src/pathtrace.c <==1/* pathtrace: a deterministic path tracer (the pathtrace-* packages), one work
2 * item a task, one tile column a lane. The final image is bit for bit the same
3 * whatever renders each part -- a GB10, an RTX 3060, an Apple GPU, a CPU lane
4 * (wasm) or the CPU reference (mesh-ref) -- because every float op is
5 * correctly rounded, unfused and FTZ (the cluster float mode), the random
6 * numbers are a hash of (seed, pixel, sample, dimension), and a pixel's samples
7 * are summed in a fixed order.
8 *
9 * A node is `warps` warps: the scene goes into the node's shared memory, every
10 * thread renders pixels thread, thread + 32 warps, .. of the tile's pass (each
11 * pixel's samples in order from +0, as pixel_pass: the bits do not depend on
12 * who renders it) into shared memory, and after a block barrier warp 0 -- one
13 * column a lane -- does the rest: digests, payload, fold, tone map. Ops that
14 * render nothing run on warp 0 alone.
15 *
16 * A work item is (tile, pass), w = pass x tiles + tile: it computes the pass
17 * sums P_p of its tile from +0, then folds them into the tile's accumulators in
18 * pass order (claim: M_PASSES p -> p | FOLDING by CAS, waiting bounded for pass
19 * p - 1), so a tile's accumulator is always ((0 + P0) + P1) + .., however its
20 * items were scheduled. pathtrace (policy job): OP_JOB item job_base + arg; a
21 * fleet job: OP_PASS, the pass sums only into the payload slot (no fold, no
22 * wait); pathtrace-live (policy periodic, arg = view_u32:V_NEXT): OP_TASK item
23 * V_NEXT + index, the whole image refining pass by pass.
24 *
25 * Ops: OP_INIT, OP_TASK, OP_JOB, OP_REQ (a scene's raw words: validated,
26 * loaded, restart), OP_RESTART, OP_TONEMAP arg (re-tone every tile from its
27 * accumulators), OP_DEFAULT, OP_PASS, OP_SET.
28 * Ported from alpha-rust-mesh programs/pathtrace/src/lib.rs. */
29#include "mesh/rt.h"
30#include "pathtrace/default_scene.h"
31#include "pathtrace/digest.h"
32#include "pathtrace/scene.h"
33#include "pathtrace/view.h"
34
35/* The renderer with the device's own f32 operations (pathtrace-core's
36 * `impl Real for f32`): mesh-ptx-link makes every one .ftz on the GPU, and the
37 * simulator runs them in flush-to-zero mode on the CPU. */
38#define PT_SUFFIX _dev
39#define PT_API static inline __attribute__((always_inline))
40#define PT_ADD(a, b) ((a) + (b))
41#define PT_SUB(a, b) ((a) - (b))
42#define PT_MUL(a, b) ((a) * (b))
43#define PT_DIV(a, b) ((a) / (b))
44#define PT_NEG(a) (-(a))
45#define PT_LT(a, b) ((a) < (b))
46#define PT_GT(a, b) ((a) > (b))
47#define PT_LE(a, b) ((a) <= (b))
48#define PT_GE(a, b) ((a) >= (b))
49#include "pathtrace/instantiate.h"
50
51#define PTP_EXEC PT_EXEC_CUDA
52
53/* Each node's copy of the scene, in device memory: the renderer reads it
54 * thousands of times a sample, every lane the same word (one cached broadcast
55 * load), where a copy per lane (local memory) or the view (host memory) is
56 * many times slower. Scratch, rewritten by every task. */
57static uint32_t ptp_scenes[MESH_PROG_MAX_NODES][PT_SCENE_MAX_WORDS];
58
59/* The node's shared memory (manifest shared_bytes): the scene, then the tile's pass sums (r g b a pixel, row-major). */
60#define PTP_SH_SCENE 0u
61#define PTP_SH_PART (4u * PT_SCENE_MAX_WORDS)
62#define PTP_SH_BYTES (PTP_SH_PART + 12u * PT_TILE_PIX)
63
64static uint32_t ptp_clamp_words(uint32_t w) { return w < PT_HEADER_WORDS ? PT_HEADER_WORDS : w > PT_SCENE_MAX_WORDS ? PT_SCENE_MAX_WORDS : w; }
65
66/* Block-collective: the view's scene into the node's shared memory
67 * (block-strided), validated (at most max_tiles; every thread the same answer):
68 * its words, or NULL when the view holds no valid scene. */
69static const uint32_t *ptp_scene_shared(const mesh_ctx *ctx, mesh_view v, uint32_t max_tiles, uint32_t *words_out) {
70 uint32_t words = ptp_clamp_words(mesh_view_u32(v, PT_V_SCENE + 4 * PT_H_WORDS));
71 mesh_shared sh;
72 if (!mesh_ctx_shared(ctx, PTP_SH_SCENE, PT_SCENE_MAX_WORDS, 4, &sh)) return NULL;
73 MESH_CTX_FOR_STRIDED(ctx, words, i) mesh_shared_set_u32(sh, i, mesh_view_u32(v, PT_V_SCENE + 4 * i));
74 mesh_ctx_block_barrier(ctx);
75 const uint32_t *sc = mesh_shared_ptr_u32(sh);
76 *words_out = words;
77 return pt_validate_in(sc, words, max_tiles) == 0 ? sc : NULL;
78}
79
80/* Block-collective: pass p of tile t, every pixel's samples in order from +0
81 * (pixel_pass), pixel i = row x 32 + column by thread i mod threads, into the
82 * node's shared pass sums; complete once it returns. */
83static bool ptp_render_pass(const mesh_ctx *ctx, const uint32_t *sc, const pt_layout *l, uint32_t t, uint32_t p, mesh_shared *part) {
84 if (!mesh_ctx_shared(ctx, PTP_SH_PART, 3 * PT_TILE_PIX, 4, part)) return false;
85 uint32_t x0, y0;
86 pt_tile_origin(sc, t, &x0, &y0);
87 MESH_CTX_FOR_STRIDED(ctx, PT_TILE_PIX, i) {
88 pt_v s = pt_pixel_pass_dev(sc, l, x0 + i % PT_TILE_W, y0 + i / PT_TILE_W, p);
89 mesh_shared_set_unchecked_f32(*part, 3 * i, s.x);
90 mesh_shared_set_unchecked_f32(*part, 3 * i + 1, s.y);
91 mesh_shared_set_unchecked_f32(*part, 3 * i + 2, s.z);
92 }
93 mesh_ctx_block_barrier(ctx);
94 return true;
95}
96
97/* Column cx of the shared pass sums (warp 0: lane = column) and its digest. */
98static uint64_t ptp_shared_col(mesh_shared part, uint32_t cx, pt_v out[PT_TILE_H]) {
99 for (uint32_t row = 0; row < PT_TILE_H; row++) {
100 uint32_t i = 3 * (row * PT_TILE_W + cx);
101 out[row] = (pt_v){mesh_shared_get_f32(part, i), mesh_shared_get_f32(part, i + 1), mesh_shared_get_f32(part, i + 2)};
102 }
103 return pt_col_digest_dev(out);
104}
105
106/* Words 0..n of src (the view at off, or the request window) into this node's
107 * scene copy; the copy, once every lane's words are in. */
108static const uint32_t *ptp_copy_words(const mesh_ctx *ctx, mesh_view src, uint32_t off, uint32_t n) {
109 uint32_t node = mesh_ctx_node(ctx) < MESH_PROG_MAX_NODES - 1 ? mesh_ctx_node(ctx) : MESH_PROG_MAX_NODES - 1;
110 uint32_t *row = ptp_scenes[node];
111 for (uint32_t i = mesh_ctx_lane(ctx); i < n; i += 32) row[i] = mesh_view_u32(src, off + 4 * i);
112 mesh_rt_warp_sync();
113 return row;
114}
115
116/* The view's scene into this node's copy (lane-strided loads), validated (at
117 * most max_tiles): the copy's words, or NULL (nothing rendered). */
118static const uint32_t *ptp_scene(const mesh_ctx *ctx, mesh_view v, uint32_t max_tiles) {
119 uint32_t words = ptp_clamp_words(mesh_view_u32(v, PT_V_SCENE + 4 * PT_H_WORDS));
120 const uint32_t *sc = ptp_copy_words(ctx, v, PT_V_SCENE, words);
121 return pt_validate_in(sc, words, max_tiles) == 0 ? sc : NULL;
122}
123
124/* This lane's column of the pass sums into the item's payload slot (ticket %
125 * 64), row-major r g b, before the task returns. */
126static void ptp_put_payload(const mesh_ctx *ctx, mesh_view v, const pt_v part[PT_TILE_H]) {
127 uint32_t base = pt_view_payload(mesh_ctx_ticket(ctx) % MESH_PROG_RESULTS) + 12 * mesh_ctx_lane(ctx);
128 for (uint32_t row = 0; row < PT_TILE_H; row++) {
129 uint32_t o = base + 12 * PT_TILE_W * row;
130 mesh_view_set_f32(v, o, part[row].x);
131 mesh_view_set_f32(v, o + 4, part[row].y);
132 mesh_view_set_f32(v, o + 8, part[row].z);
133 }
134}
135
136/* Writes the scene's header copies and zeroes the progress (a new image). */
137static void ptp_restart(const mesh_ctx *ctx, mesh_view v, const uint32_t *sc) {
138 uint32_t l = mesh_ctx_lane(ctx), tiles = pt_tiles(sc);
139 for (uint32_t i = l; i < PT_MAX_TILES * PT_META / 4; i += 32) mesh_view_set_u32(v, PT_V_META + 4 * i, 0);
140 /* A view without tiles (pathtrace-cpu's) has no pixels to clear. */
141 for (uint32_t i = l; mesh_view_len(v) >= PT_VIEW_BYTES && i < tiles * PT_TILE_PIX; i += 32) mesh_view_set_u32(v, PT_V_RGB + 4 * i, 0);
142 if (mesh_ctx_leader(ctx)) {
143 const uint32_t kv[10][2] = {{PT_V_NEXT, 0}, {PT_V_DONE, 0}, {PT_V_TILES, tiles}, {PT_V_WIDTH, sc[PT_H_WIDTH]}, {PT_V_HEIGHT, sc[PT_H_HEIGHT]},
144 {PT_V_SPP, sc[PT_H_SPP]}, {PT_V_PASSES, sc[PT_H_PASSES]}, {PT_V_LAST_TILE, 0}, {PT_V_LAST_SYSTEM, 0}, {PT_V_LAST_EXEC, 0}};
145 for (int i = 0; i < 10; i++) mesh_view_set_u32(v, kv[i][0], kv[i][1]);
146 mesh_view_set_u32(v, PT_V_GEN, mesh_view_u32(v, PT_V_GEN) + 1);
147 mesh_view_set_u32(v, PT_V_MAGIC, PT_VIEW_MAGIC);
148 }
149}
150
151static void ptp_load_default(const mesh_ctx *ctx, mesh_view v) {
152 for (uint32_t i = mesh_ctx_lane(ctx); i < PT_DEFAULT_SCENE_WORDS; i += 32) mesh_view_set_u32(v, PT_V_SCENE + 4 * i, pt_default_scene[i]);
153 ptp_restart(ctx, v, pt_default_scene);
154}
155
156static void ptp_init(const mesh_ctx *ctx) {
157 mesh_view v = mesh_ctx_view(ctx);
158 if (mesh_ctx_leader(ctx)) {
159 mesh_view_set_u32(v, PT_V_TONEMAP, PT_TONEMAP_ACES);
160 mesh_view_set_u32(v, PT_V_SCENE_ERR, 0);
161 }
162 ptp_load_default(ctx, v);
163}
164
165static uint32_t ptp_acc_at(uint32_t t, uint32_t cx) { return pt_view_acc(t) + 12 * cx; }
166
167static void ptp_load_col(mesh_view v, uint32_t t, uint32_t cx, pt_v acc[PT_TILE_H]) {
168 uint32_t base = ptp_acc_at(t, cx);
169 for (uint32_t row = 0; row < PT_TILE_H; row++) {
170 uint32_t o = base + 12 * PT_TILE_W * row;
171 acc[row] = (pt_v){mesh_view_f32(v, o), mesh_view_f32(v, o + 4), mesh_view_f32(v, o + 8)};
172 }
173}
174
175static void ptp_store_rgb(mesh_view v, uint32_t t, uint32_t cx, const uint32_t rgb[PT_TILE_H]) {
176 for (uint32_t row = 0; row < PT_TILE_H; row++) mesh_view_set_u32(v, pt_view_rgb(t) + 4 * (PT_TILE_W * row + cx), rgb[row]);
177}
178
179/* The 32 lanes' column digests folded in column order (pathtrace-core fold), on every lane. */
180static uint64_t ptp_fold(uint64_t h) {
181 uint32_t lo = (uint32_t)h, hi = (uint32_t)(h >> 32);
182 uint64_t cols[32];
183 for (uint32_t j = 0; j < 32; j++) cols[j] = (uint64_t)mesh_rt_shfl_idx(lo, j) | (uint64_t)mesh_rt_shfl_idx(hi, j) << 32;
184 return pt_fold(cols);
185}
186
187/* How long an item waits for its tile's previous pass to be folded (a lease
188 * runs items in order, so a wait is short: a node still on pass p - 1). */
189#define PTP_WAIT_NS UINT64_C(2000000000)
190#define PTP_CLAIMED 0u
191#define PTP_ALREADY 1u
192#define PTP_TIMEOUT 2u
193
194/* Lane 0 claims tile t's pass p for folding (M_PASSES: p -> p | FOLDING),
195 * waiting bounded for passes 0..p; the outcome on every lane. */
196static uint32_t ptp_claim(const mesh_ctx *ctx, mesh_view v, uint32_t t, uint32_t p) {
197 uint32_t out = PTP_TIMEOUT;
198 mesh_view w;
199 if (mesh_ctx_leader(ctx) && mesh_view_sub(v, pt_view_meta(t) + PT_M_PASSES, 4, &w)) {
200 uint64_t t0 = mesh_rt_gtimer();
201 for (;;) {
202 uint32_t cur = mesh_rt_ld_acquire(mesh_view_ptr(w));
203 if (cur == p && mesh_rt_atom_cas(mesh_view_ptr(w), p, p | PT_FOLDING) == p) { out = PTP_CLAIMED; break; }
204 if ((cur & ~PT_FOLDING) > p || ((cur & PT_FOLDING) == 0 && cur > p)) { out = PTP_ALREADY; break; }
205 if (mesh_rt_gtimer() - t0 > PTP_WAIT_NS) break;
206 mesh_rt_nanosleep(2000);
207 }
208 }
209 out = mesh_rt_shfl_idx(out, 0);
210 /* Lane 0's acquire, then the warp barrier: every lane reads the accumulators after it. */
211 mesh_rt_warp_sync();
212 return out;
213}
214
215static void ptp_bump(mesh_view v, uint32_t off) {
216 mesh_view w;
217 if (mesh_view_sub(v, off, 4, &w)) mesh_rt_atom_add(mesh_view_ptr(w), 1);
218}
219
220/* Work item (tile t, pass p): the pass sums from +0 (lane = column), then, in
221 * pass order, folded into the tile's accumulators and tone mapped. False on a
222 * timeout; else *dp_out the pass sums' digest and *da_out the accumulator
223 * digest after the fold (0 when the pass was folded already -- a re-run). */
224static bool ptp_item(const mesh_ctx *ctx, mesh_view v, const uint32_t *sc, mesh_shared part_sh, uint32_t t, uint32_t p, uint32_t tm,
225 uint64_t t0, bool pay, uint64_t *dp_out, uint64_t *da_out) {
226 uint32_t cx = mesh_ctx_lane(ctx);
227 pt_v part[PT_TILE_H];
228 uint64_t dp = ptp_fold(ptp_shared_col(part_sh, cx, part));
229 if (pay) ptp_put_payload(ctx, v, part);
230 uint32_t c = ptp_claim(ctx, v, t, p);
231 if (c == PTP_ALREADY) { *dp_out = dp; *da_out = 0; return true; }
232 if (c != PTP_CLAIMED) return false;
233 pt_v acc[PT_TILE_H];
234 if (p > 0) ptp_load_col(v, t, cx, acc);
235 else
236 for (uint32_t r = 0; r < PT_TILE_H; r++) acc[r] = (pt_v){0.0f, 0.0f, 0.0f};
237 uint32_t rgb[PT_TILE_H];
238 uint64_t ha, hr;
239 pt_fold_column_dev(sc, p + 1, tm, acc, part, rgb, &ha, &hr);
240 uint32_t base = ptp_acc_at(t, cx);
241 for (uint32_t row = 0; row < PT_TILE_H; row++) {
242 uint32_t o = base + 12 * PT_TILE_W * row;
243 mesh_view_set_f32(v, o, acc[row].x);
244 mesh_view_set_f32(v, o + 4, acc[row].y);
245 mesh_view_set_f32(v, o + 8, acc[row].z);
246 }
247 ptp_store_rgb(v, t, cx, rgb);
248 uint64_t da = ptp_fold(ha), dr = ptp_fold(hr);
249 /* Every lane's words before lane 0 releases the pass. */
250 mesh_rt_fence_acq_rel();
251 mesh_rt_warp_sync();
252 if (mesh_ctx_leader(ctx)) {
253 uint32_t m = pt_view_meta(t);
254 uint32_t us = (uint32_t)((mesh_rt_gtimer() - t0) / 1000);
255 uint32_t exec = PTP_EXEC | mesh_ctx_node(ctx) << 8;
256 const uint32_t ws[7] = {mesh_ctx_system(ctx), exec, us, (uint32_t)da, (uint32_t)(da >> 32), (uint32_t)dr, (uint32_t)(dr >> 32)};
257 for (uint32_t i = 0; i < 7; i++) mesh_view_set_u32(v, m + 4 + 4 * i, ws[i]);
258 mesh_view_set_u32(v, PT_V_LAST_TILE, t);
259 mesh_view_set_u32(v, PT_V_LAST_SYSTEM, mesh_ctx_system(ctx));
260 mesh_view_set_u32(v, PT_V_LAST_EXEC, exec);
261 mesh_rt_fence_acq_rel();
262 mesh_view w;
263 if (mesh_view_sub(v, m + PT_M_PASSES, 4, &w)) mesh_rt_st_release(mesh_view_ptr(w), p + 1);
264 ptp_bump(v, PT_V_ITEMS);
265 mesh_ctx_count(ctx, 0, 1);
266 mesh_ctx_count(ctx, 2, 1);
267 if (p + 1 == sc[PT_H_PASSES]) {
268 mesh_ctx_count(ctx, 1, 1);
269 ptp_bump(v, PT_V_DONE);
270 }
271 }
272 *dp_out = dp;
273 *da_out = da;
274 return true;
275}
276
277static void ptp_job_record(const mesh_ctx *ctx, mesh_view v, uint32_t t, uint32_t p, uint64_t dp, uint64_t da, uint64_t t0) {
278 uint32_t rec = PT_V_JOB_OUT + PT_JOB_REC * (mesh_ctx_ticket(ctx) % MESH_PROG_RESULTS);
279 uint32_t us = (uint32_t)((mesh_rt_gtimer() - t0) / 1000);
280 const uint32_t x[8] = {t, p, (uint32_t)dp, (uint32_t)(dp >> 32), (uint32_t)da, (uint32_t)(da >> 32), us, PTP_EXEC | mesh_ctx_node(ctx) << 8};
281 for (uint32_t i = 0; i < 8; i++) mesh_view_set_u32(v, rec + 4 * i, x[i]);
282}
283
284/* OP_TASK, OP_JOB, OP_PASS on the whole node: the scene and the pass sums in
285 * shared memory (every thread), then warp 0's column work. */
286static uint32_t ptp_render(const mesh_ctx *ctx, mesh_view v, uint32_t op, uint32_t arg) {
287 bool job = op == MESH_OP_JOB || op == PT_OP_PASS;
288 uint32_t fail = job ? MESH_R_JOB_FAIL : MESH_R_BAD_OP;
289 if (PTP_SH_BYTES > mesh_ctx_shared_len(ctx)) return fail;
290 uint32_t words;
291 const uint32_t *sc = ptp_scene_shared(ctx, v, op == PT_OP_PASS ? PT_FLEET_MAX_TILES : PT_MAX_TILES, &words);
292 if (!sc) return fail;
293 pt_layout l = pt_layout_of(sc);
294 uint32_t tiles = pt_tiles(sc), passes = sc[PT_H_PASSES];
295 uint64_t t0 = mesh_rt_gtimer();
296 uint32_t total = passes != 0 && tiles > UINT32_MAX / passes ? UINT32_MAX : tiles * passes;
297 /* The item, as every thread sees it (uniform: the view's words before any write of this task). */
298 uint64_t w = op == MESH_OP_TASK ? (uint64_t)arg : mesh_view_u64(v, PT_V_JOB_BASE) + arg;
299 if (w >= total) return job ? MESH_R_JOB_FAIL : MESH_R_NONE;
300 uint32_t t = (uint32_t)w % tiles, p = (uint32_t)w / tiles;
301 mesh_shared part;
302 if (!ptp_render_pass(ctx, sc, &l, t, p, &part)) return fail;
303 if (mesh_ctx_warp(ctx) != 0) return MESH_R_NONE;
304 uint32_t tm = mesh_view_u32(v, PT_V_TONEMAP);
305 if (op == PT_OP_PASS) {
306 /* A fleet job's item: the pass sums only, into the payload slot; no fold, no wait. */
307 pt_v col[PT_TILE_H];
308 uint64_t dp = ptp_fold(ptp_shared_col(part, mesh_ctx_lane(ctx), col));
309 ptp_put_payload(ctx, v, col);
310 if (mesh_ctx_leader(ctx)) {
311 ptp_job_record(ctx, v, t, p, dp, 0, t0);
312 ptp_bump(v, PT_V_ITEMS);
313 mesh_ctx_count(ctx, 0, 1);
314 }
315 return (uint32_t)dp & 0x7fffffffu;
316 }
317 if (op == MESH_OP_JOB) {
318 /* Item w = pass x tiles + tile: a lease's consecutive items are distinct
319 * tiles, and pass p - 1 of a tile ran `tiles` items earlier. */
320 uint64_t dp, da;
321 if (!ptp_item(ctx, v, sc, part, t, p, tm, t0, true, &dp, &da)) return MESH_R_JOB_FAIL;
322 if (mesh_ctx_leader(ctx)) ptp_job_record(ctx, v, t, p, dp, da, t0);
323 /* The pass sums' digest: the same wherever and however often the item runs. */
324 return (uint32_t)dp & 0x7fffffffu;
325 }
326 /* OP_TASK: pathtrace-live's work item arg (batches run in order, so the wait is nil). */
327 uint32_t gen = mesh_view_u32(v, PT_V_GEN);
328 uint64_t dp, da;
329 bool done = ptp_item(ctx, v, sc, part, t, p, tm, t0, false, &dp, &da);
330 if (mesh_ctx_leader(ctx) && mesh_view_u32(v, PT_V_GEN) == gen) ptp_bump(v, PT_V_NEXT);
331 return done ? arg / tiles + 1 : MESH_R_BAD_OP;
332}
333
334static uint32_t ptp_dec(mesh_view resp, uint32_t len, uint32_t x) {
335 uint8_t b[10];
336 int i = 10;
337 do {
338 b[--i] = (uint8_t)('0' + x % 10);
339 x /= 10;
340 } while (x != 0);
341 for (; i < 10; i++) mesh_view_set_u8(resp, len++, b[i]);
342 return len;
343}
344static uint32_t ptp_str(mesh_view resp, uint32_t len, const char *s) {
345 for (; *s; s++) mesh_view_set_u8(resp, len++, (uint8_t)*s);
346 return len;
347}
348
349/* OP_REQ: the request window holds a scene's words (little-endian): loaded if
350 * valid (then a restart); the response is one text line. */
351static uint32_t ptp_request(const mesh_ctx *ctx, mesh_view v) {
352 mesh_view req = mesh_ctx_request(ctx);
353 uint32_t n = mesh_view_len(req) / 4;
354 uint32_t m = n < PT_SCENE_MAX_WORDS ? n : PT_SCENE_MAX_WORDS;
355 const uint32_t *sc = ptp_copy_words(ctx, req, 0, m);
356 uint32_t err;
357 if (mesh_view_len(req) % 4 != 0 || n > PT_SCENE_MAX_WORDS) err = 1;
358 else if ((err = pt_validate(sc, m)) != 0) {}
359 else if (sc[PT_H_WORDS] != n) err = 3;
360 if (err == 0) {
361 for (uint32_t i = mesh_ctx_lane(ctx); i < n; i += 32) mesh_view_set_u32(v, PT_V_SCENE + 4 * i, sc[i]);
362 ptp_restart(ctx, v, sc);
363 }
364 mesh_view resp = mesh_ctx_response(ctx);
365 uint32_t len = 0;
366 if (mesh_ctx_leader(ctx)) {
367 mesh_view_set_u32(v, PT_V_SCENE_ERR, err);
368 if (err == 0) {
369 len = ptp_str(resp, len, "ok scene gen ");
370 len = ptp_dec(resp, len, mesh_view_u32(v, PT_V_GEN));
371 len = ptp_str(resp, len, " tiles ");
372 len = ptp_dec(resp, len, pt_tiles(sc));
373 } else {
374 len = ptp_str(resp, len, "err scene ");
375 len = ptp_dec(resp, len, err);
376 }
377 len = ptp_str(resp, len, "\n");
378 }
379 mesh_ctx_respond(ctx, mesh_rt_shfl_idx(len, 0));
380 return err == 0 ? MESH_R_NONE : MESH_R_BAD_OP;
381}
382
383static uint32_t ptp_task(const mesh_ctx *ctx, uint32_t op, uint32_t arg) {
384 mesh_view v = mesh_ctx_view(ctx);
385 /* A view without tiles (FLEET_VIEW_BYTES: pathtrace-cpu's) runs the fleet ops only. */
386 bool fleet_op = op == MESH_OP_INIT || op == PT_OP_DEFAULT || op == PT_OP_SET || op == PT_OP_PASS;
387 if (mesh_view_len(v) < PT_FLEET_VIEW_BYTES || (mesh_view_len(v) < PT_VIEW_BYTES && !fleet_op)) return MESH_R_BAD_OP;
388 /* The render ops use the whole node; the rest run on warp 0 (thread 0's result is the task's). */
389 if (op == MESH_OP_TASK || op == MESH_OP_JOB || op == PT_OP_PASS) return ptp_render(ctx, v, op, arg);
390 if (mesh_ctx_warp(ctx) != 0) return MESH_R_NONE;
391 switch (op) {
392 case PT_OP_TONEMAP: break;
393 case MESH_OP_INIT: ptp_init(ctx); return MESH_R_NONE;
394 case PT_OP_DEFAULT: ptp_load_default(ctx, v); return MESH_R_NONE;
395 case MESH_OP_REQ: return ptp_request(ctx, v);
396 case PT_OP_RESTART: {
397 const uint32_t *sc = ptp_scene(ctx, v, PT_MAX_TILES);
398 if (!sc) return MESH_R_BAD_OP;
399 ptp_restart(ctx, v, sc);
400 return MESH_R_NONE;
401 }
402 case PT_OP_SET: {
403 /* A job's setup: one scene word (no restart; OP_PASS validates the scene it renders). */
404 bool settable = false;
405 for (int i = 0; i < PT_SETTABLE_COUNT; i++) settable |= pt_settable[i] == (size_t)(arg >> 24);
406 if (!settable) return MESH_R_BAD_OP;
407 if (mesh_ctx_leader(ctx)) mesh_view_set_u32(v, PT_V_SCENE + 4 * (arg >> 24), arg & 0x00ffffffu);
408 return MESH_R_NONE;
409 }
410 default: return MESH_R_BAD_OP;
411 }
412 /* OP_TONEMAP: re-tone every tile from its accumulators (warp 0). */
413 const uint32_t *sc = ptp_scene(ctx, v, PT_MAX_TILES);
414 if (!sc) return MESH_R_BAD_OP;
415 uint32_t tiles = pt_tiles(sc);
416 if (arg >= PT_TONEMAPS) return MESH_R_BAD_OP;
417 if (mesh_ctx_leader(ctx)) mesh_view_set_u32(v, PT_V_TONEMAP, arg);
418 for (uint32_t t = 0; t < tiles; t++) {
419 uint32_t m = pt_view_meta(t);
420 uint32_t done = mesh_view_u32(v, m + PT_M_PASSES) & ~PT_FOLDING;
421 if (done == 0) continue;
422 uint32_t cx = mesh_ctx_lane(ctx);
423 pt_v acc[PT_TILE_H];
424 uint32_t rgb[PT_TILE_H];
425 uint64_t ha, hr;
426 ptp_load_col(v, t, cx, acc);
427 pt_retone_dev(sc, done, arg, acc, rgb, &ha, &hr);
428 ptp_store_rgb(v, t, cx, rgb);
429 hr = ptp_fold(hr);
430 if (mesh_ctx_leader(ctx)) {
431 mesh_view_set_u32(v, m + PT_M_RGB, (uint32_t)hr);
432 mesh_view_set_u32(v, m + PT_M_RGB + 4, (uint32_t)(hr >> 32));
433 }
434 }
435 if (mesh_ctx_leader(ctx)) mesh_ctx_count(ctx, 3, 1);
436 return MESH_R_NONE;
437}
438
439MESH_PROGRAM(ptp_init, ptp_task);