diff options
Diffstat (limited to 'backend/drm')
| -rw-r--r-- | backend/drm/atomic.c | 273 | ||||
| -rw-r--r-- | backend/drm/backend.c | 211 | ||||
| -rw-r--r-- | backend/drm/drm.c | 1418 | ||||
| -rw-r--r-- | backend/drm/legacy.c | 83 | ||||
| -rw-r--r-- | backend/drm/properties.c | 151 | ||||
| -rw-r--r-- | backend/drm/renderer.c | 264 | ||||
| -rw-r--r-- | backend/drm/util.c | 348 | 
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; +} | 
