aboutsummaryrefslogtreecommitdiffstats
path: root/surfaces/display.c
diff options
context:
space:
mode:
Diffstat (limited to 'surfaces/display.c')
-rw-r--r--surfaces/display.c264
1 files changed, 264 insertions, 0 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);
+}