aboutsummaryrefslogtreecommitdiff
path: root/backend/drm
diff options
context:
space:
mode:
Diffstat (limited to 'backend/drm')
-rw-r--r--backend/drm/atomic.c273
-rw-r--r--backend/drm/backend.c211
-rw-r--r--backend/drm/drm.c1418
-rw-r--r--backend/drm/legacy.c83
-rw-r--r--backend/drm/properties.c151
-rw-r--r--backend/drm/renderer.c264
-rw-r--r--backend/drm/util.c348
7 files changed, 2748 insertions, 0 deletions
diff --git a/backend/drm/atomic.c b/backend/drm/atomic.c
new file mode 100644
index 00000000..fc649d68
--- /dev/null
+++ b/backend/drm/atomic.c
@@ -0,0 +1,273 @@
+#include <gbm.h>
+#include <stdlib.h>
+#include <wlr/util/log.h>
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+#include "backend/drm/drm.h"
+#include "backend/drm/iface.h"
+#include "backend/drm/util.h"
+
+struct atomic {
+ drmModeAtomicReq *req;
+ int cursor;
+ bool failed;
+};
+
+static void atomic_begin(struct wlr_drm_crtc *crtc, struct atomic *atom) {
+ if (!crtc->atomic) {
+ crtc->atomic = drmModeAtomicAlloc();
+ if (!crtc->atomic) {
+ wlr_log_errno(WLR_ERROR, "Allocation failed");
+ atom->failed = true;
+ return;
+ }
+ }
+
+ atom->req = crtc->atomic;
+ atom->cursor = drmModeAtomicGetCursor(atom->req);
+ atom->failed = false;
+}
+
+static bool atomic_end(int drm_fd, struct atomic *atom) {
+ if (atom->failed) {
+ return false;
+ }
+
+ uint32_t flags = DRM_MODE_ATOMIC_TEST_ONLY | DRM_MODE_ATOMIC_NONBLOCK;
+ if (drmModeAtomicCommit(drm_fd, atom->req, flags, NULL)) {
+ wlr_log_errno(WLR_ERROR, "Atomic test failed");
+ drmModeAtomicSetCursor(atom->req, atom->cursor);
+ return false;
+ }
+
+ return true;
+}
+
+static bool atomic_commit(int drm_fd, struct atomic *atom,
+ struct wlr_drm_connector *conn, uint32_t flags, bool modeset) {
+ if (atom->failed) {
+ return false;
+ }
+
+ int ret = drmModeAtomicCommit(drm_fd, atom->req, flags, conn);
+ if (ret) {
+ wlr_log_errno(WLR_ERROR, "%s: Atomic commit failed (%s)",
+ conn->output.name, modeset ? "modeset" : "pageflip");
+
+ // Try to commit without new changes
+ drmModeAtomicSetCursor(atom->req, atom->cursor);
+ if (drmModeAtomicCommit(drm_fd, atom->req, flags, conn)) {
+ wlr_log_errno(WLR_ERROR,
+ "%s: Atomic commit without new changes failed (%s)",
+ conn->output.name, modeset ? "modeset" : "pageflip");
+ }
+ }
+
+ drmModeAtomicSetCursor(atom->req, 0);
+
+ return !ret;
+}
+
+static inline void atomic_add(struct atomic *atom, uint32_t id, uint32_t prop, uint64_t val) {
+ if (!atom->failed && drmModeAtomicAddProperty(atom->req, id, prop, val) < 0) {
+ wlr_log_errno(WLR_ERROR, "Failed to add atomic DRM property");
+ atom->failed = true;
+ }
+}
+
+static void set_plane_props(struct atomic *atom, struct wlr_drm_plane *plane,
+ uint32_t crtc_id, uint32_t fb_id, bool set_crtc_xy) {
+ uint32_t id = plane->id;
+ const union wlr_drm_plane_props *props = &plane->props;
+
+ // The src_* properties are in 16.16 fixed point
+ atomic_add(atom, id, props->src_x, 0);
+ atomic_add(atom, id, props->src_y, 0);
+ atomic_add(atom, id, props->src_w, (uint64_t)plane->surf.width << 16);
+ atomic_add(atom, id, props->src_h, (uint64_t)plane->surf.height << 16);
+ atomic_add(atom, id, props->crtc_w, plane->surf.width);
+ atomic_add(atom, id, props->crtc_h, plane->surf.height);
+ atomic_add(atom, id, props->fb_id, fb_id);
+ atomic_add(atom, id, props->crtc_id, crtc_id);
+ if (set_crtc_xy) {
+ atomic_add(atom, id, props->crtc_x, 0);
+ atomic_add(atom, id, props->crtc_y, 0);
+ }
+}
+
+static bool atomic_crtc_pageflip(struct wlr_drm_backend *drm,
+ struct wlr_drm_connector *conn,
+ struct wlr_drm_crtc *crtc,
+ uint32_t fb_id, drmModeModeInfo *mode) {
+ if (mode != NULL) {
+ if (crtc->mode_id != 0) {
+ drmModeDestroyPropertyBlob(drm->fd, crtc->mode_id);
+ }
+
+ if (drmModeCreatePropertyBlob(drm->fd, mode, sizeof(*mode),
+ &crtc->mode_id)) {
+ wlr_log_errno(WLR_ERROR, "Unable to create property blob");
+ return false;
+ }
+ }
+
+ uint32_t flags = DRM_MODE_PAGE_FLIP_EVENT;
+ if (mode != NULL) {
+ flags |= DRM_MODE_ATOMIC_ALLOW_MODESET;
+ } else {
+ flags |= DRM_MODE_ATOMIC_NONBLOCK;
+ }
+
+ struct atomic atom;
+ atomic_begin(crtc, &atom);
+ atomic_add(&atom, conn->id, conn->props.crtc_id, crtc->id);
+ if (mode != NULL && conn->props.link_status != 0) {
+ atomic_add(&atom, conn->id, conn->props.link_status,
+ DRM_MODE_LINK_STATUS_GOOD);
+ }
+ atomic_add(&atom, crtc->id, crtc->props.mode_id, crtc->mode_id);
+ atomic_add(&atom, crtc->id, crtc->props.active, 1);
+ set_plane_props(&atom, crtc->primary, crtc->id, fb_id, true);
+ return atomic_commit(drm->fd, &atom, conn, flags, mode);
+}
+
+static bool atomic_conn_enable(struct wlr_drm_backend *drm,
+ struct wlr_drm_connector *conn, bool enable) {
+ struct wlr_drm_crtc *crtc = conn->crtc;
+ if (crtc == NULL) {
+ return !enable;
+ }
+
+ struct atomic atom;
+ atomic_begin(crtc, &atom);
+ atomic_add(&atom, crtc->id, crtc->props.active, enable);
+ if (enable) {
+ atomic_add(&atom, conn->id, conn->props.crtc_id, crtc->id);
+ atomic_add(&atom, crtc->id, crtc->props.mode_id, crtc->mode_id);
+ } else {
+ atomic_add(&atom, conn->id, conn->props.crtc_id, 0);
+ atomic_add(&atom, crtc->id, crtc->props.mode_id, 0);
+ }
+ return atomic_commit(drm->fd, &atom, conn, DRM_MODE_ATOMIC_ALLOW_MODESET,
+ true);
+}
+
+bool legacy_crtc_set_cursor(struct wlr_drm_backend *drm,
+ struct wlr_drm_crtc *crtc, struct gbm_bo *bo);
+
+static bool atomic_crtc_set_cursor(struct wlr_drm_backend *drm,
+ struct wlr_drm_crtc *crtc, struct gbm_bo *bo) {
+ if (!crtc || !crtc->cursor) {
+ return true;
+ }
+
+ struct wlr_drm_plane *plane = crtc->cursor;
+ // We can't use atomic operations on fake planes
+ if (plane->id == 0) {
+ return legacy_crtc_set_cursor(drm, crtc, bo);
+ }
+
+ struct atomic atom;
+
+ atomic_begin(crtc, &atom);
+
+ if (bo) {
+ set_plane_props(&atom, plane, crtc->id, get_fb_for_bo(bo), false);
+ } else {
+ atomic_add(&atom, plane->id, plane->props.fb_id, 0);
+ atomic_add(&atom, plane->id, plane->props.crtc_id, 0);
+ }
+
+ return atomic_end(drm->fd, &atom);
+}
+
+bool legacy_crtc_move_cursor(struct wlr_drm_backend *drm,
+ struct wlr_drm_crtc *crtc, int x, int y);
+
+static bool atomic_crtc_move_cursor(struct wlr_drm_backend *drm,
+ struct wlr_drm_crtc *crtc, int x, int y) {
+ if (!crtc || !crtc->cursor) {
+ return true;
+ }
+
+ struct wlr_drm_plane *plane = crtc->cursor;
+ // We can't use atomic operations on fake planes
+ if (plane->id == 0) {
+ return legacy_crtc_move_cursor(drm, crtc, x, y);
+ }
+
+ struct atomic atom;
+
+ atomic_begin(crtc, &atom);
+ atomic_add(&atom, plane->id, plane->props.crtc_x, x);
+ atomic_add(&atom, plane->id, plane->props.crtc_y, y);
+ return atomic_end(drm->fd, &atom);
+}
+
+static bool atomic_crtc_set_gamma(struct wlr_drm_backend *drm,
+ struct wlr_drm_crtc *crtc, size_t size,
+ uint16_t *r, uint16_t *g, uint16_t *b) {
+ // Fallback to legacy gamma interface when gamma properties are not available
+ // (can happen on older Intel GPUs that support gamma but not degamma).
+ // TEMP: This is broken on AMDGPU. Provide a fallback to legacy until they
+ // get it fixed. Ref https://bugs.freedesktop.org/show_bug.cgi?id=107459
+ const char *no_atomic_str = getenv("WLR_DRM_NO_ATOMIC_GAMMA");
+ bool no_atomic = no_atomic_str != NULL && strcmp(no_atomic_str, "1") == 0;
+ if (crtc->props.gamma_lut == 0 || no_atomic) {
+ return legacy_iface.crtc_set_gamma(drm, crtc, size, r, g, b);
+ }
+
+ struct drm_color_lut *gamma = malloc(size * sizeof(struct drm_color_lut));
+ if (gamma == NULL) {
+ wlr_log(WLR_ERROR, "Failed to allocate gamma table");
+ return false;
+ }
+
+ for (size_t i = 0; i < size; i++) {
+ gamma[i].red = r[i];
+ gamma[i].green = g[i];
+ gamma[i].blue = b[i];
+ }
+
+ if (crtc->gamma_lut != 0) {
+ drmModeDestroyPropertyBlob(drm->fd, crtc->gamma_lut);
+ }
+
+ if (drmModeCreatePropertyBlob(drm->fd, gamma,
+ size * sizeof(struct drm_color_lut), &crtc->gamma_lut)) {
+ free(gamma);
+ wlr_log_errno(WLR_ERROR, "Unable to create property blob");
+ return false;
+ }
+ free(gamma);
+
+ struct atomic atom;
+ atomic_begin(crtc, &atom);
+ atomic_add(&atom, crtc->id, crtc->props.gamma_lut, crtc->gamma_lut);
+ return atomic_end(drm->fd, &atom);
+}
+
+static size_t atomic_crtc_get_gamma_size(struct wlr_drm_backend *drm,
+ struct wlr_drm_crtc *crtc) {
+ if (crtc->props.gamma_lut_size == 0) {
+ return legacy_iface.crtc_get_gamma_size(drm, crtc);
+ }
+
+ uint64_t gamma_lut_size;
+ if (!get_drm_prop(drm->fd, crtc->id, crtc->props.gamma_lut_size,
+ &gamma_lut_size)) {
+ wlr_log(WLR_ERROR, "Unable to get gamma lut size");
+ return 0;
+ }
+
+ return (size_t)gamma_lut_size;
+}
+
+const struct wlr_drm_interface atomic_iface = {
+ .conn_enable = atomic_conn_enable,
+ .crtc_pageflip = atomic_crtc_pageflip,
+ .crtc_set_cursor = atomic_crtc_set_cursor,
+ .crtc_move_cursor = atomic_crtc_move_cursor,
+ .crtc_set_gamma = atomic_crtc_set_gamma,
+ .crtc_get_gamma_size = atomic_crtc_get_gamma_size,
+};
diff --git a/backend/drm/backend.c b/backend/drm/backend.c
new file mode 100644
index 00000000..a9082077
--- /dev/null
+++ b/backend/drm/backend.c
@@ -0,0 +1,211 @@
+#include <assert.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <wayland-server.h>
+#include <wlr/backend/interface.h>
+#include <wlr/backend/session.h>
+#include <wlr/interfaces/wlr_output.h>
+#include <wlr/render/egl.h>
+#include <wlr/types/wlr_list.h>
+#include <wlr/util/log.h>
+#include <xf86drm.h>
+#include "backend/drm/drm.h"
+#include "util/signal.h"
+
+struct wlr_drm_backend *get_drm_backend_from_backend(
+ struct wlr_backend *wlr_backend) {
+ assert(wlr_backend_is_drm(wlr_backend));
+ return (struct wlr_drm_backend *)wlr_backend;
+}
+
+static bool backend_start(struct wlr_backend *backend) {
+ struct wlr_drm_backend *drm = get_drm_backend_from_backend(backend);
+ scan_drm_connectors(drm);
+ return true;
+}
+
+static void backend_destroy(struct wlr_backend *backend) {
+ if (!backend) {
+ return;
+ }
+
+ struct wlr_drm_backend *drm = get_drm_backend_from_backend(backend);
+
+ restore_drm_outputs(drm);
+
+ struct wlr_drm_connector *conn, *next;
+ wl_list_for_each_safe(conn, next, &drm->outputs, link) {
+ wlr_output_destroy(&conn->output);
+ }
+
+ wlr_signal_emit_safe(&backend->events.destroy, backend);
+
+ wl_list_remove(&drm->display_destroy.link);
+ wl_list_remove(&drm->session_signal.link);
+ wl_list_remove(&drm->drm_invalidated.link);
+
+ finish_drm_resources(drm);
+ finish_drm_renderer(&drm->renderer);
+ wlr_session_close_file(drm->session, drm->fd);
+ wl_event_source_remove(drm->drm_event);
+ free(drm);
+}
+
+static struct wlr_renderer *backend_get_renderer(
+ struct wlr_backend *backend) {
+ struct wlr_drm_backend *drm = get_drm_backend_from_backend(backend);
+
+ if (drm->parent) {
+ return drm->parent->renderer.wlr_rend;
+ } else {
+ return drm->renderer.wlr_rend;
+ }
+}
+
+static clockid_t backend_get_presentation_clock(struct wlr_backend *backend) {
+ struct wlr_drm_backend *drm = get_drm_backend_from_backend(backend);
+ return drm->clock;
+}
+
+static struct wlr_backend_impl backend_impl = {
+ .start = backend_start,
+ .destroy = backend_destroy,
+ .get_renderer = backend_get_renderer,
+ .get_presentation_clock = backend_get_presentation_clock,
+};
+
+bool wlr_backend_is_drm(struct wlr_backend *b) {
+ return b->impl == &backend_impl;
+}
+
+static void session_signal(struct wl_listener *listener, void *data) {
+ struct wlr_drm_backend *drm =
+ wl_container_of(listener, drm, session_signal);
+ struct wlr_session *session = data;
+
+ if (session->active) {
+ wlr_log(WLR_INFO, "DRM fd resumed");
+ scan_drm_connectors(drm);
+
+ struct wlr_drm_connector *conn;
+ wl_list_for_each(conn, &drm->outputs, link){
+ if (conn->output.enabled) {
+ wlr_output_set_mode(&conn->output, conn->output.current_mode);
+ } else {
+ enable_drm_connector(&conn->output, false);
+ }
+
+ if (!conn->crtc) {
+ continue;
+ }
+
+ struct wlr_drm_plane *plane = conn->crtc->cursor;
+ drm->iface->crtc_set_cursor(drm, conn->crtc,
+ (plane && plane->cursor_enabled) ? plane->cursor_bo : NULL);
+ drm->iface->crtc_move_cursor(drm, conn->crtc, conn->cursor_x,
+ conn->cursor_y);
+
+ if (conn->crtc->gamma_table != NULL) {
+ size_t size = conn->crtc->gamma_table_size;
+ uint16_t *r = conn->crtc->gamma_table;
+ uint16_t *g = conn->crtc->gamma_table + size;
+ uint16_t *b = conn->crtc->gamma_table + 2 * size;
+ drm->iface->crtc_set_gamma(drm, conn->crtc, size, r, g, b);
+ } else {
+ set_drm_connector_gamma(&conn->output, 0, NULL, NULL, NULL);
+ }
+ }
+ } else {
+ wlr_log(WLR_INFO, "DRM fd paused");
+ }
+}
+
+static void drm_invalidated(struct wl_listener *listener, void *data) {
+ struct wlr_drm_backend *drm =
+ wl_container_of(listener, drm, drm_invalidated);
+
+ char *name = drmGetDeviceNameFromFd2(drm->fd);
+ wlr_log(WLR_DEBUG, "%s invalidated", name);
+ free(name);
+
+ scan_drm_connectors(drm);
+}
+
+static void handle_display_destroy(struct wl_listener *listener, void *data) {
+ struct wlr_drm_backend *drm =
+ wl_container_of(listener, drm, display_destroy);
+ backend_destroy(&drm->backend);
+}
+
+struct wlr_backend *wlr_drm_backend_create(struct wl_display *display,
+ struct wlr_session *session, int gpu_fd, struct wlr_backend *parent,
+ wlr_renderer_create_func_t create_renderer_func) {
+ assert(display && session && gpu_fd >= 0);
+ assert(!parent || wlr_backend_is_drm(parent));
+
+ char *name = drmGetDeviceNameFromFd2(gpu_fd);
+ drmVersion *version = drmGetVersion(gpu_fd);
+ wlr_log(WLR_INFO, "Initializing DRM backend for %s (%s)", name, version->name);
+ free(name);
+ drmFreeVersion(version);
+
+ struct wlr_drm_backend *drm = calloc(1, sizeof(struct wlr_drm_backend));
+ if (!drm) {
+ wlr_log_errno(WLR_ERROR, "Allocation failed");
+ return NULL;
+ }
+ wlr_backend_init(&drm->backend, &backend_impl);
+
+ drm->session = session;
+ wl_list_init(&drm->outputs);
+
+ drm->fd = gpu_fd;
+ if (parent != NULL) {
+ drm->parent = get_drm_backend_from_backend(parent);
+ }
+
+ drm->drm_invalidated.notify = drm_invalidated;
+ wlr_session_signal_add(session, gpu_fd, &drm->drm_invalidated);
+
+ drm->display = display;
+ struct wl_event_loop *event_loop = wl_display_get_event_loop(display);
+
+ drm->drm_event = wl_event_loop_add_fd(event_loop, drm->fd,
+ WL_EVENT_READABLE, handle_drm_event, NULL);
+ if (!drm->drm_event) {
+ wlr_log(WLR_ERROR, "Failed to create DRM event source");
+ goto error_fd;
+ }
+
+ drm->session_signal.notify = session_signal;
+ wl_signal_add(&session->session_signal, &drm->session_signal);
+
+ if (!check_drm_features(drm)) {
+ goto error_event;
+ }
+
+ if (!init_drm_resources(drm)) {
+ goto error_event;
+ }
+
+ if (!init_drm_renderer(drm, &drm->renderer, create_renderer_func)) {
+ wlr_log(WLR_ERROR, "Failed to initialize renderer");
+ goto error_event;
+ }
+
+ drm->display_destroy.notify = handle_display_destroy;
+ wl_display_add_destroy_listener(display, &drm->display_destroy);
+
+ return &drm->backend;
+
+error_event:
+ wl_list_remove(&drm->session_signal.link);
+ wl_event_source_remove(drm->drm_event);
+error_fd:
+ wlr_session_close_file(drm->session, drm->fd);
+ free(drm);
+ return NULL;
+}
diff --git a/backend/drm/drm.c b/backend/drm/drm.c
new file mode 100644
index 00000000..735b7c29
--- /dev/null
+++ b/backend/drm/drm.c
@@ -0,0 +1,1418 @@
+#define _POSIX_C_SOURCE 200112L
+#include <assert.h>
+#include <drm_mode.h>
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+#include <errno.h>
+#include <gbm.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <wayland-server.h>
+#include <wayland-util.h>
+#include <wlr/backend/interface.h>
+#include <wlr/interfaces/wlr_output.h>
+#include <wlr/render/gles2.h>
+#include <wlr/render/wlr_renderer.h>
+#include <wlr/types/wlr_matrix.h>
+#include <wlr/util/log.h>
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+#include "backend/drm/drm.h"
+#include "backend/drm/iface.h"
+#include "backend/drm/util.h"
+#include "util/signal.h"
+
+bool check_drm_features(struct wlr_drm_backend *drm) {
+ uint64_t cap;
+ if (drm->parent) {
+ if (drmGetCap(drm->fd, DRM_CAP_PRIME, &cap) ||
+ !(cap & DRM_PRIME_CAP_IMPORT)) {
+ wlr_log(WLR_ERROR,
+ "PRIME import not supported on secondary GPU");
+ return false;
+ }
+
+ if (drmGetCap(drm->parent->fd, DRM_CAP_PRIME, &cap) ||
+ !(cap & DRM_PRIME_CAP_EXPORT)) {
+ wlr_log(WLR_ERROR,
+ "PRIME export not supported on primary GPU");
+ return false;
+ }
+ }
+
+ if (drmSetClientCap(drm->fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1)) {
+ wlr_log(WLR_ERROR, "DRM universal planes unsupported");
+ return false;
+ }
+
+ const char *no_atomic = getenv("WLR_DRM_NO_ATOMIC");
+ if (no_atomic && strcmp(no_atomic, "1") == 0) {
+ wlr_log(WLR_DEBUG,
+ "WLR_DRM_NO_ATOMIC set, forcing legacy DRM interface");
+ drm->iface = &legacy_iface;
+ } else if (drmSetClientCap(drm->fd, DRM_CLIENT_CAP_ATOMIC, 1)) {
+ wlr_log(WLR_DEBUG,
+ "Atomic modesetting unsupported, using legacy DRM interface");
+ drm->iface = &legacy_iface;
+ } else {
+ wlr_log(WLR_DEBUG, "Using atomic DRM interface");
+ drm->iface = &atomic_iface;
+ }
+
+ int ret = drmGetCap(drm->fd, DRM_CAP_TIMESTAMP_MONOTONIC, &cap);
+ drm->clock = (ret == 0 && cap == 1) ? CLOCK_MONOTONIC : CLOCK_REALTIME;
+
+ return true;
+}
+
+static int cmp_plane(const void *arg1, const void *arg2) {
+ const struct wlr_drm_plane *a = arg1;
+ const struct wlr_drm_plane *b = arg2;
+
+ return (int)a->type - (int)b->type;
+}
+
+static bool init_planes(struct wlr_drm_backend *drm) {
+ drmModePlaneRes *plane_res = drmModeGetPlaneResources(drm->fd);
+ if (!plane_res) {
+ wlr_log_errno(WLR_ERROR, "Failed to get DRM plane resources");
+ return false;
+ }
+
+ wlr_log(WLR_INFO, "Found %"PRIu32" DRM planes", plane_res->count_planes);
+
+ if (plane_res->count_planes == 0) {
+ drmModeFreePlaneResources(plane_res);
+ return true;
+ }
+
+ drm->num_planes = plane_res->count_planes;
+ drm->planes = calloc(drm->num_planes, sizeof(*drm->planes));
+ if (!drm->planes) {
+ wlr_log_errno(WLR_ERROR, "Allocation failed");
+ goto error_res;
+ }
+
+ for (size_t i = 0; i < drm->num_planes; ++i) {
+ struct wlr_drm_plane *p = &drm->planes[i];
+
+ drmModePlane *plane = drmModeGetPlane(drm->fd, plane_res->planes[i]);
+ if (!plane) {
+ wlr_log_errno(WLR_ERROR, "Failed to get DRM plane");
+ goto error_planes;
+ }
+
+ p->id = plane->plane_id;
+ p->possible_crtcs = plane->possible_crtcs;
+ uint64_t type;
+
+ if (!get_drm_plane_props(drm->fd, p->id, &p->props) ||
+ !get_drm_prop(drm->fd, p->id, p->props.type, &type)) {
+ drmModeFreePlane(plane);
+ goto error_planes;
+ }
+
+ p->type = type;
+ drm->num_type_planes[type]++;
+
+ drmModeFreePlane(plane);
+ }
+
+ wlr_log(WLR_INFO, "(%zu overlay, %zu primary, %zu cursor)",
+ drm->num_overlay_planes,
+ drm->num_primary_planes,
+ drm->num_cursor_planes);
+
+ qsort(drm->planes, drm->num_planes, sizeof(*drm->planes), cmp_plane);
+
+ drm->overlay_planes = drm->planes;
+ drm->primary_planes = drm->overlay_planes
+ + drm->num_overlay_planes;
+ drm->cursor_planes = drm->primary_planes
+ + drm->num_primary_planes;
+
+ drmModeFreePlaneResources(plane_res);
+ return true;
+
+error_planes:
+ free(drm->planes);
+error_res:
+ drmModeFreePlaneResources(plane_res);
+ return false;
+}
+
+bool init_drm_resources(struct wlr_drm_backend *drm) {
+ drmModeRes *res = drmModeGetResources(drm->fd);
+ if (!res) {
+ wlr_log_errno(WLR_ERROR, "Failed to get DRM resources");
+ return false;
+ }
+
+ wlr_log(WLR_INFO, "Found %d DRM CRTCs", res->count_crtcs);
+
+ drm->num_crtcs = res->count_crtcs;
+ if (drm->num_crtcs == 0) {
+ drmModeFreeResources(res);
+ return true;
+ }
+
+ drm->crtcs = calloc(drm->num_crtcs, sizeof(drm->crtcs[0]));
+ if (!drm->crtcs) {
+ wlr_log_errno(WLR_ERROR, "Allocation failed");
+ goto error_res;
+ }
+
+ for (size_t i = 0; i < drm->num_crtcs; ++i) {
+ struct wlr_drm_crtc *crtc = &drm->crtcs[i];
+ crtc->id = res->crtcs[i];
+ crtc->legacy_crtc = drmModeGetCrtc(drm->fd, crtc->id);
+ get_drm_crtc_props(drm->fd, crtc->id, &crtc->props);
+ }
+
+ if (!init_planes(drm)) {
+ goto error_crtcs;
+ }
+
+ drmModeFreeResources(res);
+
+ return true;
+
+error_crtcs:
+ free(drm->crtcs);
+error_res:
+ drmModeFreeResources(res);
+ return false;
+}
+
+void finish_drm_resources(struct wlr_drm_backend *drm) {
+ if (!drm) {
+ return;
+ }
+
+ for (size_t i = 0; i < drm->num_crtcs; ++i) {
+ struct wlr_drm_crtc *crtc = &drm->crtcs[i];
+ drmModeAtomicFree(crtc->atomic);
+ drmModeFreeCrtc(crtc->legacy_crtc);
+ if (crtc->mode_id) {
+ drmModeDestroyPropertyBlob(drm->fd, crtc->mode_id);
+ }
+ if (crtc->gamma_lut) {
+ drmModeDestroyPropertyBlob(drm->fd, crtc->gamma_lut);
+ }
+ free(crtc->gamma_table);
+ }
+ for (size_t i = 0; i < drm->num_planes; ++i) {
+ struct wlr_drm_plane *plane = &drm->planes[i];
+ if (plane->cursor_bo) {
+ gbm_bo_destroy(plane->cursor_bo);
+ }
+ }
+
+ free(drm->crtcs);
+ free(drm->planes);
+}
+
+static struct wlr_drm_connector *get_drm_connector_from_output(
+ struct wlr_output *wlr_output) {
+ assert(wlr_output_is_drm(wlr_output));
+ return (struct wlr_drm_connector *)wlr_output;
+}
+
+static bool drm_connector_make_current(struct wlr_output *output,
+ int *buffer_age) {
+ struct wlr_drm_connector *conn = get_drm_connector_from_output(output);
+ return make_drm_surface_current(&conn->crtc->primary->surf, buffer_age);
+}
+
+static bool drm_connector_swap_buffers(struct wlr_output *output,
+ pixman_region32_t *damage) {
+ struct wlr_drm_connector *conn = get_drm_connector_from_output(output);
+ struct wlr_drm_backend *drm = get_drm_backend_from_backend(output->backend);
+ if (!drm->session->active) {
+ return false;
+ }
+
+ struct wlr_drm_crtc *crtc = conn->crtc;
+ if (!crtc) {
+ return false;
+ }
+ struct wlr_drm_plane *plane = crtc->primary;
+
+ struct gbm_bo *bo = swap_drm_surface_buffers(&plane->surf, damage);
+ if (drm->parent) {
+ bo = copy_drm_surface_mgpu(&plane->mgpu_surf, bo);
+ }
+ uint32_t fb_id = get_fb_for_bo(bo);
+
+ if (conn->pageflip_pending) {
+ wlr_log(WLR_ERROR, "Skipping pageflip on output '%s'", conn->output.name);
+ return false;
+ }
+
+ if (!drm->iface->crtc_pageflip(drm, conn, crtc, fb_id, NULL)) {
+ return false;
+ }
+
+ conn->pageflip_pending = true;
+ wlr_output_update_enabled(output, true);
+ return true;
+}
+
+static void fill_empty_gamma_table(size_t size,
+ uint16_t *r, uint16_t *g, uint16_t *b) {
+ for (uint32_t i = 0; i < size; ++i) {
+ uint16_t val = (uint32_t)0xffff * i / (size - 1);
+ r[i] = g[i] = b[i] = val;
+ }
+}
+
+static size_t drm_connector_get_gamma_size(struct wlr_output *output) {
+ struct wlr_drm_connector *conn = get_drm_connector_from_output(output);
+ struct wlr_drm_backend *drm = get_drm_backend_from_backend(output->backend);
+
+ if (conn->crtc) {
+ return drm->iface->crtc_get_gamma_size(drm, conn->crtc);
+ }
+
+ return 0;
+}
+
+bool set_drm_connector_gamma(struct wlr_output *output, size_t size,
+ const uint16_t *r, const uint16_t *g, const uint16_t *b) {
+ struct wlr_drm_connector *conn = get_drm_connector_from_output(output);
+ struct wlr_drm_backend *drm = get_drm_backend_from_backend(output->backend);
+
+ if (!conn->crtc) {
+ return false;
+ }
+
+ bool reset = false;
+ if (size == 0) {
+ reset = true;
+ size = drm_connector_get_gamma_size(output);
+ if (size == 0) {
+ return false;
+ }
+ }
+
+ uint16_t *gamma_table = malloc(3 * size * sizeof(uint16_t));
+ if (gamma_table == NULL) {
+ wlr_log(WLR_ERROR, "Failed to allocate gamma table");
+ return false;
+ }
+ uint16_t *_r = gamma_table;
+ uint16_t *_g = gamma_table + size;
+ uint16_t *_b = gamma_table + 2 * size;
+
+ if (reset) {
+ fill_empty_gamma_table(size, _r, _g, _b);
+ } else {
+ memcpy(_r, r, size * sizeof(uint16_t));
+ memcpy(_g, g, size * sizeof(uint16_t));
+ memcpy(_b, b, size * sizeof(uint16_t));
+ }
+
+ bool ok = drm->iface->crtc_set_gamma(drm, conn->crtc, size, _r, _g, _b);
+ if (ok) {
+ wlr_output_update_needs_swap(output);
+
+ free(conn->crtc->gamma_table);
+ conn->crtc->gamma_table = gamma_table;
+ conn->crtc->gamma_table_size = size;
+ } else {
+ free(gamma_table);
+ }
+ return ok;
+}
+
+static bool drm_connector_export_dmabuf(struct wlr_output *output,
+ struct wlr_dmabuf_attributes *attribs) {
+ struct wlr_drm_connector *conn = get_drm_connector_from_output(output);
+ struct wlr_drm_backend *drm = get_drm_backend_from_backend(output->backend);
+
+ if (!drm->session->active) {
+ return false;
+ }
+
+ struct wlr_drm_crtc *crtc = conn->crtc;
+ if (!crtc) {
+ return false;
+ }
+ struct wlr_drm_plane *plane = crtc->primary;
+ struct wlr_drm_surface *surf = &plane->surf;
+
+ return export_drm_bo(surf->back, attribs);
+}
+
+static void drm_connector_start_renderer(struct wlr_drm_connector *conn) {
+ if (conn->state != WLR_DRM_CONN_CONNECTED) {
+ return;
+ }
+
+ wlr_log(WLR_DEBUG, "Starting renderer on output '%s'", conn->output.name);
+
+ struct wlr_drm_backend *drm =
+ get_drm_backend_from_backend(conn->output.backend);
+ struct wlr_drm_crtc *crtc = conn->crtc;
+ if (!crtc) {
+ return;
+ }
+ struct wlr_drm_plane *plane = crtc->primary;
+
+ struct gbm_bo *bo = get_drm_surface_front(
+ drm->parent ? &plane->mgpu_surf : &plane->surf);
+ uint32_t fb_id = get_fb_for_bo(bo);
+
+ struct wlr_drm_mode *mode = (struct wlr_drm_mode *)conn->output.current_mode;
+ if (drm->iface->crtc_pageflip(drm, conn, crtc, fb_id, &mode->drm_mode)) {
+ conn->pageflip_pending = true;
+ wlr_output_update_enabled(&conn->output, true);
+ } else {
+ wl_event_source_timer_update(conn->retry_pageflip,
+ 1000000.0f / conn->output.current_mode->refresh);
+ }
+}
+
+static bool drm_connector_set_mode(struct wlr_output *output,
+ struct wlr_output_mode *mode);
+
+static void realloc_crtcs(struct wlr_drm_backend *drm, bool *changed_outputs);
+
+static void attempt_enable_needs_modeset(struct wlr_drm_backend *drm) {
+ // Try to modeset any output that has a desired mode and a CRTC (ie. was
+ // lacking a CRTC on last modeset)
+ struct wlr_drm_connector *conn;
+ wl_list_for_each(conn, &drm->outputs, link) {
+ if (conn->state == WLR_DRM_CONN_NEEDS_MODESET &&
+ conn->crtc != NULL && conn->desired_mode != NULL &&
+ conn->desired_enabled) {
+ drm_connector_set_mode(&conn->output, conn->desired_mode);
+ }
+ }
+}
+
+bool enable_drm_connector(struct wlr_output *output, bool enable) {
+ struct wlr_drm_connector *conn = get_drm_connector_from_output(output);
+ struct wlr_drm_backend *drm = get_drm_backend_from_backend(output->backend);
+ if (conn->state != WLR_DRM_CONN_CONNECTED
+ && conn->state != WLR_DRM_CONN_NEEDS_MODESET) {
+ return false;
+ }
+
+ conn->desired_enabled = enable;
+
+ if (enable && conn->crtc == NULL) {
+ // Maybe we can steal a CRTC from a disabled output
+ realloc_crtcs(drm, NULL);
+ }
+
+ bool ok = drm->iface->conn_enable(drm, conn, enable);
+ if (!ok) {
+ return false;
+ }
+
+ if (enable) {
+ drm_connector_start_renderer(conn);
+ } else {
+ realloc_crtcs(drm, NULL);
+
+ attempt_enable_needs_modeset(drm);
+ }
+
+ wlr_output_update_enabled(&conn->output, enable);
+ return true;
+}
+
+static ssize_t connector_index_from_crtc(struct wlr_drm_backend *drm,
+ struct wlr_drm_crtc *crtc) {
+ size_t i = 0;
+ struct wlr_drm_connector *conn;
+ wl_list_for_each(conn, &drm->outputs, link) {
+ if (conn->crtc == crtc) {
+ return i;
+ }
+ ++i;
+ }
+ return -1;
+}
+
+static void realloc_planes(struct wlr_drm_backend *drm, const uint32_t *crtc_in,
+ bool *changed_outputs) {
+ wlr_log(WLR_DEBUG, "Reallocating planes");
+
+ // overlay, primary, cursor
+ for (size_t type = 0; type < 3; ++type) {
+ if (drm->num_type_planes[type] == 0) {
+ continue;
+ }
+
+ uint32_t possible[drm->num_type_planes[type] + 1];
+ uint32_t crtc[drm->num_crtcs + 1];
+ uint32_t crtc_res[drm->num_crtcs + 1];
+
+ for (size_t i = 0; i < drm->num_type_planes[type]; ++i) {
+ possible[i] = drm->type_planes[type][i].possible_crtcs;
+ }
+
+ for (size_t i = 0; i < drm->num_crtcs; ++i) {
+ if (crtc_in[i] == UNMATCHED) {
+ crtc[i] = SKIP;
+ } else if (drm->crtcs[i].planes[type]) {
+ crtc[i] = drm->crtcs[i].planes[type]
+ - drm->type_planes[type];
+ } else {
+ crtc[i] = UNMATCHED;
+ }
+ }
+
+ match_obj(drm->num_type_planes[type], possible,
+ drm->num_crtcs, crtc, crtc_res);
+
+ for (size_t i = 0; i < drm->num_crtcs; ++i) {
+ if (crtc_res[i] == UNMATCHED || crtc_res[i] == SKIP) {
+ continue;
+ }
+
+ struct wlr_drm_crtc *c = &drm->crtcs[i];
+ struct wlr_drm_plane **old = &c->planes[type];
+ struct wlr_drm_plane *new = &drm->type_planes[type][crtc_res[i]];
+
+ if (*old != new) {
+ wlr_log(WLR_DEBUG,
+ "Assigning plane %d -> %d (type %zu) to CRTC %d",
+ *old ? (int)(*old)->id : -1,
+ new ? (int)new->id : -1,
+ type,
+ c->id);
+
+ ssize_t conn_idx = connector_index_from_crtc(drm, c);
+ if (conn_idx >= 0) {
+ changed_outputs[conn_idx] = true;
+ }
+ if (*old) {
+ finish_drm_surface(&(*old)->surf);
+ }
+ finish_drm_surface(&new->surf);
+ *old = new;
+ }
+ }
+ }
+}
+
+static void drm_connector_cleanup(struct wlr_drm_connector *conn);
+
+static bool drm_connector_set_mode(struct wlr_output *output,
+ struct wlr_output_mode *mode) {
+ struct wlr_drm_connector *conn = get_drm_connector_from_output(output);
+ struct wlr_drm_backend *drm = get_drm_backend_from_backend(output->backend);
+ if (conn->crtc == NULL) {
+ // Maybe we can steal a CRTC from a disabled output
+ realloc_crtcs(drm, NULL);
+ }
+ if (conn->crtc == NULL) {
+ wlr_log(WLR_ERROR, "Cannot modeset '%s': no CRTC for this connector",
+ conn->output.name);
+ // Save the desired mode for later, when we'll get a proper CRTC
+ conn->desired_mode = mode;
+ return false;
+ }
+
+ wlr_log(WLR_INFO, "Modesetting '%s' with '%ux%u@%u mHz'",
+ conn->output.name, mode->width, mode->height, mode->refresh);
+
+ if (!init_drm_plane_surfaces(conn->crtc->primary, drm,
+ mode->width, mode->height, GBM_FORMAT_XRGB8888)) {
+ wlr_log(WLR_ERROR, "Failed to initialize renderer for plane");
+ return false;
+ }
+
+ conn->state = WLR_DRM_CONN_CONNECTED;
+ conn->desired_mode = NULL;
+ wlr_output_update_mode(&conn->output, mode);
+ wlr_output_update_enabled(&conn->output, true);
+ conn->desired_enabled = true;
+
+ drm_connector_start_renderer(conn);
+
+ // When switching VTs, the mode is not updated but the buffers become
+ // invalid, so we need to manually damage the output here
+ wlr_output_damage_whole(&conn->output);
+
+ return true;
+}
+
+bool wlr_drm_connector_add_mode(struct wlr_output *output,
+ const drmModeModeInfo *modeinfo) {
+ struct wlr_drm_connector *conn = get_drm_connector_from_output(output);
+
+ if (modeinfo->type != DRM_MODE_TYPE_USERDEF) {
+ return false;
+ }
+
+ struct wlr_output_mode *wlr_mode;
+ wl_list_for_each(wlr_mode, &conn->output.modes, link) {
+ struct wlr_drm_mode *mode = (struct wlr_drm_mode *)wlr_mode;
+ if (memcmp(&mode->drm_mode, modeinfo, sizeof(*modeinfo)) == 0) {
+ return true;
+ }
+ }
+
+ struct wlr_drm_mode *mode = calloc(1, sizeof(*mode));
+ if (!mode) {
+ return false;
+ }
+ memcpy(&mode->drm_mode, modeinfo, sizeof(*modeinfo));
+
+ mode->wlr_mode.width = mode->drm_mode.hdisplay;
+ mode->wlr_mode.height = mode->drm_mode.vdisplay;
+ mode->wlr_mode.refresh = calculate_refresh_rate(modeinfo);
+
+ wlr_log(WLR_INFO, "Registered custom mode "
+ "%"PRId32"x%"PRId32"@%"PRId32,
+ mode->wlr_mode.width, mode->wlr_mode.height,
+ mode->wlr_mode.refresh);
+ wl_list_insert(&conn->output.modes, &mode->wlr_mode.link);
+ return true;
+}
+
+static void drm_connector_transform(struct wlr_output *output,
+ enum wl_output_transform transform) {
+ output->transform = transform;
+}
+
+static bool drm_connector_set_cursor(struct wlr_output *output,
+ struct wlr_texture *texture, int32_t scale,
+ enum wl_output_transform transform,
+ int32_t hotspot_x, int32_t hotspot_y, bool update_texture) {
+ struct wlr_drm_connector *conn = get_drm_connector_from_output(output);
+ struct wlr_drm_backend *drm = get_drm_backend_from_backend(output->backend);
+
+ struct wlr_drm_crtc *crtc = conn->crtc;
+ if (!crtc) {
+ return false;
+ }
+
+ struct wlr_drm_plane *plane = crtc->cursor;
+ if (!plane) {
+ // We don't have a real cursor plane, so we make a fake one
+ plane = calloc(1, sizeof(*plane));
+ if (!plane) {
+ wlr_log_errno(WLR_ERROR, "Allocation failed");
+ return false;
+ }
+ crtc->cursor = plane;
+ }
+
+ if (!plane->surf.gbm) {
+ int ret;
+ uint64_t w, h;
+ ret = drmGetCap(drm->fd, DRM_CAP_CURSOR_WIDTH, &w);
+ w = ret ? 64 : w;
+ ret = drmGetCap(drm->fd, DRM_CAP_CURSOR_HEIGHT, &h);
+ h = ret ? 64 : h;
+
+ struct wlr_drm_renderer *renderer =
+ drm->parent ? &drm->parent->renderer : &drm->renderer;
+
+ if (!init_drm_surface(&plane->surf, renderer, w, h,
+ GBM_FORMAT_ARGB8888, 0)) {
+ wlr_log(WLR_ERROR, "Cannot allocate cursor resources");
+ return false;
+ }
+
+ plane->cursor_bo = gbm_bo_create(drm->renderer.gbm, w, h,
+ GBM_FORMAT_ARGB8888, GBM_BO_USE_CURSOR | GBM_BO_USE_WRITE);
+ if (!plane->cursor_bo) {
+ wlr_log_errno(WLR_ERROR, "Failed to create cursor bo");
+ return false;
+ }
+ }
+
+ wlr_matrix_projection(plane->matrix, plane->surf.width,
+ plane->surf.height, output->transform);
+
+ struct wlr_box hotspot = { .x = hotspot_x, .y = hotspot_y };
+ wlr_box_transform(&hotspot, &hotspot,
+ wlr_output_transform_invert(output->transform),
+ plane->surf.width, plane->surf.height);
+
+ if (plane->cursor_hotspot_x != hotspot.x ||
+ plane->cursor_hotspot_y != hotspot.y) {
+ // Update cursor hotspot
+ conn->cursor_x -= hotspot.x - plane->cursor_hotspot_x;
+ conn->cursor_y -= hotspot.y - plane->cursor_hotspot_y;
+ plane->cursor_hotspot_x = hotspot.x;
+ plane->cursor_hotspot_y = hotspot.y;
+
+ if (!drm->iface->crtc_move_cursor(drm, conn->crtc, conn->cursor_x,
+ conn->cursor_y)) {
+ return false;
+ }
+
+ wlr_output_update_needs_swap(output);
+ }
+
+ if (!update_texture) {
+ // Don't update cursor image
+ return true;
+ }
+
+ plane->cursor_enabled = false;
+ if (texture != NULL) {
+ int width, height;
+ wlr_texture_get_size(texture, &width, &height);
+ width = width * output->scale / scale;
+ height = height * output->scale / scale;
+
+ if (width > (int)plane->surf.width || height > (int)plane->surf.height) {
+ wlr_log(WLR_ERROR, "Cursor too large (max %dx%d)",
+ (int)plane->surf.width, (int)plane->surf.height);
+ return false;
+ }
+
+ uint32_t bo_width = gbm_bo_get_width(plane->cursor_bo);
+ uint32_t bo_height = gbm_bo_get_height(plane->cursor_bo);
+
+ uint32_t bo_stride;
+ void *bo_data;
+ if (!gbm_bo_map(plane->cursor_bo, 0, 0, bo_width, bo_height,
+ GBM_BO_TRANSFER_WRITE, &bo_stride, &bo_data)) {
+ wlr_log_errno(WLR_ERROR, "Unable to map buffer");
+ return false;
+ }
+
+ make_drm_surface_current(&plane->surf, NULL);
+
+ struct wlr_renderer *rend = plane->surf.renderer->wlr_rend;
+
+ struct wlr_box cursor_box = { .width = width, .height = height };
+
+ float matrix[9];
+ wlr_matrix_project_box(matrix, &cursor_box, transform, 0, plane->matrix);
+
+ wlr_renderer_begin(rend, plane->surf.width, plane->surf.height);
+ wlr_renderer_clear(rend, (float[]){ 0.0, 0.0, 0.0, 0.0 });
+ wlr_render_texture_with_matrix(rend, texture, matrix, 1.0);
+ wlr_renderer_end(rend);
+
+ wlr_renderer_read_pixels(rend, WL_SHM_FORMAT_ARGB8888, NULL, bo_stride,
+ plane->surf.width, plane->surf.height, 0, 0, 0, 0, bo_data);
+
+ swap_drm_surface_buffers(&plane->surf, NULL);
+
+ gbm_bo_unmap(plane->cursor_bo, bo_data);
+
+ plane->cursor_enabled = true;
+ }
+
+ if (!drm->session->active) {
+ return true; // will be committed when session is resumed
+ }
+
+ struct gbm_bo *bo = plane->cursor_enabled ? plane->cursor_bo : NULL;
+ bool ok = drm->iface->crtc_set_cursor(drm, crtc, bo);
+ if (ok) {
+ wlr_output_update_needs_swap(output);
+ }
+ return ok;
+}
+
+static bool drm_connector_move_cursor(struct wlr_output *output,
+ int x, int y) {
+ struct wlr_drm_connector *conn = get_drm_connector_from_output(output);
+ struct wlr_drm_backend *drm = get_drm_backend_from_backend(output->backend);
+ if (!conn->crtc) {
+ return false;
+ }
+ struct wlr_drm_plane *plane = conn->crtc->cursor;
+
+ struct wlr_box box = { .x = x, .y = y };
+
+ int width, height;
+ wlr_output_transformed_resolution(output, &width, &height);
+
+ enum wl_output_transform transform =
+ wlr_output_transform_invert(output->transform);
+ wlr_box_transform(&box, &box, transform, width, height);
+
+ if (plane != NULL) {
+ box.x -= plane->cursor_hotspot_x;
+ box.y -= plane->cursor_hotspot_y;
+ }
+
+ conn->cursor_x = box.x;
+ conn->cursor_y = box.y;
+
+ if (!drm->session->active) {
+ return true; // will be committed when session is resumed
+ }
+
+ bool ok = drm->iface->crtc_move_cursor(drm, conn->crtc, box.x, box.y);
+ if (ok) {
+ wlr_output_update_needs_swap(output);
+ }
+ return ok;
+}
+
+static bool drm_connector_schedule_frame(struct wlr_output *output) {
+ struct wlr_drm_connector *conn = get_drm_connector_from_output(output);
+ struct wlr_drm_backend *drm = get_drm_backend_from_backend(output->backend);
+ if (!drm->session->active) {
+ return false;
+ }
+
+ // We need to figure out where we are in the vblank cycle
+ // TODO: try using drmWaitVBlank and fallback to pageflipping
+
+ struct wlr_drm_crtc *crtc = conn->crtc;
+ if (!crtc) {
+ return false;
+ }
+ struct wlr_drm_plane *plane = crtc->primary;
+ struct gbm_bo *bo = plane->surf.back;
+ if (!bo) {
+ // We haven't swapped buffers yet -- can't do a pageflip
+ wlr_output_send_frame(output);
+ return true;
+ }
+ if (drm->parent) {
+ bo = copy_drm_surface_mgpu(&plane->mgpu_surf, bo);
+ }
+ uint32_t fb_id = get_fb_for_bo(bo);
+
+ if (conn->pageflip_pending) {
+ wlr_log(WLR_ERROR, "Skipping pageflip on output '%s'",
+ conn->output.name);
+ return true;
+ }
+
+ if (!drm->iface->crtc_pageflip(drm, conn, crtc, fb_id, NULL)) {
+ return false;
+ }
+
+ conn->pageflip_pending = true;
+ wlr_output_update_enabled(output, true);
+ return true;
+}
+
+static void drm_connector_destroy(struct wlr_output *output) {
+ struct wlr_drm_connector *conn = get_drm_connector_from_output(output);
+ drm_connector_cleanup(conn);
+ drmModeFreeCrtc(conn->old_crtc);
+ wl_event_source_remove(conn->retry_pageflip);
+ wl_list_remove(&conn->link);
+ free(conn);
+}
+
+static const struct wlr_output_impl output_impl = {
+ .enable = enable_drm_connector,
+ .set_mode = drm_connector_set_mode,
+ .transform = drm_connector_transform,
+ .set_cursor = drm_connector_set_cursor,
+ .move_cursor = drm_connector_move_cursor,
+ .destroy = drm_connector_destroy,
+ .make_current = drm_connector_make_current,
+ .swap_buffers = drm_connector_swap_buffers,
+ .set_gamma = set_drm_connector_gamma,
+ .get_gamma_size = drm_connector_get_gamma_size,
+ .export_dmabuf = drm_connector_export_dmabuf,
+ .schedule_frame = drm_connector_schedule_frame,
+};
+
+bool wlr_output_is_drm(struct wlr_output *output) {
+ return output->impl == &output_impl;
+}
+
+static int retry_pageflip(void *data) {
+ struct wlr_drm_connector *conn = data;
+ wlr_log(WLR_INFO, "%s: Retrying pageflip", conn->output.name);
+ drm_connector_start_renderer(conn);
+ return 0;
+}
+
+static const int32_t subpixel_map[] = {
+ [DRM_MODE_SUBPIXEL_UNKNOWN] = WL_OUTPUT_SUBPIXEL_UNKNOWN,
+ [DRM_MODE_SUBPIXEL_HORIZONTAL_RGB] = WL_OUTPUT_SUBPIXEL_HORIZONTAL_RGB,
+ [DRM_MODE_SUBPIXEL_HORIZONTAL_BGR] = WL_OUTPUT_SUBPIXEL_HORIZONTAL_BGR,
+ [DRM_MODE_SUBPIXEL_VERTICAL_RGB] = WL_OUTPUT_SUBPIXEL_VERTICAL_RGB,
+ [DRM_MODE_SUBPIXEL_VERTICAL_BGR] = WL_OUTPUT_SUBPIXEL_VERTICAL_BGR,
+ [DRM_MODE_SUBPIXEL_NONE] = WL_OUTPUT_SUBPIXEL_NONE,
+};
+
+static void dealloc_crtc(struct wlr_drm_connector *conn) {
+ struct wlr_drm_backend *drm =
+ get_drm_backend_from_backend(conn->output.backend);
+ if (conn->crtc == NULL) {
+ return;
+ }
+
+ wlr_log(WLR_DEBUG, "De-allocating CRTC %zu for output '%s'",
+ conn->crtc - drm->crtcs, conn->output.name);
+
+ set_drm_connector_gamma(&conn->output, 0, NULL, NULL, NULL);
+
+ for (size_t type = 0; type < 3; ++type) {
+ struct wlr_drm_plane *plane = conn->crtc->planes[type];
+ if (plane == NULL) {
+ continue;
+ }
+
+ finish_drm_surface(&plane->surf);
+ conn->crtc->planes[type] = NULL;
+ }
+
+ drm->iface->conn_enable(drm, conn, false);
+
+ conn->crtc = NULL;
+}
+
+static void realloc_crtcs(struct wlr_drm_backend *drm, bool *changed_outputs) {
+ size_t num_outputs = wl_list_length(&drm->outputs);
+
+ if (changed_outputs == NULL) {
+ changed_outputs = calloc(num_outputs, sizeof(bool));
+ if (changed_outputs == NULL) {
+ wlr_log(WLR_ERROR, "Allocation failed");
+ return;
+ }
+ }
+
+ wlr_log(WLR_DEBUG, "Reallocating CRTCs");
+
+ uint32_t crtc[drm->num_crtcs + 1];
+ for (size_t i = 0; i < drm->num_crtcs; ++i) {
+ crtc[i] = UNMATCHED;
+ }
+
+ struct wlr_drm_connector *connectors[num_outputs + 1];
+
+ uint32_t possible_crtc[num_outputs + 1];
+ memset(possible_crtc, 0, sizeof(possible_crtc));
+
+ wlr_log(WLR_DEBUG, "State before reallocation:");
+ ssize_t i = -1;
+ struct wlr_drm_connector *conn;
+ wl_list_for_each(conn, &drm->outputs, link) {
+ i++;
+ connectors[i] = conn;
+
+ wlr_log(WLR_DEBUG, " '%s' crtc=%d state=%d desired_enabled=%d",
+ conn->output.name,
+ conn->crtc ? (int)(conn->crtc - drm->crtcs) : -1,
+ conn->state, conn->desired_enabled);
+
+ if (conn->crtc) {
+ crtc[conn->crtc - drm->crtcs] = i;
+ }
+
+ // Only search CRTCs for user-enabled outputs (that are already
+ // connected or in need of a modeset)
+ if ((conn->state == WLR_DRM_CONN_CONNECTED ||
+ conn->state == WLR_DRM_CONN_NEEDS_MODESET) &&
+ conn->desired_enabled) {
+ possible_crtc[i] = conn->possible_crtc;
+ }
+ }
+
+ uint32_t crtc_res[drm->num_crtcs + 1];
+ match_obj(wl_list_length(&drm->outputs), possible_crtc,
+ drm->num_crtcs, crtc, crtc_res);
+
+ bool matched[num_outputs + 1];
+ memset(matched, false, sizeof(matched));
+ for (size_t i = 0; i < drm->num_crtcs; ++i) {
+ if (crtc_res[i] != UNMATCHED) {
+ matched[crtc_res[i]] = true;
+ }
+ }
+
+ for (size_t i = 0; i < drm->num_crtcs; ++i) {
+ // We don't want any of the current monitors to be deactivated
+ if (crtc[i] != UNMATCHED && !matched[crtc[i]] &&
+ connectors[crtc[i]]->desired_enabled) {
+ wlr_log(WLR_DEBUG, "Could not match a CRTC for connected output %d",
+ crtc[i]);
+ return;
+ }
+ }
+
+ for (size_t i = 0; i < drm->num_crtcs; ++i) {
+ if (crtc_res[i] == crtc[i]) {
+ continue;
+ }
+
+ // De-allocate this CRTC on previous output
+ if (crtc[i] != UNMATCHED) {
+ changed_outputs[crtc[i]] = true;
+ dealloc_crtc(connectors[crtc[i]]);
+ }
+
+ // Assign this CRTC to next output
+ if (crtc_res[i] != UNMATCHED) {
+ changed_outputs[crtc_res[i]] = true;
+
+ struct wlr_drm_connector *conn = connectors[crtc_res[i]];
+ dealloc_crtc(conn);
+ conn->crtc = &drm->crtcs[i];
+
+ wlr_log(WLR_DEBUG, "Assigning CRTC %zu to output %d -> %d '%s'",
+ i, crtc[i], crtc_res[i], conn->output.name);
+ }
+ }
+
+ wlr_log(WLR_DEBUG, "State after reallocation:");
+ wl_list_for_each(conn, &drm->outputs, link) {
+ wlr_log(WLR_DEBUG, " '%s' crtc=%d state=%d desired_enabled=%d",
+ conn->output.name,
+ conn->crtc ? (int)(conn->crtc - drm->crtcs) : -1,
+ conn->state, conn->desired_enabled);
+ }
+
+ realloc_planes(drm, crtc_res, changed_outputs);
+
+ // We need to reinitialize any plane that has changed
+ i = -1;
+ wl_list_for_each(conn, &drm->outputs, link) {
+ i++;
+ struct wlr_output_mode *mode = conn->output.current_mode;
+
+ if (conn->state != WLR_DRM_CONN_CONNECTED || !changed_outputs[i]
+ || conn->crtc == NULL) {
+ continue;
+ }
+
+ if (!init_drm_plane_surfaces(conn->crtc->primary, drm,
+ mode->width, mode->height, GBM_FORMAT_XRGB8888)) {
+ wlr_log(WLR_ERROR, "Failed to initialize renderer for plane");
+ drm_connector_cleanup(conn);
+ break;
+ }
+
+ drm_connector_start_renderer(conn);
+
+ wlr_output_damage_whole(&conn->output);
+ }
+}
+
+static uint32_t get_possible_crtcs(int fd, drmModeRes *res,
+ drmModeConnector *conn, bool is_mst) {
+ uint32_t ret = 0;
+
+ for (int i = 0; i < conn->count_encoders; ++i) {
+ drmModeEncoder *enc = drmModeGetEncoder(fd, conn->encoders[i]);
+ if (!enc) {
+ continue;
+ }
+
+ ret |= enc->possible_crtcs;
+
+ drmModeFreeEncoder(enc);
+ }
+
+ // Sometimes DP MST connectors report no encoders, so we'll loop though
+ // all of the encoders of the MST type instead.
+ // TODO: See if there is a better solution.
+
+ if (!is_mst || ret) {
+ return ret;
+ }
+
+ for (int i = 0; i < res->count_encoders; ++i) {
+ drmModeEncoder *enc = drmModeGetEncoder(fd, res->encoders[i]);
+ if (!enc) {
+ continue;
+ }
+
+ if (enc->encoder_type == DRM_MODE_ENCODER_DPMST) {
+ ret |= enc->possible_crtcs;
+ }
+
+ drmModeFreeEncoder(enc);
+ }
+
+ return ret;
+}
+
+void scan_drm_connectors(struct wlr_drm_backend *drm) {
+ wlr_log(WLR_INFO, "Scanning DRM connectors");
+
+ drmModeRes *res = drmModeGetResources(drm->fd);
+ if (!res) {
+ wlr_log_errno(WLR_ERROR, "Failed to get DRM resources");
+ return;
+ }
+
+ size_t seen_len = wl_list_length(&drm->outputs);
+ // +1 so length can never be 0, which is undefined behaviour.
+ // Last element isn't used.
+ bool seen[seen_len + 1];
+ memset(seen, false, sizeof(seen));
+ size_t new_outputs_len = 0;
+ struct wlr_drm_connector *new_outputs[res->count_connectors + 1];
+
+ for (int i = 0; i < res->count_connectors; ++i) {
+ drmModeConnector *drm_conn = drmModeGetConnector(drm->fd,
+ res->connectors[i]);
+ if (!drm_conn) {
+ wlr_log_errno(WLR_ERROR, "Failed to get DRM connector");
+ continue;
+ }
+ drmModeEncoder *curr_enc = drmModeGetEncoder(drm->fd,
+ drm_conn->encoder_id);
+
+ ssize_t index = -1;
+ struct wlr_drm_connector *c, *wlr_conn = NULL;
+ wl_list_for_each(c, &drm->outputs, link) {
+ index++;
+ if (c->id == drm_conn->connector_id) {
+ wlr_conn = c;
+ break;
+ }
+ }
+
+ if (!wlr_conn) {
+ wlr_conn = calloc(1, sizeof(*wlr_conn));
+ if (!wlr_conn) {
+ wlr_log_errno(WLR_ERROR, "Allocation failed");
+ drmModeFreeEncoder(curr_enc);
+ drmModeFreeConnector(drm_conn);
+ continue;
+ }
+ wlr_output_init(&wlr_conn->output, &drm->backend, &output_impl,
+ drm->display);
+
+ struct wl_event_loop *ev = wl_display_get_event_loop(drm->display);
+ wlr_conn->retry_pageflip = wl_event_loop_add_timer(ev, retry_pageflip,
+ wlr_conn);
+
+ wlr_conn->state = WLR_DRM_CONN_DISCONNECTED;
+ wlr_conn->id = drm_conn->connector_id;
+
+ snprintf(wlr_conn->output.name, sizeof(wlr_conn->output.name),
+ "%s-%"PRIu32, conn_get_name(drm_conn->connector_type),
+ drm_conn->connector_type_id);
+
+ if (curr_enc) {
+ wlr_conn->old_crtc = drmModeGetCrtc(drm->fd, curr_enc->crtc_id);
+ }
+
+ wl_list_insert(drm->outputs.prev, &wlr_conn->link);
+ wlr_log(WLR_INFO, "Found connector '%s'", wlr_conn->output.name);
+ } else {
+ seen[index] = true;
+ }
+
+ if (curr_enc) {
+ for (size_t i = 0; i < drm->num_crtcs; ++i) {
+ if (drm->crtcs[i].id == curr_enc->crtc_id) {
+ wlr_conn->crtc = &drm->crtcs[i];
+ break;
+ }
+ }
+ } else {
+ wlr_conn->crtc = NULL;
+ }
+
+ // This can only happen *after* hotplug, since we haven't read the
+ // connector properties yet
+ if (wlr_conn->props.link_status != 0) {
+ uint64_t link_status;
+ if (!get_drm_prop(drm->fd, wlr_conn->id,
+ wlr_conn->props.link_status, &link_status)) {
+ wlr_log(WLR_ERROR, "Failed to get link status for '%s'",
+ wlr_conn->output.name);
+ continue;
+ }
+
+ if (link_status == DRM_MODE_LINK_STATUS_BAD) {
+ // We need to reload our list of modes and force a modeset
+ wlr_log(WLR_INFO, "Bad link for '%s'", wlr_conn->output.name);
+ drm_connector_cleanup(wlr_conn);
+ }
+ }
+
+ if (wlr_conn->state == WLR_DRM_CONN_DISCONNECTED &&
+ drm_conn->connection == DRM_MODE_CONNECTED) {
+ wlr_log(WLR_INFO, "'%s' connected", wlr_conn->output.name);
+ wlr_log(WLR_DEBUG, "Current CRTC: %d",
+ wlr_conn->crtc ? (int)wlr_conn->crtc->id : -1);
+
+ wlr_conn->output.phys_width = drm_conn->mmWidth;
+ wlr_conn->output.phys_height = drm_conn->mmHeight;
+ wlr_log(WLR_INFO, "Physical size: %"PRId32"x%"PRId32,
+ wlr_conn->output.phys_width, wlr_conn->output.phys_height);
+ wlr_conn->output.subpixel = subpixel_map[drm_conn->subpixel];
+
+ get_drm_connector_props(drm->fd, wlr_conn->id, &wlr_conn->props);
+
+ size_t edid_len = 0;
+ uint8_t *edid = get_drm_prop_blob(drm->fd,
+ wlr_conn->id, wlr_conn->props.edid, &edid_len);
+ parse_edid(&wlr_conn->output, edid_len, edid);
+ free(edid);
+
+ wlr_log(WLR_INFO, "Detected modes:");
+
+ for (int i = 0; i < drm_conn->count_modes; ++i) {
+ struct wlr_drm_mode *mode = calloc(1, sizeof(*mode));
+ if (!mode) {
+ wlr_log_errno(WLR_ERROR, "Allocation failed");
+ continue;
+ }
+
+ if (drm_conn->modes[i].flags & DRM_MODE_FLAG_INTERLACE) {
+ free(mode);
+ continue;
+ }
+
+ mode->drm_mode = drm_conn->modes[i];
+ mode->wlr_mode.width = mode->drm_mode.hdisplay;
+ mode->wlr_mode.height = mode->drm_mode.vdisplay;
+ mode->wlr_mode.refresh = calculate_refresh_rate(&mode->drm_mode);
+
+ wlr_log(WLR_INFO, " %"PRId32"x%"PRId32"@%"PRId32,
+ mode->wlr_mode.width, mode->wlr_mode.height,
+ mode->wlr_mode.refresh);
+
+ wl_list_insert(&wlr_conn->output.modes, &mode->wlr_mode.link);
+ }
+
+ wlr_conn->possible_crtc = get_possible_crtcs(drm->fd, res, drm_conn,
+ wlr_conn->props.path != 0);
+ if (wlr_conn->possible_crtc == 0) {
+ wlr_log(WLR_ERROR, "No CRTC possible for connector '%s'",
+ wlr_conn->output.name);
+ }
+
+ wlr_output_update_enabled(&wlr_conn->output, wlr_conn->crtc != NULL);
+ wlr_conn->desired_enabled = true;
+
+ wlr_conn->state = WLR_DRM_CONN_NEEDS_MODESET;
+ new_outputs[new_outputs_len++] = wlr_conn;
+ } else if ((wlr_conn->state == WLR_DRM_CONN_CONNECTED ||
+ wlr_conn->state == WLR_DRM_CONN_NEEDS_MODESET) &&
+ drm_conn->connection != DRM_MODE_CONNECTED) {
+ wlr_log(WLR_INFO, "'%s' disconnected", wlr_conn->output.name);
+
+ drm_connector_cleanup(wlr_conn);
+ }
+
+ drmModeFreeEncoder(curr_enc);
+ drmModeFreeConnector(drm_conn);
+ }
+
+ drmModeFreeResources(res);
+
+ // Iterate in reverse order because we'll remove items from the list and
+ // still want indices to remain correct.
+ struct wlr_drm_connector *conn, *tmp_conn;
+ size_t index = wl_list_length(&drm->outputs);
+ wl_list_for_each_reverse_safe(conn, tmp_conn, &drm->outputs, link) {
+ index--;
+ if (index >= seen_len || seen[index]) {
+ continue;
+ }
+
+ wlr_log(WLR_INFO, "'%s' disappeared", conn->output.name);
+ drm_connector_cleanup(conn);
+
+ if (conn->pageflip_pending) {
+ conn->state = WLR_DRM_CONN_DISAPPEARED;
+ } else {
+ wlr_output_destroy(&conn->output);
+ }
+ }
+
+ bool changed_outputs[wl_list_length(&drm->outputs) + 1];
+ memset(changed_outputs, false, sizeof(changed_outputs));
+ for (size_t i = 0; i < new_outputs_len; ++i) {
+ struct wlr_drm_connector *conn = new_outputs[i];
+
+ ssize_t pos = -1;
+ struct wlr_drm_connector *c;
+ wl_list_for_each(c, &drm->outputs, link) {
+ ++pos;
+ if (c == conn) {
+ break;
+ }
+ }
+ assert(pos >= 0);
+
+ changed_outputs[pos] = true;
+ }
+
+ realloc_crtcs(drm, changed_outputs);
+
+ for (size_t i = 0; i < new_outputs_len; ++i) {
+ struct wlr_drm_connector *conn = new_outputs[i];
+
+ wlr_log(WLR_INFO, "Requesting modeset for '%s'",
+ conn->output.name);
+ wlr_signal_emit_safe(&drm->backend.events.new_output,
+ &conn->output);
+ }
+
+ attempt_enable_needs_modeset(drm);
+}
+
+static int mhz_to_nsec(int mhz) {
+ return 1000000000000LL / mhz;
+}
+
+static void page_flip_handler(int fd, unsigned seq,
+ unsigned tv_sec, unsigned tv_usec, void *data) {
+ struct wlr_drm_connector *conn = data;
+ struct wlr_drm_backend *drm =
+ get_drm_backend_from_backend(conn->output.backend);
+
+ conn->pageflip_pending = false;
+
+ if (conn->state == WLR_DRM_CONN_DISAPPEARED) {
+ wlr_output_destroy(&conn->output);
+ return;
+ }
+
+ if (conn->state != WLR_DRM_CONN_CONNECTED || conn->crtc == NULL) {
+ return;
+ }
+
+ post_drm_surface(&conn->crtc->primary->surf);
+ if (drm->parent) {
+ post_drm_surface(&conn->crtc->primary->mgpu_surf);
+ }
+
+ struct timespec present_time = {
+ .tv_sec = tv_sec,
+ .tv_nsec = tv_usec * 1000,
+ };
+ struct wlr_output_event_present present_event = {
+ .when = &present_time,
+ .seq = seq,
+ .refresh = mhz_to_nsec(conn->output.refresh),
+ .flags = WLR_OUTPUT_PRESENT_VSYNC | WLR_OUTPUT_PRESENT_HW_CLOCK |
+ WLR_OUTPUT_PRESENT_HW_COMPLETION,
+ };
+ wlr_output_send_present(&conn->output, &present_event);
+
+ if (drm->session->active) {
+ wlr_output_send_frame(&conn->output);
+ }
+}
+
+int handle_drm_event(int fd, uint32_t mask, void *data) {
+ drmEventContext event = {
+ .version = 2,
+ .page_flip_handler = page_flip_handler,
+ };
+
+ drmHandleEvent(fd, &event);
+ return 1;
+}
+
+void restore_drm_outputs(struct wlr_drm_backend *drm) {
+ uint64_t to_close = (1L << wl_list_length(&drm->outputs)) - 1;
+
+ struct wlr_drm_connector *conn;
+ wl_list_for_each(conn, &drm->outputs, link) {
+ if (conn->state == WLR_DRM_CONN_CONNECTED) {
+ conn->state = WLR_DRM_CONN_CLEANUP;
+ }
+ }
+
+ time_t timeout = time(NULL) + 5;
+
+ while (to_close && time(NULL) < timeout) {
+ handle_drm_event(drm->fd, 0, NULL);
+ size_t i = 0;
+ struct wlr_drm_connector *conn;
+ wl_list_for_each(conn, &drm->outputs, link) {
+ if (conn->state != WLR_DRM_CONN_CLEANUP || !conn->pageflip_pending) {
+ to_close &= ~(1 << i);
+ }
+ i++;
+ }
+ }
+
+ if (to_close) {
+ wlr_log(WLR_ERROR, "Timed out stopping output renderers");
+ }
+
+ wl_list_for_each(conn, &drm->outputs, link) {
+ drmModeCrtc *crtc = conn->old_crtc;
+ if (!crtc) {
+ continue;
+ }
+
+ drmModeSetCrtc(drm->fd, crtc->crtc_id, crtc->buffer_id, crtc->x, crtc->y,
+ &conn->id, 1, &crtc->mode);
+ }
+}
+
+static void drm_connector_cleanup(struct wlr_drm_connector *conn) {
+ if (!conn) {
+ return;
+ }
+
+ switch (conn->state) {
+ case WLR_DRM_CONN_CONNECTED:
+ case WLR_DRM_CONN_CLEANUP:;
+ struct wlr_drm_crtc *crtc = conn->crtc;
+ if (crtc != NULL) {
+ for (int i = 0; i < 3; ++i) {
+ if (!crtc->planes[i]) {
+ continue;
+ }
+
+ finish_drm_surface(&crtc->planes[i]->surf);
+ finish_drm_surface(&crtc->planes[i]->mgpu_surf);
+ if (crtc->planes[i]->id == 0) {
+ free(crtc->planes[i]);
+ crtc->planes[i] = NULL;
+ }
+ }
+ }
+
+ conn->output.current_mode = NULL;
+ conn->desired_mode = NULL;
+ struct wlr_drm_mode *mode, *tmp;
+ wl_list_for_each_safe(mode, tmp, &conn->output.modes, wlr_mode.link) {
+ wl_list_remove(&mode->wlr_mode.link);
+ free(mode);
+ }
+
+ conn->output.enabled = false;
+ conn->output.width = conn->output.height = conn->output.refresh = 0;
+
+ memset(&conn->output.make, 0, sizeof(conn->output.make));
+ memset(&conn->output.model, 0, sizeof(conn->output.model));
+ memset(&conn->output.serial, 0, sizeof(conn->output.serial));
+
+ if (conn->output.idle_frame != NULL) {
+ wl_event_source_remove(conn->output.idle_frame);
+ conn->output.idle_frame = NULL;
+ }
+ conn->output.needs_swap = false;
+ conn->output.frame_pending = false;
+
+ /* Fallthrough */
+ case WLR_DRM_CONN_NEEDS_MODESET:
+ wlr_log(WLR_INFO, "Emitting destruction signal for '%s'",
+ conn->output.name);
+ dealloc_crtc(conn);
+ conn->possible_crtc = 0;
+ conn->desired_mode = NULL;
+ wlr_signal_emit_safe(&conn->output.events.destroy, &conn->output);
+ break;
+ case WLR_DRM_CONN_DISCONNECTED:
+ break;
+ case WLR_DRM_CONN_DISAPPEARED:
+ return; // don't change state
+ }
+
+ conn->state = WLR_DRM_CONN_DISCONNECTED;
+}
diff --git a/backend/drm/legacy.c b/backend/drm/legacy.c
new file mode 100644
index 00000000..182c7a95
--- /dev/null
+++ b/backend/drm/legacy.c
@@ -0,0 +1,83 @@
+#include <gbm.h>
+#include <wlr/util/log.h>
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+#include "backend/drm/drm.h"
+#include "backend/drm/iface.h"
+#include "backend/drm/util.h"
+
+static bool legacy_crtc_pageflip(struct wlr_drm_backend *drm,
+ struct wlr_drm_connector *conn, struct wlr_drm_crtc *crtc,
+ uint32_t fb_id, drmModeModeInfo *mode) {
+ if (mode) {
+ if (drmModeSetCrtc(drm->fd, crtc->id, fb_id, 0, 0,
+ &conn->id, 1, mode)) {
+ wlr_log_errno(WLR_ERROR, "%s: Failed to set CRTC", conn->output.name);
+ return false;
+ }
+ }
+
+ if (drmModePageFlip(drm->fd, crtc->id, fb_id, DRM_MODE_PAGE_FLIP_EVENT, conn)) {
+ wlr_log_errno(WLR_ERROR, "%s: Failed to page flip", conn->output.name);
+ return false;
+ }
+
+ return true;
+}
+
+static bool legacy_conn_enable(struct wlr_drm_backend *drm,
+ struct wlr_drm_connector *conn, bool enable) {
+ int ret = drmModeConnectorSetProperty(drm->fd, conn->id, conn->props.dpms,
+ enable ? DRM_MODE_DPMS_ON : DRM_MODE_DPMS_OFF);
+ return ret >= 0;
+}
+
+bool legacy_crtc_set_cursor(struct wlr_drm_backend *drm,
+ struct wlr_drm_crtc *crtc, struct gbm_bo *bo) {
+ if (!crtc || !crtc->cursor) {
+ return true;
+ }
+
+ if (!bo) {
+ if (drmModeSetCursor(drm->fd, crtc->id, 0, 0, 0)) {
+ wlr_log_errno(WLR_DEBUG, "Failed to clear hardware cursor");
+ return false;
+ }
+ return true;
+ }
+
+ struct wlr_drm_plane *plane = crtc->cursor;
+
+ if (drmModeSetCursor(drm->fd, crtc->id, gbm_bo_get_handle(bo).u32,
+ plane->surf.width, plane->surf.height)) {
+ wlr_log_errno(WLR_DEBUG, "Failed to set hardware cursor");
+ return false;
+ }
+
+ return true;
+}
+
+bool legacy_crtc_move_cursor(struct wlr_drm_backend *drm,
+ struct wlr_drm_crtc *crtc, int x, int y) {
+ return !drmModeMoveCursor(drm->fd, crtc->id, x, y);
+}
+
+bool legacy_crtc_set_gamma(struct wlr_drm_backend *drm,
+ struct wlr_drm_crtc *crtc, size_t size,
+ uint16_t *r, uint16_t *g, uint16_t *b) {
+ return !drmModeCrtcSetGamma(drm->fd, crtc->id, (uint32_t)size, r, g, b);
+}
+
+size_t legacy_crtc_get_gamma_size(struct wlr_drm_backend *drm,
+ struct wlr_drm_crtc *crtc) {
+ return (size_t)crtc->legacy_crtc->gamma_size;
+}
+
+const struct wlr_drm_interface legacy_iface = {
+ .conn_enable = legacy_conn_enable,
+ .crtc_pageflip = legacy_crtc_pageflip,
+ .crtc_set_cursor = legacy_crtc_set_cursor,
+ .crtc_move_cursor = legacy_crtc_move_cursor,
+ .crtc_set_gamma = legacy_crtc_set_gamma,
+ .crtc_get_gamma_size = legacy_crtc_get_gamma_size,
+};
diff --git a/backend/drm/properties.c b/backend/drm/properties.c
new file mode 100644
index 00000000..5541d1be
--- /dev/null
+++ b/backend/drm/properties.c
@@ -0,0 +1,151 @@
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <wlr/util/log.h>
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+#include "backend/drm/properties.h"
+
+/*
+ * Creates a mapping between property names and an array index where to store
+ * the ids. The prop_info arrays must be sorted by name, as bsearch is used to
+ * search them.
+ */
+struct prop_info {
+ const char *name;
+ size_t index;
+};
+
+static const struct prop_info connector_info[] = {
+#define INDEX(name) (offsetof(union wlr_drm_connector_props, name) / sizeof(uint32_t))
+ { "CRTC_ID", INDEX(crtc_id) },
+ { "DPMS", INDEX(dpms) },
+ { "EDID", INDEX(edid) },
+ { "PATH", INDEX(path) },
+ { "link-status", INDEX(link_status) },
+#undef INDEX
+};
+
+static const struct prop_info crtc_info[] = {
+#define INDEX(name) (offsetof(union wlr_drm_crtc_props, name) / sizeof(uint32_t))
+ { "ACTIVE", INDEX(active) },
+ { "GAMMA_LUT", INDEX(gamma_lut) },
+ { "GAMMA_LUT_SIZE", INDEX(gamma_lut_size) },
+ { "MODE_ID", INDEX(mode_id) },
+ { "rotation", INDEX(rotation) },
+ { "scaling mode", INDEX(scaling_mode) },
+#undef INDEX
+};
+
+static const struct prop_info plane_info[] = {
+#define INDEX(name) (offsetof(union wlr_drm_plane_props, name) / sizeof(uint32_t))
+ { "CRTC_H", INDEX(crtc_h) },
+ { "CRTC_ID", INDEX(crtc_id) },
+ { "CRTC_W", INDEX(crtc_w) },
+ { "CRTC_X", INDEX(crtc_x) },
+ { "CRTC_Y", INDEX(crtc_y) },
+ { "FB_ID", INDEX(fb_id) },
+ { "SRC_H", INDEX(src_h) },
+ { "SRC_W", INDEX(src_w) },
+ { "SRC_X", INDEX(src_x) },
+ { "SRC_Y", INDEX(src_y) },
+ { "type", INDEX(type) },
+#undef INDEX
+};
+
+static int cmp_prop_info(const void *arg1, const void *arg2) {
+ const char *key = arg1;
+ const struct prop_info *elem = arg2;
+
+ return strcmp(key, elem->name);
+}
+
+static bool scan_properties(int fd, uint32_t id, uint32_t type, uint32_t *result,
+ const struct prop_info *info, size_t info_len) {
+ drmModeObjectProperties *props = drmModeObjectGetProperties(fd, id, type);
+ if (!props) {
+ wlr_log_errno(WLR_ERROR, "Failed to get DRM object properties");
+ return false;
+ }
+
+ for (uint32_t i = 0; i < props->count_props; ++i) {
+ drmModePropertyRes *prop = drmModeGetProperty(fd, props->props[i]);
+ if (!prop) {
+ wlr_log_errno(WLR_ERROR, "Failed to get DRM object property");
+ continue;
+ }
+
+ const struct prop_info *p =
+ bsearch(prop->name, info, info_len, sizeof(info[0]), cmp_prop_info);
+ if (p) {
+ result[p->index] = prop->prop_id;
+ }
+
+ drmModeFreeProperty(prop);
+ }
+
+ drmModeFreeObjectProperties(props);
+ return true;
+}
+
+bool get_drm_connector_props(int fd, uint32_t id,
+ union wlr_drm_connector_props *out) {
+ return scan_properties(fd, id, DRM_MODE_OBJECT_CONNECTOR, out->props,
+ connector_info, sizeof(connector_info) / sizeof(connector_info[0]));
+}
+
+bool get_drm_crtc_props(int fd, uint32_t id, union wlr_drm_crtc_props *out) {
+ return scan_properties(fd, id, DRM_MODE_OBJECT_CRTC, out->props,
+ crtc_info, sizeof(crtc_info) / sizeof(crtc_info[0]));
+}
+
+bool get_drm_plane_props(int fd, uint32_t id, union wlr_drm_plane_props *out) {
+ return scan_properties(fd, id, DRM_MODE_OBJECT_PLANE, out->props,
+ plane_info, sizeof(plane_info) / sizeof(plane_info[0]));
+}
+
+bool get_drm_prop(int fd, uint32_t obj, uint32_t prop, uint64_t *ret) {
+ drmModeObjectProperties *props =
+ drmModeObjectGetProperties(fd, obj, DRM_MODE_OBJECT_ANY);
+ if (!props) {
+ return false;
+ }
+
+ bool found = false;
+
+ for (uint32_t i = 0; i < props->count_props; ++i) {
+ if (props->props[i] == prop) {
+ *ret = props->prop_values[i];
+ found = true;
+ break;
+ }
+ }
+
+ drmModeFreeObjectProperties(props);
+ return found;
+}
+
+void *get_drm_prop_blob(int fd, uint32_t obj, uint32_t prop, size_t *ret_len) {
+ uint64_t blob_id;
+ if (!get_drm_prop(fd, obj, prop, &blob_id)) {
+ return NULL;
+ }
+
+ drmModePropertyBlobRes *blob = drmModeGetPropertyBlob(fd, blob_id);
+ if (!blob) {
+ return NULL;
+ }
+
+ void *ptr = malloc(blob->length);
+ if (!ptr) {
+ drmModeFreePropertyBlob(blob);
+ return NULL;
+ }
+
+ memcpy(ptr, blob->data, blob->length);
+ *ret_len = blob->length;
+
+ drmModeFreePropertyBlob(blob);
+ return ptr;
+}
diff --git a/backend/drm/renderer.c b/backend/drm/renderer.c
new file mode 100644
index 00000000..70b1bcbe
--- /dev/null
+++ b/backend/drm/renderer.c
@@ -0,0 +1,264 @@
+#include <assert.h>
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+#include <gbm.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <wayland-util.h>
+#include <wlr/render/egl.h>
+#include <wlr/render/gles2.h>
+#include <wlr/render/wlr_renderer.h>
+#include <wlr/types/wlr_matrix.h>
+#include <wlr/util/log.h>
+#include "backend/drm/drm.h"
+#include "glapi.h"
+
+#ifndef DRM_FORMAT_MOD_LINEAR
+#define DRM_FORMAT_MOD_LINEAR 0
+#endif
+
+bool init_drm_renderer(struct wlr_drm_backend *drm,
+ struct wlr_drm_renderer *renderer, wlr_renderer_create_func_t create_renderer_func) {
+ renderer->gbm = gbm_create_device(drm->fd);
+ if (!renderer->gbm) {
+ wlr_log(WLR_ERROR, "Failed to create GBM device");
+ return false;
+ }
+
+ if (!create_renderer_func) {
+ create_renderer_func = wlr_renderer_autocreate;
+ }
+
+ static EGLint config_attribs[] = {
+ EGL_RED_SIZE, 1,
+ EGL_GREEN_SIZE, 1,
+ EGL_BLUE_SIZE, 1,
+ EGL_ALPHA_SIZE, 1,
+ EGL_NONE,
+ };
+
+ renderer->wlr_rend = create_renderer_func(&renderer->egl,
+ EGL_PLATFORM_GBM_MESA, renderer->gbm,
+ config_attribs, GBM_FORMAT_ARGB8888);
+
+ if (!renderer->wlr_rend) {
+ wlr_log(WLR_ERROR, "Failed to create EGL/WLR renderer");
+ goto error_gbm;
+ }
+
+ renderer->fd = drm->fd;
+ return true;
+
+error_gbm:
+ gbm_device_destroy(renderer->gbm);
+ return false;
+}
+
+void finish_drm_renderer(struct wlr_drm_renderer *renderer) {
+ if (!renderer) {
+ return;
+ }
+
+ wlr_renderer_destroy(renderer->wlr_rend);
+ wlr_egl_finish(&renderer->egl);
+ gbm_device_destroy(renderer->gbm);
+}
+
+bool init_drm_surface(struct wlr_drm_surface *surf,
+ struct wlr_drm_renderer *renderer, uint32_t width, uint32_t height,
+ uint32_t format, uint32_t flags) {
+ if (surf->width == width && surf->height == height) {
+ return true;
+ }
+
+ surf->renderer = renderer;
+ surf->width = width;
+ surf->height = height;
+
+ if (surf->gbm) {
+ if (surf->front) {
+ gbm_surface_release_buffer(surf->gbm, surf->front);
+ surf->front = NULL;
+ }
+ if (surf->back) {
+ gbm_surface_release_buffer(surf->gbm, surf->back);
+ surf->back = NULL;
+ }
+ gbm_surface_destroy(surf->gbm);
+ }
+ wlr_egl_destroy_surface(&surf->renderer->egl, surf->egl);
+
+ surf->gbm = gbm_surface_create(renderer->gbm, width, height,
+ format, GBM_BO_USE_RENDERING | flags);
+ if (!surf->gbm) {
+ wlr_log_errno(WLR_ERROR, "Failed to create GBM surface");
+ goto error_zero;
+ }
+
+ surf->egl = wlr_egl_create_surface(&renderer->egl, surf->gbm);
+ if (surf->egl == EGL_NO_SURFACE) {
+ wlr_log(WLR_ERROR, "Failed to create EGL surface");
+ goto error_gbm;
+ }
+
+ return true;
+
+error_gbm:
+ gbm_surface_destroy(surf->gbm);
+error_zero:
+ memset(surf, 0, sizeof(*surf));
+ return false;
+}
+
+void finish_drm_surface(struct wlr_drm_surface *surf) {
+ if (!surf || !surf->renderer) {
+ return;
+ }
+
+ if (surf->front) {
+ gbm_surface_release_buffer(surf->gbm, surf->front);
+ }
+ if (surf->back) {
+ gbm_surface_release_buffer(surf->gbm, surf->back);
+ }
+
+ wlr_egl_destroy_surface(&surf->renderer->egl, surf->egl);
+ if (surf->gbm) {
+ gbm_surface_destroy(surf->gbm);
+ }
+
+ memset(surf, 0, sizeof(*surf));
+}
+
+bool make_drm_surface_current(struct wlr_drm_surface *surf,
+ int *buffer_damage) {
+ return wlr_egl_make_current(&surf->renderer->egl, surf->egl, buffer_damage);
+}
+
+struct gbm_bo *swap_drm_surface_buffers(struct wlr_drm_surface *surf,
+ pixman_region32_t *damage) {
+ if (surf->front) {
+ gbm_surface_release_buffer(surf->gbm, surf->front);
+ }
+
+ wlr_egl_swap_buffers(&surf->renderer->egl, surf->egl, damage);
+
+ surf->front = surf->back;
+ surf->back = gbm_surface_lock_front_buffer(surf->gbm);
+ return surf->back;
+}
+
+struct gbm_bo *get_drm_surface_front(struct wlr_drm_surface *surf) {
+ if (surf->front) {
+ return surf->front;
+ }
+
+ make_drm_surface_current(surf, NULL);
+ struct wlr_renderer *renderer = surf->renderer->wlr_rend;
+ wlr_renderer_begin(renderer, surf->width, surf->height);
+ wlr_renderer_clear(renderer, (float[]){ 0.0, 0.0, 0.0, 1.0 });
+ wlr_renderer_end(renderer);
+ return swap_drm_surface_buffers(surf, NULL);
+}
+
+void post_drm_surface(struct wlr_drm_surface *surf) {
+ if (surf->front) {
+ gbm_surface_release_buffer(surf->gbm, surf->front);
+ surf->front = NULL;
+ }
+}
+
+bool export_drm_bo(struct gbm_bo *bo, struct wlr_dmabuf_attributes *attribs) {
+ memset(attribs, 0, sizeof(struct wlr_dmabuf_attributes));
+
+ attribs->n_planes = gbm_bo_get_plane_count(bo);
+ if (attribs->n_planes > WLR_DMABUF_MAX_PLANES) {
+ return false;
+ }
+
+ attribs->width = gbm_bo_get_width(bo);
+ attribs->height = gbm_bo_get_height(bo);
+ attribs->format = gbm_bo_get_format(bo);
+ attribs->modifier = gbm_bo_get_modifier(bo);
+
+ for (int i = 0; i < attribs->n_planes; ++i) {
+ attribs->offset[i] = gbm_bo_get_offset(bo, i);
+ attribs->stride[i] = gbm_bo_get_stride_for_plane(bo, i);
+ attribs->fd[i] = gbm_bo_get_fd(bo);
+ if (attribs->fd[i] < 0) {
+ for (int j = 0; j < i; ++j) {
+ close(attribs->fd[j]);
+ }
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static void free_tex(struct gbm_bo *bo, void *data) {
+ struct wlr_texture *tex = data;
+ wlr_texture_destroy(tex);
+}
+
+static struct wlr_texture *get_tex_for_bo(struct wlr_drm_renderer *renderer,
+ struct gbm_bo *bo) {
+ struct wlr_texture *tex = gbm_bo_get_user_data(bo);
+ if (tex) {
+ return tex;
+ }
+
+ struct wlr_dmabuf_attributes attribs;
+ if (!export_drm_bo(bo, &attribs)) {
+ return NULL;
+ }
+
+ tex = wlr_texture_from_dmabuf(renderer->wlr_rend, &attribs);
+ if (tex) {
+ gbm_bo_set_user_data(bo, tex, free_tex);
+ }
+
+ return tex;
+}
+
+struct gbm_bo *copy_drm_surface_mgpu(struct wlr_drm_surface *dest,
+ struct gbm_bo *src) {
+ make_drm_surface_current(dest, NULL);
+
+ struct wlr_texture *tex = get_tex_for_bo(dest->renderer, src);
+ assert(tex);
+
+ float mat[9];
+ wlr_matrix_projection(mat, 1, 1, WL_OUTPUT_TRANSFORM_NORMAL);
+
+ struct wlr_renderer *renderer = dest->renderer->wlr_rend;
+ wlr_renderer_begin(renderer, dest->width, dest->height);
+ wlr_renderer_clear(renderer, (float[]){ 0.0, 0.0, 0.0, 1.0 });
+ wlr_render_texture_with_matrix(renderer, tex, mat, 1.0f);
+ wlr_renderer_end(renderer);
+
+ return swap_drm_surface_buffers(dest, NULL);
+}
+
+bool init_drm_plane_surfaces(struct wlr_drm_plane *plane,
+ struct wlr_drm_backend *drm, int32_t width, uint32_t height,
+ uint32_t format) {
+ if (!drm->parent) {
+ return init_drm_surface(&plane->surf, &drm->renderer, width, height,
+ format, GBM_BO_USE_SCANOUT);
+ }
+
+ if (!init_drm_surface(&plane->surf, &drm->parent->renderer,
+ width, height, format, GBM_BO_USE_LINEAR)) {
+ return false;
+ }
+
+ if (!init_drm_surface(&plane->mgpu_surf, &drm->renderer,
+ width, height, format, GBM_BO_USE_SCANOUT)) {
+ finish_drm_surface(&plane->surf);
+ return false;
+ }
+
+ return true;
+}
diff --git a/backend/drm/util.c b/backend/drm/util.c
new file mode 100644
index 00000000..6f2dd5be
--- /dev/null
+++ b/backend/drm/util.c
@@ -0,0 +1,348 @@
+#include <drm_mode.h>
+#include <drm.h>
+#include <gbm.h>
+#include <stdio.h>
+#include <string.h>
+#include <wlr/util/log.h>
+#include "backend/drm/util.h"
+
+int32_t calculate_refresh_rate(const drmModeModeInfo *mode) {
+ int32_t refresh = (mode->clock * 1000000LL / mode->htotal +
+ mode->vtotal / 2) / mode->vtotal;
+
+ if (mode->flags & DRM_MODE_FLAG_INTERLACE) {
+ refresh *= 2;
+ }
+
+ if (mode->flags & DRM_MODE_FLAG_DBLSCAN) {
+ refresh /= 2;
+ }
+
+ if (mode->vscan > 1) {
+ refresh /= mode->vscan;
+ }
+
+ return refresh;
+}
+
+// Constructed from http://edid.tv/manufacturer
+static const char *get_manufacturer(uint16_t id) {
+#define ID(a, b, c) ((a & 0x1f) << 10) | ((b & 0x1f) << 5) | (c & 0x1f)
+ switch (id) {
+ case ID('A', 'A', 'A'): return "Avolites Ltd";
+ case ID('A', 'C', 'I'): return "Ancor Communications Inc";
+ case ID('A', 'C', 'R'): return "Acer Technologies";
+ case ID('A', 'D', 'A'): return "Addi-Data GmbH";
+ case ID('A', 'P', 'P'): return "Apple Computer Inc";
+ case ID('A', 'S', 'K'): return "Ask A/S";
+ case ID('A', 'V', 'T'): return "Avtek (Electronics) Pty Ltd";
+ case ID('B', 'N', 'O'): return "Bang & Olufsen";
+ case ID('C', 'M', 'N'): return "Chimei Innolux Corporation";
+ case ID('C', 'M', 'O'): return "Chi Mei Optoelectronics corp.";
+ case ID('C', 'R', 'O'): return "Extraordinary Technologies PTY Limited";
+ case ID('D', 'E', 'L'): return "Dell Inc.";
+ case ID('D', 'G', 'C'): return "Data General Corporation";
+ case ID('D', 'O', 'N'): return "DENON, Ltd.";
+ case ID('E', 'N', 'C'): return "Eizo Nanao Corporation";
+ case ID('E', 'P', 'H'): return "Epiphan Systems Inc.";
+ case ID('E', 'X', 'P'): return "Data Export Corporation";
+ case ID('F', 'N', 'I'): return "Funai Electric Co., Ltd.";
+ case ID('F', 'U', 'S'): return "Fujitsu Siemens Computers GmbH";
+ case ID('G', 'S', 'M'): return "Goldstar Company Ltd";
+ case ID('H', 'I', 'Q'): return "Kaohsiung Opto Electronics Americas, Inc.";
+ case ID('H', 'S', 'D'): return "HannStar Display Corp";
+ case ID('H', 'T', 'C'): return "Hitachi Ltd";
+ case ID('H', 'W', 'P'): return "Hewlett Packard";
+ case ID('I', 'N', 'T'): return "Interphase Corporation";
+ case ID('I', 'N', 'X'): return "Communications Supply Corporation (A division of WESCO)";
+ case ID('I', 'T', 'E'): return "Integrated Tech Express Inc";
+ case ID('I', 'V', 'M'): return "Iiyama North America";
+ case ID('L', 'E', 'N'): return "Lenovo Group Limited";
+ case ID('M', 'A', 'X'): return "Rogen Tech Distribution Inc";
+ case ID('M', 'E', 'G'): return "Abeam Tech Ltd";
+ case ID('M', 'E', 'I'): return "Panasonic Industry Company";
+ case ID('M', 'T', 'C'): return "Mars-Tech Corporation";
+ case ID('M', 'T', 'X'): return "Matrox";
+ case ID('N', 'E', 'C'): return "NEC Corporation";
+ case ID('N', 'E', 'X'): return "Nexgen Mediatech Inc.";
+ case ID('O', 'N', 'K'): return "ONKYO Corporation";
+ case ID('O', 'R', 'N'): return "ORION ELECTRIC CO., LTD.";
+ case ID('O', 'T', 'M'): return "Optoma Corporation";
+ case ID('O', 'V', 'R'): return "Oculus VR, Inc.";
+ case ID('P', 'H', 'L'): return "Philips Consumer Electronics Company";
+ case ID('P', 'I', 'O'): return "Pioneer Electronic Corporation";
+ case ID('P', 'N', 'R'): return "Planar Systems, Inc.";
+ case ID('Q', 'D', 'S'): return "Quanta Display Inc.";
+ case ID('R', 'A', 'T'): return "Rent-A-Tech";
+ case ID('R', 'E', 'N'): return "Renesas Technology Corp.";
+ case ID('S', 'A', 'M'): return "Samsung Electric Company";
+ case ID('S', 'A', 'N'): return "Sanyo Electric Co., Ltd.";
+ case ID('S', 'E', 'C'): return "Seiko Epson Corporation";
+ case ID('S', 'H', 'P'): return "Sharp Corporation";
+ case ID('S', 'I', 'I'): return "Silicon Image, Inc.";
+ case ID('S', 'N', 'Y'): return "Sony";
+ case ID('S', 'T', 'D'): return "STD Computer Inc";
+ case ID('S', 'V', 'S'): return "SVSI";
+ case ID('S', 'Y', 'N'): return "Synaptics Inc";
+ case ID('T', 'C', 'L'): return "Technical Concepts Ltd";
+ case ID('T', 'O', 'P'): return "Orion Communications Co., Ltd.";
+ case ID('T', 'S', 'B'): return "Toshiba America Info Systems Inc";
+ case ID('T', 'S', 'T'): return "Transtream Inc";
+ case ID('U', 'N', 'K'): return "Unknown";
+ case ID('V', 'E', 'S'): return "Vestel Elektronik Sanayi ve Ticaret A. S.";
+ case ID('V', 'I', 'T'): return "Visitech AS";
+ case ID('V', 'I', 'Z'): return "VIZIO, Inc";
+ case ID('V', 'S', 'C'): return "ViewSonic Corporation";
+ case ID('Y', 'M', 'H'): return "Yamaha Corporation";
+ default: return "Unknown";
+ }
+#undef ID
+}
+
+/* See https://en.wikipedia.org/wiki/Extended_Display_Identification_Data for layout of EDID data.
+ * We don't parse the EDID properly. We just expect to receive valid data.
+ */
+void parse_edid(struct wlr_output *restrict output, size_t len, const uint8_t *data) {
+ if (!data || len < 128) {
+ snprintf(output->make, sizeof(output->make), "<Unknown>");
+ snprintf(output->model, sizeof(output->model), "<Unknown>");
+ return;
+ }
+
+ uint16_t id = (data[8] << 8) | data[9];
+ snprintf(output->make, sizeof(output->make), "%s", get_manufacturer(id));
+
+ uint16_t model = data[10] | (data[11] << 8);
+ snprintf(output->model, sizeof(output->model), "0x%04X", model);
+
+ uint32_t serial = data[12] | (data[13] << 8) | (data[14] << 8) | (data[15] << 8);
+ snprintf(output->serial, sizeof(output->serial), "0x%08X", serial);
+
+ for (size_t i = 72; i <= 108; i += 18) {
+ uint16_t flag = (data[i] << 8) | data[i + 1];
+ if (flag == 0 && data[i + 3] == 0xFC) {
+ sprintf(output->model, "%.13s", &data[i + 5]);
+
+ // Monitor names are terminated by newline if they're too short
+ char *nl = strchr(output->model, '\n');
+ if (nl) {
+ *nl = '\0';
+ }
+ } else if (flag == 0 && data[i + 3] == 0xFF) {
+ sprintf(output->serial, "%.13s", &data[i + 5]);
+
+ // Monitor serial numbers are terminated by newline if they're too
+ // short
+ char *nl = strchr(output->serial, '\n');
+ if (nl) {
+ *nl = '\0';
+ }
+ }
+ }
+}
+
+const char *conn_get_name(uint32_t type_id) {
+ switch (type_id) {
+ case DRM_MODE_CONNECTOR_Unknown: return "Unknown";
+ case DRM_MODE_CONNECTOR_VGA: return "VGA";
+ case DRM_MODE_CONNECTOR_DVII: return "DVI-I";
+ case DRM_MODE_CONNECTOR_DVID: return "DVI-D";
+ case DRM_MODE_CONNECTOR_DVIA: return "DVI-A";
+ case DRM_MODE_CONNECTOR_Composite: return "Composite";
+ case DRM_MODE_CONNECTOR_SVIDEO: return "SVIDEO";
+ case DRM_MODE_CONNECTOR_LVDS: return "LVDS";
+ case DRM_MODE_CONNECTOR_Component: return "Component";
+ case DRM_MODE_CONNECTOR_9PinDIN: return "DIN";
+ case DRM_MODE_CONNECTOR_DisplayPort: return "DP";
+ case DRM_MODE_CONNECTOR_HDMIA: return "HDMI-A";
+ case DRM_MODE_CONNECTOR_HDMIB: return "HDMI-B";
+ case DRM_MODE_CONNECTOR_TV: return "TV";
+ case DRM_MODE_CONNECTOR_eDP: return "eDP";
+ case DRM_MODE_CONNECTOR_VIRTUAL: return "Virtual";
+ case DRM_MODE_CONNECTOR_DSI: return "DSI";
+#ifdef DRM_MODE_CONNECTOR_DPI
+ case DRM_MODE_CONNECTOR_DPI: return "DPI";
+#endif
+ default: return "Unknown";
+ }
+}
+
+static void free_fb(struct gbm_bo *bo, void *data) {
+ uint32_t id = (uintptr_t)data;
+
+ if (id) {
+ struct gbm_device *gbm = gbm_bo_get_device(bo);
+ drmModeRmFB(gbm_device_get_fd(gbm), id);
+ }
+}
+
+uint32_t get_fb_for_bo(struct gbm_bo *bo) {
+ uint32_t id = (uintptr_t)gbm_bo_get_user_data(bo);
+ if (id) {
+ return id;
+ }
+
+ struct gbm_device *gbm = gbm_bo_get_device(bo);
+
+ int fd = gbm_device_get_fd(gbm);
+ uint32_t width = gbm_bo_get_width(bo);
+ uint32_t height = gbm_bo_get_height(bo);
+ uint32_t handles[4] = {gbm_bo_get_handle(bo).u32};
+ uint32_t pitches[4] = {gbm_bo_get_stride(bo)};
+ uint32_t offsets[4] = {gbm_bo_get_offset(bo, 0)};
+ uint32_t format = gbm_bo_get_format(bo);
+
+ if (drmModeAddFB2(fd, width, height, format, handles, pitches, offsets, &id, 0)) {
+ wlr_log_errno(WLR_ERROR, "Unable to add DRM framebuffer");
+ }
+
+ gbm_bo_set_user_data(bo, (void *)(uintptr_t)id, free_fb);
+
+ return id;
+}
+
+static inline bool is_taken(size_t n, const uint32_t arr[static n], uint32_t key) {
+ for (size_t i = 0; i < n; ++i) {
+ if (arr[i] == key) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/*
+ * Store all of the non-recursive state in a struct, so we aren't literally
+ * passing 12 arguments to a function.
+ */
+struct match_state {
+ const size_t num_objs;
+ const uint32_t *restrict objs;
+ const size_t num_res;
+ size_t score;
+ size_t replaced;
+ uint32_t *restrict res;
+ uint32_t *restrict best;
+ const uint32_t *restrict orig;
+ bool exit_early;
+};
+
+/*
+ * skips: The number of SKIP elements encountered so far.
+ * score: The number of resources we've matched so far.
+ * replaced: The number of changes from the original solution.
+ * i: The index of the current element.
+ *
+ * This tries to match a solution as close to st->orig as it can.
+ *
+ * Returns whether we've set a new best element with this solution.
+ */
+static bool match_obj_(struct match_state *st, size_t skips, size_t score, size_t replaced, size_t i) {
+ // Finished
+ if (i >= st->num_res) {
+ if (score > st->score ||
+ (score == st->score && replaced < st->replaced)) {
+ st->score = score;
+ st->replaced = replaced;
+ memcpy(st->best, st->res, sizeof(st->best[0]) * st->num_res);
+
+ st->exit_early = (st->score == st->num_res - skips
+ || st->score == st->num_objs)
+ && st->replaced == 0;
+
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ if (st->orig[i] == SKIP) {
+ st->res[i] = SKIP;
+ return match_obj_(st, skips + 1, score, replaced, i + 1);
+ }
+
+ bool has_best = false;
+
+ /*
+ * Attempt to use the current solution first, to try and avoid
+ * recalculating everything
+ */
+ if (st->orig[i] != UNMATCHED && !is_taken(i, st->res, st->orig[i])) {
+ st->res[i] = st->orig[i];
+ size_t obj_score = st->objs[st->res[i]] != 0 ? 1 : 0;
+ if (match_obj_(st, skips, score + obj_score, replaced, i + 1)) {
+ has_best = true;
+ }
+ }
+ if (st->orig[i] == UNMATCHED) {
+ st->res[i] = UNMATCHED;
+ if (match_obj_(st, skips, score, replaced, i + 1)) {
+ has_best = true;
+ }
+ }
+ if (st->exit_early) {
+ return true;
+ }
+
+ if (st->orig[i] != UNMATCHED) {
+ ++replaced;
+ }
+
+ for (size_t candidate = 0; candidate < st->num_objs; ++candidate) {
+ // We tried this earlier
+ if (candidate == st->orig[i]) {
+ continue;
+ }
+
+ // Not compatible
+ if (!(st->objs[candidate] & (1 << i))) {
+ continue;
+ }
+
+ // Already taken
+ if (is_taken(i, st->res, candidate)) {
+ continue;
+ }
+
+ st->res[i] = candidate;
+ size_t obj_score = st->objs[candidate] != 0 ? 1 : 0;
+ if (match_obj_(st, skips, score + obj_score, replaced, i + 1)) {
+ has_best = true;
+ }
+
+ if (st->exit_early) {
+ return true;
+ }
+ }
+
+ if (has_best) {
+ return true;
+ }
+
+ // Maybe this resource can't be matched
+ st->res[i] = UNMATCHED;
+ return match_obj_(st, skips, score, replaced, i + 1);
+}
+
+size_t match_obj(size_t num_objs, const uint32_t objs[static restrict num_objs],
+ size_t num_res, const uint32_t res[static restrict num_res],
+ uint32_t out[static restrict num_res]) {
+ uint32_t solution[num_res];
+ for (size_t i = 0; i < num_res; ++i) {
+ solution[i] = UNMATCHED;
+ }
+
+ struct match_state st = {
+ .num_objs = num_objs,
+ .num_res = num_res,
+ .score = 0,
+ .replaced = SIZE_MAX,
+ .objs = objs,
+ .res = solution,
+ .best = out,
+ .orig = res,
+ .exit_early = false,
+ };
+
+ match_obj_(&st, 0, 0, 0, 0);
+ return st.score;
+}