skip to content
==> src/render.c <==
1/* ui_render.rs, ported. */
2#include "mesh/top_ui_render.h"
3
4#include "pathtrace/view.h"
5
6#include <math.h>
7#include <stdlib.h>
8#include <string.h>
9
10static float fractf(float x) { return x - truncf(x); }
11
12tui_color mt_hue(float h) {
13    float h0 = fractf(h) * 6.0f, s = 0.85f, v = 0.95f;
14    uint32_t i = mt_sat_u32(floorf(h0));
15    float fr = fractf(h0);
16    float p = v * (1.0f - s), q = v * (1.0f - s * fr), t = v * (1.0f - s * (1.0f - fr));
17    float r, g, b;
18    switch (i % 6) {
19    case 0: r = v, g = t, b = p; break;
20    case 1: r = q, g = v, b = p; break;
21    case 2: r = p, g = v, b = t; break;
22    case 3: r = p, g = q, b = v; break;
23    case 4: r = t, g = p, b = v; break;
24    default: r = v, g = p, b = q; break;
25    }
26    return TUI_RGB(mt_sat_u8(r * 255.0f), mt_sat_u8(g * 255.0f), mt_sat_u8(b * 255.0f));
27}
28
29/* Who rendered a tile: a fleet member (its system's colour), else its executor, coloured by node. */
30static const char *owner(mt_frame *f, uint32_t system, uint32_t exec, tui_color *c) {
31    if (system != MESH_PEER_NONE) {
32        *c = mt_sys_color(system);
33        return MT_FMT(f, "system %u", system);
34    }
35    uint32_t e = exec & 0xff;
36    *c = mt_hue((float)((exec >> 8) & 0xff) * 0.618034f);
37    return e == PT_EXEC_CUDA ? "cuda" : e == PT_EXEC_METAL ? "metal" : e == PT_EXEC_WASM ? "wasm" : "?";
38}
39
40static void rgb_of(tui_color c, float out[3]) {
41    if (c.kind == TUI_COLOR_RGB) {
42        out[0] = c.r;
43        out[1] = c.g;
44        out[2] = c.b;
45    } else {
46        out[0] = out[1] = out[2] = 128.0f;
47    }
48}
49
50typedef struct {
51    uint32_t passes;
52    tui_color color;
53} tile_meta;
54
55typedef struct {
56    uint32_t iw, ih, tx;
57    float s;
58    const uint8_t *px;
59    const tile_meta *meta;
60    bool marks;
61} image;
62
63static tui_color sample(const image *im, uint16_t ox, uint16_t oy) {
64    uint32_t iw = im->iw, ih = im->ih;
65    uint32_t sx0 = mt_sat_u32((float)ox / im->s), sx1 = mt_sat_u32((float)(uint16_t)(ox + 1) / im->s);
66    uint32_t sy0 = mt_sat_u32((float)oy / im->s), sy1 = mt_sat_u32((float)(uint16_t)(oy + 1) / im->s);
67    if (sx1 > iw) sx1 = iw;
68    if (sy1 > ih) sy1 = ih;
69    sx1 = sx1 > sx0 + 1 ? sx1 : sx0 + 1;
70    if (sx1 > iw) sx1 = iw;
71    sy1 = sy1 > sy0 + 1 ? sy1 : sy0 + 1;
72    if (sy1 > ih) sy1 = ih;
73    uint32_t r = 0, g = 0, b = 0, n = 0;
74    for (uint32_t y = sy0; y < sy1; y++)
75        for (uint32_t x = sx0; x < sx1; x++) {
76            uint32_t t = (y / PT_TILE_H) * im->tx + x / PT_TILE_W;
77            size_t o = 4 * ((size_t)t * PT_TILE_PIX + (y % PT_TILE_H) * PT_TILE_W + x % PT_TILE_W);
78            r += im->px[o];
79            g += im->px[o + 1];
80            b += im->px[o + 2];
81            n++;
82        }
83    if (n < 1) n = 1;
84    float rf = (float)(r / n), gf = (float)(g / n), bf = (float)(b / n);
85    if (im->marks) {
86        uint32_t cx = (sx0 + sx1) / 2, cy = (sy0 + sy1) / 2;
87        if (cx > iw - 1) cx = iw - 1;
88        if (cy > ih - 1) cy = ih - 1;
89        const tile_meta *mt = &im->meta[(cy / PT_TILE_H) * im->tx + cx / PT_TILE_W];
90        float m[3] = {40.0f, 40.0f, 48.0f};
91        if (mt->passes != 0) rgb_of(mt->color, m);
92        /* Tile edges in the owner's colour, the inside half tinted. */
93        bool edge = sx0 % PT_TILE_W < (sx1 - sx0) || sy0 % PT_TILE_H < (sy1 - sy0);
94        float a = edge ? 0.8f : 0.35f;
95        rf = rf * (1.0f - a) + m[0] * a;
96        gf = gf * (1.0f - a) + m[1] * a;
97        bf = bf * (1.0f - a) + m[2] * a;
98    }
99    return TUI_RGB(mt_sat_u8(rf), mt_sat_u8(gf), mt_sat_u8(bf));
100}
101
102/* The image (tile-major 0xffBBGGRR bytes, iw x ih) fitted into img_a, box-filtered. */
103static void draw_image(mt_frame *f, tui_rect img_a, uint32_t iw, uint32_t ih, const uint8_t *px, const tile_meta *meta, bool marks) {
104    image im = {iw, ih, iw / PT_TILE_W, 0.0f, px, meta, marks};
105    float sa = (float)img_a.width / (float)iw, sb = 2.0f * (float)img_a.height / (float)ih;
106    im.s = sa <= sb ? sa : sb; /* f32::min */
107    if (sa != sa) im.s = sb;
108    uint16_t ow = tui_max16(mt_sat_u16((float)iw * im.s), 1), oh = tui_max16(mt_sat_u16((float)ih * im.s), 2);
109    uint16_t x0 = (uint16_t)(img_a.x + (img_a.width - tui_min16(ow, img_a.width)) / 2);
110    uint16_t y0 = (uint16_t)(img_a.y + (img_a.height - tui_min16((uint16_t)(oh / 2), img_a.height)) / 2);
111    for (uint16_t cy = 0; cy < tui_min16((uint16_t)(oh / 2), img_a.height); cy++)
112        for (uint16_t cx = 0; cx < tui_min16(ow, img_a.width); cx++) {
113            tui_color top = sample(&im, cx, (uint16_t)(2 * cy)), bot = sample(&im, cx, (uint16_t)(2 * cy + 1));
114            tui_cell *cell = tui_buffer_cell(f->buf, (uint16_t)(x0 + cx), (uint16_t)(y0 + cy));
115            if (!cell) continue;
116            tui_cell_set_str(cell, "▀");
117            cell->fg = top;
118            cell->bg = bot;
119        }
120}
121
122static const char *repeat(mt_frame *f, const char *s, size_t n) {
123    size_t l = strlen(s);
124    char *o = tui_arena_alloc(f->a, l * n + 1);
125    for (size_t i = 0; i < n; i++) memcpy(o + i * l, s, l);
126    return o;
127}
128
129/* Fleet mode: a job's image as the focused member assembles it, tiles by machine. */
130static void draw_fleet(mt_frame *f, tui_rect area, const mt_app *app, const mt_fleet_image *img) {
131    const mt_machine *m = mt_app_focused(app);
132#define G(k) mt_fleet_image_get(img, k)
133#define N(k) mt_fleet_image_num(img, k)
134    uint64_t items = N("items"), folded = N("folded"), tiles = img->ntiles;
135    double pct = items > 0 ? 100.0 * (double)folded / (double)items : 0.0;
136    const char *sha = G("sha256");
137    const char *extra = MT_FMT(f, "%s job %s  %sx%s  %s/%llu tiles  passes %s%%  %llu spp  %s Msamples/s  %s  assembled on system %s%s", G("program"),
138                               G("job"), G("width"), G("height"), G("tiles_done"), (unsigned long long)tiles, mt_ff(f, pct, 1),
139                               (unsigned long long)(N("passes") * N("spp")), mt_ff(f, img->msps, 2), G("tone"), G("system"),
140                               strlen(sha) == 64 ? MT_FMT(f, "  sha256 %.16s..", sha) : "");
141    tui_color color;
142    tui_line title = mt_machine_title(f, m, true, extra, &color);
143    tui_block block = mt_panel(f, title, color);
144    tui_rect inner = tui_block_inner(&block, area);
145    mt_block(f, &block, area);
146    tui_rect r[3];
147    tui_vsplit(inner, 3, (tui_constraint[]){TUI_FILL(1), TUI_LENGTH(1), TUI_LENGTH(1)}, r);
148    tile_meta *meta = tui_arena_alloc(f->a, (tiles ? tiles : 1) * sizeof *meta);
149    for (size_t t = 0; t < tiles; t++) {
150        meta[t].passes = img->tiles[t][0];
151        meta[t].color = img->tiles[t][1] == UINT32_MAX ? MT_DIM : mt_sys_color(img->tiles[t][1]);
152    }
153    uint32_t iw = (uint32_t)N("width"), ih = (uint32_t)N("height");
154    if (iw >= PT_TILE_W && ih >= PT_TILE_H) draw_image(f, r[0], iw, ih, img->px, meta, app->render_marks);
155    /* Progress: the passes folded (each machine's share in its colour), the state, the digests. */
156    uint64_t w = tui_min16(tui_sat_sub(r[1].width, 2), 60);
157    tui_line spans = tui_line_span(f->a, tui_span_raw(" "));
158    size_t used = 0;
159    uint64_t cum = 0;
160    for (size_t i = 0; i < img->nmembers; i++) {
161        /* Cumulative rounding: the shares fill exactly the folded part. */
162        cum += img->members[i].folded;
163        size_t c = items ? (size_t)(cum * w / items) : 0;
164        size_t cells = c > used ? c - used : 0;
165        used += cells;
166        tui_line_push(f->a, &spans, mt_fgs(repeat(f, "█", cells), mt_sys_color(img->members[i].k)));
167    }
168    tui_line_push(f->a, &spans, mt_fgs(repeat(f, "░", w > used ? w - used : 0), MT_DIM));
169    tui_line_push(f->a, &spans, tui_span_raw(MT_FMT(f, " %llu/%llu passes  %s  acc %s  pixels %s", (unsigned long long)folded,
170                                                    (unsigned long long)items, G("state"), G("acc"), G("pixels"))));
171    if (N("bad") > 0) tui_line_push(f->a, &spans, mt_fgs(MT_FMT(f, "  %s bad payloads", G("bad")), MT_BAD));
172    if (N("unavailable") > 0) tui_line_push(f->a, &spans, mt_fgs(MT_FMT(f, "  %s blobs held by no member up", G("unavailable")), MT_BAD));
173    mt_par_line(f, spans, r[1]);
174    /* The legend: per machine, its passes folded (share) and the tiles whose last pass is its. */
175    tui_line legend = tui_line_span(f->a, mt_label(app->render_marks ? " tiles by machine: " : " machines: "));
176    for (size_t i = 0; i < img->nmembers; i++) {
177        uint32_t k = img->members[i].k;
178        const char *host = "";
179        for (size_t q = 0; q < app->nmachines; q++) {
180            const mt_machine *x = &app->machines[q];
181            uint32_t sk;
182            if (x->has_snap && mt_snapshot_system(&x->snap, &sk) && sk == k) {
183                char h[97];
184                mt_snapshot_hostname(&x->snap, h);
185                host = tui_arena_strdup(f->a, h);
186                break;
187            }
188        }
189        uint64_t n = img->members[i].folded;
190        tui_line_push(f->a, &legend, mt_fgs("■ ", mt_sys_color(k)));
191        tui_line_push(f->a, &legend, tui_span_raw(MT_FMT(f, "%u%s %llu (%s%%) %u tiles  ", k, *host ? MT_FMT(f, " %s", host) : "",
192                                                         (unsigned long long)n, mt_ff(f, folded > 0 ? 100.0 * (double)n / (double)folded : 0.0, 0),
193                                                         img->members[i].tiles)));
194    }
195    mt_par_line(f, legend, r[2]);
196#undef G
197#undef N
198}
199
200static void center_line(mt_frame *f, tui_rect area, tui_span s) {
201    tui_paragraph p = tui_paragraph_new(tui_text_line(f->a, tui_line_span(f->a, s)));
202    p.alignment = TUI_ALIGN_CENTER;
203    tui_paragraph_render(f->a, &p, mt_rect(area.x, area.y, area.width, 1), f->buf);
204}
205
206typedef struct {
207    const char *name;
208    tui_color c;
209    uint32_t n;
210    uint32_t nodes[256];
211    size_t nnodes;
212} owner_count;
213
214void mt_draw_render(mt_frame *f, tui_rect area, const mt_app *app) {
215    const mt_machine *m = mt_app_focused(app);
216    if (m->has_fleet) {
217        draw_fleet(f, area, app, &m->fleet);
218        return;
219    }
220    char pname[64];
221    uint32_t tiles = 0;
222    bool has_prog = mt_app_render_program(app, pname, &tiles);
223    const mt_section *p = has_prog ? mt_machine_program(m, pname) : NULL;
224#define W(o) mt_section_view_u32(p, (o))
225    const char *title_extra = "render";
226    if (has_prog && p) {
227        uint32_t vt = W(PT_V_TILES), passes = W(PT_V_PASSES), spp = W(PT_V_SPP);
228        uint64_t done_passes = 0;
229        for (uint32_t t = 0; t < vt; t++) done_passes += W(pt_view_meta(t) + PT_M_PASSES) & ~PT_FOLDING;
230        uint64_t total = (uint64_t)vt * passes;
231        float items_s = 0.0f;
232        for (size_t i = 0; i < MESH_COUNTERS; i++) {
233            char cn[3 * MESH_NAME_BYTES + 1];
234            mt_section_counter_name(p, i, cn);
235            if (strcmp(cn, "items") == 0) {
236                items_s = mt_machine_counter_rate(m, mt_section_slot(p), i);
237                break;
238            }
239        }
240        double msps = (double)items_s * (double)PT_TILE_PIX * (double)spp / 1e6;
241        title_extra = MT_FMT(f, "%s  %ux%u  %u/%u tiles  passes %s%%  %llu spp  %s Msamples/s  %s", pname, W(PT_V_WIDTH), W(PT_V_HEIGHT), W(PT_V_DONE),
242                             vt, mt_ff(f, total > 0 ? 100.0 * (double)done_passes / (double)total : 0.0, 1),
243                             (unsigned long long)(done_passes * spp / (vt > 1 ? vt : 1)), mt_ff(f, msps, 2), W(PT_V_TONEMAP) == 1 ? "reinhard" : "aces");
244    }
245    tui_color color;
246    tui_line title = mt_machine_title(f, m, true, title_extra, &color);
247    tui_block block = mt_panel(f, title, color);
248    tui_rect inner = tui_block_inner(&block, area);
249    mt_block(f, &block, area);
250    if (!has_prog || !p) {
251        center_line(f, inner,
252                    mt_styled(" pathtrace is not loaded here: `load pathtrace-live` + `start pathtrace-live`, or `load pathtrace` + `job submit "
253                              "pathtrace items 9600` ",
254                              tui_fg_bg(MT_BG, MT_WARN)));
255        return;
256    }
257    tui_rect r[2];
258    tui_vsplit(inner, 2, (tui_constraint[]){TUI_FILL(1), TUI_LENGTH(1)}, r);
259    uint32_t iw = W(PT_V_WIDTH), ih = W(PT_V_HEIGHT);
260    /* Every tile's owner and passes, from the snapshot's meta. */
261    tile_meta *meta = tui_arena_alloc(f->a, (tiles ? tiles : 1) * sizeof *meta);
262    const char **names = tui_arena_alloc(f->a, (tiles ? tiles : 1) * sizeof *names);
263    for (uint32_t t = 0; t < tiles; t++) {
264        uint32_t b = pt_view_meta(t);
265        meta[t].passes = W(b + PT_M_PASSES) & ~PT_FOLDING;
266        names[t] = owner(f, W(b + PT_M_SYSTEM), W(b + PT_M_EXEC), &meta[t].color);
267    }
268    bool have_px = m->has_render && m->render_len == (size_t)tiles * PT_TILE_PIX * 4;
269    if (have_px) {
270        draw_image(f, r[0], iw, ih, m->render_px, meta, app->render_marks);
271    } else {
272        const char *msg = m->render_err ? m->render_err : "fetching the image";
273        center_line(f, r[0], mt_fgs(MT_FMT(f, " %s ", msg), MT_DIM));
274    }
275    /* The legend: tiles per owner (rendered at least one pass). */
276    owner_count *owners = tui_arena_alloc(f->a, (tiles ? tiles : 1) * sizeof *owners);
277    size_t no = 0;
278    for (uint32_t t = 0; t < tiles; t++) {
279        if (meta[t].passes == 0) continue;
280        uint32_t node = (W(pt_view_meta(t) + PT_M_EXEC) >> 8) & 0xff;
281        owner_count *o = NULL;
282        for (size_t i = 0; i < no && !o; i++)
283            if (strcmp(owners[i].name, names[t]) == 0) o = &owners[i];
284        if (!o) {
285            o = &owners[no++];
286            o->name = names[t];
287            o->c = meta[t].color;
288        }
289        o->n++;
290        bool seen = false;
291        for (size_t i = 0; i < o->nnodes; i++) seen |= o->nodes[i] == node;
292        if (!seen && o->nnodes < 256) o->nodes[o->nnodes++] = node;
293    }
294    tui_line spans = tui_line_span(f->a, mt_label(app->render_marks ? " tiles by renderer: " : " tiles: "));
295    for (size_t i = 0; i < no; i++) {
296        tui_line_push(f->a, &spans, mt_fgs("■ ", owners[i].c));
297        const char *by = strncmp(owners[i].name, "system", 6) == 0 ? "" : MT_FMT(f, " (last pass on %zu nodes)", owners[i].nnodes);
298        tui_line_push(f->a, &spans, tui_span_raw(MT_FMT(f, "%s %u%s  ", owners[i].name, owners[i].n, by)));
299    }
300    tui_line_push(f->a, &spans, mt_label(MT_FMT(f, " gen %u items %u", W(PT_V_GEN), W(PT_V_ITEMS))));
301    mt_par_line(f, spans, r[1]);
302#undef W
303}