From 574cca6b45819092693ce46b44a7735a5190166d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20H=C4=83loiu?= Date: Sun, 26 Mar 2017 21:33:16 +0300 Subject: [PATCH] Add initial EGLStream implementation --- README.rst | 48 +++++--- src/compositor/output.h | 1 + src/platform/backend/backend.h | 2 + src/platform/backend/drm.c | 210 ++++++++++++++++++++++++-------- src/platform/context/egl.c | 213 ++++++++++++++++++++++++++++++--- src/platform/context/egl.h | 5 + 6 files changed, 397 insertions(+), 82 deletions(-) diff --git a/README.rst b/README.rst index 7832d127..86ccea3e 100644 --- a/README.rst +++ b/README.rst @@ -12,6 +12,8 @@ FEATURES +------------------+-----------------------+ | Renderers | EGL, GLESv2 | +------------------+-----------------------+ +| Buffer API | GBM, EGL streams | ++------------------+-----------------------+ | TTY session | logind, legacy (suid) | +------------------+-----------------------+ | Input | libinput, xkb | @@ -70,23 +72,25 @@ ENV VARIABLES ``wlc`` reads the following env variables. -+----------------------+------------------------------------------------------+ -| ``WLC_DRM_DEVICE`` | Device to use in DRM mode. (card0 default) | -+----------------------+------------------------------------------------------+ -| ``WLC_SHM`` | Set 1 to force EGL clients to use shared memory. | -+----------------------+------------------------------------------------------+ -| ``WLC_OUTPUTS`` | Number of fake outputs in X11 mode. | -+----------------------+------------------------------------------------------+ -| ``WLC_XWAYLAND`` | Set 0 to disable Xwayland. | -+----------------------+------------------------------------------------------+ -| ``WLC_LIBINPUT`` | Set 1 to force libinput. (Even on X11) | -+----------------------+------------------------------------------------------+ -| ``WLC_REPEAT_DELAY`` | Keyboard repeat delay. | -+----------------------+------------------------------------------------------+ -| ``WLC_REPEAT_RATE`` | Keyboard repeat rate. | -+----------------------+------------------------------------------------------+ -| ``WLC_DEBUG`` | Enable debug channels (comma separated) | -+----------------------+------------------------------------------------------+ ++-----------------------+-----------------------------------------------------+ +| ``WLC_DRM_DEVICE`` | Device to use in DRM mode. (card0 default) | ++-----------------------+-----------------------------------------------------+ +| ``WLC_USE_EGLDEVICE`` | Set 1 to force EGL streams instead of GBM. | ++-----------------------+-----------------------------------------------------+ +| ``WLC_SHM`` | Set 1 to force EGL clients to use shared memory. | ++-----------------------+-----------------------------------------------------+ +| ``WLC_OUTPUTS`` | Number of fake outputs in X11 mode. | ++-----------------------+-----------------------------------------------------+ +| ``WLC_XWAYLAND`` | Set 0 to disable Xwayland. | ++-----------------------+-----------------------------------------------------+ +| ``WLC_LIBINPUT`` | Set 1 to force libinput. (Even on X11) | ++-----------------------+-----------------------------------------------------+ +| ``WLC_REPEAT_DELAY`` | Keyboard repeat delay. | ++-----------------------+-----------------------------------------------------+ +| ``WLC_REPEAT_RATE`` | Keyboard repeat rate. | ++-----------------------+-----------------------------------------------------+ +| ``WLC_DEBUG`` | Enable debug channels (comma separated) | ++-----------------------+-----------------------------------------------------+ KEYBOARD LAYOUT --------------- @@ -103,6 +107,16 @@ If you have ``logind``, you don't have to do anything. Without ``logind`` you need to suid your binary to root user. The permissions will be dropped runtime. +BUFFER API +---------- + +``wlc`` supports both ``GBM`` and ``EGL streams`` buffer APIs. ``GBM`` is used by default and is supported by most GPU drivers except the NVIDIA proprietary driver. + +If you have a NVIDIA GPU using the proprietary driver you need to: + +- enable DRM KMS using the ``nvidia-drm.modeset=1`` kernel parameter +- enable the ``EGL streams`` support by setting the ``WLC_USE_EGLDEVICE`` environment variable: ``export WLC_USE_EGLDEVICE=1`` + ISSUES ------ diff --git a/src/compositor/output.h b/src/compositor/output.h index d546ffbc..0116fe88 100644 --- a/src/compositor/output.h +++ b/src/compositor/output.h @@ -34,6 +34,7 @@ struct wlc_output_information { int32_t physical_width, physical_height; int32_t subpixel; uint32_t connector_id; + uint32_t crtc_id; enum wl_output_transform transform; enum wlc_connector_type connector; }; diff --git a/src/platform/backend/backend.h b/src/platform/backend/backend.h index 5522d860..5db25069 100644 --- a/src/platform/backend/backend.h +++ b/src/platform/backend/backend.h @@ -13,6 +13,8 @@ struct wlc_backend_surface { EGLNativeDisplayType display; EGLNativeWindowType window; EGLint display_type; + int drm_fd; + bool use_egldevice; struct { WLC_NONULL void (*terminate)(struct wlc_backend_surface *surface); diff --git a/src/platform/backend/drm.c b/src/platform/backend/drm.c index 6f4e56bb..9d8ed640 100644 --- a/src/platform/backend/drm.c +++ b/src/platform/backend/drm.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -17,6 +18,7 @@ #include "backend.h" #include "compositor/compositor.h" #include "compositor/output.h" +#include "platform/context/egl.h" #include "session/fd.h" // FIXME: Contains global state (event_source && fd) @@ -27,13 +29,14 @@ struct drm_output_information { drmModeConnector *connector; drmModeEncoder *encoder; drmModeCrtc *crtc; + drmModeModeInfo mode; struct wlc_output_information info; uint32_t width, height; }; struct drm_surface { - struct gbm_device *device; - struct gbm_surface *surface; + void *device; + struct gbm_surface *gbm_surface; drmModeConnector *connector; drmModeEncoder *encoder; drmModeCrtc *crtc; @@ -50,10 +53,8 @@ struct drm_surface { }; static struct { - struct gbm_device *device; -} gbm; - -static struct { + bool use_egldevice; + void *device; int fd; struct wl_event_source *event_source; } drm; @@ -61,12 +62,12 @@ static struct { static void release_fb(struct gbm_surface *surface, struct drm_fb *fb) { - assert(surface && fb); + assert(fb); if (fb->fd > 0) drmModeRmFB(drm.fd, fb->fd); - if (fb->bo) + if (surface && fb->bo) gbm_surface_release_buffer(surface, fb->bo); fb->bo = NULL; @@ -81,9 +82,11 @@ page_flip_handler(int fd, unsigned int frame, unsigned int sec, unsigned int use struct wlc_backend_surface *bsurface = data; struct drm_surface *dsurface = bsurface->internal; - uint8_t next = (dsurface->index + 1) % NUM_FBS; - release_fb(dsurface->surface, &dsurface->fb[next]); - dsurface->index = next; + if (!drm.use_egldevice) { + uint8_t next = (dsurface->index + 1) % NUM_FBS; + release_fb(dsurface->gbm_surface, &dsurface->fb[next]); + dsurface->index = next; + } struct timespec ts; ts.tv_sec = sec; @@ -107,7 +110,7 @@ drm_event(int fd, uint32_t mask, void *data) } static bool -create_fb(struct gbm_surface *surface, struct drm_fb *fb) +create_gbm_fb(struct gbm_surface *surface, struct drm_fb *fb) { assert(surface && fb); @@ -141,6 +144,56 @@ create_fb(struct gbm_surface *surface, struct drm_fb *fb) return false; } +static uint32_t +create_dumb_fb(uint32_t width, uint32_t height) +{ + struct drm_mode_destroy_dumb destroy_request = { 0 }; + struct drm_mode_create_dumb create_request = { 0 }; + create_request.width = width; + create_request.height = height; + create_request.bpp = 32; /* RGBX8888 */ + + if (drmIoctl(drm.fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_request) < 0) { + goto failed_ioctl; + } + + uint32_t fd; + if (drmModeAddFB(drm.fd, width, height, 24, 32, create_request.pitch, create_request.handle, &fd)) { + goto fail_add_fb; + } + + struct drm_mode_map_dumb map_request = { 0 }; + map_request.handle = create_request.handle; + if (drmIoctl(drm.fd, DRM_IOCTL_MODE_MAP_DUMB, &map_request)) { + goto fail_map; + } + + uint8_t *map; + map = mmap(0, create_request.size, PROT_READ | PROT_WRITE, MAP_SHARED, drm.fd, map_request.offset); + if (map == MAP_FAILED) { + goto fail_map; + } + + memset(map, 0, create_request.size); + + return fd; + +failed_ioctl: + wlc_log(WLC_LOG_WARN, "Failed ioctl to create dumb fb"); + goto fail; +fail_add_fb: + wlc_log(WLC_LOG_WARN, "Failed to add dumb fb"); + goto fail_destroy_dumb; +fail_map: + wlc_log(WLC_LOG_WARN, "Failed ioctl to map dumb fb"); + drmModeRmFB(drm.fd, fd); +fail_destroy_dumb: + destroy_request.handle = create_request.handle; + drmIoctl(drm.fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_request); +fail: + return 0; +} + static bool page_flip(struct wlc_backend_surface *bsurface) { @@ -148,12 +201,12 @@ page_flip(struct wlc_backend_surface *bsurface) struct drm_surface *dsurface = bsurface->internal; assert(!dsurface->flipping); struct drm_fb *fb = &dsurface->fb[dsurface->index]; - release_fb(dsurface->surface, fb); + release_fb(dsurface->gbm_surface, fb); struct wlc_output *o; except((o = wl_container_of(bsurface, o, bsurface))); - if (!create_fb(dsurface->surface, fb)) + if (!create_gbm_fb(dsurface->gbm_surface, fb)) return false; if (fb->stride != dsurface->stride) { @@ -175,7 +228,7 @@ page_flip(struct wlc_backend_surface *bsurface) failed_to_page_flip: wlc_log(WLC_LOG_WARN, "Failed to page flip: %m"); fail: - release_fb(dsurface->surface, fb); + release_fb(dsurface->gbm_surface, fb); return false; } @@ -209,15 +262,15 @@ surface_release(struct wlc_backend_surface *bsurface) { struct drm_surface *dsurface = bsurface->internal; struct drm_fb *fb = &dsurface->fb[dsurface->index]; - release_fb(dsurface->surface, fb); + release_fb(dsurface->gbm_surface, fb); drmModeSetCrtc(drm.fd, dsurface->crtc->crtc_id, dsurface->crtc->buffer_id, dsurface->crtc->x, dsurface->crtc->y, &dsurface->connector->connector_id, 1, &dsurface->crtc->mode); if (dsurface->crtc) drmModeFreeCrtc(dsurface->crtc); - if (dsurface->surface) - gbm_surface_destroy(dsurface->surface); + if (!drm.use_egldevice && dsurface->gbm_surface) + gbm_surface_destroy(dsurface->gbm_surface); if (dsurface->encoder) drmModeFreeEncoder(dsurface->encoder); @@ -229,7 +282,16 @@ surface_release(struct wlc_backend_surface *bsurface) } static bool -add_output(struct gbm_device *device, struct gbm_surface *surface, struct drm_output_information *info) +set_crtc_default_mode(struct drm_output_information *info) +{ + int fb = 0; + if (!(fb = create_dumb_fb(info->width, info->height))) + return false; + return drmModeSetCrtc(drm.fd, info->crtc->crtc_id, fb, 0, 0, &info->connector->connector_id, 1, &info->mode) == 0; +} + +static bool +add_output(void *device, struct gbm_surface *surface, struct drm_output_information *info) { struct wlc_backend_surface bsurface; if (!wlc_backend_surface(&bsurface, surface_release, sizeof(struct drm_surface))) @@ -239,11 +301,13 @@ add_output(struct gbm_device *device, struct gbm_surface *surface, struct drm_ou dsurface->connector = info->connector; dsurface->encoder = info->encoder; dsurface->crtc = info->crtc; - dsurface->surface = surface; + dsurface->gbm_surface = surface; dsurface->device = device; + bsurface.use_egldevice = drm.use_egldevice; + bsurface.drm_fd = drm.fd; bsurface.display = (EGLNativeDisplayType)device; - bsurface.display_type = EGL_PLATFORM_GBM_MESA; + bsurface.display_type = (drm.use_egldevice ? EGL_PLATFORM_DEVICE_EXT : EGL_PLATFORM_GBM_KHR); bsurface.window = (EGLNativeWindowType)surface; bsurface.api.sleep = surface_sleep; bsurface.api.page_flip = page_flip; @@ -256,13 +320,12 @@ add_output(struct gbm_device *device, struct gbm_surface *surface, struct drm_ou } static drmModeEncoder* -find_encoder_for_connector(int fd, drmModeRes *resources, drmModeConnector *connector, int32_t *out_crtc_id) +find_encoder_for_connector(int fd, drmModeRes *resources, drmModeConnector *connector) { - assert(resources && connector && out_crtc_id); + assert(resources && connector); drmModeEncoder *encoder = drmModeGetEncoder(fd, connector->encoder_id); if (encoder) { - *out_crtc_id = encoder->crtc_id; return encoder; } else { drmModeFreeEncoder(encoder); @@ -273,15 +336,36 @@ find_encoder_for_connector(int fd, drmModeRes *resources, drmModeConnector *conn if (!(encoder = drmModeGetEncoder(fd, resources->encoders[e]))) continue; - for (int c = 0; c < resources->count_crtcs; ++c) { - if (!(encoder->possible_crtcs & (1 << c))) - continue; + return encoder; + } - *out_crtc_id = resources->crtcs[c]; - return encoder; + return NULL; +} + +static drmModeCrtc* +find_crtc_for_encoder(int fd, drmModeRes *resources, drmModeEncoder *encoder, uint32_t *used_crtcs, int used_crtcs_num) +{ + assert(resources && encoder); + + drmModeCrtc *crtc = drmModeGetCrtc(fd, encoder->crtc_id); + if (crtc) + return crtc; + + for (int c = 0; c < resources->count_crtcs; ++c) { + uint32_t crtc_id = resources->crtcs[c]; + + bool used = false; + for (int i = 0; i < used_crtcs_num; i++) { + if (used_crtcs[i] == crtc_id) { + used = true; + break; + } } - drmModeFreeEncoder(encoder); + if (used || !(encoder->possible_crtcs & (1 << c)) || !(crtc = drmModeGetCrtc(fd, crtc_id))) + continue; + + return crtc; } return NULL; @@ -323,6 +407,11 @@ query_drm(int fd, struct chck_iter_pool *out_infos) goto resources_fail; } + int used_crtcs_num = 0; + uint32_t *used_crtcs; + if (!(used_crtcs = malloc(resources->count_crtcs * sizeof(uint32_t)))) + goto resources_fail; + for (int c = 0; c < resources->count_connectors; c++) { drmModeConnector *connector; if (!(connector = drmModeGetConnector(fd, resources->connectors[c]))) { @@ -336,17 +425,16 @@ query_drm(int fd, struct chck_iter_pool *out_infos) continue; } - int32_t crtc_id; drmModeEncoder *encoder; - if (!(encoder = find_encoder_for_connector(fd, resources, connector, &crtc_id))) { + if (!(encoder = find_encoder_for_connector(fd, resources, connector))) { wlc_log(WLC_LOG_WARN, "Failed to find encoder for connector %d", c); drmModeFreeConnector(connector); continue; } drmModeCrtc *crtc; - if (!(crtc = drmModeGetCrtc(drm.fd, crtc_id))) { - wlc_log(WLC_LOG_WARN, "Failed to get crtc for connector %d (with id: %d)", c, crtc_id); + if (!(crtc = find_crtc_for_encoder(fd, resources, encoder, used_crtcs, used_crtcs_num))) { + wlc_log(WLC_LOG_WARN, "Failed to get crtc for connector %d", c); drmModeFreeEncoder(encoder); drmModeFreeConnector(connector); continue; @@ -367,6 +455,7 @@ query_drm(int fd, struct chck_iter_pool *out_infos) info->info.physical_height = connector->mmHeight; info->info.subpixel = connector->subpixel; info->info.connector_id = connector->connector_type_id; + info->info.crtc_id = crtc->crtc_id; info->info.connector = wlc_connector_for_drm_connector(connector->connector_type); for (int i = 0; i < connector->count_modes; ++i) { @@ -380,6 +469,7 @@ query_drm(int fd, struct chck_iter_pool *out_infos) if (!info->width && !info->height) { info->width = connector->modes[i].hdisplay; info->height = connector->modes[i].vdisplay; + info->mode = connector->modes[i]; } } @@ -387,17 +477,32 @@ query_drm(int fd, struct chck_iter_pool *out_infos) mode.flags |= WL_OUTPUT_MODE_CURRENT; info->width = connector->modes[i].hdisplay; info->height = connector->modes[i].vdisplay; + info->mode = connector->modes[i]; } wlc_log(WLC_LOG_INFO, "MODE: (%d) %ux%u@%u %s", c, mode.width, mode.height, mode.refresh, (mode.flags & WL_OUTPUT_MODE_CURRENT ? "*" : (mode.flags & WL_OUTPUT_MODE_PREFERRED ? "!" : ""))); wlc_output_information_add_mode(&info->info, &mode); } + if (!info->width && !info->height && connector->count_modes) { + struct wlc_output_mode *mode; + mode = chck_iter_pool_get(&info->info.modes, 0); + mode->flags |= WL_OUTPUT_MODE_PREFERRED; + info->width = mode->width; + info->height = mode->height; + info->mode = connector->modes[0]; + } + info->crtc = crtc; info->encoder = encoder; info->connector = connector; + + used_crtcs[used_crtcs_num] = crtc->crtc_id; + used_crtcs_num++; } + free(used_crtcs); + return true; resources_fail: @@ -412,13 +517,12 @@ terminate(void) if (drm.event_source) wl_event_source_remove(drm.event_source); - if (gbm.device) - gbm_device_destroy(gbm.device); + if (!drm.use_egldevice && drm.device) + gbm_device_destroy(drm.device); wlc_fd_close(drm.fd); memset(&drm, 0, sizeof(drm)); - memset(&gbm, 0, sizeof(gbm)); wlc_log(WLC_LOG_INFO, "Closed drm"); } @@ -473,11 +577,14 @@ update_outputs(struct chck_pool *outputs) if (outputs && output_exists_for_connector(outputs, info->connector)) continue; + if (drm.use_egldevice && !set_crtc_default_mode(info)) + continue; + struct gbm_surface *surface; - if (!(surface = gbm_surface_create(gbm.device, info->width, info->height, GBM_BO_FORMAT_XRGB8888, GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING))) + if (!drm.use_egldevice && !(surface = gbm_surface_create(drm.device, info->width, info->height, GBM_BO_FORMAT_XRGB8888, GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING))) continue; - count += (add_output(gbm.device, surface, info) ? 1 : 0); + count += (add_output(drm.device, surface, info) ? 1 : 0); } chck_iter_pool_release(&infos); @@ -487,6 +594,7 @@ update_outputs(struct chck_pool *outputs) bool wlc_drm(struct wlc_backend *backend) { + chck_cstr_to_bool(getenv("WLC_USE_EGLDEVICE"), &drm.use_egldevice); drm.fd = -1; const char *device = getenv("WLC_DRM_DEVICE"); @@ -503,15 +611,20 @@ wlc_drm(struct wlc_backend *backend) if (drm.fd < 0) goto card_open_fail; - /* GBM will load a dri driver, but even though they need symbols from - * libglapi, in some version of Mesa they are not linked to it. Since - * only the gl-renderer module links to it, the call above won't make - * these symbols globally available, and loading the DRI driver fails. - * Workaround this by dlopen()'ing libglapi with RTLD_GLOBAL. */ - dlopen("libglapi.so.0", RTLD_LAZY | RTLD_GLOBAL); + if (!drm.use_egldevice) { + /* GBM will load a dri driver, but even though they need symbols from + * libglapi, in some version of Mesa they are not linked to it. Since + * only the gl-renderer module links to it, the call above won't make + * these symbols globally available, and loading the DRI driver fails. + * Workaround this by dlopen()'ing libglapi with RTLD_GLOBAL. */ + dlopen("libglapi.so.0", RTLD_LAZY | RTLD_GLOBAL); - if (!(gbm.device = gbm_create_device(drm.fd))) - goto gbm_device_fail; + if (!(drm.device = gbm_create_device(drm.fd))) + goto gbm_device_fail; + } else { + if (!(drm.device = get_egl_device())) + goto egl_device_fail; + } if (!(drm.event_source = wl_event_loop_add_fd(wlc_event_loop(), drm.fd, WL_EVENT_READABLE, drm_event, NULL))) goto fail; @@ -525,6 +638,9 @@ wlc_drm(struct wlc_backend *backend) goto fail; gbm_device_fail: wlc_log(WLC_LOG_WARN, "gbm_create_device failed"); + goto fail; +egl_device_fail: + wlc_log(WLC_LOG_WARN, "Failed to get EGL device"); fail: terminate(); return false; diff --git a/src/platform/context/egl.c b/src/platform/context/egl.c index 323304ed..700e8e9d 100644 --- a/src/platform/context/egl.c +++ b/src/platform/context/egl.c @@ -14,12 +14,49 @@ #include "compositor/output.h" #include "platform/backend/backend.h" +#ifndef EGL_NV_stream_attrib +#define EGL_NV_stream_attrib 1 +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLStreamKHR EGLAPIENTRY eglCreateStreamAttribNV(EGLDisplay dpy, const EGLAttrib *attrib_list); +EGLAPI EGLBoolean EGLAPIENTRY eglSetStreamAttribNV(EGLDisplay dpy, EGLStreamKHR stream, EGLenum attribute, EGLAttrib value); +EGLAPI EGLBoolean EGLAPIENTRY eglQueryStreamAttribNV(EGLDisplay dpy, EGLStreamKHR stream, EGLenum attribute, EGLAttrib *value); +EGLAPI EGLBoolean EGLAPIENTRY eglStreamConsumerAcquireAttribNV(EGLDisplay dpy, EGLStreamKHR stream, const EGLAttrib *attrib_list); +EGLAPI EGLBoolean EGLAPIENTRY eglStreamConsumerReleaseAttribNV(EGLDisplay dpy, EGLStreamKHR stream, const EGLAttrib *attrib_list); +#endif +typedef EGLStreamKHR (EGLAPIENTRYP PFNEGLCREATESTREAMATTRIBNVPROC)(EGLDisplay dpy, const EGLAttrib *attrib_list); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLSETSTREAMATTRIBNVPROC)(EGLDisplay dpy, EGLStreamKHR stream, EGLenum attribute, EGLAttrib value); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYSTREAMATTRIBNVPROC)(EGLDisplay dpy, EGLStreamKHR stream, EGLenum attribute, EGLAttrib *value); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLSTREAMCONSUMERACQUIREATTRIBNVPROC)(EGLDisplay dpy, EGLStreamKHR stream, const EGLAttrib *attrib_list); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLSTREAMCONSUMERRELEASEATTRIBNVPROC)(EGLDisplay dpy, EGLStreamKHR stream, const EGLAttrib *attrib_list); +#endif /* EGL_NV_stream_attrib */ + +#ifndef EGL_EXT_stream_acquire_mode +#define EGL_EXT_stream_acquire_mode 1 +#define EGL_CONSUMER_AUTO_ACQUIRE_EXT 0x332B +typedef EGLBoolean (EGLAPIENTRYP PFNEGLSTREAMCONSUMERACQUIREATTRIBEXTPROC)(EGLDisplay dpy, EGLStreamKHR stream, const EGLAttrib *attrib_list); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglStreamConsumerAcquireAttribEXT(EGLDisplay dpy, EGLStreamKHR stream, const EGLAttrib *attrib_list); +#endif +#endif /* EGL_EXT_stream_acquire_mode */ + +#ifndef EGL_NV_output_drm_flip_event +#define EGL_NV_output_drm_flip_event 1 +#define EGL_DRM_FLIP_EVENT_DATA_NV 0x333E +#endif /* EGL_NV_output_drm_flip_event */ + +/* XXX khronos eglext.h does not yet have EGL_DRM_MASTER_FD_EXT */ +#ifndef EGL_DRM_MASTER_FD_EXT +#define EGL_DRM_MASTER_FD_EXT 0x333C +#endif + struct ctx { const char *extensions; + const char *device_extensions; struct wl_display *wl_display; EGLDisplay display; EGLContext context; EGLSurface surface; + EGLStreamKHR stream; EGLConfig config; bool flip_failed; @@ -31,6 +68,13 @@ struct ctx { PFNEGLBINDWAYLANDDISPLAYWL eglBindWaylandDisplayWL; PFNEGLUNBINDWAYLANDDISPLAYWL eglUnbindWaylandDisplayWL; PFNEGLSWAPBUFFERSWITHDAMAGEEXTPROC eglSwapBuffersWithDamage; + // Needed for EGL streams + PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT; + PFNEGLGETOUTPUTLAYERSEXTPROC eglGetOutputLayersEXT; + PFNEGLCREATESTREAMKHRPROC eglCreateStreamKHR; + PFNEGLSTREAMCONSUMEROUTPUTEXTPROC eglStreamConsumerOutputEXT; + PFNEGLCREATESTREAMPRODUCERSURFACEKHRPROC eglCreateStreamProducerSurfaceKHR; + PFNEGLSTREAMCONSUMERACQUIREATTRIBNVPROC eglStreamConsumerAcquireAttribNV; } api; }; @@ -98,15 +142,15 @@ egl_call(const char *func, uint32_t line, const char *eglfunc) #define EGL_CALL(x) x; egl_call(__PRETTY_FUNCTION__, __LINE__, __STRING(x)) WLC_PURE static bool -has_extension(const struct ctx *context, const char *extension) +is_extension_supported(const char *extensions, const char *extension) { - assert(context && extension); + assert(extension); - if (!context->extensions) + if (!extensions) return false; size_t len = strlen(extension), pos; - const char *s = context->extensions; + const char *s = extensions; while ((pos = strcspn(s, " ")) != 0) { size_t next = pos + (s[pos] != 0); @@ -119,6 +163,50 @@ has_extension(const struct ctx *context, const char *extension) return false; } +WLC_PURE static bool +has_extension(const struct ctx *context, const char *extension) +{ + assert(context && extension); + return is_extension_supported(context->extensions, extension); +} + +WLC_PURE static bool +has_device_extension(const struct ctx *context, const char *extension) +{ + assert(context && extension); + return is_extension_supported(context->device_extensions, extension); +} + +EGLDeviceEXT +get_egl_device() +{ + PFNEGLQUERYDEVICESEXTPROC eglQueryDevicesEXT = (void*)eglGetProcAddress("eglQueryDevicesEXT"); + PFNEGLQUERYDEVICESTRINGEXTPROC eglQueryDeviceStringEXT = (void*)eglGetProcAddress("eglQueryDeviceStringEXT"); + + const char *extensions = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); + if (!is_extension_supported(extensions, "EGL_EXT_device_base") && + (!is_extension_supported(extensions, "EGL_EXT_device_enumeration") || + !is_extension_supported(extensions, "EGL_EXT_device_query"))) + return NULL; + + EGLint num_devices; + if (!eglQueryDevicesEXT(0, NULL, &num_devices) || num_devices < 1) + return NULL; + + EGLDeviceEXT devices[255]; + if (!eglQueryDevicesEXT(num_devices, devices, &num_devices)) + return NULL; + + for (int i = 0; i < num_devices; i++) { + EGLDeviceEXT device = devices[i]; + const char *device_extensions = eglQueryDeviceStringEXT(device, EGL_EXTENSIONS); + if (is_extension_supported(device_extensions, "EGL_EXT_device_drm") && device != EGL_NO_DEVICE_EXT) + return device; + } + + return NULL; +} + static void terminate(struct ctx *context) { @@ -170,25 +258,67 @@ terminate(struct ctx *context) * like mesa will be able to adverise these (even though it can do EGL 1.5). */ static EGLDisplay -get_display(struct ctx *context, EGLint type, void *native) +get_display(struct ctx *context, EGLint type, void *native, int drm_fd) { - PFNEGLGETPLATFORMDISPLAYEXTPROC getPlatformDisplayEXT; + /* In practice any EGL 1.5 implementation would support the EXT extension */ + if (context->api.eglGetPlatformDisplayEXT) { + if (has_extension(context, "EGL_EXT_platform_device") && has_device_extension(context, "EGL_EXT_device_drm")) { + /* + * Provide the DRM fd when creating the EGLDisplay, so that the + * EGL implementation can make any necessary DRM calls using the + * same fd as the application. + */ + EGLint attribs[] = { + EGL_DRM_MASTER_FD_EXT, drm_fd, + EGL_NONE + }; - /* Initialize extensions to those of the NULL display, for has_extension */ - context->extensions = EGL_CALL(eglQueryString(NULL, EGL_EXTENSIONS)); + return context->api.eglGetPlatformDisplayEXT(type, native, attribs); + } - /* In practise any EGL 1.5 implementation would support the EXT extension */ - if (has_extension(context, "EGL_EXT_platform_base")) { - PFNEGLGETPLATFORMDISPLAYEXTPROC getPlatformDisplayEXT = - (void *) eglGetProcAddress("eglGetPlatformDisplayEXT"); - if (getPlatformDisplayEXT) - return getPlatformDisplayEXT(type, native, NULL); + return context->api.eglGetPlatformDisplayEXT(type, native, NULL); } /* Welp, everything is awful. */ return eglGetDisplay(native); } +static EGLSurface +create_surface_egl_device(struct ctx *context, struct wlc_backend_surface *bsurface) +{ + struct wlc_output *output; + output = wl_container_of(bsurface, output, bsurface); + + EGLint n = 0; + EGLOutputLayerEXT egl_layer; + EGLAttrib layer_attribs[] = { + EGL_DRM_CRTC_EXT, output->information.crtc_id, + EGL_NONE, + }; + + if (!context->api.eglGetOutputLayersEXT(context->display, layer_attribs, &egl_layer, 1, &n) || !n) { + return EGL_NO_SURFACE; + } + + if (!context->api.eglStreamConsumerOutputEXT(context->display, context->stream, egl_layer)) { + return EGL_NO_SURFACE; + } + + EGLint surface_attribs[] = { + EGL_WIDTH, output->mode.w, + EGL_HEIGHT, output->mode.h, + EGL_NONE + }; + + return context->api.eglCreateStreamProducerSurfaceKHR(context->display, context->config, context->stream, surface_attribs); +} + +static EGLSurface +create_surface_gbm(EGLDisplay display, EGLConfig config, EGLNativeWindowType window) +{ + return eglCreateWindowSurface(display, config, window, NULL); +} + static struct ctx* create_context(struct wlc_backend_surface *bsurface) { @@ -198,7 +328,24 @@ create_context(struct wlc_backend_surface *bsurface) if (!(context = calloc(1, sizeof(struct ctx)))) return NULL; - context->display = get_display(context, bsurface->display_type, bsurface->display); + /* Initialize extensions to those of the NULL display, for has_extension */ + context->extensions = EGL_CALL(eglQueryString(NULL, EGL_EXTENSIONS)); + if (has_extension(context, "EGL_EXT_platform_base")) { + context->api.eglGetPlatformDisplayEXT = (void*)eglGetProcAddress("eglGetPlatformDisplayEXT"); + } + + if (bsurface->use_egldevice) { + PFNEGLQUERYDEVICESTRINGEXTPROC eglQueryDeviceStringEXT = (void*)eglGetProcAddress("eglQueryDeviceStringEXT"); + context->device_extensions = EGL_CALL(eglQueryDeviceStringEXT(bsurface->display, EGL_EXTENSIONS)); + + context->api.eglGetOutputLayersEXT = (void*)eglGetProcAddress("eglGetOutputLayersEXT"); + context->api.eglCreateStreamKHR = (void*)eglGetProcAddress("eglCreateStreamKHR"); + context->api.eglStreamConsumerOutputEXT = (void*)eglGetProcAddress("eglStreamConsumerOutputEXT"); + context->api.eglCreateStreamProducerSurfaceKHR = (void*)eglGetProcAddress("eglCreateStreamProducerSurfaceKHR"); + context->api.eglStreamConsumerAcquireAttribNV = (void*)eglGetProcAddress("eglStreamConsumerAcquireAttribNV"); + } + + context->display = get_display(context, bsurface->display_type, bsurface->display, bsurface->drm_fd); if (!context->display) goto egl_fail; @@ -214,7 +361,7 @@ create_context(struct wlc_backend_surface *bsurface) } configs[] = { { (const EGLint[]){ - EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_SURFACE_TYPE, bsurface->use_egldevice ? EGL_STREAM_BIT_KHR : EGL_WINDOW_BIT, EGL_RED_SIZE, 1, EGL_GREEN_SIZE, 1, EGL_BLUE_SIZE, 1, @@ -245,7 +392,20 @@ create_context(struct wlc_backend_surface *bsurface) if ((context->context = eglCreateContext(context->display, context->config, EGL_NO_CONTEXT, context_attribs)) == EGL_NO_CONTEXT) goto egl_fail; - if ((context->surface = eglCreateWindowSurface(context->display, context->config, bsurface->window, NULL)) == EGL_NO_SURFACE) + if (bsurface->use_egldevice) { + EGLint stream_attribs[] = { + EGL_STREAM_FIFO_LENGTH_KHR, 1, + EGL_CONSUMER_AUTO_ACQUIRE_EXT, EGL_FALSE, + EGL_NONE + }; + + context->stream = context->api.eglCreateStreamKHR(context->display, stream_attribs); + if (context->stream == EGL_NO_STREAM_KHR) + goto egl_fail; + } + + context->surface = bsurface->use_egldevice ? create_surface_egl_device(context, bsurface) : create_surface_gbm(context->display, context->config, bsurface->window); + if (context->surface == EGL_NO_SURFACE) goto egl_fail; if (!eglMakeCurrent(context->display, context->surface, context->surface, context->context)) @@ -357,6 +517,21 @@ bind_to_wl_display(struct ctx *context, struct wl_display *wl_display) return (context->wl_display ? true : false); } +static bool +output_stream_flip(struct ctx *context, struct wlc_backend_surface *bsurface) +{ + EGLAttrib acquire_attribs[] = { + EGL_DRM_FLIP_EVENT_DATA_NV, (EGLAttrib)bsurface, + EGL_NONE + }; + + if (context->stream != EGL_NO_STREAM_KHR) { + return context->api.eglStreamConsumerAcquireAttribNV(context->display, context->stream, acquire_attribs) == EGL_TRUE; + } + + return true; +} + static void swap(struct ctx *context, struct wlc_backend_surface *bsurface) { @@ -372,7 +547,9 @@ swap(struct ctx *context, struct wlc_backend_surface *bsurface) if (!context->flip_failed) ret = EGL_CALL(eglSwapBuffers(context->display, context->surface)); - if (ret == EGL_TRUE && bsurface->api.page_flip) + if (ret == EGL_TRUE && bsurface->use_egldevice) { + output_stream_flip(context, bsurface); + } else if (ret == EGL_TRUE && bsurface->api.page_flip) context->flip_failed = !bsurface->api.page_flip(bsurface); } diff --git a/src/platform/context/egl.h b/src/platform/context/egl.h index a58fee44..f95de45f 100644 --- a/src/platform/context/egl.h +++ b/src/platform/context/egl.h @@ -1,9 +1,14 @@ #ifndef _WLC_EGL_H_ #define _WLC_EGL_H_ +#include +#include + struct wlc_context_api; struct wlc_backend_surface; +EGLDeviceEXT get_egl_device(void); + void* wlc_egl(struct wlc_backend_surface *bsurface, struct wlc_context_api *api); #endif /* _WLC_EGL_H_ */