picom/src/backend/gl/glx.c

659 lines
18 KiB
C

// SPDX-License-Identifier: MIT
/*
* Compton - a compositor for X11
*
* Based on `xcompmgr` - Copyright (c) 2003, Keith Packard
*
* Copyright (c) 2011-2013, Christopher Jeffrey
* See LICENSE-mit for more information.
*
*/
#include <GL/glx.h>
#include "backend/backend.h"
#include "backend/gl/gl_common.h"
#include "string_utils.h"
/// @brief Wrapper of a GLX FBConfig.
typedef struct glx_fbconfig {
GLXFBConfig cfg;
GLint texture_fmt;
GLint texture_tgts;
bool y_inverted;
} glx_fbconfig_t;
struct _glx_win_data {
gl_texture_t texture;
GLXPixmap glpixmap;
xcb_pixmap_t pixmap;
};
struct _glx_data {
int glx_event;
int glx_error;
GLXContext ctx;
gl_cap_t cap;
gl_win_shader_t win_shader;
gl_blur_shader_t blur_shader[MAX_BLUR_PASS];
glx_fbconfig_t *fbconfigs[OPENGL_MAX_DEPTH + 1];
void (*glXBindTexImage)(Display *display, GLXDrawable drawable, int buffer,
const int *attrib_list);
void (*glXReleaseTexImage)(Display *display, GLXDrawable drawable, int buffer);
};
/**
* Check if a GLX extension exists.
*/
static inline bool glx_has_extension(session_t *ps, const char *ext) {
const char *glx_exts = glXQueryExtensionsString(ps->dpy, ps->scr);
if (!glx_exts) {
log_error("Failed get GLX extension list.");
return false;
}
int len = strlen(ext);
char *found = strstr(glx_exts, ext);
if (!found) {
log_info("Missing GLX extension %s.", ext);
return false;
}
// Make sure extension names are not crazy...
assert(found[len] == ' ' || found[len] == 0);
return true;
}
/**
* @brief Release binding of a texture.
*/
static void glx_release_pixmap(struct _glx_data *gd, Display *dpy, struct _glx_win_data *wd) {
// Release binding
if (wd->glpixmap && wd->texture.texture) {
glBindTexture(wd->texture.target, wd->texture.texture);
gd->glXReleaseTexImage(dpy, wd->glpixmap, GLX_FRONT_LEFT_EXT);
glBindTexture(wd->texture.target, 0);
}
// Free GLX Pixmap
if (wd->glpixmap) {
glXDestroyPixmap(dpy, wd->glpixmap);
wd->glpixmap = 0;
}
gl_check_err();
}
/**
* Free a glx_texture_t.
*/
static void attr_unused glx_release_win(struct _glx_data *gd, Display *dpy,
struct _glx_win_data *wd) {
glx_release_pixmap(gd, dpy, wd);
glDeleteTextures(1, &wd->texture.texture);
// Free structure itself
free(wd);
}
/**
* Free GLX part of win.
*/
static inline void free_win_res_glx(session_t *ps, win *w) {
/*free_paint_glx(ps, &w->paint);*/
/*free_paint_glx(ps, &w->shadow_paint);*/
/*free_glx_bc(ps, &w->glx_blur_cache);*/
}
static inline int glx_cmp_fbconfig_cmpattr(session_t *ps, const glx_fbconfig_t *pfbc_a,
const glx_fbconfig_t *pfbc_b, int attr) {
int attr_a = 0, attr_b = 0;
// TODO: Error checking
glXGetFBConfigAttrib(ps->dpy, pfbc_a->cfg, attr, &attr_a);
glXGetFBConfigAttrib(ps->dpy, pfbc_b->cfg, attr, &attr_b);
return attr_a - attr_b;
}
/**
* Compare two GLX FBConfig's to find the preferred one.
*/
static int
glx_cmp_fbconfig(session_t *ps, const glx_fbconfig_t *pfbc_a, const glx_fbconfig_t *pfbc_b) {
int result = 0;
if (!pfbc_a)
return -1;
if (!pfbc_b)
return 1;
int tmpattr;
// Avoid 10-bit colors
glXGetFBConfigAttrib(ps->dpy, pfbc_a->cfg, GLX_RED_SIZE, &tmpattr);
if (tmpattr != 8)
return -1;
glXGetFBConfigAttrib(ps->dpy, pfbc_b->cfg, GLX_RED_SIZE, &tmpattr);
if (tmpattr != 8)
return 1;
#define P_CMPATTR_LT(attr) \
{ \
if ((result = glx_cmp_fbconfig_cmpattr(ps, pfbc_a, pfbc_b, (attr)))) \
return -result; \
}
#define P_CMPATTR_GT(attr) \
{ \
if ((result = glx_cmp_fbconfig_cmpattr(ps, pfbc_a, pfbc_b, (attr)))) \
return result; \
}
P_CMPATTR_LT(GLX_BIND_TO_TEXTURE_RGBA_EXT);
P_CMPATTR_LT(GLX_DOUBLEBUFFER);
P_CMPATTR_LT(GLX_STENCIL_SIZE);
P_CMPATTR_LT(GLX_DEPTH_SIZE);
P_CMPATTR_GT(GLX_BIND_TO_MIPMAP_TEXTURE_EXT);
return 0;
}
/**
* @brief Update the FBConfig of given depth.
*/
static inline void
glx_update_fbconfig_bydepth(session_t *ps, struct _glx_data *gd, int depth, glx_fbconfig_t *pfbcfg) {
// Make sure the depth is sane
if (depth < 0 || depth > OPENGL_MAX_DEPTH)
return;
// Compare new FBConfig with current one
if (glx_cmp_fbconfig(ps, gd->fbconfigs[depth], pfbcfg) < 0) {
log_debug("(depth %d): %p overrides %p, target %#x.\n", depth, pfbcfg->cfg,
gd->fbconfigs[depth] ? gd->fbconfigs[depth]->cfg : 0,
pfbcfg->texture_tgts);
if (!gd->fbconfigs[depth]) {
gd->fbconfigs[depth] = cmalloc(glx_fbconfig_t);
}
(*gd->fbconfigs[depth]) = *pfbcfg;
}
}
/**
* Get GLX FBConfigs for all depths.
*/
static bool glx_update_fbconfig(struct _glx_data *gd, session_t *ps) {
// Acquire all FBConfigs and loop through them
int nele = 0;
GLXFBConfig *pfbcfgs = glXGetFBConfigs(ps->dpy, ps->scr, &nele);
for (GLXFBConfig *pcur = pfbcfgs; pcur < pfbcfgs + nele; pcur++) {
glx_fbconfig_t fbinfo = {
.cfg = *pcur,
.texture_fmt = 0,
.texture_tgts = 0,
.y_inverted = false,
};
int id = (int)(pcur - pfbcfgs);
int depth = 0, depth_alpha = 0, val = 0;
// Skip over multi-sampled visuals
// http://people.freedesktop.org/~glisse/0001-glx-do-not-use-multisample-visual-config-for-front-o.patch
#ifdef GLX_SAMPLES
if (Success == glXGetFBConfigAttrib(ps->dpy, *pcur, GLX_SAMPLES, &val) &&
val > 1)
continue;
#endif
if (Success != glXGetFBConfigAttrib(ps->dpy, *pcur, GLX_BUFFER_SIZE, &depth) ||
Success != glXGetFBConfigAttrib(ps->dpy, *pcur, GLX_ALPHA_SIZE,
&depth_alpha)) {
log_error("Failed to retrieve buffer size and alpha size of "
"FBConfig %d.",
id);
continue;
}
if (Success != glXGetFBConfigAttrib(ps->dpy, *pcur,
GLX_BIND_TO_TEXTURE_TARGETS_EXT,
&fbinfo.texture_tgts)) {
log_error("Failed to retrieve BIND_TO_TEXTURE_TARGETS_EXT of "
"FBConfig %d.",
id);
continue;
}
int visualdepth = 0;
{
XVisualInfo *pvi = glXGetVisualFromFBConfig(ps->dpy, *pcur);
if (!pvi) {
// On nvidia-drivers-325.08 this happens slightly too often...
// log_error("Failed to retrieve X Visual of FBConfig %d.", id);
continue;
}
visualdepth = pvi->depth;
cxfree(pvi);
}
bool rgb = false;
bool rgba = false;
if (depth >= 32 && depth_alpha &&
Success == glXGetFBConfigAttrib(ps->dpy, *pcur,
GLX_BIND_TO_TEXTURE_RGBA_EXT, &val) &&
val)
rgba = true;
if (Success == glXGetFBConfigAttrib(ps->dpy, *pcur,
GLX_BIND_TO_TEXTURE_RGB_EXT, &val) &&
val)
rgb = true;
if (Success == glXGetFBConfigAttrib(ps->dpy, *pcur, GLX_Y_INVERTED_EXT, &val))
fbinfo.y_inverted = val;
{
int tgtdpt = depth - depth_alpha;
if (tgtdpt == visualdepth && tgtdpt < 32 && rgb) {
fbinfo.texture_fmt = GLX_TEXTURE_FORMAT_RGB_EXT;
glx_update_fbconfig_bydepth(ps, gd, tgtdpt, &fbinfo);
}
}
if (depth == visualdepth && rgba) {
fbinfo.texture_fmt = GLX_TEXTURE_FORMAT_RGBA_EXT;
glx_update_fbconfig_bydepth(ps, gd, depth, &fbinfo);
}
}
cxfree(pfbcfgs);
// Sanity checks
if (!gd->fbconfigs[ps->depth]) {
log_error("No FBConfig found for default depth %d.", ps->depth);
return false;
}
if (!gd->fbconfigs[32]) {
log_error("No FBConfig found for depth 32. compton may not work "
"correctly");
}
return true;
}
#ifdef DEBUG_GLX_DEBUG_CONTEXT
static inline GLXFBConfig
get_fbconfig_from_visualinfo(session_t *ps, const XVisualInfo *visualinfo) {
int nelements = 0;
GLXFBConfig *fbconfigs = glXGetFBConfigs(ps->dpy, visualinfo->screen, &nelements);
for (int i = 0; i < nelements; ++i) {
int visual_id = 0;
if (Success == glXGetFBConfigAttrib(ps->dpy, fbconfigs[i], GLX_VISUAL_ID,
&visual_id) &&
visual_id == visualinfo->visualid)
return fbconfigs[i];
}
return NULL;
}
static void glx_debug_msg_callback(GLenum source, GLenum type, GLuint id, GLenum severity,
GLsizei length, const GLchar *message, GLvoid *userParam) {
log_trace("(): source 0x%04X, type 0x%04X, id %u, severity 0x%0X, \"%s\"", source,
type, id, severity, message);
}
#endif
/**
* Destroy GLX related resources.
*/
void glx_deinit(void *backend_data, session_t *ps) {
struct _glx_data *gd = backend_data;
// Free all GLX resources of windows
for (win *w = ps->list; w; w = w->next)
free_win_res_glx(ps, w);
// Free GLSL shaders/programs
for (int i = 0; i < MAX_BLUR_PASS; ++i) {
gl_free_blur_shader(&gd->blur_shader[i]);
}
gl_free_prog_main(ps, &gd->win_shader);
gl_check_err();
// Free FBConfigs
for (int i = 0; i <= OPENGL_MAX_DEPTH; ++i) {
free(gd->fbconfigs[i]);
gd->fbconfigs[i] = NULL;
}
// Destroy GLX context
if (gd->ctx) {
glXDestroyContext(ps->dpy, gd->ctx);
gd->ctx = 0;
}
free(gd);
}
/**
* Initialize OpenGL.
*/
static void *glx_init(session_t *ps) {
bool success = false;
auto gd = ccalloc(1, struct _glx_data);
XVisualInfo *pvis = NULL;
// Check for GLX extension
if (!glXQueryExtension(ps->dpy, &gd->glx_event, &gd->glx_error)) {
log_error("No GLX extension.");
goto end;
}
// Get XVisualInfo
int nitems = 0;
XVisualInfo vreq = {.visualid = ps->vis};
pvis = XGetVisualInfo(ps->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->dpy, pvis, GLX_USE_GL, &value) || !value) {
log_error("Root visual is not a GL visual.");
goto end;
}
if (glXGetConfig(ps->dpy, pvis, GLX_DOUBLEBUFFER, &value) || !value) {
log_error("Root visual is not a double buffered GL visual.");
goto end;
}
// Ensure GLX_EXT_texture_from_pixmap exists
if (!glx_has_extension(ps, "GLX_EXT_texture_from_pixmap"))
goto end;
// Initialize GLX data structure
for (int i = 0; i < MAX_BLUR_PASS; ++i) {
gd->blur_shader[i] = (gl_blur_shader_t){.frag_shader = -1,
.prog = -1,
.unifm_offset_x = -1,
.unifm_offset_y = -1,
.unifm_factor_center = -1};
}
// Get GLX context
gd->ctx = glXCreateContext(ps->dpy, pvis, NULL, GL_TRUE);
if (!gd->ctx) {
log_error("Failed to get GLX context.");
goto end;
}
// Attach GLX context
GLXDrawable tgt = ps->overlay;
if (!tgt) {
tgt = ps->root;
}
if (!glXMakeCurrent(ps->dpy, tgt, gd->ctx)) {
log_error("Failed to attach GLX context.");
goto end;
}
#ifdef DEBUG_GLX_DEBUG_CONTEXT
f_DebugMessageCallback p_DebugMessageCallback =
(f_DebugMessageCallback)glXGetProcAddress((const GLubyte *)"glDebugMessageCal"
"lback");
if (!p_DebugMessageCallback) {
log_error("Failed to get glDebugMessageCallback(0.");
goto glx_init_end;
}
p_DebugMessageCallback(glx_debug_msg_callback, ps);
#endif
// Ensure we have a stencil buffer. X Fixes does not guarantee rectangles
// in regions don't overlap, so we must use stencil buffer to make sure
// we don't paint a region for more than one time, I think?
if (!ps->o.glx_no_stencil) {
GLint val = 0;
glGetIntegerv(GL_STENCIL_BITS, &val);
if (!val) {
log_error("Target window doesn't have stencil buffer.");
goto end;
}
}
// Check GL_ARB_texture_non_power_of_two, requires a GLX context and
// must precede FBConfig fetching
gd->cap.non_power_of_two_texture = gl_has_extension(ps, "GL_ARB_texture_non_"
"power_of_two");
gd->glXBindTexImage = (void *)glXGetProcAddress((const GLubyte *)"glXBindTexImage"
"EXT");
gd->glXReleaseTexImage = (void *)glXGetProcAddress((const GLubyte *)"glXReleaseTe"
"xImageEXT");
if (!gd->glXBindTexImage || !gd->glXReleaseTexImage) {
log_error("Failed to acquire glXBindTexImageEXT() and/or "
"glXReleaseTexImageEXT(), make sure your OpenGL supports"
"GLX_EXT_texture_from_pixmap");
goto end;
}
// Acquire FBConfigs
if (!glx_update_fbconfig(gd, ps))
goto end;
// Render preparations
gl_resize(ps->root_width, ps->root_height);
glDisable(GL_DEPTH_TEST);
glDepthMask(GL_FALSE);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
glDisable(GL_BLEND);
if (!ps->o.glx_no_stencil) {
// Initialize stencil buffer
glClear(GL_STENCIL_BUFFER_BIT);
glDisable(GL_STENCIL_TEST);
glStencilMask(0x1);
glStencilFunc(GL_EQUAL, 0x1, 0x1);
}
// Clear screen
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
// glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// glXSwapBuffers(ps->dpy, get_tgt_window(ps));
// Initialize blur filters
gl_create_blur_filters(ps, gd->blur_shader, &gd->cap);
success = true;
end:
if (pvis)
XFree(pvis);
if (!success) {
glx_deinit(gd, ps);
return NULL;
}
return gd;
}
void *glx_prepare_win(void *backend_data, session_t *ps, win *w) {
struct _glx_data *gd = backend_data;
// Retrieve pixmap parameters, if they aren't provided
if (w->g.depth > OPENGL_MAX_DEPTH) {
log_error("Requested depth %d higher than max possible depth %d.",
w->g.depth, OPENGL_MAX_DEPTH);
return false;
}
const glx_fbconfig_t *pcfg = gd->fbconfigs[w->g.depth];
if (!pcfg) {
log_error("Couldn't find FBConfig with requested depth %d", w->g.depth);
return false;
}
// 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
GLenum tex_tgt = 0;
if (GLX_TEXTURE_2D_BIT_EXT & pcfg->texture_tgts && gd->cap.non_power_of_two_texture)
tex_tgt = GLX_TEXTURE_2D_EXT;
else if (GLX_TEXTURE_RECTANGLE_BIT_EXT & pcfg->texture_tgts)
tex_tgt = GLX_TEXTURE_RECTANGLE_EXT;
else if (!(GLX_TEXTURE_2D_BIT_EXT & pcfg->texture_tgts))
tex_tgt = GLX_TEXTURE_RECTANGLE_EXT;
else
tex_tgt = GLX_TEXTURE_2D_EXT;
log_debug("depth %d, tgt %#x, rgba %d\n", w->g.depth, tex_tgt,
(GLX_TEXTURE_FORMAT_RGBA_EXT == pcfg->texture_fmt));
GLint attrs[] = {
GLX_TEXTURE_FORMAT_EXT, pcfg->texture_fmt, GLX_TEXTURE_TARGET_EXT, tex_tgt, 0,
};
auto wd = ccalloc(1, struct _glx_win_data);
wd->texture.target =
(GLX_TEXTURE_2D_EXT == tex_tgt ? GL_TEXTURE_2D : GL_TEXTURE_RECTANGLE);
wd->texture.y_inverted = pcfg->y_inverted;
if (ps->has_name_pixmap) {
wd->pixmap = xcb_generate_id(ps->c);
xcb_composite_name_window_pixmap(ps->c, w->id, wd->pixmap);
} else {
wd->pixmap = w->id;
}
if (!wd->pixmap) {
log_error("Failed to get pixmap for window %#010x", w->id);
goto err;
}
wd->glpixmap = glXCreatePixmap(ps->dpy, pcfg->cfg, wd->pixmap, attrs);
if (!wd->glpixmap) {
log_error("Failed to create glpixmap for window %#010x", w->id);
goto err;
}
// Create texture
GLuint texture = 0;
GLuint target = wd->texture.target;
glGenTextures(1, &texture);
if (!texture) {
log_error("Failed to generate texture for window %#010x", w->id);
goto err;
}
glBindTexture(target, texture);
glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glBindTexture(target, 0);
wd->texture.texture = texture;
return wd;
err:
if (wd->pixmap && wd->pixmap != w->id) {
xcb_free_pixmap(ps->c, wd->pixmap);
}
if (wd->glpixmap) {
glXDestroyPixmap(ps->dpy, wd->glpixmap);
}
free(wd);
return NULL;
}
/**
* Bind a X pixmap to an OpenGL texture.
*/
void glx_render_win(void *backend_data, session_t *ps, win *w, void *win_data,
const region_t *reg_paint) {
struct _glx_data *gd = backend_data;
struct _glx_win_data *wd = win_data;
assert(wd->pixmap);
assert(wd->glpixmap);
assert(wd->texture.texture);
glBindTexture(wd->texture.target, wd->texture.texture);
gd->glXBindTexImage(ps->dpy, wd->glpixmap, GLX_FRONT_LEFT_EXT, NULL);
glBindTexture(wd->texture.target, 0);
gl_check_err();
}
/**
* Preprocess function before start painting.
*/
static void attr_unused
glx_paint_pre(session_t *ps, region_t *preg) {
// glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Get buffer age
bool trace_damage = (ps->o.glx_swap_method < 0 || ps->o.glx_swap_method > 1);
// Trace raw damage regions
region_t newdamage;
pixman_region32_init(&newdamage);
if (trace_damage)
copy_region(&newdamage, preg);
// We use GLX buffer_age extension to decide which pixels in
// the back buffer is reusable, and limit our redrawing
int buffer_age = 0;
// Query GLX_EXT_buffer_age for buffer age
if (ps->o.glx_swap_method == SWAPM_BUFFER_AGE) {
unsigned val = 0;
glXQueryDrawable(ps->dpy, get_tgt_window(ps),
GLX_BACK_BUFFER_AGE_EXT, &val);
buffer_age = val;
}
// Buffer age too high
if (buffer_age > CGLX_MAX_BUFFER_AGE + 1)
buffer_age = 0;
assert(buffer_age >= 0);
if (buffer_age) {
// Determine paint area
for (int i = 0; i < buffer_age - 1; ++i)
pixman_region32_union(preg, preg, &ps->all_damage_last[i]);
} else
// buffer_age == 0 means buffer age is not available, paint everything
copy_region(preg, &ps->screen_reg);
if (trace_damage) {
// XXX use a circular queue instead of memmove
pixman_region32_fini(&ps->all_damage_last[CGLX_MAX_BUFFER_AGE - 1]);
memmove(ps->all_damage_last + 1, ps->all_damage_last,
(CGLX_MAX_BUFFER_AGE - 1) * sizeof(region_t *));
ps->all_damage_last[0] = newdamage;
}
//gl_set_clip(ps, preg);
#ifdef DEBUG_GLX_PAINTREG
glx_render_color(ps, 0, 0, ps->root_width, ps->root_height, 0, *preg, NULL);
#endif
gl_check_err();
}
backend_info_t glx_backend = {
.init = glx_init,
.deinit = glx_deinit,
.prepare_win = glx_prepare_win,
.render_win = glx_render_win,
};