aboutsummaryrefslogtreecommitdiffstats
path: root/surfaces/main.c
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-09-27 14:59:54 +0100
committereunhhu <[email protected]>2026-09-27 14:59:54 +0100
commit5313285bbfedf6154d2611c4923cff458d7ef271 (patch)
tree81a9d1516f5666c351cf716b394523255d9a5183 /surfaces/main.c
parent993871c6dfa19cf4e0bd4a16c9aad26cb0025d70 (diff)
downloadvega-5313285bbfedf6154d2611c4923cff458d7ef271.tar.gz
vega-5313285bbfedf6154d2611c4923cff458d7ef271.zip
Put the frames on a display through KMS, checked by writeback
vega display draws the same frames on a display with the kernel's mode setting and no library between: a connected connector's mode on a CRTC, its primary plane flipped between two dumb buffers by atomic commits, the grid as many cells as fit. Frames are drawn in cached memory and copied over, since blending over write-combined buffers read them back slowly (a 1080p frame took 370 ms; now 20.6 ms with a vertical sync and the check below). This Pi has no screen but its display controller has writeback connectors: --writeback sets a mode on one and takes every frame back as the controller put it out. Each came back exactly as drawn, and the new check finds it the pixel surface's frame for the same grid, pixel for pixel.
Diffstat (limited to 'surfaces/main.c')
-rw-r--r--surfaces/main.c89
1 files changed, 79 insertions, 10 deletions
diff --git a/surfaces/main.c b/surfaces/main.c
index d00bca6..0d02cce 100644
--- a/surfaces/main.c
+++ b/surfaces/main.c
@@ -23,6 +23,11 @@
* pixels --symbols FONT.vsym [--size PX] --grid WxH --out PREFIX
* the TUI's frames drawn with a font's glyphs, driven by a
* script: each show writes PREFIX-N.ppm after the text
+ * display --symbols FONT.vsym [--size PX] [--card PATH] [--writeback WxH --out PREFIX]
+ * the same on a display through KMS, as many cells as fit,
+ * driven by a script; with writeback each shown frame comes
+ * back from the display controller, is set against what was
+ * drawn and written to PREFIX-N.ppm
* With --store the app's states live in that tree store: the app continues
* where it was, whichever surface runs it. With --watch (tui, web) a module
* written again replaces the running one and states keep their values by
@@ -459,7 +464,7 @@ static int interactive(void) {
/* ---- scripts -------------------------------------------------------- */
-typedef enum { SURFACE_CLI, SURFACE_SAY, SURFACE_TUI, SURFACE_WEB, SURFACE_PIXELS } Surface;
+typedef enum { SURFACE_CLI, SURFACE_SAY, SURFACE_TUI, SURFACE_WEB, SURFACE_PIXELS, SURFACE_DISPLAY } Surface;
/* The pixel surface's font and where its frames go. */
static VegaFace face;
@@ -490,11 +495,48 @@ static bool write_frame(const VegaGrid *grid, char path[512]) {
return ok;
}
+static VegaDisplay *display;
+
+/* A frame on the display; with writeback, what came back is set against it
+ * and written (differing: how many pixels are not what was drawn). */
+static bool show_frame(const VegaGrid *grid, char path[512], unsigned long *differing) {
+ unsigned width, height;
+ size_t stride, captured_stride;
+ uint32_t *out = vega_display_buffer(display, &width, &height, &stride);
+ vega_pixels_clear(out, stride, width, height);
+ vega_pixels(grid, &face, out, stride);
+ if (!vega_display_show(display)) return false;
+ const uint32_t *back = vega_display_captured(display, &captured_stride);
+ *differing = 0;
+ path[0] = 0;
+ if (!back || !frames_prefix) return true;
+ /* The capture read once, a row at a time (it is not cached memory either). */
+ uint32_t *row = malloc((size_t)width * sizeof(*row));
+ unsigned char *rgb = malloc((size_t)width * height * 3);
+ bool ok = row && rgb;
+ for (unsigned y = 0; ok && y < height; ++y) {
+ memcpy(row, back + (size_t)y * captured_stride, (size_t)width * sizeof(*row));
+ for (unsigned x = 0; x < width; ++x) {
+ *differing += (row[x] & 0xffffff) != (out[(size_t)y * stride + x] & 0xffffff);
+ unsigned char *p = rgb + ((size_t)y * width + x) * 3;
+ p[0] = (unsigned char)(row[x] >> 16), p[1] = (unsigned char)(row[x] >> 8), p[2] = (unsigned char)row[x];
+ }
+ }
+ snprintf(path, 512, "%s-%u.ppm", frames_prefix, ++frames);
+ FILE *f = ok ? fopen(path, "wb") : NULL;
+ ok = f && fprintf(f, "P6\n%u %u\n255\n", width, height) > 0 && fwrite(rgb, 3, (size_t)width * height, f) == (size_t)width * height;
+ if (f && fclose(f)) ok = false;
+ free(row);
+ free(rgb);
+ return ok;
+}
+
+
/* painting: show writes the terminal bytes of the frame, not the grid. */
static int script(Surface surface, unsigned width, unsigned height, bool painting) {
static char line[1 << 16];
VegaGrid grid = {0};
- bool gridded = surface == SURFACE_TUI || surface == SURFACE_PIXELS;
+ bool gridded = surface == SURFACE_TUI || surface == SURFACE_PIXELS || surface == SURFACE_DISPLAY;
if (gridded && !vega_grid(&grid, width, height)) return 2;
bool shown = false;
drawn = gridded ? &grid : NULL;
@@ -572,9 +614,13 @@ static int script(Surface surface, unsigned width, unsigned height, bool paintin
dump(&grid);
printf("cells %lu\n", vega_evaluations() - counted);
char path[512];
+ unsigned long differing;
if (surface == SURFACE_PIXELS) {
if (!write_frame(&grid, path)) { fprintf(stderr, "vega: cannot write %s\n", path); return 1; }
printf("frame %s %dx%d\n", path, (int)grid.width * face.cell_width, (int)grid.height * face.cell_height);
+ } else if (surface == SURFACE_DISPLAY) {
+ if (!show_frame(&grid, path, &differing)) { fputs("vega: the display did not take the frame\n", stderr); return 1; }
+ if (path[0]) printf("frame %s differing %lu grid %ux%u\n", path, differing, grid.width, grid.height);
}
}
counted = vega_evaluations();
@@ -594,6 +640,9 @@ static int script(Surface surface, unsigned width, unsigned height, bool paintin
return 0;
}
+/* Surfaces that draw glyphs. */
+static bool glyphed(const char *surface) { return !strcmp(surface, "pixels") || !strcmp(surface, "display"); }
+
/* The symbols a pixel surface draws with, read whole and kept. */
static bool open_face(const char *path, float size) {
static VegaSymbols symbols;
@@ -614,8 +663,9 @@ static bool open_face(const char *path, float size) {
int main(int argc, char **argv) {
unsigned width = 0, height = 0, port = 8080;
- const char *symbols_path = NULL;
+ const char *symbols_path = NULL, *card = "/dev/dri/card1";
float size = 16;
+ bool writeback = false;
bool dumped = false, painting = false, watch = false, usage = argc < 3;
const char *store = NULL, *listen_on = "127.0.0.1";
for (int i = 3; !usage && i < argc; i += 2) {
@@ -633,29 +683,47 @@ int main(int argc, char **argv) {
(!strcmp(argv[i], "--grid") && !strcmp(argv[1], "pixels"))) &&
sscanf(argv[i + 1], "%ux%u", &width, &height) == 2 && width && height && width <= 1000 && height <= 1000)
dumped = true, painting = !strcmp(argv[i], "--paint");
- else if (!strcmp(argv[i], "--symbols") && !strcmp(argv[1], "pixels")) symbols_path = argv[i + 1];
- else if (!strcmp(argv[i], "--out") && !strcmp(argv[1], "pixels")) frames_prefix = argv[i + 1];
- else if (!strcmp(argv[i], "--size") && !strcmp(argv[1], "pixels") && sscanf(argv[i + 1], "%f", &size) == 1) {}
+ else if (!strcmp(argv[i], "--writeback") && !strcmp(argv[1], "display") &&
+ sscanf(argv[i + 1], "%ux%u", &width, &height) == 2 && width && height)
+ writeback = true;
+ else if (!strcmp(argv[i], "--card") && !strcmp(argv[1], "display")) card = argv[i + 1];
+ else if (!strcmp(argv[i], "--symbols") && glyphed(argv[1])) symbols_path = argv[i + 1];
+ else if (!strcmp(argv[i], "--out") && glyphed(argv[1])) frames_prefix = argv[i + 1];
+ else if (!strcmp(argv[i], "--size") && glyphed(argv[1]) && sscanf(argv[i + 1], "%f", &size) == 1) {}
else usage = true;
}
- bool pixels = argc > 1 && !strcmp(argv[1], "pixels");
+ bool pixels = argc > 1 && !strcmp(argv[1], "pixels"), on_display = argc > 1 && !strcmp(argv[1], "display");
if (usage || (dumped && strcmp(argv[1], "tui") && !pixels) || (!strcmp(argv[1], "check") && argc != 3) ||
- (pixels && (!dumped || !symbols_path || !frames_prefix))) {
+ (pixels && (!dumped || !symbols_path || !frames_prefix)) || (on_display && (!symbols_path || (writeback && !frames_prefix)))) {
fputs("vega tui|cli|say MODULE.pmod [--store PATH]\n"
"vega tui MODULE.pmod [--store PATH] [--watch] | --dump|--paint WIDTHxHEIGHT < script\n"
"vega web MODULE.pmod [--store PATH] [--listen ADDRESS] [--port N] [--watch]\n"
"vega pixels MODULE.pmod [--store PATH] --symbols FONT.vsym [--size PX] --grid WIDTHxHEIGHT --out PREFIX < script\n"
+ "vega display MODULE.pmod [--store PATH] --symbols FONT.vsym [--size PX] [--card PATH]\n"
+ " [--writeback WIDTHxHEIGHT --out PREFIX] < script\n"
"vega check MODULE.pmod\n", stderr);
return 2;
}
- if (pixels && !open_face(symbols_path, size)) return 2;
+ if ((pixels || on_display) && !open_face(symbols_path, size)) return 2;
+ if (on_display) {
+ char error[256];
+ if (!(display = vega_display_open(card, writeback, writeback ? width : 0, writeback ? height : 0, error, sizeof(error)))) {
+ fprintf(stderr, "vega: %s\n", error);
+ return 2;
+ }
+ unsigned pixels_across, pixels_down;
+ size_t stride;
+ vega_display_buffer(display, &pixels_across, &pixels_down, &stride);
+ width = pixels_across / (unsigned)face.cell_width;
+ height = pixels_down / (unsigned)face.cell_height;
+ }
if (!strcmp(argv[1], "check")) {
if (!load_module(argv[2], &modules[0])) return 2;
return vega_check(&modules[0], print_finding, argv[2]) ? 1 : 0;
}
Surface surface = !strcmp(argv[1], "cli") ? SURFACE_CLI : !strcmp(argv[1], "say") ? SURFACE_SAY :
!strcmp(argv[1], "tui") ? SURFACE_TUI : !strcmp(argv[1], "web") ? SURFACE_WEB :
- pixels ? SURFACE_PIXELS : (Surface)-1;
+ pixels ? SURFACE_PIXELS : on_display ? SURFACE_DISPLAY : (Surface)-1;
if ((int)surface < 0) { fprintf(stderr, "vega: unknown surface %s\n", argv[1]); return 2; }
if (!load(argv[2], store)) return 2;
if (watch && !dumped) {
@@ -667,5 +735,6 @@ int main(int argc, char **argv) {
/* Frame changes are stored with events; keep the last ones too. */
if (pure_app_save(&app) != PURE_OK) { fputs("vega: cannot save\n", stderr); status = status ? status : 1; }
pure_app_stop(&app);
+ vega_display_close(display);
return status;
}