// SPDX-License-Identifier: MIT /* * Compton - a compositor for X11 * * Based on `xcompmgr` - Copyright (c) 2003, Keith Packard * * Copyright (c) 2011-2013, Christopher Jeffrey * Copyright (c) 2019 Yuxuan Shui * See LICENSE-mit for more information. * */ #include #include #include #include #include #include #include #include #include #include "backend/backend.h" #include "backend/backend_common.h" #include "backend/gl/gl_common.h" #include "backend/gl/glx.h" #include "common.h" #include "compiler.h" #include "config.h" #include "log.h" #include "picom.h" #include "region.h" #include "utils.h" #include "win.h" #include "x.h" struct _glx_pixmap { GLXPixmap glpixmap; xcb_pixmap_t pixmap; bool owned; }; struct _glx_data { struct gl_data gl; xcb_window_t target_win; GLXContext ctx; }; #define glXGetFBConfigAttribChecked(a, b, attr, c) \ do { \ if (glXGetFBConfigAttrib(a, b, attr, c)) { \ log_info("Cannot get FBConfig attribute " #attr); \ break; \ } \ } while (0) bool glx_find_fbconfig(struct x_connection *c, struct xvisual_info m, struct glx_fbconfig_info *info) { log_debug("Looking for FBConfig for RGBA%d%d%d%d, depth %d", m.red_size, m.blue_size, m.green_size, m.alpha_size, m.visual_depth); info->cfg = NULL; int ncfg; // clang-format off GLXFBConfig *cfg = glXChooseFBConfig(c->dpy, c->screen, (int[]){ GLX_RENDER_TYPE, GLX_RGBA_BIT, GLX_DRAWABLE_TYPE, GLX_PIXMAP_BIT, GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR, GLX_X_RENDERABLE, true, GLX_FRAMEBUFFER_SRGB_CAPABLE_EXT, (GLint)GLX_DONT_CARE, GLX_BUFFER_SIZE, m.red_size + m.green_size + m.blue_size + m.alpha_size, GLX_RED_SIZE, m.red_size, GLX_BLUE_SIZE, m.blue_size, GLX_GREEN_SIZE, m.green_size, GLX_ALPHA_SIZE, m.alpha_size, GLX_STENCIL_SIZE, 0, GLX_DEPTH_SIZE, 0, 0 }, &ncfg); // clang-format on int texture_tgts, y_inverted, texture_fmt; bool found = false; int min_cost = INT_MAX; GLXFBConfig ret; for (int i = 0; i < ncfg; i++) { int depthbuf, stencil, doublebuf, bufsize; glXGetFBConfigAttribChecked(c->dpy, cfg[i], GLX_BUFFER_SIZE, &bufsize); glXGetFBConfigAttribChecked(c->dpy, cfg[i], GLX_DEPTH_SIZE, &depthbuf); glXGetFBConfigAttribChecked(c->dpy, cfg[i], GLX_STENCIL_SIZE, &stencil); glXGetFBConfigAttribChecked(c->dpy, cfg[i], GLX_DOUBLEBUFFER, &doublebuf); if (depthbuf + stencil + bufsize * (doublebuf + 1) >= min_cost) { continue; } int red, green, blue; glXGetFBConfigAttribChecked(c->dpy, cfg[i], GLX_RED_SIZE, &red); glXGetFBConfigAttribChecked(c->dpy, cfg[i], GLX_BLUE_SIZE, &blue); glXGetFBConfigAttribChecked(c->dpy, cfg[i], GLX_GREEN_SIZE, &green); if (red != m.red_size || green != m.green_size || blue != m.blue_size) { // Color size doesn't match, this cannot work continue; } int rgb, rgba; glXGetFBConfigAttribChecked(c->dpy, cfg[i], GLX_BIND_TO_TEXTURE_RGB_EXT, &rgb); glXGetFBConfigAttribChecked(c->dpy, cfg[i], GLX_BIND_TO_TEXTURE_RGBA_EXT, &rgba); if (!rgb && !rgba) { log_info("FBConfig is neither RGBA nor RGB, we cannot " "handle this setup."); continue; } int visual; glXGetFBConfigAttribChecked(c->dpy, cfg[i], GLX_VISUAL_ID, &visual); if (m.visual_depth != -1 && x_get_visual_depth(c, (xcb_visualid_t)visual) != m.visual_depth) { // FBConfig and the correspondent X Visual might not have the same // depth. (e.g. 32 bit FBConfig with a 24 bit Visual). This is // quite common, seen in both open source and proprietary drivers. // // If the FBConfig has a matching depth but its visual doesn't, we // still cannot use it. continue; } // All check passed, we are using this one. found = true; ret = cfg[i]; glXGetFBConfigAttribChecked( c->dpy, cfg[i], GLX_BIND_TO_TEXTURE_TARGETS_EXT, &texture_tgts); glXGetFBConfigAttribChecked(c->dpy, cfg[i], GLX_Y_INVERTED_EXT, &y_inverted); // Prefer the texture format with matching alpha, with the other one as // fallback if (m.alpha_size) { texture_fmt = rgba ? GLX_TEXTURE_FORMAT_RGBA_EXT : GLX_TEXTURE_FORMAT_RGB_EXT; } else { texture_fmt = rgb ? GLX_TEXTURE_FORMAT_RGB_EXT : GLX_TEXTURE_FORMAT_RGBA_EXT; } min_cost = depthbuf + stencil + bufsize * (doublebuf + 1); } free(cfg); if (found) { info->cfg = ret; info->texture_tgts = texture_tgts; info->texture_fmt = texture_fmt; info->y_inverted = y_inverted; } return found; } /** * Free a glx_texture_t. */ static void glx_release_image(backend_t *base, struct gl_texture *tex) { struct _glx_pixmap *p = tex->user_data; // Release binding if (p->glpixmap && tex->texture) { glBindTexture(GL_TEXTURE_2D, tex->texture); glXReleaseTexImageEXT(base->c->dpy, p->glpixmap, GLX_FRONT_LEFT_EXT); glBindTexture(GL_TEXTURE_2D, 0); } // Free GLX Pixmap if (p->glpixmap) { glXDestroyPixmap(base->c->dpy, p->glpixmap); p->glpixmap = 0; } if (p->owned) { xcb_free_pixmap(base->c->c, p->pixmap); p->pixmap = XCB_NONE; } free(p); tex->user_data = NULL; } /** * Destroy GLX related resources. */ void glx_deinit(backend_t *base) { struct _glx_data *gd = (void *)base; gl_deinit(&gd->gl); // Destroy GLX context if (gd->ctx) { glXMakeCurrent(base->c->dpy, None, NULL); glXDestroyContext(base->c->dpy, gd->ctx); gd->ctx = 0; } free(gd); } static void *glx_decouple_user_data(backend_t *base attr_unused, void *ud attr_unused) { auto ret = cmalloc(struct _glx_pixmap); ret->owned = false; ret->glpixmap = 0; ret->pixmap = 0; return ret; } static bool glx_set_swap_interval(int interval, Display *dpy, GLXDrawable drawable) { bool vsync_enabled = false; if (glxext.has_GLX_MESA_swap_control) { vsync_enabled = (glXSwapIntervalMESA((uint)interval) == 0); } if (!vsync_enabled && glxext.has_GLX_SGI_swap_control) { vsync_enabled = (glXSwapIntervalSGI(interval) == 0); } if (!vsync_enabled && glxext.has_GLX_EXT_swap_control) { // glXSwapIntervalEXT doesn't return if it's successful glXSwapIntervalEXT(dpy, drawable, interval); vsync_enabled = true; } return vsync_enabled; } /** * Initialize OpenGL. */ static backend_t *glx_init(session_t *ps, xcb_window_t target) { bool success = false; glxext_init(ps->c.dpy, ps->c.screen); auto gd = ccalloc(1, struct _glx_data); init_backend_base(&gd->gl.base, ps); gd->target_win = target; XVisualInfo *pvis = NULL; // Check for GLX extension if (!ps->glx_exists) { log_error("No GLX extension."); goto end; } // Get XVisualInfo int nitems = 0; XVisualInfo vreq = {.visualid = ps->c.screen_info->root_visual}; pvis = XGetVisualInfo(ps->c.dpy, VisualIDMask, &vreq, &nitems); if (!pvis) { log_error("Failed to acquire XVisualInfo for current visual."); goto end; } // Ensure the visual is double-buffered int value = 0; if (glXGetConfig(ps->c.dpy, pvis, GLX_USE_GL, &value) || !value) { log_error("Root visual is not a GL visual."); goto end; } if (glXGetConfig(ps->c.dpy, pvis, GLX_STENCIL_SIZE, &value) || !value) { log_error("Root visual lacks stencil buffer."); goto end; } if (glXGetConfig(ps->c.dpy, pvis, GLX_DOUBLEBUFFER, &value) || !value) { log_error("Root visual is not a double buffered GL visual."); goto end; } if (glXGetConfig(ps->c.dpy, pvis, GLX_RGBA, &value) || !value) { log_error("Root visual is a color index visual, not supported"); goto end; } if (!glxext.has_GLX_EXT_texture_from_pixmap) { log_error("GLX_EXT_texture_from_pixmap is not supported by your driver"); goto end; } if (!glxext.has_GLX_ARB_create_context) { log_error("GLX_ARB_create_context is not supported by your driver"); goto end; } // Find a fbconfig with visualid matching the one from the target win, so we can // be sure that the fbconfig is compatible with our target window. int ncfgs; GLXFBConfig *cfg = glXGetFBConfigs(ps->c.dpy, ps->c.screen, &ncfgs); bool found = false; for (int i = 0; i < ncfgs; i++) { int visualid; glXGetFBConfigAttribChecked(ps->c.dpy, cfg[i], GLX_VISUAL_ID, &visualid); if ((VisualID)visualid != pvis->visualid) { continue; } int *attributes = (int[]){GLX_CONTEXT_MAJOR_VERSION_ARB, 3, GLX_CONTEXT_MINOR_VERSION_ARB, 3, GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB, 0, 0, 0}; if (glxext.has_GLX_ARB_create_context_robustness) { attributes[6] = GLX_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB; attributes[7] = GLX_LOSE_CONTEXT_ON_RESET_ARB; } gd->ctx = glXCreateContextAttribsARB(ps->c.dpy, cfg[i], 0, true, attributes); free(cfg); if (!gd->ctx) { log_error("Failed to get GLX context."); goto end; } found = true; break; } if (!found) { log_error("Couldn't find a suitable fbconfig for the target window"); goto end; } // Attach GLX context GLXDrawable tgt = gd->target_win; if (!glXMakeCurrent(ps->c.dpy, tgt, 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; } gd->gl.decouple_texture_user_data = glx_decouple_user_data; gd->gl.release_user_data = glx_release_image; if (ps->o.vsync) { if (!glx_set_swap_interval(1, ps->c.dpy, tgt)) { log_error("Failed to enable vsync."); } } else { glx_set_swap_interval(0, ps->c.dpy, tgt); } success = true; end: if (pvis) { XFree(pvis); } if (!success) { glx_deinit(&gd->gl.base); return NULL; } return &gd->gl.base; } static void * glx_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, bool owned) { struct _glx_pixmap *glxpixmap = NULL; // Retrieve pixmap parameters, if they aren't provided if (fmt.visual_depth > OPENGL_MAX_DEPTH) { log_error("Requested depth %d higher than max possible depth %d.", fmt.visual_depth, OPENGL_MAX_DEPTH); return NULL; } if (fmt.visual_depth < 0) { log_error("Pixmap %#010x with invalid depth %d", pixmap, fmt.visual_depth); return NULL; } auto r = xcb_get_geometry_reply(base->c->c, xcb_get_geometry(base->c->c, pixmap), NULL); if (!r) { log_error("Invalid pixmap %#010x", pixmap); return NULL; } log_trace("Binding pixmap %#010x", pixmap); auto wd = default_new_backend_image(r->width, r->height); auto inner = ccalloc(1, struct gl_texture); inner->width = r->width; inner->height = r->height; wd->inner = (struct backend_image_inner_base *)inner; free(r); struct glx_fbconfig_info fbcfg; if (!glx_find_fbconfig(base->c, fmt, &fbcfg)) { log_error("Couldn't find FBConfig with requested visual %x", fmt.visual); goto err; } // Choose a suitable texture target for our pixmap. // Refer to GLX_EXT_texture_om_pixmap spec to see what are the mean // of the bits in texture_tgts if (!(fbcfg.texture_tgts & GLX_TEXTURE_2D_BIT_EXT)) { log_error("Cannot bind pixmap to GL_TEXTURE_2D, giving up"); goto err; } log_debug("depth %d, rgba %d", fmt.visual_depth, (fbcfg.texture_fmt == GLX_TEXTURE_FORMAT_RGBA_EXT)); GLint attrs[] = { GLX_TEXTURE_FORMAT_EXT, fbcfg.texture_fmt, GLX_TEXTURE_TARGET_EXT, GLX_TEXTURE_2D_EXT, 0, }; inner->y_inverted = fbcfg.y_inverted; glxpixmap = cmalloc(struct _glx_pixmap); glxpixmap->pixmap = pixmap; glxpixmap->glpixmap = glXCreatePixmap(base->c->dpy, fbcfg.cfg, pixmap, attrs); glxpixmap->owned = owned; if (!glxpixmap->glpixmap) { log_error("Failed to create glpixmap for pixmap %#010x", pixmap); goto err; } log_trace("GLXPixmap %#010lx", glxpixmap->glpixmap); // Create texture inner->user_data = glxpixmap; inner->texture = gl_new_texture(GL_TEXTURE_2D); inner->has_alpha = fmt.alpha_size != 0; wd->inner->refcount = 1; glBindTexture(GL_TEXTURE_2D, inner->texture); glXBindTexImageEXT(base->c->dpy, glxpixmap->glpixmap, GLX_FRONT_LEFT_EXT, NULL); glBindTexture(GL_TEXTURE_2D, 0); gl_check_err(); return wd; err: if (glxpixmap && glxpixmap->glpixmap) { glXDestroyPixmap(base->c->dpy, glxpixmap->glpixmap); } free(glxpixmap); if (owned) { xcb_free_pixmap(base->c->c, pixmap); } free(wd); return NULL; } static void glx_present(backend_t *base, const region_t *region attr_unused) { struct _glx_data *gd = (void *)base; gl_present(base, region); glXSwapBuffers(base->c->dpy, gd->target_win); } static int glx_buffer_age(backend_t *base) { if (!glxext.has_GLX_EXT_buffer_age) { return -1; } struct _glx_data *gd = (void *)base; unsigned int val; glXQueryDrawable(base->c->dpy, gd->target_win, GLX_BACK_BUFFER_AGE_EXT, &val); return (int)val ?: -1; } static void glx_diagnostics(backend_t *base) { bool warn_software_rendering = false; const char *software_renderer_names[] = {"llvmpipe", "SWR", "softpipe"}; auto glx_vendor = glXGetClientString(base->c->dpy, GLX_VENDOR); printf("* Driver vendors:\n"); printf(" * GLX: %s\n", glx_vendor); printf(" * GL: %s\n", glGetString(GL_VENDOR)); auto gl_renderer = (const char *)glGetString(GL_RENDERER); printf("* GL renderer: %s\n", gl_renderer); if (strcmp(glx_vendor, "Mesa Project and SGI") == 0) { 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; } } } #ifdef GLX_MESA_query_renderer if (glxext.has_GLX_MESA_query_renderer) { unsigned int accelerated = 0; glXQueryCurrentRendererIntegerMESA(GLX_RENDERER_ACCELERATED_MESA, &accelerated); printf("* Accelerated: %d\n", accelerated); // Trust GLX_MESA_query_renderer when it's available warn_software_rendering = (accelerated == 0); } #endif 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 glx_ops = { .init = glx_init, .deinit = glx_deinit, .bind_pixmap = glx_bind_pixmap, .release_image = gl_release_image, .prepare = gl_prepare, .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 = glx_present, .buffer_age = glx_buffer_age, .last_render_time = gl_last_render_time, .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 = glx_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? }; struct glxext_info glxext = {0}; void glxext_init(Display *dpy, int screen) { if (glxext.initialized) { return; } glxext.initialized = true; #define check_ext(name) \ glxext.has_##name = epoxy_has_glx_extension(dpy, screen, #name); \ log_info("Extension " #name " - %s", glxext.has_##name ? "present" : "absent") check_ext(GLX_SGI_video_sync); check_ext(GLX_SGI_swap_control); check_ext(GLX_OML_sync_control); check_ext(GLX_MESA_swap_control); check_ext(GLX_EXT_swap_control); check_ext(GLX_EXT_texture_from_pixmap); check_ext(GLX_ARB_create_context); check_ext(GLX_EXT_buffer_age); check_ext(GLX_ARB_create_context_robustness); #ifdef GLX_MESA_query_renderer check_ext(GLX_MESA_query_renderer); #endif #undef check_ext }