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