aboutsummaryrefslogtreecommitdiffstats
path: root/surfaces
diff options
context:
space:
mode:
Diffstat (limited to 'surfaces')
-rw-r--r--surfaces/display.c264
-rw-r--r--surfaces/main.c89
-rw-r--r--surfaces/pixels.c4
-rw-r--r--surfaces/vega.h16
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