diff --git a/src/backend/backend.c b/src/backend/backend.c index 1a107a2b..032c301b 100644 --- a/src/backend/backend.c +++ b/src/backend/backend.c @@ -16,6 +16,7 @@ extern struct backend_operations xrender_ops, dummy_ops; #ifdef CONFIG_OPENGL extern struct backend_operations glx_ops; +extern struct backend_operations egl_ops; #endif struct backend_operations *backend_list[NUM_BKEND] = { @@ -23,6 +24,7 @@ struct backend_operations *backend_list[NUM_BKEND] = { [BKEND_DUMMY] = &dummy_ops, #ifdef CONFIG_OPENGL [BKEND_GLX] = &glx_ops, + [BKEND_EGL] = &egl_ops, #endif }; diff --git a/src/backend/gl/egl.c b/src/backend/gl/egl.c new file mode 100644 index 00000000..f7849d5f --- /dev/null +++ b/src/backend/gl/egl.c @@ -0,0 +1,424 @@ +// SPDX-License-Identifier: MPL-2.0 +/* + * Copyright (c) 2022 Yuxuan Shui + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "backend/backend.h" +#include "backend/backend_common.h" +#include "backend/gl/egl.h" +#include "backend/gl/gl_common.h" +#include "common.h" +#include "compiler.h" +#include "config.h" +#include "log.h" +#include "picom.h" +#include "utils.h" +#include "x.h" + +struct egl_pixmap { + EGLImage image; + xcb_pixmap_t pixmap; + bool owned; +}; + +struct egl_data { + struct gl_data gl; + EGLDisplay display; + EGLSurface target_win; + EGLContext ctx; +}; + +/** + * Free a glx_texture_t. + */ +static void egl_release_image(backend_t *base, struct gl_texture *tex) { + struct egl_data *gd = (void *)base; + struct egl_pixmap *p = tex->user_data; + // Release binding + if (p->image != EGL_NO_IMAGE) { + eglDestroyImage(gd->display, p->image); + p->image = EGL_NO_IMAGE; + } + + if (p->owned) { + xcb_free_pixmap(base->c, p->pixmap); + p->pixmap = XCB_NONE; + } + + free(p); + tex->user_data = NULL; +} + +/** + * Destroy GLX related resources. + */ +void egl_deinit(backend_t *base) { + struct egl_data *gd = (void *)base; + + gl_deinit(&gd->gl); + + // Destroy GLX context + if (gd->ctx) { + eglMakeCurrent(gd->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + eglDestroyContext(gd->display, gd->ctx); + gd->ctx = 0; + } + + free(gd); +} + +static void *egl_decouple_user_data(backend_t *base attr_unused, void *ud attr_unused) { + auto ret = cmalloc(struct egl_pixmap); + ret->owned = false; + ret->image = EGL_NO_IMAGE; + ret->pixmap = 0; + return ret; +} + +static bool egl_set_swap_interval(int interval, EGLDisplay dpy) { + return eglSwapInterval(dpy, interval); +} + +/** + * Initialize OpenGL. + */ +static backend_t *egl_init(session_t *ps) { + // Check if we have the X11 platform + const char *exts = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); + if (strstr(exts, "EGL_EXT_platform_x11") == NULL) { + log_error("X11 platform not available."); + return NULL; + } + + bool success = false; + auto gd = ccalloc(1, struct egl_data); + gd->display = eglGetPlatformDisplay(EGL_PLATFORM_X11_EXT, ps->dpy, + (EGLAttrib[]){ + EGL_PLATFORM_X11_SCREEN_EXT, + ps->scr, + EGL_NONE, + }); + if (gd->display == EGL_NO_DISPLAY) { + log_error("Failed to get EGL display."); + goto end; + } + + EGLint major, minor; + if (!eglInitialize(gd->display, &major, &minor)) { + log_error("Failed to initialize EGL."); + goto end; + } + + // Check if EGL supports OpenGL + const char *apis = eglQueryString(gd->display, EGL_CLIENT_APIS); + if (strstr(apis, "OpenGL") == NULL) { + log_error("EGL does not support OpenGL."); + goto end; + } + + eglext_init(gd->display); + init_backend_base(&gd->gl.base, ps); + if (!eglext.has_EGL_KHR_image_pixmap) { + log_error("EGL_KHR_image_pixmap not available."); + goto end; + } + + int ncfgs = 0; + if (eglGetConfigs(gd->display, NULL, 0, &ncfgs) != EGL_TRUE) { + log_error("Failed to get EGL configs."); + goto end; + } + + auto visual_info = x_get_visual_info(ps->c, ps->vis); + EGLConfig *cfgs = ccalloc(ncfgs, EGLConfig); + // clang-format off + if (eglChooseConfig(gd->display, + (EGLint[]){ + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, + EGL_RED_SIZE, visual_info.red_size, + EGL_GREEN_SIZE, visual_info.green_size, + EGL_BLUE_SIZE, visual_info.blue_size, + EGL_ALPHA_SIZE, visual_info.alpha_size, + EGL_STENCIL_SIZE, 1, + EGL_CONFIG_CAVEAT, EGL_NONE, + EGL_NONE, + }, cfgs, ncfgs, &ncfgs) != EGL_TRUE) { + log_error("Failed to choose EGL config for the root window."); + goto end; + } + // clang-format on + + EGLConfig target_cfg = cfgs[0]; + free(cfgs); + + gd->target_win = eglCreatePlatformWindowSurface( + gd->display, target_cfg, (xcb_window_t[]){session_get_target_window(ps)}, NULL); + if (gd->target_win == EGL_NO_SURFACE) { + log_error("Failed to create EGL surface."); + goto end; + } + + if (eglBindAPI(EGL_OPENGL_API) != EGL_TRUE) { + log_error("Failed to bind OpenGL API."); + goto end; + } + + gd->ctx = eglCreateContext(gd->display, target_cfg, NULL, NULL); + if (!gd->ctx) { + log_error("Failed to get GLX context."); + goto end; + } + + if (!eglMakeCurrent(gd->display, gd->target_win, gd->target_win, gd->ctx)) { + log_error("Failed to attach GLX context."); + goto end; + } + + if (!gl_init(&gd->gl, ps)) { + log_error("Failed to setup OpenGL"); + goto end; + } + if (!gd->gl.has_egl_image_storage) { + log_error("GL_EXT_EGL_image_storage extension not available."); + goto end; + } + + gd->gl.decouple_texture_user_data = egl_decouple_user_data; + gd->gl.release_user_data = egl_release_image; + + if (ps->o.vsync) { + if (!egl_set_swap_interval(1, gd->display)) { + log_error("Failed to enable vsync. %#x", eglGetError()); + } + } else { + egl_set_swap_interval(0, gd->display); + } + + success = true; + +end: + if (!success) { + egl_deinit(&gd->gl.base); + return NULL; + } + + return &gd->gl.base; +} + +static void * +egl_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, bool owned) { + struct egl_data *gd = (void *)base; + struct egl_pixmap *eglpixmap = NULL; + + auto r = xcb_get_geometry_reply(base->c, xcb_get_geometry(base->c, pixmap), NULL); + if (!r) { + log_error("Invalid pixmap %#010x", pixmap); + return NULL; + } + + log_trace("Binding pixmap %#010x", pixmap); + auto wd = ccalloc(1, struct backend_image); + wd->max_brightness = 1; + auto inner = ccalloc(1, struct gl_texture); + inner->width = wd->ewidth = r->width; + inner->height = wd->eheight = r->height; + wd->inner = (struct backend_image_inner_base *)inner; + free(r); + + log_debug("depth %d", fmt.visual_depth); + + inner->y_inverted = true; + + eglpixmap = cmalloc(struct egl_pixmap); + eglpixmap->pixmap = pixmap; + eglpixmap->image = eglCreateImage(gd->display, gd->ctx, EGL_NATIVE_PIXMAP_KHR, + (EGLClientBuffer)(uintptr_t)pixmap, NULL); + eglpixmap->owned = owned; + + if (eglpixmap->image == EGL_NO_IMAGE) { + log_error("Failed to create eglpixmap for pixmap %#010x", pixmap); + goto err; + } + + log_trace("EGLImage %p", eglpixmap->image); + + // Create texture + inner->user_data = eglpixmap; + inner->texture = gl_new_texture(GL_TEXTURE_2D); + inner->has_alpha = fmt.alpha_size != 0; + wd->opacity = 1; + wd->color_inverted = false; + wd->dim = 0; + wd->inner->refcount = 1; + glBindTexture(GL_TEXTURE_2D, inner->texture); + glEGLImageTargetTexStorageEXT(GL_TEXTURE_2D, eglpixmap->image, NULL); + glBindTexture(GL_TEXTURE_2D, 0); + + gl_check_err(); + return wd; +err: + if (eglpixmap && eglpixmap->image) { + eglDestroyImage(gd->display, eglpixmap->image); + } + free(eglpixmap); + + if (owned) { + xcb_free_pixmap(base->c, pixmap); + } + free(wd); + return NULL; +} + +static void egl_present(backend_t *base, const region_t *region attr_unused) { + struct egl_data *gd = (void *)base; + gl_present(base, region); + eglSwapBuffers(gd->display, gd->target_win); + if (!gd->gl.is_nvidia) { + glFinish(); + } +} + +static int egl_buffer_age(backend_t *base) { + if (!eglext.has_EGL_EXT_buffer_age) { + return -1; + } + + struct egl_data *gd = (void *)base; + EGLint val; + eglQuerySurface(gd->display, (EGLSurface)gd->target_win, EGL_BUFFER_AGE_EXT, &val); + return (int)val ?: -1; +} + +static void egl_diagnostics(backend_t *base) { + struct egl_data *gd = (void *)base; + bool warn_software_rendering = false; + const char *software_renderer_names[] = {"llvmpipe", "SWR", "softpipe"}; + auto egl_vendor = eglQueryString(gd->display, EGL_VENDOR); + printf("* Driver vendors:\n"); + printf(" * EGL: %s\n", egl_vendor); + if (eglext.has_EGL_MESA_query_driver) { + printf(" * EGL driver: %s\n", eglGetDisplayDriverName(gd->display)); + } + printf(" * GL: %s\n", glGetString(GL_VENDOR)); + + auto gl_renderer = (const char *)glGetString(GL_RENDERER); + printf("* GL renderer: %s\n", gl_renderer); + if (strstr(egl_vendor, "Mesa")) { + for (size_t i = 0; i < ARR_SIZE(software_renderer_names); i++) { + if (strstr(gl_renderer, software_renderer_names[i]) != NULL) { + warn_software_rendering = true; + break; + } + } + } + + if (warn_software_rendering) { + printf("\n(You are using a software renderer. Unless you are doing this\n" + "intentionally, this means you don't have a graphics driver\n" + "properly installed. Performance will suffer. Please fix this\n" + "before reporting your issue.)\n"); + } +} + +struct backend_operations egl_ops = { + .init = egl_init, + .deinit = egl_deinit, + .bind_pixmap = egl_bind_pixmap, + .release_image = gl_release_image, + .compose = gl_compose, + .image_op = gl_image_op, + .set_image_property = gl_set_image_property, + .clone_image = default_clone_image, + .blur = gl_blur, + .is_image_transparent = default_is_image_transparent, + .present = egl_present, + .buffer_age = egl_buffer_age, + .create_shadow_context = gl_create_shadow_context, + .destroy_shadow_context = gl_destroy_shadow_context, + .render_shadow = backend_render_shadow_from_mask, + .shadow_from_mask = gl_shadow_from_mask, + .make_mask = gl_make_mask, + .fill = gl_fill, + .create_blur_context = gl_create_blur_context, + .destroy_blur_context = gl_destroy_blur_context, + .get_blur_size = gl_get_blur_size, + .diagnostics = egl_diagnostics, + .device_status = gl_device_status, + .create_shader = gl_create_window_shader, + .destroy_shader = gl_destroy_window_shader, + .get_shader_attributes = gl_get_shader_attributes, + .max_buffer_age = 5, // Why? +}; + +PFNEGLGETDISPLAYDRIVERNAMEPROC eglGetDisplayDriverName; +/** + * Check if a GLX extension exists. + */ +static inline bool egl_has_extension(EGLDisplay dpy, const char *ext) { + const char *egl_exts = eglQueryString(dpy, EGL_EXTENSIONS); + if (!egl_exts) { + log_error("Failed get EGL extension list."); + return false; + } + + auto inlen = strlen(ext); + const char *curr = egl_exts; + bool match = false; + while (curr && !match) { + const char *end = strchr(curr, ' '); + if (!end) { + // Last extension string + match = strcmp(ext, curr) == 0; + } else if (curr + inlen == end) { + // Length match, do match string + match = strncmp(ext, curr, (unsigned long)(end - curr)) == 0; + } + curr = end ? end + 1 : NULL; + } + + if (!match) { + log_info("Missing EGL extension %s.", ext); + } else { + log_info("Found EGL extension %s.", ext); + } + + return match; +} + +struct eglext_info eglext = {0}; + +void eglext_init(EGLDisplay dpy) { + if (eglext.initialized) { + return; + } + eglext.initialized = true; +#define check_ext(name) eglext.has_##name = egl_has_extension(dpy, #name) + check_ext(EGL_EXT_buffer_age); + check_ext(EGL_EXT_create_context_robustness); + check_ext(EGL_KHR_image_pixmap); +#ifdef EGL_MESA_query_driver + check_ext(EGL_MESA_query_driver); +#endif +#undef check_ext + + // Checking if the returned function pointer is NULL is not really necessary, + // or maybe not even useful, since eglGetProcAddress might always return + // something. We are doing it just for completeness' sake. + +#ifdef EGL_MESA_query_driver + eglGetDisplayDriverName = + (PFNEGLGETDISPLAYDRIVERNAMEPROC)eglGetProcAddress("eglGetDisplayDriverName"); + if (!eglGetDisplayDriverName) { + eglext.has_EGL_MESA_query_driver = false; + } +#endif +} diff --git a/src/backend/gl/egl.h b/src/backend/gl/egl.h new file mode 100644 index 00000000..171b1735 --- /dev/null +++ b/src/backend/gl/egl.h @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) Yuxuan Shui +#pragma once +#include +// Older version of glx.h defines function prototypes for these extensions... +// Rename them to avoid conflicts +#include +#include +#include +#include +#include +#include + +#include "compiler.h" +#include "log.h" +#include "utils.h" +#include "x.h" + +struct eglext_info { + bool initialized; + bool has_EGL_MESA_query_driver; + bool has_EGL_EXT_buffer_age; + bool has_EGL_EXT_create_context_robustness; + bool has_EGL_KHR_image_pixmap; +}; + +extern struct eglext_info eglext; + +#ifdef EGL_MESA_query_driver +extern PFNEGLGETDISPLAYDRIVERNAMEPROC eglGetDisplayDriverName; +#endif + +void eglext_init(EGLDisplay); diff --git a/src/backend/gl/gl_common.c b/src/backend/gl/gl_common.c index e019bd6e..7ade2339 100644 --- a/src/backend/gl/gl_common.c +++ b/src/backend/gl/gl_common.c @@ -933,6 +933,7 @@ bool gl_init(struct gl_data *gd, session_t *ps) { gd->is_nvidia = false; } gd->has_robustness = gl_has_extension("GL_ARB_robustness"); + gd->has_egl_image_storage = gl_has_extension("GL_EXT_EGL_image_storage"); gl_check_err(); return true; diff --git a/src/backend/gl/gl_common.h b/src/backend/gl/gl_common.h index 2c288529..3a788651 100644 --- a/src/backend/gl/gl_common.h +++ b/src/backend/gl/gl_common.h @@ -97,6 +97,8 @@ struct gl_data { bool is_nvidia; // If ARB_robustness extension is present bool has_robustness; + // If EXT_EGL_image_storage extension is present + bool has_egl_image_storage; // Height and width of the root window int height, width; // Hash-table of window shaders diff --git a/src/backend/meson.build b/src/backend/meson.build index 511394b8..1e8e72bf 100644 --- a/src/backend/meson.build +++ b/src/backend/meson.build @@ -3,5 +3,5 @@ srcs += [ files('backend_common.c', 'xrender/xrender.c', 'dummy/dummy.c', 'backe # enable opengl if get_option('opengl') - srcs += [ files('gl/gl_common.c', 'gl/glx.c', 'gl/blur.c', 'gl/shaders.c') ] + srcs += [ files('gl/gl_common.c', 'gl/glx.c', 'gl/blur.c', 'gl/shaders.c', 'gl/egl.c') ] endif diff --git a/src/config.h b/src/config.h index 93ae09e2..e1dc2dd4 100644 --- a/src/config.h +++ b/src/config.h @@ -36,6 +36,7 @@ enum backend { BKEND_GLX, BKEND_XR_GLX_HYBRID, BKEND_DUMMY, + BKEND_EGL, NUM_BKEND, }; diff --git a/src/meson.build b/src/meson.build index 0a882f93..60d83a89 100644 --- a/src/meson.build +++ b/src/meson.build @@ -59,7 +59,7 @@ endif if get_option('opengl') cflags += ['-DCONFIG_OPENGL', '-DGL_GLEXT_PROTOTYPES'] - deps += [dependency('gl', required: true)] + deps += [dependency('gl', required: true), dependency('egl', required: true)] srcs += [ 'opengl.c' ] endif diff --git a/src/picom.c b/src/picom.c index e1c1f1e7..bac03b0f 100644 --- a/src/picom.c +++ b/src/picom.c @@ -88,6 +88,7 @@ const char *const BACKEND_STRS[] = {[BKEND_XRENDER] = "xrender", [BKEND_GLX] = "glx", [BKEND_XR_GLX_HYBRID] = "xr_glx_hybrid", [BKEND_DUMMY] = "dummy", + [BKEND_EGL] = "egl", NULL}; // clang-format on