skip to content
==> src/pathtrace.c <==
1/* pathtrace for the wasm executor (alpha-rust-mesh programs/pathtrace/wasm; programs/pathtrace has
2 * the design): a node is one CPU thread, so a task renders all 32 columns of its tile in a loop --
3 * pathtrace's tile in Z (@ajax/pathtrace-z, every f32 op flushed: the cluster float mode), the very
4 * code of the CPU reference, so a CPU lane's tile is bit for bit a GPU's. The same ops, view
5 * layout and results as the GPU images. */
6#include "mesh/abi/ring.h"
7#include "mesh/rt_wasm.h"
8#include "pathtrace/default_scene.h"
9#include "pathtrace/scene.h"
10#include "pathtrace/view.h"
11#include "pathtrace/z.h"
12
13#define EXEC PT_EXEC_WASM
14#define PIX PT_TILE_PIX
15
16static uint32_t scene_buf[PT_SCENE_MAX_WORDS];
17static pt_v part[PIX], acc[PIX];
18static uint32_t words_buf[3 * PIX];
19static uint32_t rgb[PIX];
20
21static uint32_t clamp(uint32_t x, uint32_t lo, uint32_t hi) { return x < lo ? lo : x > hi ? hi : x; }
22
23/* The view's scene (validated, at most max_tiles) into scene_buf: its words, or 0. */
24static uint32_t scene(mesh_view v, uint32_t max_tiles) {
25    uint32_t words = clamp(mesh_view_u32(v, PT_V_SCENE + 4 * PT_H_WORDS), PT_HEADER_WORDS, PT_SCENE_MAX_WORDS);
26    mesh_view_read_u32s(v, PT_V_SCENE, scene_buf, words);
27    return pt_validate_in(scene_buf, words, max_tiles) == 0 ? words : 0;
28}
29
30static void to_words(const pt_v *a) {
31    for (uint32_t i = 0; i < PIX; i++) {
32        words_buf[3 * i] = mesh_ftz_bits(a[i].x);
33        words_buf[3 * i + 1] = mesh_ftz_bits(a[i].y);
34        words_buf[3 * i + 2] = mesh_ftz_bits(a[i].z);
35    }
36}
37
38/* The item's pass sums into its payload slot (ticket % 64), row-major r g b. */
39static void put_payload(const mesh_ctx *ctx, mesh_view v) {
40    to_words(part);
41    mesh_view_write_u32s(v, pt_view_payload(mesh_ctx_ticket(ctx) % MESH_PROG_RESULTS), words_buf, 3 * PIX);
42}
43
44static const uint32_t zeros[PT_MAX_TILES * PT_META / 4];
45
46static void restart(mesh_view v, const uint32_t *sc) {
47    uint32_t tiles = pt_tiles(sc);
48    mesh_view_write_u32s(v, PT_V_META, zeros, PT_MAX_TILES * PT_META / 4);
49    if (mesh_view_len(v) >= PT_VIEW_BYTES) {
50        /* tiles x PIX zero words, a meta-sized block at a time. */
51        uint32_t n = tiles * PIX, off = PT_V_RGB;
52        const uint32_t chunk = PT_MAX_TILES * PT_META / 4;
53        while (n > 0) {
54            uint32_t k = n < chunk ? n : chunk;
55            mesh_view_write_u32s(v, off, zeros, k);
56            off += 4 * k;
57            n -= k;
58        }
59    }
60    const uint32_t offs[10] = {PT_V_NEXT, PT_V_DONE, PT_V_TILES, PT_V_WIDTH, PT_V_HEIGHT,
61                               PT_V_SPP, PT_V_PASSES, PT_V_LAST_TILE, PT_V_LAST_SYSTEM, PT_V_LAST_EXEC};
62    const uint32_t vals[10] = {0, 0, tiles, sc[PT_H_WIDTH], sc[PT_H_HEIGHT], sc[PT_H_SPP], sc[PT_H_PASSES], 0, 0, 0};
63    for (int i = 0; i < 10; i++) mesh_view_set_u32(v, offs[i], vals[i]);
64    mesh_view_set_u32(v, PT_V_GEN, mesh_view_u32(v, PT_V_GEN) + 1u);
65    mesh_view_set_u32(v, PT_V_MAGIC, PT_VIEW_MAGIC);
66}
67
68static void load_default(mesh_view v) {
69    mesh_view_write_u32s(v, PT_V_SCENE, pt_default_scene, PT_DEFAULT_SCENE_WORDS);
70    restart(v, pt_default_scene);
71}
72
73static void init(const mesh_ctx *ctx) {
74    mesh_view v = mesh_ctx_view(ctx);
75    mesh_view_set_u32(v, PT_V_TONEMAP, PT_TONEMAP_ACES);
76    mesh_view_set_u32(v, PT_V_SCENE_ERR, 0);
77    load_default(v);
78}
79
80static void read_acc(mesh_view v, uint32_t t) {
81    mesh_view_read_u32s(v, pt_view_acc(t), words_buf, 3 * PIX);
82    for (uint32_t i = 0; i < PIX; i++) {
83        acc[i].x = mesh_ftz_from_bits(words_buf[3 * i]);
84        acc[i].y = mesh_ftz_from_bits(words_buf[3 * i + 1]);
85        acc[i].z = mesh_ftz_from_bits(words_buf[3 * i + 2]);
86    }
87}
88
89static void write_acc(mesh_view v, uint32_t t) {
90    to_words(acc);
91    mesh_view_write_u32s(v, pt_view_acc(t), words_buf, 3 * PIX);
92}
93
94/* How long an item waits for its tile's previous pass to be folded. */
95#define WAIT_NS 2000000000ull
96
97static void zero_v(pt_v *a) {
98    for (uint32_t i = 0; i < PIX; i++) a[i].x = a[i].y = a[i].z = 0.0f;
99}
100
101/* Work item (tile t, pass p): the pass sums from +0, then, in pass order (M_PASSES claimed
102 * p -> p | FOLDING, waiting bounded for passes 0..p), folded into the tile's accumulators and tone
103 * mapped. *dp: the pass-sum digest; *da: the accumulator digest after the fold, or 0 when it was
104 * folded already. false: the wait timed out. */
105static bool item(const mesh_ctx *ctx, mesh_view v, const uint32_t *sc, uint32_t t, uint32_t p, uint32_t tm, uint64_t t0,
106                 bool pay, uint64_t *dp, uint64_t *da) {
107    zero_v(part);
108    *dp = pt_tile_pass_z(sc, t, p, part);
109    *da = 0;
110    if (pay) put_payload(ctx, v);
111    uint32_t m = pt_view_meta(t);
112    for (;;) {
113        uint32_t cur = mesh_view_u32(v, m + PT_M_PASSES);
114        if (cur == p && mesh_view_cas32(v, m + PT_M_PASSES, p, p | PT_FOLDING) == p) break;
115        if ((cur & ~PT_FOLDING) > p || (!(cur & PT_FOLDING) && cur > p)) return true;
116        if (mesh_ctx_host_ns(ctx) - t0 > WAIT_NS) return false;
117    }
118    if (p > 0) read_acc(v, t);
119    else zero_v(acc);
120    uint64_t dr;
121    pt_tile_fold_z(sc, p + 1, tm, acc, part, rgb, da, &dr);
122    write_acc(v, t);
123    mesh_view_write_u32s(v, pt_view_rgb(t), rgb, PIX);
124    uint32_t exec = EXEC | mesh_ctx_node(ctx) << 8;
125    uint32_t us = (uint32_t)((mesh_ctx_host_ns(ctx) - t0) / 1000);
126    const uint32_t meta[7] = {mesh_ctx_system(ctx), exec, us, (uint32_t)*da, (uint32_t)(*da >> 32), (uint32_t)dr, (uint32_t)(dr >> 32)};
127    mesh_view_write_u32s(v, m + 4, meta, 7);
128    mesh_view_set_u32(v, PT_V_LAST_TILE, t);
129    mesh_view_set_u32(v, PT_V_LAST_SYSTEM, mesh_ctx_system(ctx));
130    mesh_view_set_u32(v, PT_V_LAST_EXEC, exec);
131    mesh_view_cas32(v, m + PT_M_PASSES, p | PT_FOLDING, p + 1);
132    mesh_view_add32(v, PT_V_ITEMS, 1);
133    mesh_ctx_count(ctx, 0, 1);
134    mesh_ctx_count(ctx, 2, 1);
135    if (p + 1 == sc[PT_H_PASSES]) {
136        mesh_ctx_count(ctx, 1, 1);
137        mesh_view_add32(v, PT_V_DONE, 1);
138    }
139    return true;
140}
141
142/* OP_REQ: a scene's raw words: validated, loaded, restart; one text line back. */
143static uint32_t request(const mesh_ctx *ctx, mesh_view v) {
144    mesh_view req = mesh_ctx_request(ctx);
145    uint32_t n = mesh_view_len(req) / 4;
146    uint32_t k = n < PT_SCENE_MAX_WORDS ? n : PT_SCENE_MAX_WORDS;
147    mesh_view_read_u32s(req, 0, scene_buf, k);
148    uint32_t err;
149    if (mesh_view_len(req) % 4 != 0 || n > PT_SCENE_MAX_WORDS) err = 1;
150    else if ((err = pt_validate(scene_buf, k)) != 0) {
151    } else if (scene_buf[PT_H_WORDS] != n) err = 3;
152    else err = 0;
153    if (err == 0) {
154        mesh_view_write_u32s(v, PT_V_SCENE, scene_buf, k);
155        restart(v, scene_buf);
156    }
157    mesh_view_set_u32(v, PT_V_SCENE_ERR, err);
158    char line[64];
159    uint32_t len = 0;
160#define PUT(s)                                    \
161    for (const char *q_ = (s); *q_; q_++) line[len++] = *q_
162#define NUM(x)                                    \
163    do {                                          \
164        char d_[12];                              \
165        int i_ = 0;                               \
166        uint32_t y_ = (x);                        \
167        do {                                      \
168            d_[i_++] = (char)('0' + y_ % 10);     \
169            y_ /= 10;                             \
170        } while (y_);                             \
171        while (i_) line[len++] = d_[--i_];        \
172    } while (0)
173    if (err == 0) {
174        PUT("ok scene gen ");
175        NUM(mesh_view_u32(v, PT_V_GEN));
176        PUT(" tiles ");
177        NUM(pt_tiles(scene_buf));
178        PUT("\n");
179    } else {
180        PUT("err scene ");
181        NUM(err);
182        PUT("\n");
183    }
184    mesh_view_write(mesh_ctx_response(ctx), 0, line, len);
185    mesh_ctx_respond(ctx, len);
186    return err == 0 ? MESH_R_NONE : MESH_R_BAD_OP;
187}
188
189static bool settable(uint32_t word) {
190    for (int i = 0; i < PT_SETTABLE_COUNT; i++)
191        if (pt_settable[i] == word) return true;
192    return false;
193}
194
195static uint32_t task(const mesh_ctx *ctx, uint32_t op, uint32_t arg) {
196    mesh_view v = mesh_ctx_view(ctx);
197    /* A view without tiles (FLEET_VIEW_BYTES: pathtrace-cpu's) runs the fleet ops only. */
198    uint32_t len = mesh_view_len(v);
199    if (len < PT_FLEET_VIEW_BYTES ||
200        (len < PT_VIEW_BYTES && !(op == MESH_OP_INIT || op == PT_OP_DEFAULT || op == PT_OP_SET || op == PT_OP_PASS)))
201        return MESH_R_BAD_OP;
202    switch (op) {
203    case MESH_OP_TASK:
204    case MESH_OP_JOB:
205    case PT_OP_TONEMAP:
206    case PT_OP_PASS: break;
207    case MESH_OP_INIT: init(ctx); return MESH_R_NONE;
208    case PT_OP_DEFAULT: load_default(v); return MESH_R_NONE;
209    case MESH_OP_REQ: return request(ctx, v);
210    case PT_OP_RESTART:
211        if (!scene(v, PT_MAX_TILES)) return MESH_R_BAD_OP;
212        restart(v, scene_buf);
213        return MESH_R_NONE;
214    case PT_OP_SET:
215        if (!settable(arg >> 24)) return MESH_R_BAD_OP;
216        mesh_view_set_u32(v, PT_V_SCENE + 4 * (arg >> 24), arg & 0x00ffffffu);
217        return MESH_R_NONE;
218    default: return MESH_R_BAD_OP;
219    }
220    bool job = op == MESH_OP_JOB || op == PT_OP_PASS;
221    if (!scene(v, op == PT_OP_PASS ? PT_FLEET_MAX_TILES : PT_MAX_TILES)) return job ? MESH_R_JOB_FAIL : MESH_R_BAD_OP;
222    const uint32_t *sc = scene_buf;
223    uint32_t tiles = pt_tiles(sc), passes = sc[PT_H_PASSES];
224    uint64_t t0 = mesh_ctx_host_ns(ctx);
225    if (op == PT_OP_TONEMAP) {
226        if (arg >= PT_TONEMAPS) return MESH_R_BAD_OP;
227        mesh_view_set_u32(v, PT_V_TONEMAP, arg);
228        for (uint32_t t = 0; t < tiles; t++) {
229            uint32_t m = pt_view_meta(t);
230            uint32_t done = mesh_view_u32(v, m + PT_M_PASSES) & ~PT_FOLDING;
231            if (done == 0) continue;
232            read_acc(v, t);
233            uint64_t hr = pt_tile_retone_z(sc, done, arg, acc, rgb);
234            mesh_view_write_u32s(v, pt_view_rgb(t), rgb, PIX);
235            const uint32_t w[2] = {(uint32_t)hr, (uint32_t)(hr >> 32)};
236            mesh_view_write_u32s(v, m + PT_M_RGB, w, 2);
237        }
238        mesh_ctx_count(ctx, 3, 1);
239        return MESH_R_NONE;
240    }
241    uint32_t tm = mesh_view_u32(v, PT_V_TONEMAP);
242    uint64_t total64 = (uint64_t)tiles * passes;
243    uint32_t total = total64 > UINT32_MAX ? UINT32_MAX : (uint32_t)total64;
244    if (op == PT_OP_PASS) {
245        /* A fleet job's item: the pass sums only, into the payload slot; no fold, no wait. */
246        uint64_t w = mesh_view_u64(v, PT_V_JOB_BASE) + arg;
247        if (w >= total) return MESH_R_JOB_FAIL;
248        uint32_t t = (uint32_t)w % tiles, p = (uint32_t)w / tiles;
249        zero_v(part);
250        uint64_t dp = pt_tile_pass_z(sc, t, p, part);
251        put_payload(ctx, v);
252        uint32_t us = (uint32_t)((mesh_ctx_host_ns(ctx) - t0) / 1000);
253        const uint32_t rec[8] = {t, p, (uint32_t)dp, (uint32_t)(dp >> 32), 0, 0, us, EXEC | mesh_ctx_node(ctx) << 8};
254        mesh_view_write_u32s(v, PT_V_JOB_OUT + PT_JOB_REC * (mesh_ctx_ticket(ctx) % MESH_PROG_RESULTS), rec, 8);
255        mesh_view_add32(v, PT_V_ITEMS, 1);
256        mesh_ctx_count(ctx, 0, 1);
257        return (uint32_t)dp & 0x7fffffffu;
258    }
259    if (op == MESH_OP_JOB) {
260        uint64_t w = mesh_view_u64(v, PT_V_JOB_BASE) + arg;
261        if (w >= total) return MESH_R_JOB_FAIL;
262        uint32_t t = (uint32_t)w % tiles, p = (uint32_t)w / tiles;
263        uint64_t dp, da;
264        if (!item(ctx, v, sc, t, p, tm, t0, true, &dp, &da)) return MESH_R_JOB_FAIL;
265        uint32_t us = (uint32_t)((mesh_ctx_host_ns(ctx) - t0) / 1000);
266        const uint32_t rec[8] = {t, p, (uint32_t)dp, (uint32_t)(dp >> 32), (uint32_t)da, (uint32_t)(da >> 32), us,
267                                 EXEC | mesh_ctx_node(ctx) << 8};
268        mesh_view_write_u32s(v, PT_V_JOB_OUT + PT_JOB_REC * (mesh_ctx_ticket(ctx) % MESH_PROG_RESULTS), rec, 8);
269        return (uint32_t)dp & 0x7fffffffu;
270    }
271    if (arg >= total) return MESH_R_NONE;
272    uint32_t gen = mesh_view_u32(v, PT_V_GEN);
273    uint64_t dp, da;
274    bool done = item(ctx, v, sc, arg % tiles, arg / tiles, tm, t0, false, &dp, &da);
275    if (mesh_view_u32(v, PT_V_GEN) == gen) mesh_view_add32(v, PT_V_NEXT, 1);
276    return done ? arg / tiles + 1 : MESH_R_BAD_OP;
277}
278
279MESH_PROGRAM(init, task)