diff options
Diffstat (limited to 'backend')
| -rw-r--r-- | backend/CMakeLists.txt | 21 | ||||
| -rw-r--r-- | backend/backend.c | 36 | ||||
| -rw-r--r-- | backend/drm/backend.c | 103 | ||||
| -rw-r--r-- | backend/drm/drm.c | 418 | ||||
| -rw-r--r-- | backend/drm/udev.c | 214 | ||||
| -rw-r--r-- | backend/egl.c | 178 | 
6 files changed, 966 insertions, 4 deletions
| diff --git a/backend/CMakeLists.txt b/backend/CMakeLists.txt index 830a2158..fc1793cf 100644 --- a/backend/CMakeLists.txt +++ b/backend/CMakeLists.txt @@ -1,17 +1,30 @@  include_directories(  	${PROTOCOLS_INCLUDE_DIRS}  	${WAYLAND_INCLUDE_DIR} +	${DRM_INCLUDE_DIRS}  )  add_library(wlr-backend -    wayland/backend.c -    wayland/registry.c -    wayland/wl_seat.c -    wayland/wl_output.c +    #wayland/backend.c +    #wayland/registry.c +    #wayland/wl_seat.c +    #wayland/wl_output.c +    drm/backend.c +    drm/drm.c +    drm/udev.c +    backend.c +    egl.c  )  target_link_libraries(wlr-backend      wlr-common      wlr-wayland      ${WAYLAND_LIBRARIES} +    ${DRM_LIBRARIES} +    ${GBM_LIBRARIES} +    ${GLESv2_LIBRARIES} +    ${EGL_LIBRARIES} +    ${SYSTEMD_LIBRARIES} +    ${UDEV_LIBRARIES} +    ${GBM_LIBRARIES}  ) diff --git a/backend/backend.c b/backend/backend.c new file mode 100644 index 00000000..753bb3ae --- /dev/null +++ b/backend/backend.c @@ -0,0 +1,36 @@ +#include <wayland-server.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include "common/log.h" +#include "backend.h" + +struct wlr_backend *wlr_backend_create(const struct wlr_backend_impl *impl, +		struct wlr_backend_state *state) { +	struct wlr_backend *backend = calloc(1, sizeof(struct wlr_backend)); +	if (!backend) { +		wlr_log(L_ERROR, "Allocation failed: %s", strerror(errno)); +		return NULL; +	} +	backend->state = state; +	backend->impl = impl; +	wl_signal_init(&backend->events.output_add); +	wl_signal_init(&backend->events.output_remove); +	wl_signal_init(&backend->events.keyboard_add); +	wl_signal_init(&backend->events.keyboard_remove); +	wl_signal_init(&backend->events.pointer_add); +	wl_signal_init(&backend->events.pointer_remove); +	wl_signal_init(&backend->events.touch_add); +	wl_signal_init(&backend->events.touch_remove); +	return backend; +} + +bool wlr_backend_init(struct wlr_backend *backend) { +	return backend->impl->init(backend->state); +} + +void wlr_backend_destroy(struct wlr_backend *backend) { +	backend->impl->destroy(backend->state); +	// TODO: free outputs +	free(backend); +} diff --git a/backend/drm/backend.c b/backend/drm/backend.c new file mode 100644 index 00000000..0da84745 --- /dev/null +++ b/backend/drm/backend.c @@ -0,0 +1,103 @@ +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <wayland-server.h> + +#include <wlr/session.h> +#include <wlr/common/list.h> + +#include "backend.h" +#include "backend/drm/backend.h" +#include "backend/drm/drm.h" +#include "backend/drm/udev.h" +#include "common/log.h" + +static bool wlr_drm_backend_init(struct wlr_backend_state *state) { +	wlr_drm_scan_connectors(state); +	return true; +} + +static void wlr_drm_backend_destroy(struct wlr_backend_state *state) { +	if (!state) { +		return; +	} +	// TODO: free outputs in shared backend code +	wlr_drm_renderer_free(&state->renderer); +	wlr_udev_free(&state->udev); +	wlr_session_close_file(state->session, state->fd); +	wlr_session_finish(state->session); +	wl_event_source_remove(state->drm_event); +	free(state); +} + +static struct wlr_backend_impl backend_impl = { +	.init = wlr_drm_backend_init, +	.destroy = wlr_drm_backend_destroy +}; + +struct wlr_backend *wlr_drm_backend_create(struct wl_display *display, +		struct wlr_session *session) { +	struct wlr_backend_state *state = calloc(1, sizeof(struct wlr_backend_state)); +	if (!state) { +		wlr_log(L_ERROR, "Allocation failed: %s", strerror(errno)); +		return NULL; +	} + +	struct wlr_backend *backend = wlr_backend_create(&backend_impl, state); +	if (!backend) { +		wlr_log(L_ERROR, "Allocation failed: %s", strerror(errno)); +		return NULL; +	} + +	state->backend = backend; +	state->session = session; +	state->outputs = list_create(); +	if (!state->outputs) { +		wlr_log(L_ERROR, "Failed to allocate list"); +		goto error_backend; +	} + +	if (!wlr_udev_init(display, &state->udev)) { +		wlr_log(L_ERROR, "Failed to start udev"); +		goto error_list; +	} + +	state->fd = wlr_udev_find_gpu(&state->udev, state->session); +	if (state->fd == -1) { +		wlr_log(L_ERROR, "Failed to open DRM device"); +		goto error_udev; +	} + +	struct wl_event_loop *event_loop = wl_display_get_event_loop(display); + +	state->drm_event = wl_event_loop_add_fd(event_loop, state->fd, +		WL_EVENT_READABLE, wlr_drm_event, NULL); +	if (!state->drm_event) { +		wlr_log(L_ERROR, "Failed to create DRM event source"); +		goto error_fd; +	} + +	// TODO: what is the difference between the per-output renderer and this +	// one? +	if (!wlr_drm_renderer_init(&state->renderer, state->fd)) { +		wlr_log(L_ERROR, "Failed to initialize renderer"); +		goto error_event; +	} + +	return backend; + +error_event: +	wl_event_source_remove(state->drm_event); +error_fd: +	wlr_session_close_file(state->session, state->fd); +error_udev: +	wlr_udev_free(&state->udev); +error_list: +	list_free(state->outputs); +error_backend: +	free(state); +	free(backend); +	return NULL; +} diff --git a/backend/drm/drm.c b/backend/drm/drm.c new file mode 100644 index 00000000..7cbd3e4a --- /dev/null +++ b/backend/drm/drm.c @@ -0,0 +1,418 @@ +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <inttypes.h> +#include <errno.h> +#include <xf86drm.h> +#include <xf86drmMode.h> +#include <drm_mode.h> +#include <EGL/egl.h> +#include <EGL/eglext.h> +#include <gbm.h> +#include <GLES3/gl3.h> +#include <wayland-server.h> + +#include "wayland.h" +#include "backend.h" +#include "backend/drm/backend.h" +#include "backend/drm/drm.h" +#include "common/log.h" + +static const char *conn_name[] = { +	[DRM_MODE_CONNECTOR_Unknown]     = "Unknown", +	[DRM_MODE_CONNECTOR_VGA]         = "VGA", +	[DRM_MODE_CONNECTOR_DVII]        = "DVI-I", +	[DRM_MODE_CONNECTOR_DVID]        = "DVI-D", +	[DRM_MODE_CONNECTOR_DVIA]        = "DVI-A", +	[DRM_MODE_CONNECTOR_Composite]   = "Composite", +	[DRM_MODE_CONNECTOR_SVIDEO]      = "SVIDEO", +	[DRM_MODE_CONNECTOR_LVDS]        = "LVDS", +	[DRM_MODE_CONNECTOR_Component]   = "Component", +	[DRM_MODE_CONNECTOR_9PinDIN]     = "DIN", +	[DRM_MODE_CONNECTOR_DisplayPort] = "DP", +	[DRM_MODE_CONNECTOR_HDMIA]       = "HDMI-A", +	[DRM_MODE_CONNECTOR_HDMIB]       = "HDMI-B", +	[DRM_MODE_CONNECTOR_TV]          = "TV", +	[DRM_MODE_CONNECTOR_eDP]         = "eDP", +	[DRM_MODE_CONNECTOR_VIRTUAL]     = "Virtual", +	[DRM_MODE_CONNECTOR_DSI]         = "DSI", +}; + +bool wlr_drm_renderer_init(struct wlr_drm_renderer *renderer, int fd) { +	renderer->gbm = gbm_create_device(fd); +	if (!renderer->gbm) { +		wlr_log(L_ERROR, "Failed to create GBM device: %s", strerror(errno)); +		return false; +	} + +	if (!wlr_egl_init(&renderer->egl, EGL_PLATFORM_GBM_MESA, renderer->gbm)) { +		gbm_device_destroy(renderer->gbm); +		return false; +	} + +	renderer->fd = fd; +	return true; +} + +void wlr_drm_renderer_free(struct wlr_drm_renderer *renderer) { +	if (!renderer) { +		return; +	} +	wlr_egl_free(&renderer->egl); +	gbm_device_destroy(renderer->gbm); +} + +static void free_fb(struct gbm_bo *bo, void *data) { +	uint32_t *id = data; + +	if (id && *id) { +		drmModeRmFB(gbm_bo_get_fd(bo), *id); +	} + +	free(id); +} + +static uint32_t get_fb_for_bo(int fd, struct gbm_bo *bo) { +	uint32_t *id = gbm_bo_get_user_data(bo); + +	if (id) { +		return *id; +	} + +	id = calloc(1, sizeof *id); +	if (!id) { +		wlr_log(L_ERROR, "Allocation failed: %s", strerror(errno)); +		return 0; +	} + +	drmModeAddFB(fd, gbm_bo_get_width(bo), gbm_bo_get_height(bo), 24, 32, +		     gbm_bo_get_stride(bo), gbm_bo_get_handle(bo).u32, id); + +	gbm_bo_set_user_data(bo, id, free_fb); + +	return *id; +} + +void wlr_drm_output_begin(struct wlr_output *output) { +	struct wlr_output_state *_output = output->state; +	struct wlr_drm_renderer *renderer = _output->renderer; +	eglMakeCurrent(renderer->egl.display, _output->egl, +			_output->egl, renderer->egl.context); +} + +void wlr_drm_output_end(struct wlr_output *output) { +	struct wlr_output_state *_output = output->state; +	struct wlr_drm_renderer *renderer = _output->renderer; + +	eglSwapBuffers(renderer->egl.display, _output->egl); +	struct gbm_bo *bo = gbm_surface_lock_front_buffer(_output->gbm); +	uint32_t fb_id = get_fb_for_bo(renderer->fd, bo); +	drmModePageFlip(renderer->fd, _output->crtc, fb_id, DRM_MODE_PAGE_FLIP_EVENT, _output); +	gbm_surface_release_buffer(_output->gbm, bo); +	_output->pageflip_pending = true; +} + +static bool display_init_renderer(struct wlr_drm_renderer *renderer, +	struct wlr_output_state *output) { +	struct wlr_output_mode *mode = output->wlr_output->current_mode; +	output->renderer = renderer; +	output->gbm = gbm_surface_create(renderer->gbm, mode->width, +		mode->height, GBM_FORMAT_XRGB8888, GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING); +	if (!output->gbm) { +		wlr_log(L_ERROR, "Failed to create GBM surface for %s: %s", output->name, +			strerror(errno)); +		return false; +	} + +	output->egl = wlr_egl_create_surface(&renderer->egl, output->gbm); +	if (output->egl == EGL_NO_SURFACE) { +		wlr_log(L_ERROR, "Failed to create EGL surface for %s", output->name); +		return false; +	} + +	// Render black frame +	eglMakeCurrent(renderer->egl.display, output->egl, output->egl, renderer->egl.context); + +	glViewport(0, 0, output->width, output->height); +	glClearColor(0.0, 0.0, 0.0, 1.0); +	glClear(GL_COLOR_BUFFER_BIT); + +	eglSwapBuffers(renderer->egl.display, output->egl); + +	struct gbm_bo *bo = gbm_surface_lock_front_buffer(output->gbm); +	uint32_t fb_id = get_fb_for_bo(renderer->fd, bo); + +	drmModeSetCrtc(renderer->fd, output->crtc, fb_id, 0, 0, +			&output->connector, 1, &mode->state->mode); +	drmModePageFlip(renderer->fd, output->crtc, fb_id, +			DRM_MODE_PAGE_FLIP_EVENT, output); + +	gbm_surface_release_buffer(output->gbm, bo); +	return true; +} + +static int find_id(const void *item, const void *cmp_to) { +	const struct wlr_output_state *output = item; +	const uint32_t *id = cmp_to; + +	if (output->connector < *id) { +		return -1; +	} else if (output->connector > *id) { +		return 1; +	} else { +		return 0; +	} +} + +static bool wlr_drm_output_set_mode(struct wlr_output_state *output, +		struct wlr_output_mode *mode) { +	struct wlr_backend_state *state = +		wl_container_of(output->renderer, state, renderer); + +	wlr_log(L_INFO, "Modesetting '%s' with '%ux%u@%u mHz'", output->name, +			mode->width, mode->height, mode->refresh); + +	drmModeConnector *conn = drmModeGetConnector(state->fd, output->connector); +	if (!conn) { +		wlr_log(L_ERROR, "Failed to get DRM connector"); +		goto error; +	} + +	if (conn->connection != DRM_MODE_CONNECTED || conn->count_modes == 0) { +		wlr_log(L_ERROR, "%s is not connected", output->name); +		goto error; +	} + +	drmModeRes *res = drmModeGetResources(state->fd); +	if (!res) { +		wlr_log(L_ERROR, "Failed to get DRM resources"); +		goto error; +	} + +	bool success = false; +	for (int i = 0; !success && i < conn->count_encoders; ++i) { +		drmModeEncoder *enc = drmModeGetEncoder(state->fd, conn->encoders[i]); +		if (!enc) { +			continue; +		} + +		for (int j = 0; j < res->count_crtcs; ++j) { +			if ((enc->possible_crtcs & (1 << j)) == 0) { +				continue; +			} + +			if ((state->taken_crtcs & (1 << j)) == 0) { +				state->taken_crtcs |= 1 << j; +				output->crtc = res->crtcs[j]; +				success = true; +				break; +			} +		} +		drmModeFreeEncoder(enc); +	} + +	drmModeFreeResources(res); + +	if (!success) { +		wlr_log(L_ERROR, "Failed to find CRTC for %s", output->name); +		goto error; +	} + +	output->state = DRM_OUTPUT_CONNECTED; +	output->width = mode->width; +	output->height = mode->height; +	output->wlr_output->current_mode = mode; + +	if (!display_init_renderer(&state->renderer, output)) { +		wlr_log(L_ERROR, "Failed to initalise renderer for %s", output->name); +		goto error; +	} + +	drmModeFreeConnector(conn); +	return true; + +error: +	wlr_drm_output_cleanup(output, false); +	drmModeFreeConnector(conn); +	return false; +} + +static void wlr_drm_output_destroy(struct wlr_output_state *output) { +	wlr_drm_output_cleanup(output, true); +	wlr_drm_renderer_free(output->renderer); +	free(output); +} + +static struct wlr_output_impl output_impl = { +	.set_mode = wlr_drm_output_set_mode, +	.destroy = wlr_drm_output_destroy, +}; + +void wlr_drm_scan_connectors(struct wlr_backend_state *state) { +	wlr_log(L_INFO, "Scanning DRM connectors"); + +	drmModeRes *res = drmModeGetResources(state->fd); +	if (!res) { +		wlr_log(L_ERROR, "Failed to get DRM resources"); +		return; +	} + +	for (int i = 0; i < res->count_connectors; ++i) { +		uint32_t id = res->connectors[i]; + +		drmModeConnector *conn = drmModeGetConnector(state->fd, id); +		if (!conn) { +			wlr_log(L_ERROR, "Failed to get DRM connector"); +			continue; +		} + +		struct wlr_output_state *output; +		struct wlr_output *wlr_output; +		int index = list_seq_find(state->outputs, find_id, &id); + +		if (index == -1) { +			output = calloc(1, sizeof(struct wlr_output_state)); +			if (!state) { +				drmModeFreeConnector(conn); +				wlr_log(L_ERROR, "Allocation failed: %s", strerror(errno)); +				return; +			} +			wlr_output = output->wlr_output = wlr_output_create(&output_impl, output); +			if (!wlr_output) { +				drmModeFreeConnector(conn); +				wlr_log(L_ERROR, "Allocation failed: %s", strerror(errno)); +				return; +			} + +			output->renderer = &state->renderer; +			output->state = DRM_OUTPUT_DISCONNECTED; +			output->connector = id; +			// TODO: Populate more wlr_output fields +			// TODO: Move this to wlr_output->name +			snprintf(output->name, sizeof(output->name), "%s-%"PRIu32, +				 conn_name[conn->connector_type], +				 conn->connector_type_id); + +			drmModeEncoder *curr_enc = drmModeGetEncoder(state->fd, conn->encoder_id); +			if (curr_enc) { +				output->old_crtc = drmModeGetCrtc(state->fd, curr_enc->crtc_id); +				free(curr_enc); +			} + +			list_add(state->outputs, output); +			wlr_log(L_INFO, "Found display '%s'", output->name); +		} else { +			output = state->outputs->items[index]; +			wlr_output = output->wlr_output; +		} + +		// TODO: move state into wlr_output +		if (output->state == DRM_OUTPUT_DISCONNECTED && +			conn->connection == DRM_MODE_CONNECTED) { + +			wlr_log(L_INFO, "'%s' connected", output->name); +			wlr_log(L_INFO, "Detected modes:"); + +			for (int i = 0; i < conn->count_modes; ++i) { +				struct wlr_output_mode_state *_state = calloc(1, +						sizeof(struct wlr_output_mode_state)); +				_state->mode = conn->modes[i]; +				struct wlr_output_mode *mode = calloc(1, +						sizeof(struct wlr_output_mode)); +				mode->width = _state->mode.hdisplay; +				// TODO: Calculate more accurate refresh rate +				// TODO: Check that this refresh rate is mHz +				mode->height = _state->mode.vdisplay; +				mode->state = _state; + +				wlr_log(L_INFO, "  %"PRIu16"@%"PRIu16"@%"PRIu32, +					_state->mode.hdisplay, _state->mode.vdisplay, +					_state->mode.vrefresh); + +				list_add(wlr_output->modes, mode); +			} + +			output->state = DRM_OUTPUT_NEEDS_MODESET; +			wlr_log(L_INFO, "Sending modesetting signal for '%s'", output->name); +			wl_signal_emit(&state->backend->events.output_add, wlr_output); +		} else if (output->state == DRM_OUTPUT_CONNECTED && +			conn->connection != DRM_MODE_CONNECTED) { + +			wlr_log(L_INFO, "'%s' disconnected", output->name); +			// TODO: Destroy +			wlr_drm_output_cleanup(output, false); +		} + +		drmModeFreeConnector(conn); +	} + +	drmModeFreeResources(res); +} + +static void page_flip_handler(int fd, unsigned seq, +		unsigned tv_sec, unsigned tv_usec, void *user) { +	struct wlr_output_state *output = user; +	struct wlr_backend_state *state = +		wl_container_of(output->renderer, state, renderer); + +	output->pageflip_pending = false; +	if (output->state == DRM_OUTPUT_CONNECTED) { +		wl_signal_emit(&output->wlr_output->events.frame, output->wlr_output); +	} +} + +int wlr_drm_event(int fd, uint32_t mask, void *data) { +	drmEventContext event = { +		.version = DRM_EVENT_CONTEXT_VERSION, +		.page_flip_handler = page_flip_handler, +	}; + +	drmHandleEvent(fd, &event); +	return 1; +} + +static void restore_output(struct wlr_output_state *output, int fd) { +	// Wait for any pending pageflips to finish +	while (output->pageflip_pending) { +		wlr_drm_event(fd, 0, NULL); +	} + +	drmModeCrtc *crtc = output->old_crtc; +	if (!crtc) { +		return; +	} + +	drmModeSetCrtc(fd, crtc->crtc_id, crtc->buffer_id, crtc->x, crtc->y, +		&output->connector, 1, &crtc->mode); +	drmModeFreeCrtc(crtc); +} + +void wlr_drm_output_cleanup(struct wlr_output_state *output, bool restore) { +	if (!output) { +		return; +	} + +	struct wlr_drm_renderer *renderer = output->renderer; +	struct wlr_backend_state *state = wl_container_of(renderer, state, renderer); + +	switch (output->state) { +	case DRM_OUTPUT_CONNECTED: +		eglDestroySurface(renderer->egl.display, output->egl); +		gbm_surface_destroy(output->gbm); +		output->egl = EGL_NO_SURFACE; +		output->gbm = NULL; +		/* Fallthrough */ +	case DRM_OUTPUT_NEEDS_MODESET: +		output->state = DRM_OUTPUT_DISCONNECTED; +		if (restore) { +			restore_output(output, renderer->fd); +		} +		wlr_log(L_INFO, "Emmiting destruction signal for '%s'", output->name); +		wl_signal_emit(&state->backend->events.output_remove, output->wlr_output); +		break; +	case DRM_OUTPUT_DISCONNECTED: +		break; +	} +	// TODO: free wlr_output +} diff --git a/backend/drm/udev.c b/backend/drm/udev.c new file mode 100644 index 00000000..e5470157 --- /dev/null +++ b/backend/drm/udev.c @@ -0,0 +1,214 @@ +#define _POSIX_C_SOURCE 200809L + +#include <libudev.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <stdint.h> +#include <xf86drm.h> +#include <xf86drmMode.h> +#include <wayland-server.h> + +#include <wlr/session.h> + +#include "backend.h" +#include "backend/drm/backend.h" +#include "backend/drm/udev.h" +#include "backend/drm/drm.h" +#include "common/log.h" + +/* Tests if 'path' is KMS compatible by trying to open it. + * It leaves the open device in *fd_out it it succeeds. + */ +static bool device_is_kms(struct wlr_session *restrict session, +	const char *restrict path, int *restrict fd_out) { + +	int fd; + +	if (!path) { +		return false; +	} + +	fd = wlr_session_open_file(session, path); +	if (fd < 0) { +		return false; +	} + +	drmModeRes *res = drmModeGetResources(fd); +	if (!res) { +		goto out_fd; +	} + +	if (res->count_crtcs <= 0 || res->count_connectors <= 0 || +		res->count_encoders <= 0) { + +		goto out_res; +	} + +	if (*fd_out >= 0) { +		wlr_session_close_file(session, *fd_out); +	} + +	*fd_out = fd; + +	drmModeFreeResources(res); +	return true; + +out_res: +	drmModeFreeResources(res); +out_fd: +	wlr_session_close_file(session, fd); +	return false; +} + +/* Tries to find the primary GPU by checking for the "boot_vga" attribute. + * If it's not found, it returns the first valid GPU it finds. + */ +int wlr_udev_find_gpu(struct wlr_udev *udev, struct wlr_session *session) { +	struct udev_enumerate *en = udev_enumerate_new(udev->udev); +	if (!en) { +		wlr_log(L_ERROR, "Failed to create udev enumeration"); +		return -1; +	} + +	udev_enumerate_add_match_subsystem(en, "drm"); +	udev_enumerate_add_match_sysname(en, "card[0-9]*"); +	udev_enumerate_scan_devices(en); + +	struct udev_list_entry *entry; +	int fd = -1; +	char *drm_path = NULL; + +	udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(en)) { +		bool is_boot_vga = false; + +		const char *path = udev_list_entry_get_name(entry); +		struct udev_device *dev = udev_device_new_from_syspath(udev->udev, path); +		if (!dev) { +			continue; +		} + +		/* +		const char *seat = udev_device_get_property_value(dev, "ID_SEAT"); +		if (!seat) +			seat = "seat0"; +		if (strcmp(session->seat, seat) != 0) { +			udev_device_unref(dev); +			continue; +		} +		*/ + +		// This is owned by 'dev', so we don't need to free it +		struct udev_device *pci = +			udev_device_get_parent_with_subsystem_devtype(dev, "pci", NULL); + +		if (pci) { +			const char *id = udev_device_get_sysattr_value(pci, "boot_vga"); +			if (id && strcmp(id, "1") == 0) { +				is_boot_vga = true; +			} +		} + +		// We already have a valid GPU +		if (!is_boot_vga && fd >= 0) { +			udev_device_unref(dev); +			continue; +		} + +		path = udev_device_get_devnode(dev); +		if (!device_is_kms(session, path, &fd)) { +			udev_device_unref(dev); +			continue; +		} + +		free(drm_path); +		drm_path = strdup(path); + +		udev_device_unref(dev); + +		// We've found the primary GPU +		if (is_boot_vga) { +			break; +		} +	} + +	udev_enumerate_unref(en); + +	udev->drm_path = drm_path; +	return fd; +} + +static int udev_event(int fd, uint32_t mask, void *data) { +	struct wlr_udev *udev = data; +	struct wlr_backend_state *state = wl_container_of(udev, state, udev); + +	struct udev_device *dev = udev_monitor_receive_device(udev->mon); +	if (!dev) { +		return 1; +	} + +	const char *path = udev_device_get_devnode(dev); +	if (!path || strcmp(path, udev->drm_path) != 0) { +		goto out; +	} + +	const char *action = udev_device_get_action(dev); +	if (!action || strcmp(action, "change") != 0) { +		goto out; +	} + +	wlr_drm_scan_connectors(state); + +out: +	udev_device_unref(dev); +	return 1; +} + +bool wlr_udev_init(struct wl_display *display, struct wlr_udev *udev) { +	udev->udev = udev_new(); +	if (!udev->udev) { +		wlr_log(L_ERROR, "Failed to create udev context"); +		return false; +	} + +	udev->mon = udev_monitor_new_from_netlink(udev->udev, "udev"); +	if (!udev->mon) { +		wlr_log(L_ERROR, "Failed to create udev monitor"); +		goto error_udev; +	} + +	udev_monitor_filter_add_match_subsystem_devtype(udev->mon, "drm", NULL); +	udev_monitor_enable_receiving(udev->mon); + +	struct wl_event_loop *event_loop = wl_display_get_event_loop(display); +	int fd = udev_monitor_get_fd(udev->mon); + +	udev->event = wl_event_loop_add_fd(event_loop, fd, WL_EVENT_READABLE, +		udev_event, udev); +	if (!udev->event) { +		wlr_log(L_ERROR, "Failed to create udev event source"); +		goto error_mon; +	} + +	udev->drm_path = NULL; + +	return true; + +error_mon: +	udev_monitor_unref(udev->mon); +error_udev: +	udev_unref(udev->udev); +	return false; +} + +void wlr_udev_free(struct wlr_udev *udev) { +	if (!udev) { +		return; +	} + +	wl_event_source_remove(udev->event); + +	udev_monitor_unref(udev->mon); +	udev_unref(udev->udev); +	free(udev->drm_path); +} diff --git a/backend/egl.c b/backend/egl.c new file mode 100644 index 00000000..2aac25b7 --- /dev/null +++ b/backend/egl.c @@ -0,0 +1,178 @@ +#include <EGL/egl.h> +#include <EGL/eglext.h> +#include <GLES3/gl3.h> +#include <gbm.h> // GBM_FORMAT_XRGB8888 + +#include "backend/egl.h" +#include "common/log.h" + +static const char *egl_error(void) { +	switch (eglGetError()) { +	case EGL_SUCCESS: +		return "Success"; +	case EGL_NOT_INITIALIZED: +		return "Not initialized"; +	case EGL_BAD_ACCESS: +		return "Bad access"; +	case EGL_BAD_ALLOC: +		return "Bad alloc"; +	case EGL_BAD_ATTRIBUTE: +		return "Bad attribute"; +	case EGL_BAD_CONTEXT: +		return "Bad Context"; +	case EGL_BAD_CONFIG: +		return "Bad Config"; +	case EGL_BAD_CURRENT_SURFACE: +		return "Bad current surface"; +	case EGL_BAD_DISPLAY: +		return "Bad display"; +	case EGL_BAD_SURFACE: +		return "Bad surface"; +	case EGL_BAD_MATCH: +		return "Bad match"; +	case EGL_BAD_PARAMETER: +		return "Bad parameter"; +	case EGL_BAD_NATIVE_PIXMAP: +		return "Bad native pixmap"; +	case EGL_BAD_NATIVE_WINDOW: +		return "Bad native window"; +	case EGL_CONTEXT_LOST: +		return "Context lost"; +	default: +		return "Unknown"; +	} +} + +// EGL extensions +PFNEGLGETPLATFORMDISPLAYEXTPROC get_platform_display; +PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC create_platform_window_surface; + +static bool egl_exts() { +	get_platform_display = (PFNEGLGETPLATFORMDISPLAYEXTPROC) +		eglGetProcAddress("eglGetPlatformDisplayEXT"); + +	if (!get_platform_display) { +		wlr_log(L_ERROR, "Failed to load EGL extension 'eglGetPlatformDisplayEXT'"); +		return false; +	} + +	create_platform_window_surface = (PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC) +		eglGetProcAddress("eglCreatePlatformWindowSurfaceEXT"); + +	if (!get_platform_display) { +		wlr_log(L_ERROR, +			"Failed to load EGL extension 'eglCreatePlatformWindowSurfaceEXT'"); +		return false; +	} + +	return true; +} + +static bool egl_get_config(EGLDisplay disp, EGLConfig *out) { +	EGLint count = 0, matched = 0, ret; + +	ret = eglGetConfigs(disp, NULL, 0, &count); +	if (ret == EGL_FALSE || count == 0) { +		return false; +	} + +	EGLConfig configs[count]; + +	ret = eglChooseConfig(disp, NULL, configs, count, &matched); +	if (ret == EGL_FALSE) { +		return false; +	} + +	for (int i = 0; i < matched; ++i) { +		EGLint gbm_format; + +		if (!eglGetConfigAttrib(disp, +					configs[i], +					EGL_NATIVE_VISUAL_ID, +					&gbm_format)) { +			continue; +		} + +		// XXX: Is GBM_FORMAT_XRGB8888 what we want? +		// I don't know if this works for wl_displays. +		if (gbm_format == GBM_FORMAT_XRGB8888) { +			*out = configs[i]; +			return true; +		} +	} + +	return false; +} + + +bool wlr_egl_init(struct wlr_egl *egl, EGLenum platform, void *display) { +	if (!egl_exts()) { +		return false; +	} +	 +	if (eglBindAPI(EGL_OPENGL_ES_API) == EGL_FALSE) { +		wlr_log(L_ERROR, "Failed to bind to the OpenGL ES API: %s", egl_error()); +		goto error; +	} + +	egl->display = get_platform_display(platform, display, NULL); +	if (egl->display == EGL_NO_DISPLAY) { +		wlr_log(L_ERROR, "Failed to create EGL display: %s", egl_error()); +		goto error; +	} + +	EGLint major, minor; +	if (eglInitialize(egl->display, &major, &minor) == EGL_FALSE) { +		wlr_log(L_ERROR, "Failed to initialize EGL: %s", egl_error()); +		goto error; +	} + +	if (!egl_get_config(egl->display, &egl->config)) { +		wlr_log(L_ERROR, "Failed to get EGL config"); +		goto error; +	} + +	static const EGLint attribs[] = {EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE}; + +	egl->context = eglCreateContext(egl->display, egl->config, +		EGL_NO_CONTEXT, attribs); + +	if (egl->context == EGL_NO_CONTEXT) { +		wlr_log(L_ERROR, "Failed to create EGL context: %s", egl_error()); +		goto error; +	} + +	eglMakeCurrent(egl->display, EGL_NO_SURFACE, EGL_NO_SURFACE, egl->context); +	wlr_log(L_INFO, "Using EGL %d.%d", (int)major, (int)minor); +	wlr_log(L_INFO, "Supported EGL extensions: %s", eglQueryString(egl->display, +		EGL_EXTENSIONS)); +	wlr_log(L_INFO, "Using %s", glGetString(GL_VERSION)); +	wlr_log(L_INFO, "Supported OpenGL ES extensions: %s", glGetString(GL_EXTENSIONS)); + +	return true; + +error: +	eglTerminate(egl->display); +	eglReleaseThread(); +	eglMakeCurrent(EGL_NO_DISPLAY, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + +	return false; +} + +void wlr_egl_free(struct wlr_egl *egl) { +	eglDestroyContext(egl->display, egl->context); +	eglTerminate(egl->display); +	eglReleaseThread(); +	eglMakeCurrent(EGL_NO_DISPLAY, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);	 +} + +EGLSurface wlr_egl_create_surface(struct wlr_egl *egl, void *window) { +	EGLSurface surf = create_platform_window_surface(egl->display, egl->config, +		window, NULL); +	if (surf == EGL_NO_SURFACE) { +		wlr_log(L_ERROR, "Failed to create EGL surface: %s", egl_error()); +		return EGL_NO_SURFACE; +	} + +	return surf; +} | 
