skip to content
==> src/assemble.c <==
1/* assemble.rs's Assembly, part_of, job_scene and renders. */
2#include "mesh/assemble.h"
3#include "mesh/abi/ops.h"
4#include "mesh/assemble_bmp.h"
5#include "mesh/ftz.h"
6#include "mesh/ref/pathtrace.h"
7#include "mesh/ref/sha256d.h"
8#include "mesh/strbuf.h"
9#include "pathtrace/digest.h"
10#include "pathtrace/job_scene.h"
11#include "pathtrace/scene.h"
12#include "pathtrace/view.h"
13#include "pathtrace/z.h"
14
15#include <stdlib.h>
16#include <string.h>
17
18#define PX PT_TILE_PIX
19
20typedef struct {
21    uint64_t item;
22    char *why;
23} bad;
24
25struct masm {
26    mj_job_id job;
27    uint32_t sc[PT_DEFAULT_SCENE_WORDS];
28    uint32_t tiles, passes, tone;
29    uint32_t *next;  /* per tile: the next pass to fold */
30    uint32_t **acc;  /* per tile: the accumulators, once a pass is folded */
31    uint32_t *rgb;   /* tiles x PX */
32    bool *dirty;
33    uint32_t *owner;
34    momap *pending;  /* item -> masm_item: committed, not folded yet */
35    momap *ready;    /* item -> uint32_t *: blobs fetched ahead of their turn */
36    momap *by_member;
37    uint64_t folded, committed;
38    bad *bad;
39    size_t nbad;
40};
41
42bool masm_part_of(const uint8_t *out, size_t n, uint64_t *part) {
43    size_t at = 4 * (size_t)PT_J_PART;
44    if (n < at + 8) return false;
45    uint64_t v = 0;
46    for (int i = 7; i >= 0; i--) v = v << 8 | out[at + (size_t)i];
47    *part = v;
48    return true;
49}
50
51bool masm_job_scene(const mj_setup *setup, size_t n, uint32_t *sc, char **err) {
52    pt_setup_task *t = n ? mxalloc(n * sizeof *t) : NULL;
53    for (size_t i = 0; i < n; i++) t[i] = (pt_setup_task){setup[i].op, setup[i].arg};
54    long bad_at = pt_job_scene(t, n, sc);
55    free(t);
56    if (bad_at >= 0) {
57        if (err)
58            *err = mfmt("setup %u:0x%x: not a scene setting (5:0, 7:<word << 24 | value>)", setup[bad_at].op, setup[bad_at].arg);
59        return false;
60    }
61    uint32_t rule = pt_validate_in(sc, PT_DEFAULT_SCENE_WORDS, PT_FLEET_MAX_TILES);
62    if (rule) {
63        if (err) *err = mfmt("the job's scene is not valid (rule %u)", rule);
64        return false;
65    }
66    return true;
67}
68
69bool masm_renders(const char *program, uint32_t op) {
70    return strncmp(program, "pathtrace", 9) == 0 && (op == PT_OP_PASS || op == MESH_OP_JOB);
71}
72
73static void ready_free(void *v) { free(*(uint32_t **)v); }
74
75masm *masm_new(mj_job_id job, const mj_setup *setup, size_t nsetup, uint32_t tone, char **err) {
76    masm *a = mxalloc(sizeof *a);
77    if (!masm_job_scene(setup, nsetup, a->sc, err)) {
78        free(a);
79        return NULL;
80    }
81    a->job = job;
82    a->tiles = pt_tiles(a->sc);
83    a->passes = a->sc[PT_H_PASSES];
84    a->tone = tone;
85    a->next = mxalloc(a->tiles * sizeof *a->next);
86    a->acc = mxalloc(a->tiles * sizeof *a->acc);
87    a->rgb = mxalloc((size_t)a->tiles * PX * sizeof *a->rgb);
88    a->dirty = mxalloc(a->tiles * sizeof *a->dirty);
89    a->owner = mxalloc(a->tiles * sizeof *a->owner);
90    for (uint32_t t = 0; t < a->tiles; t++) a->owner[t] = UINT32_MAX;
91    a->pending = momap_new(1, sizeof(masm_item), NULL, NULL);
92    a->ready = momap_new(1, sizeof(uint32_t *), ready_free, NULL);
93    a->by_member = momap_new(1, sizeof(uint64_t), NULL, NULL);
94    return a;
95}
96
97void masm_free(masm *a) {
98    if (!a) return;
99    for (uint32_t t = 0; t < a->tiles; t++) free(a->acc[t]);
100    free(a->acc);
101    free(a->next);
102    free(a->rgb);
103    free(a->dirty);
104    free(a->owner);
105    momap_free(a->pending);
106    momap_free(a->ready);
107    momap_free(a->by_member);
108    for (size_t i = 0; i < a->nbad; i++) free(a->bad[i].why);
109    free(a->bad);
110    free(a);
111}
112
113mj_job_id masm_job(const masm *a) { return a->job; }
114const uint32_t *masm_scene(const masm *a) { return a->sc; }
115uint32_t masm_tiles(const masm *a) { return a->tiles; }
116uint32_t masm_passes(const masm *a) { return a->passes; }
117uint32_t masm_tone(const masm *a) { return a->tone; }
118uint64_t masm_items(const masm *a) { return (uint64_t)a->tiles * a->passes; }
119uint32_t masm_width(const masm *a) { return a->sc[PT_H_WIDTH]; }
120uint32_t masm_height(const masm *a) { return a->sc[PT_H_HEIGHT]; }
121uint32_t masm_spp(const masm *a) { return a->sc[PT_H_SPP]; }
122bool masm_complete(const masm *a) { return a->folded == masm_items(a); }
123uint64_t masm_folded(const masm *a) { return a->folded; }
124uint64_t masm_committed(const masm *a) { return a->committed; }
125const momap *masm_by_member(const masm *a) { return a->by_member; }
126size_t masm_bad_count(const masm *a) { return a->nbad; }
127
128const char *masm_bad_at(const masm *a, size_t i, uint64_t *item) {
129    if (item) *item = a->bad[i].item;
130    return a->bad[i].why;
131}
132
133uint32_t masm_tiles_done(const masm *a) {
134    uint32_t n = 0;
135    for (uint32_t t = 0; t < a->tiles; t++) n += a->next[t] == a->passes;
136    return n;
137}
138
139void masm_tile(const masm *a, uint32_t t, uint32_t *passes, uint32_t *owner) {
140    *passes = a->next[t];
141    *owner = a->owner[t];
142}
143
144static void split(const masm *a, uint64_t item, uint32_t *t, uint32_t *p) {
145    *t = (uint32_t)(item % a->tiles);
146    *p = (uint32_t)(item / a->tiles);
147}
148
149static bool is_folded(const masm *a, uint64_t item) {
150    uint32_t t, p;
151    split(a, item, &t, &p);
152    return p < a->next[t];
153}
154
155void masm_commit(masm *a, uint64_t item, masm_item it) {
156    if (item >= masm_items(a) || is_folded(a, item) || momap_get1(a->pending, item)) return;
157    *(masm_item *)momap_put1(a->pending, item, NULL) = it;
158    a->committed++;
159}
160
161static bool is_bad(const masm *a, uint64_t item) {
162    for (size_t i = 0; i < a->nbad; i++)
163        if (a->bad[i].item == item) return true;
164    return false;
165}
166
167size_t masm_wants(const masm *a, bool (*asked)(void *ctx, uint64_t item), void *ctx, size_t budget, uint64_t *items, masm_item *its) {
168    /* Candidates by (pass, tile): every tile's window, collected then ordered. */
169    typedef struct {
170        uint32_t p, t;
171        uint64_t item;
172        masm_item it;
173    } cand;
174    cand *v = NULL;
175    size_t n = 0;
176    for (uint32_t t = 0; t < a->tiles; t++) {
177        uint32_t nx = a->next[t], end = nx + MESH_ASSEMBLE_WINDOW < a->passes ? nx + MESH_ASSEMBLE_WINDOW : a->passes;
178        for (uint32_t p = nx; p < end; p++) {
179            uint64_t item = (uint64_t)p * a->tiles + t;
180            const masm_item *it = momap_get1(a->pending, item);
181            if (it && !momap_get1(a->ready, item) && !(asked && asked(ctx, item)) && !is_bad(a, item)) {
182                v = mxrealloc(v, (n + 1) * sizeof *v);
183                v[n++] = (cand){p, t, item, *it};
184            }
185        }
186    }
187    /* Insertion into (p, t) order: the tiles loop already gives t ascending within each p. */
188    for (size_t i = 1; i < n; i++) {
189        cand x = v[i];
190        size_t j = i;
191        while (j > 0 && (v[j - 1].p > x.p || (v[j - 1].p == x.p && v[j - 1].t > x.t))) {
192            v[j] = v[j - 1];
193            j--;
194        }
195        v[j] = x;
196    }
197    size_t k = n < budget ? n : budget;
198    for (size_t i = 0; i < k; i++) {
199        items[i] = v[i].item;
200        if (its) its[i] = v[i].it;
201    }
202    free(v);
203    return k;
204}
205
206const masm_item *masm_wanted(const masm *a, uint64_t item) { return momap_get1(a->pending, item); }
207
208/* acc += P, word by word in the cluster float mode Z: pathtrace-core's fold_column add. */
209static void fold(masm *a, uint32_t t, const uint32_t *part) {
210    if (!a->acc[t]) a->acc[t] = mxalloc(3 * PX * sizeof(uint32_t));
211    uint32_t *acc = a->acc[t];
212    for (size_t i = 0; i < 3 * PX; i++) acc[i] = mesh_ftz_bits(mesh_ftz_add(mesh_ftz_from_bits(acc[i]), mesh_ftz_from_bits(part[i])));
213    a->dirty[t] = true;
214}
215
216long masm_blob(masm *a, uint64_t item, const uint8_t *bytes, size_t n, char **err) {
217    const masm_item *ip = momap_get1(a->pending, item);
218    if (!ip) return 0;
219    masm_item it = *ip;
220    char *why = NULL;
221    if (n != PT_PAYLOAD || it.len != PT_PAYLOAD) why = mfmt("payload of %zu bytes, not %u", n, (unsigned)PT_PAYLOAD);
222    uint32_t *words = NULL;
223    if (!why) {
224        words = mxalloc(n);
225        for (size_t i = 0; i < n / 4; i++)
226            words[i] = (uint32_t)bytes[4 * i] | (uint32_t)bytes[4 * i + 1] << 8 | (uint32_t)bytes[4 * i + 2] << 16 | (uint32_t)bytes[4 * i + 3] << 24;
227        uint64_t d = pt_payload_digest(words);
228        if (d != it.part) why = mfmt("pass-sum digest %016llx, its record says %016llx", (unsigned long long)d, (unsigned long long)it.part);
229    }
230    if (why) {
231        free(words);
232        uint32_t t, p;
233        split(a, item, &t, &p);
234        char *e = mfmt("item %llu (tile %u pass %u, system %u): %s", (unsigned long long)item, t, p, it.member, why);
235        free(why);
236        a->bad = mxrealloc(a->bad, (a->nbad + 1) * sizeof *a->bad);
237        a->bad[a->nbad++] = (bad){item, mstrdup(e)};
238        if (err) *err = e;
239        else free(e);
240        return -1;
241    }
242    uint32_t **slot = momap_put1(a->ready, item, NULL);
243    free(*slot);
244    *slot = words;
245    uint32_t t, p0;
246    split(a, item, &t, &p0);
247    long folded = 0;
248    for (;;) {
249        uint32_t p = a->next[t];
250        if (p >= a->passes) break;
251        uint64_t w = (uint64_t)p * a->tiles + t;
252        uint32_t *ws = NULL;
253        if (!momap_take(a->ready, &w, &ws)) break;
254        masm_item done;
255        momap_take(a->pending, &w, &done);
256        fold(a, t, ws);
257        free(ws);
258        a->next[t] = p + 1;
259        a->owner[t] = done.member;
260        (*(uint64_t *)momap_put1(a->by_member, done.member, NULL))++;
261        a->folded++;
262        folded++;
263    }
264    return folded;
265}
266
267void masm_set_tone(masm *a, uint32_t tone) {
268    if (tone != a->tone && tone < PT_TONEMAPS) {
269        a->tone = tone;
270        for (uint32_t t = 0; t < a->tiles; t++) a->dirty[t] = true;
271    }
272}
273
274const uint32_t *masm_rgb(masm *a) {
275    for (uint32_t t = 0; t < a->tiles; t++) {
276        bool d = a->dirty[t];
277        a->dirty[t] = false;
278        if (!d || !a->acc[t]) continue;
279        pt_v *v = mxalloc(PX * sizeof *v);
280        for (size_t i = 0; i < PX; i++)
281            v[i] = (pt_v){mesh_ftz_from_bits(a->acc[t][3 * i]), mesh_ftz_from_bits(a->acc[t][3 * i + 1]), mesh_ftz_from_bits(a->acc[t][3 * i + 2])};
282        pt_tile_retone_z(a->sc, a->next[t], a->tone, v, a->rgb + (size_t)t * PX);
283        free(v);
284    }
285    return a->rgb;
286}
287
288const uint32_t *masm_acc(const masm *a, uint32_t t) { return a->acc[t]; }
289
290void masm_digests(masm *a, uint64_t *acc, uint64_t *pixels) {
291    uint64_t h = PT_FNV_OFFSET;
292    for (uint32_t t = 0; t < a->tiles; t++)
293        for (size_t i = 0; i < 3 * PX; i++) h = pt_fnv(h, a->acc[t] ? a->acc[t][i] : 0);
294    *acc = h;
295    *pixels = pt_fnv_words(masm_rgb(a), (size_t)a->tiles * PX);
296}
297
298uint8_t *masm_raster(masm *a, size_t *len) {
299    size_t n = (size_t)masm_width(a) * masm_height(a) * 3;
300    uint8_t *r = mxalloc(n);
301    mesh_pt_raster(a->sc, masm_rgb(a), r);
302    *len = n;
303    return r;
304}
305
306uint8_t *masm_ppm(masm *a, size_t *len) {
307    size_t rn;
308    uint8_t *r = masm_raster(a, &rn);
309    uint8_t *p = mesh_pt_ppm(masm_width(a), masm_height(a), r, len);
310    free(r);
311    return p;
312}
313
314uint8_t *masm_bmp_of(masm *a, size_t *len) {
315    size_t rn;
316    uint8_t *r = masm_raster(a, &rn);
317    uint8_t *b = masm_bmp(masm_width(a), masm_height(a), r, len);
318    free(r);
319    return b;
320}
321
322char *masm_sha256(masm *a) {
323    size_t n;
324    uint8_t *p = masm_ppm(a, &n);
325    uint8_t h[32];
326    mesh_sha256(p, n, h);
327    free(p);
328    char *x = mxalloc(65);
329    mesh_sha_hex(h, 32, x);
330    return x;
331}