==> 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)