diff options
| author | eunhhu <[email protected]> | 2026-09-27 14:59:54 +0100 |
|---|---|---|
| committer | eunhhu <[email protected]> | 2026-09-27 14:59:54 +0100 |
| commit | 5313285bbfedf6154d2611c4923cff458d7ef271 (patch) | |
| tree | 81a9d1516f5666c351cf716b394523255d9a5183 /surfaces | |
| parent | 993871c6dfa19cf4e0bd4a16c9aad26cb0025d70 (diff) | |
| download | vega-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')
| -rw-r--r-- | surfaces/display.c | 264 | ||||
| -rw-r--r-- | surfaces/main.c | 89 | ||||
| -rw-r--r-- | surfaces/pixels.c | 4 | ||||
| -rw-r--r-- | surfaces/vega.h | 16 |
4 files changed, 363 insertions, 10 deletions
diff --git a/surfaces/display.c b/surfaces/display.c new file mode 100644 index 0000000..9207963 --- /dev/null +++ b/surfaces/display.c @@ -0,0 +1,264 @@ +#define _DEFAULT_SOURCE +#include "vega.h" +#include <drm/drm.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_mode.h> +#include <errno.h> +#include <fcntl.h> +#include <poll.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <unistd.h> + +/* A display through KMS (the kernel's mode setting), with no library between: + * a connector's mode on a CRTC, its primary plane showing one of two dumb + * buffers (0xRRGGBB pixels the surface draws into), flipped by atomic + * commits. On a writeback connector (a display controller's output written + * back to memory) each shown frame is captured, so the whole path can be + * checked with no screen attached. */ + +typedef struct { + uint32_t handle, id, pitch; /* dumb buffer, framebuffer */ + uint64_t size; + uint32_t *pixels; +} Buffer; + +struct VegaDisplay { + int fd; + uint32_t connector, crtc, plane, mode_blob; + struct drm_mode_modeinfo mode; + bool writeback, set; + Buffer buffers[2], capture; + unsigned back; + /* Frames are drawn here, in cached memory, then copied to the buffer + * shown: dumb buffers are write-combined, and blending reads what it + * draws over, which such memory makes slow. */ + uint32_t *shadow; + /* property ids */ + uint32_t connector_crtc, crtc_mode, crtc_active, plane_fb, plane_crtc, plane_src[4], plane_dst[4], writeback_fb, + writeback_fence; +}; + +enum { PRIMARY = 1 }; /* the value of a plane's "type" property for a primary plane */ + +static int io(int fd, unsigned long request, void *arg) { + int r; + do { r = ioctl(fd, request, arg); } while (r && (errno == EINTR || errno == EAGAIN)); + return r; +} + +/* The id of an object's property by name, and its value. */ +static uint32_t property(int fd, uint32_t object, uint32_t type, const char *name, uint64_t *value) { + struct drm_mode_obj_get_properties list = {.obj_id = object, .obj_type = type}; + if (io(fd, DRM_IOCTL_MODE_OBJ_GETPROPERTIES, &list) || !list.count_props) return 0; + uint32_t *ids = calloc(list.count_props, sizeof(*ids)); + uint64_t *values = calloc(list.count_props, sizeof(*values)); + uint32_t found = 0; + list.props_ptr = (uintptr_t)ids; + list.prop_values_ptr = (uintptr_t)values; + if (ids && values && !io(fd, DRM_IOCTL_MODE_OBJ_GETPROPERTIES, &list)) + for (uint32_t i = 0; i < list.count_props && !found; ++i) { + struct drm_mode_get_property p = {.prop_id = ids[i]}; + if (!io(fd, DRM_IOCTL_MODE_GETPROPERTY, &p) && !strcmp(p.name, name)) { + found = ids[i]; + if (value) *value = values[i]; + } + } + free(ids); + free(values); + return found; +} + +static bool make_buffer(int fd, unsigned width, unsigned height, Buffer *out) { + struct drm_mode_create_dumb create = {.width = width, .height = height, .bpp = 32}; + if (io(fd, DRM_IOCTL_MODE_CREATE_DUMB, &create)) return false; + *out = (Buffer){.handle = create.handle, .pitch = create.pitch, .size = create.size}; + struct drm_mode_fb_cmd2 fb = {.width = width, .height = height, .pixel_format = DRM_FORMAT_XRGB8888, + .handles = {create.handle}, .pitches = {create.pitch}}; + struct drm_mode_map_dumb map = {.handle = create.handle}; + if (io(fd, DRM_IOCTL_MODE_ADDFB2, &fb) || io(fd, DRM_IOCTL_MODE_MAP_DUMB, &map)) return false; + out->id = fb.fb_id; + void *pixels = mmap(NULL, create.size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, (off_t)map.offset); + if (pixels == MAP_FAILED) return false; + out->pixels = pixels; + return true; +} + +static void free_buffer(int fd, Buffer *buffer) { + if (buffer->pixels) munmap(buffer->pixels, buffer->size); + if (buffer->id) io(fd, DRM_IOCTL_MODE_RMFB, &buffer->id); + if (buffer->handle) { + struct drm_mode_destroy_dumb destroy = {.handle = buffer->handle}; + io(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy); + } + *buffer = (Buffer){0}; +} + +#define FAIL(...) do { snprintf(error, error_size, __VA_ARGS__); vega_display_close(d); return NULL; } while (0) + +VegaDisplay *vega_display_open(const char *card, bool writeback, unsigned width, unsigned height, char *error, + size_t error_size) { + VegaDisplay *d = calloc(1, sizeof(*d)); + if (!d) { snprintf(error, error_size, "out of memory"); return NULL; } + d->fd = open(card, O_RDWR | O_CLOEXEC); + d->writeback = writeback; + if (d->fd < 0) FAIL("cannot open %s: %s", card, strerror(errno)); + struct drm_set_client_cap caps[] = {{DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1}, {DRM_CLIENT_CAP_ATOMIC, 1}, + {DRM_CLIENT_CAP_WRITEBACK_CONNECTORS, 1}}; + for (unsigned i = 0; i < (writeback ? 3u : 2u); ++i) + if (io(d->fd, DRM_IOCTL_SET_CLIENT_CAP, &caps[i])) FAIL("%s has no atomic mode setting", card); + struct drm_mode_card_res res = {0}; + if (io(d->fd, DRM_IOCTL_MODE_GETRESOURCES, &res)) FAIL("%s sets no modes", card); + uint32_t crtcs[16] = {0}, connectors[16] = {0}, encoders[16] = {0}; + if (res.count_crtcs > 16 || res.count_connectors > 16 || res.count_encoders > 16) FAIL("too many outputs"); + res = (struct drm_mode_card_res){.crtc_id_ptr = (uintptr_t)crtcs, .connector_id_ptr = (uintptr_t)connectors, + .encoder_id_ptr = (uintptr_t)encoders, .count_crtcs = res.count_crtcs, + .count_connectors = res.count_connectors, .count_encoders = res.count_encoders}; + if (io(d->fd, DRM_IOCTL_MODE_GETRESOURCES, &res)) FAIL("%s sets no modes", card); + /* The first connected connector of the kind asked for, its preferred (or + * asked) mode, and a CRTC its encoder can drive. */ + uint32_t possible = 0; + for (uint32_t i = 0; i < res.count_connectors && !d->connector; ++i) { + struct drm_mode_modeinfo modes[64]; + struct drm_mode_get_connector c = {.connector_id = connectors[i]}; + if (io(d->fd, DRM_IOCTL_MODE_GETCONNECTOR, &c)) continue; + bool back = c.connector_type == DRM_MODE_CONNECTOR_WRITEBACK; + if (back != writeback || c.connection != 1 || !c.count_modes || c.count_modes > 64) continue; + uint32_t encoder_ids[8] = {0}; + struct drm_mode_get_connector full = {.connector_id = connectors[i], .modes_ptr = (uintptr_t)modes, + .count_modes = c.count_modes, .encoders_ptr = (uintptr_t)encoder_ids, + .count_encoders = c.count_encoders < 8 ? c.count_encoders : 8}; + if (io(d->fd, DRM_IOCTL_MODE_GETCONNECTOR, &full) || !full.count_encoders) continue; + int chosen = -1; + for (uint32_t m = 0; m < full.count_modes && chosen < 0; ++m) + if (width ? modes[m].hdisplay == width && modes[m].vdisplay == height : (modes[m].type & DRM_MODE_TYPE_PREFERRED)) + chosen = (int)m; + if (chosen < 0 && !width) chosen = 0; + if (chosen < 0) continue; + struct drm_mode_get_encoder e = {.encoder_id = encoder_ids[0]}; + if (io(d->fd, DRM_IOCTL_MODE_GETENCODER, &e)) continue; + d->connector = connectors[i]; + d->mode = modes[chosen]; + possible = e.possible_crtcs; + } + if (!d->connector) FAIL(writeback ? "no writeback connector with a %ux%u mode" : "no display connected (%ux%u)", width, height); + for (uint32_t i = 0; i < res.count_crtcs && !d->crtc; ++i) + if (possible & (1u << i)) d->crtc = crtcs[i]; + /* The CRTC's primary plane. */ + struct drm_mode_get_plane_res planes = {0}; + uint32_t plane_ids[64] = {0}; + if (io(d->fd, DRM_IOCTL_MODE_GETPLANERESOURCES, &planes) || planes.count_planes > 64) FAIL("no planes"); + planes.plane_id_ptr = (uintptr_t)plane_ids; + if (io(d->fd, DRM_IOCTL_MODE_GETPLANERESOURCES, &planes)) FAIL("no planes"); + int crtc_index = -1; + for (uint32_t i = 0; i < res.count_crtcs; ++i) if (crtcs[i] == d->crtc) crtc_index = (int)i; + for (uint32_t i = 0; i < planes.count_planes && !d->plane && crtc_index >= 0; ++i) { + struct drm_mode_get_plane p = {.plane_id = plane_ids[i]}; + uint64_t type = 0; + if (io(d->fd, DRM_IOCTL_MODE_GETPLANE, &p) || !(p.possible_crtcs & (1u << crtc_index))) continue; + if (property(d->fd, plane_ids[i], DRM_MODE_OBJECT_PLANE, "type", &type) && type == PRIMARY) + d->plane = plane_ids[i]; + } + if (!d->crtc || !d->plane) FAIL("no CRTC and plane for the connector"); + const char *src[4] = {"SRC_X", "SRC_Y", "SRC_W", "SRC_H"}, *dst[4] = {"CRTC_X", "CRTC_Y", "CRTC_W", "CRTC_H"}; + d->connector_crtc = property(d->fd, d->connector, DRM_MODE_OBJECT_CONNECTOR, "CRTC_ID", NULL); + d->crtc_mode = property(d->fd, d->crtc, DRM_MODE_OBJECT_CRTC, "MODE_ID", NULL); + d->crtc_active = property(d->fd, d->crtc, DRM_MODE_OBJECT_CRTC, "ACTIVE", NULL); + d->plane_fb = property(d->fd, d->plane, DRM_MODE_OBJECT_PLANE, "FB_ID", NULL); + d->plane_crtc = property(d->fd, d->plane, DRM_MODE_OBJECT_PLANE, "CRTC_ID", NULL); + for (int i = 0; i < 4; ++i) { + d->plane_src[i] = property(d->fd, d->plane, DRM_MODE_OBJECT_PLANE, src[i], NULL); + d->plane_dst[i] = property(d->fd, d->plane, DRM_MODE_OBJECT_PLANE, dst[i], NULL); + } + if (writeback) { + d->writeback_fb = property(d->fd, d->connector, DRM_MODE_OBJECT_CONNECTOR, "WRITEBACK_FB_ID", NULL); + d->writeback_fence = property(d->fd, d->connector, DRM_MODE_OBJECT_CONNECTOR, "WRITEBACK_OUT_FENCE_PTR", NULL); + if (!d->writeback_fb || !d->writeback_fence) FAIL("the writeback connector lacks its properties"); + } + if (!d->connector_crtc || !d->crtc_mode || !d->crtc_active || !d->plane_fb || !d->plane_crtc) FAIL("missing properties"); + struct drm_mode_create_blob blob = {.data = (uintptr_t)&d->mode, .length = sizeof(d->mode)}; + if (io(d->fd, DRM_IOCTL_MODE_CREATEPROPBLOB, &blob)) FAIL("cannot make the mode"); + d->mode_blob = blob.blob_id; + for (int i = 0; i < 2; ++i) + if (!make_buffer(d->fd, d->mode.hdisplay, d->mode.vdisplay, &d->buffers[i])) FAIL("cannot make buffers"); + if (writeback && !make_buffer(d->fd, d->mode.hdisplay, d->mode.vdisplay, &d->capture)) FAIL("cannot make buffers"); + if (!(d->shadow = malloc((size_t)d->mode.hdisplay * d->mode.vdisplay * sizeof(*d->shadow)))) FAIL("out of memory"); + return d; +} + +uint32_t *vega_display_buffer(VegaDisplay *d, unsigned *width, unsigned *height, size_t *stride) { + *width = d->mode.hdisplay; + *height = d->mode.vdisplay; + *stride = d->mode.hdisplay; + return d->shadow; +} + +const uint32_t *vega_display_captured(const VegaDisplay *d, size_t *stride) { + *stride = d->capture.pitch / 4; + return d->writeback ? d->capture.pixels : NULL; +} + +/* One atomic commit: the back buffer on the plane (and, the first time, the + * mode on the CRTC and the CRTC on the connector); on writeback, the capture + * buffer and a fence for when it holds the frame. */ +bool vega_display_show(VegaDisplay *d) { + uint32_t objects[3], counts[3] = {0}, ids[24]; + uint64_t values[24]; + unsigned n = 0, o = 0; + int fence = -1; +#define SET(object, id, value) do { \ + if (!o || objects[o - 1] != (object)) objects[o++] = (object); \ + ++counts[o - 1]; ids[n] = (id); values[n++] = (value); } while (0) + Buffer *shown = &d->buffers[d->back]; + for (unsigned y = 0; y < d->mode.vdisplay; ++y) + memcpy((unsigned char *)shown->pixels + (size_t)y * shown->pitch, d->shadow + (size_t)y * d->mode.hdisplay, + (size_t)d->mode.hdisplay * sizeof(*d->shadow)); + if (!d->set || d->writeback) SET(d->connector, d->connector_crtc, d->crtc); + if (d->writeback) { + SET(d->connector, d->writeback_fb, d->capture.id); + SET(d->connector, d->writeback_fence, (uintptr_t)&fence); + } + if (!d->set) { + SET(d->crtc, d->crtc_mode, d->mode_blob); + SET(d->crtc, d->crtc_active, 1); + } + SET(d->plane, d->plane_fb, shown->id); + SET(d->plane, d->plane_crtc, d->crtc); + uint64_t size[4] = {0, 0, (uint64_t)d->mode.hdisplay << 16, (uint64_t)d->mode.vdisplay << 16}; + uint64_t place[4] = {0, 0, d->mode.hdisplay, d->mode.vdisplay}; + for (int i = 0; i < 4; ++i) { + if (d->plane_src[i]) SET(d->plane, d->plane_src[i], size[i]); + if (d->plane_dst[i]) SET(d->plane, d->plane_dst[i], place[i]); + } +#undef SET + struct drm_mode_atomic commit = {.flags = d->set ? 0 : DRM_MODE_ATOMIC_ALLOW_MODESET, .count_objs = o, + .objs_ptr = (uintptr_t)objects, .count_props_ptr = (uintptr_t)counts, + .props_ptr = (uintptr_t)ids, .prop_values_ptr = (uintptr_t)values}; + if (io(d->fd, DRM_IOCTL_MODE_ATOMIC, &commit)) return false; + d->set = true; + d->back ^= 1; + if (fence >= 0) { /* the frame is in the capture buffer when the fence signals */ + struct pollfd wait = {.fd = fence, .events = POLLIN}; + bool done = poll(&wait, 1, 1000) == 1; + close(fence); + if (!done) return false; + } + return true; +} + +void vega_display_close(VegaDisplay *d) { + if (!d) return; + if (d->fd >= 0) { + for (int i = 0; i < 2; ++i) free_buffer(d->fd, &d->buffers[i]); + free_buffer(d->fd, &d->capture); + free(d->shadow); + if (d->mode_blob) { + struct drm_mode_destroy_blob blob = {.blob_id = d->mode_blob}; + io(d->fd, DRM_IOCTL_MODE_DESTROYPROPBLOB, &blob); + } + close(d->fd); + } + free(d); +} 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; } diff --git a/surfaces/pixels.c b/surfaces/pixels.c index 67e7bc5..f671143 100644 --- a/surfaces/pixels.c +++ b/surfaces/pixels.c @@ -67,6 +67,10 @@ static void missing(uint32_t *out, size_t stride, int x, int y, int width, int h for (int j = top; j <= bottom; ++j) out[(size_t)j * stride + (size_t)left] = out[(size_t)j * stride + (size_t)right] = color; } +void vega_pixels_clear(uint32_t *out, size_t stride, unsigned width, unsigned height) { + fill(out, stride, 0, 0, (int)width, (int)height, background); +} + void vega_pixels(const VegaGrid *grid, VegaFace *face, uint32_t *out, size_t stride) { int width = (int)grid->width * face->cell_width, height = (int)grid->height * face->cell_height; /* Backgrounds first: a wide glyph spans its neighbour's cell. */ diff --git a/surfaces/vega.h b/surfaces/vega.h index b8e0044..69eba0b 100644 --- a/surfaces/vega.h +++ b/surfaces/vega.h @@ -135,4 +135,20 @@ void vega_face_close(VegaFace *); /* The grid into out: 0xRRGGBB pixels, grid->width * cell_width across and * grid->height * cell_height down, stride pixels apart. */ void vega_pixels(const VegaGrid *, VegaFace *, uint32_t *out, size_t stride); +/* width x height of out in the background's colour. */ +void vega_pixels_clear(uint32_t *out, size_t stride, unsigned width, unsigned height); + +/* A display through KMS: the first connected connector (or, with writeback, + * a writeback connector: the frames come back to memory, so no screen is + * needed to check them), its preferred mode or width x height. */ +typedef struct VegaDisplay VegaDisplay; +VegaDisplay *vega_display_open(const char *card, bool writeback, unsigned width, unsigned height, char *error, + size_t error_size); +/* The buffer to draw the next frame into: 0xRRGGBB, stride pixels apart. */ +uint32_t *vega_display_buffer(VegaDisplay *, unsigned *width, unsigned *height, size_t *stride); +/* Show it (and, on writeback, wait until the frame has come back). */ +bool vega_display_show(VegaDisplay *); +/* Writeback: the last frame as the display controller put it out. */ +const uint32_t *vega_display_captured(const VegaDisplay *, size_t *stride); +void vega_display_close(VegaDisplay *); #endif |
