skip to content
==> 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);