diff options
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 |
