0c4b690b2b
This commit introduced a new, modular backend interface. The interface is not very good, since I don't think I fully understand all the requirements writing a backend have. But this is a good first step. This commit also includes an initial xrender backend written using the new interface, and some opengl backend related helper functions, which are taken from the old opengl backend. However, there is not integration with the core compton yet. compton will still use the old backend code. This commit is here so we can get the automated build test. What is implemented in the new xrender backend: * Windows with transparency * Shadow * Opacity * Wallpaper (getting the root pixmap) * Blur Known problem with the xrender backend: * It is slower Things that still need to be figured out: * What is the better way to add vsync to the new backends Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
883 lines
26 KiB
C
883 lines
26 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/gl/glx.h"
|
|
#include "backend/backend.h"
|
|
#include "backend/gl/gl_common.h"
|
|
|
|
/// @brief Wrapper of a GLX FBConfig.
|
|
typedef struct {
|
|
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_blur_shader_t blur_shader[MAX_BLUR_PASS];
|
|
|
|
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);
|
|
|
|
// Make sure extension names are not crazy...
|
|
assert(found[len] == ' ' || found[len] == 0);
|
|
return found != NULL;
|
|
}
|
|
|
|
/**
|
|
* @brief Release binding of a texture.
|
|
*/
|
|
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 glx_release_win(struct _glx_data *gd, Display *dpy, gl_texture_t *ptex) {
|
|
glx_release_pixmap(gd, dpy, ptex);
|
|
glDeleteTextures(1, &ptex->texture);
|
|
|
|
// Free structure itself
|
|
free(ptex);
|
|
}
|
|
|
|
/**
|
|
* 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);*/
|
|
}
|
|
>>>>>>> 4bc5ef8... wip glx backend:src/backend/gl/glx.c
|
|
|
|
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, 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, ps->psglx->fbconfigs[depth], pfbcfg) < 0) {
|
|
log_debug(
|
|
"(depth %d): %p overrides %p, target %#x.\n", depth, pfbcfg->cfg,
|
|
ps->psglx->fbconfigs[depth] ? ps->psglx->fbconfigs[depth]->cfg : 0,
|
|
pfbcfg->texture_tgts);
|
|
if (!ps->psglx->fbconfigs[depth]) {
|
|
ps->psglx->fbconfigs[depth] = cmalloc(glx_fbconfig_t);
|
|
}
|
|
(*ps->psglx->fbconfigs[depth]) = *pfbcfg;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get GLX FBConfigs for all depths.
|
|
*/
|
|
static bool glx_update_fbconfig(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, tgtdpt, &fbinfo);
|
|
}
|
|
}
|
|
|
|
if (depth == visualdepth && rgba) {
|
|
fbinfo.texture_fmt = GLX_TEXTURE_FORMAT_RGBA_EXT;
|
|
glx_update_fbconfig_bydepth(ps, depth, &fbinfo);
|
|
}
|
|
}
|
|
|
|
cxfree(pfbcfgs);
|
|
|
|
// Sanity checks
|
|
if (!ps->psglx->fbconfigs[ps->depth]) {
|
|
log_error("No FBConfig found for default depth %d.", ps->depth);
|
|
return false;
|
|
}
|
|
|
|
if (!ps->psglx->fbconfigs[32]) {
|
|
log_error("No FBConfig found for depth 32. Expect crazy things.");
|
|
}
|
|
|
|
log_trace("%d-bit: %p, 32-bit: %p", ps->depth,
|
|
ps->psglx->fbconfigs[ps->depth]->cfg, ps->psglx->fbconfigs[32]->cfg);
|
|
|
|
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]);
|
|
}
|
|
|
|
glx_free_prog_main(ps, &ps->o.glx_prog_win);
|
|
|
|
gl_check_err();
|
|
|
|
// Free FBConfigs
|
|
for (int i = 0; i <= OPENGL_MAX_DEPTH; ++i) {
|
|
free(ps->psglx->fbconfigs[i]);
|
|
ps->psglx->fbconfigs[i] = NULL;
|
|
}
|
|
|
|
// Destroy GLX context
|
|
if (gd->ctx) {
|
|
glXDestroyContext(ps->dpy, gd->ctx);
|
|
gd->ctx = 0;
|
|
}
|
|
|
|
free(gd);
|
|
}
|
|
|
|
/**
|
|
* Initialize OpenGL.
|
|
*/
|
|
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, None, 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");
|
|
|
|
// Acquire function addresses
|
|
#if 0
|
|
psglx->glStringMarkerGREMEDY = (f_StringMarkerGREMEDY)
|
|
glXGetProcAddress((const GLubyte *) "glStringMarkerGREMEDY");
|
|
psglx->glFrameTerminatorGREMEDY = (f_FrameTerminatorGREMEDY)
|
|
glXGetProcAddress((const GLubyte *) "glFrameTerminatorGREMEDY");
|
|
#endif
|
|
|
|
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(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));
|
|
|
|
success = true;
|
|
|
|
end:
|
|
cxfree(pvis);
|
|
|
|
if (!success) {
|
|
glx_deinit(gd, ps);
|
|
return NULL;
|
|
}
|
|
|
|
return gd;
|
|
}
|
|
|
|
/**
|
|
* Initialize GLX blur filter.
|
|
*/
|
|
bool glx_init_blur(session_t *ps) {
|
|
assert(ps->o.blur_kerns[0]);
|
|
|
|
// Allocate PBO if more than one blur kernel is present
|
|
if (ps->o.blur_kerns[1]) {
|
|
// Try to generate a framebuffer
|
|
GLuint fbo = 0;
|
|
glGenFramebuffers(1, &fbo);
|
|
if (!fbo) {
|
|
log_error("Failed to generate Framebuffer. Cannot do "
|
|
"multi-pass blur with GLX backend.");
|
|
return false;
|
|
}
|
|
glDeleteFramebuffers(1, &fbo);
|
|
}
|
|
|
|
{
|
|
char *lc_numeric_old = strdup(setlocale(LC_NUMERIC, NULL));
|
|
// Enforce LC_NUMERIC locale "C" here to make sure decimal point is sane
|
|
// Thanks to hiciu for reporting.
|
|
setlocale(LC_NUMERIC, "C");
|
|
|
|
static const char *FRAG_SHADER_BLUR_PREFIX = "#version 110\n"
|
|
"%s"
|
|
"uniform float offset_x;\n"
|
|
"uniform float offset_y;\n"
|
|
"uniform float "
|
|
"factor_center;\n"
|
|
"uniform %s tex_scr;\n"
|
|
"\n"
|
|
"void main() {\n"
|
|
" vec4 sum = vec4(0.0, "
|
|
"0.0, 0.0, 0.0);\n";
|
|
static const char *FRAG_SHADER_BLUR_ADD = " sum += float(%.7g) * "
|
|
"%s(tex_scr, "
|
|
"vec2(gl_TexCoord[0].x + "
|
|
"offset_x * float(%d), "
|
|
"gl_TexCoord[0].y + offset_y * "
|
|
"float(%d)));\n";
|
|
static const char *FRAG_SHADER_BLUR_ADD_GPUSHADER4 = " sum += "
|
|
"float(%.7g) * "
|
|
"%sOffset(tex_scr, "
|
|
"vec2(gl_TexCoord[0]"
|
|
".x, "
|
|
"gl_TexCoord[0].y), "
|
|
"ivec2(%d, %d));\n";
|
|
static const char *FRAG_SHADER_BLUR_SUFFIX = " sum += %s(tex_scr, "
|
|
"vec2(gl_TexCoord[0].x, "
|
|
"gl_TexCoord[0].y)) * "
|
|
"factor_center;\n"
|
|
" gl_FragColor = sum / "
|
|
"(factor_center + "
|
|
"float(%.7g));\n"
|
|
"}\n";
|
|
|
|
const bool use_texture_rect = !ps->psglx->has_texture_non_power_of_two;
|
|
const char *sampler_type =
|
|
(use_texture_rect ? "sampler2DRect" : "sampler2D");
|
|
const char *texture_func =
|
|
(use_texture_rect ? "texture2DRect" : "texture2D");
|
|
const char *shader_add = FRAG_SHADER_BLUR_ADD;
|
|
char *extension = strdup("");
|
|
if (use_texture_rect)
|
|
mstrextend(&extension, "#extension GL_ARB_texture_rectangle : "
|
|
"require\n");
|
|
if (ps->o.glx_use_gpushader4) {
|
|
mstrextend(&extension, "#extension GL_EXT_gpu_shader4 : "
|
|
"require\n");
|
|
shader_add = FRAG_SHADER_BLUR_ADD_GPUSHADER4;
|
|
}
|
|
|
|
for (int i = 0; i < MAX_BLUR_PASS && ps->o.blur_kerns[i]; ++i) {
|
|
xcb_render_fixed_t *kern = ps->o.blur_kerns[i];
|
|
if (!kern)
|
|
break;
|
|
|
|
glx_blur_pass_t *ppass = &ps->psglx->blur_passes[i];
|
|
|
|
// Build shader
|
|
{
|
|
int wid = XFIXED_TO_DOUBLE(kern[0]),
|
|
hei = XFIXED_TO_DOUBLE(kern[1]);
|
|
int nele = wid * hei - 1;
|
|
unsigned int len =
|
|
strlen(FRAG_SHADER_BLUR_PREFIX) +
|
|
strlen(sampler_type) + strlen(extension) +
|
|
(strlen(shader_add) + strlen(texture_func) + 42) *
|
|
nele +
|
|
strlen(FRAG_SHADER_BLUR_SUFFIX) +
|
|
strlen(texture_func) + 12 + 1;
|
|
char *shader_str = ccalloc(len, char);
|
|
char *pc = shader_str;
|
|
sprintf(pc, FRAG_SHADER_BLUR_PREFIX, extension,
|
|
sampler_type);
|
|
pc += strlen(pc);
|
|
assert(strlen(shader_str) < len);
|
|
|
|
double sum = 0.0;
|
|
for (int j = 0; j < hei; ++j) {
|
|
for (int k = 0; k < wid; ++k) {
|
|
if (hei / 2 == j && wid / 2 == k)
|
|
continue;
|
|
double val = XFIXED_TO_DOUBLE(
|
|
kern[2 + j * wid + k]);
|
|
if (0.0 == val)
|
|
continue;
|
|
sum += val;
|
|
sprintf(pc, shader_add, val, texture_func,
|
|
k - wid / 2, j - hei / 2);
|
|
pc += strlen(pc);
|
|
assert(strlen(shader_str) < len);
|
|
}
|
|
}
|
|
|
|
sprintf(pc, FRAG_SHADER_BLUR_SUFFIX, texture_func, sum);
|
|
assert(strlen(shader_str) < len);
|
|
ppass->frag_shader =
|
|
glx_create_shader(GL_FRAGMENT_SHADER, shader_str);
|
|
free(shader_str);
|
|
}
|
|
|
|
if (!ppass->frag_shader) {
|
|
log_error("Failed to create fragment shader %d.", i);
|
|
return false;
|
|
}
|
|
|
|
// Build program
|
|
ppass->prog = glx_create_program(&ppass->frag_shader, 1);
|
|
if (!ppass->prog) {
|
|
log_error("Failed to create GLSL program.");
|
|
return false;
|
|
}
|
|
|
|
// Get uniform addresses
|
|
#define P_GET_UNIFM_LOC(name, target) \
|
|
{ \
|
|
ppass->target = glGetUniformLocation(ppass->prog, name); \
|
|
if (ppass->target < 0) { \
|
|
log_error("Failed to get location of %d-th uniform '" name "'. " \
|
|
"Mig" \
|
|
"ht " \
|
|
"be " \
|
|
"tro" \
|
|
"ubl" \
|
|
"eso" \
|
|
"me" \
|
|
".", \
|
|
i); \
|
|
} \
|
|
}
|
|
|
|
P_GET_UNIFM_LOC("factor_center", unifm_factor_center);
|
|
if (!ps->o.glx_use_gpushader4) {
|
|
P_GET_UNIFM_LOC("offset_x", unifm_offset_x);
|
|
P_GET_UNIFM_LOC("offset_y", unifm_offset_y);
|
|
}
|
|
|
|
#undef P_GET_UNIFM_LOC
|
|
}
|
|
free(extension);
|
|
|
|
// Restore LC_NUMERIC
|
|
setlocale(LC_NUMERIC, lc_numeric_old);
|
|
free(lc_numeric_old);
|
|
}
|
|
|
|
glx_check_err(ps);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Bind a X pixmap to an OpenGL texture.
|
|
*/
|
|
bool glx_render_win(session_t *ps, glx_texture_t **pptex, xcb_pixmap_t pixmap,
|
|
unsigned width, unsigned height, unsigned depth) {
|
|
if (ps->o.backend != BKEND_GLX && ps->o.backend != BKEND_XR_GLX_HYBRID)
|
|
return true;
|
|
|
|
if (!pixmap) {
|
|
log_error("Binding to an empty pixmap %#010x. This can't work.", pixmap);
|
|
return false;
|
|
}
|
|
|
|
glx_texture_t *ptex = *pptex;
|
|
bool need_release = true;
|
|
|
|
// Allocate structure
|
|
if (!ptex) {
|
|
static const glx_texture_t GLX_TEX_DEF = {
|
|
.texture = 0,
|
|
.glpixmap = 0,
|
|
.pixmap = 0,
|
|
.target = 0,
|
|
.width = 0,
|
|
.height = 0,
|
|
.depth = 0,
|
|
.y_inverted = false,
|
|
};
|
|
|
|
ptex = cmalloc(glx_texture_t);
|
|
memcpy(ptex, &GLX_TEX_DEF, sizeof(glx_texture_t));
|
|
*pptex = ptex;
|
|
}
|
|
|
|
// Release pixmap if parameters are inconsistent
|
|
if (ptex->texture && ptex->pixmap != pixmap) {
|
|
glx_release_pixmap(ps, ptex);
|
|
}
|
|
|
|
// Create GLX pixmap
|
|
if (!ptex->glpixmap) {
|
|
need_release = false;
|
|
|
|
// Retrieve pixmap parameters, if they aren't provided
|
|
if (!(width && height && depth)) {
|
|
Window rroot = None;
|
|
int rx = 0, ry = 0;
|
|
unsigned rbdwid = 0;
|
|
if (!XGetGeometry(ps->dpy, pixmap, &rroot, &rx, &ry, &width,
|
|
&height, &rbdwid, &depth)) {
|
|
log_error("Failed to query info of pixmap %#010x.", pixmap);
|
|
return false;
|
|
}
|
|
if (depth > OPENGL_MAX_DEPTH) {
|
|
log_error("Requested depth %d higher than max possible "
|
|
"depth %d.",
|
|
depth, OPENGL_MAX_DEPTH);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
const glx_fbconfig_t *pcfg = ps->psglx->fbconfigs[depth];
|
|
if (!pcfg) {
|
|
log_error("Couldn't find FBConfig with requested depth %d", 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 &&
|
|
ps->psglx->has_texture_non_power_of_two)
|
|
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", 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,
|
|
};
|
|
|
|
ptex->glpixmap = glXCreatePixmap(ps->dpy, pcfg->cfg, pixmap, attrs);
|
|
ptex->pixmap = pixmap;
|
|
ptex->target =
|
|
(GLX_TEXTURE_2D_EXT == tex_tgt ? GL_TEXTURE_2D : GL_TEXTURE_RECTANGLE);
|
|
ptex->width = width;
|
|
ptex->height = height;
|
|
ptex->depth = depth;
|
|
ptex->y_inverted = pcfg->y_inverted;
|
|
}
|
|
if (!ptex->glpixmap) {
|
|
log_error("Failed to allocate GLX pixmap.");
|
|
return false;
|
|
}
|
|
|
|
glEnable(ptex->target);
|
|
|
|
// Create texture
|
|
if (!ptex->texture) {
|
|
need_release = false;
|
|
|
|
GLuint texture = 0;
|
|
glGenTextures(1, &texture);
|
|
glBindTexture(ptex->target, texture);
|
|
|
|
glTexParameteri(ptex->target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
|
glTexParameteri(ptex->target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
glTexParameteri(ptex->target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
glTexParameteri(ptex->target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
|
|
glBindTexture(ptex->target, 0);
|
|
|
|
ptex->texture = texture;
|
|
}
|
|
if (!ptex->texture) {
|
|
log_error("Failed to allocate texture.");
|
|
return false;
|
|
}
|
|
|
|
glBindTexture(ptex->target, ptex->texture);
|
|
|
|
// The specification requires rebinding whenever the content changes...
|
|
// We can't follow this, too slow.
|
|
if (need_release)
|
|
ps->psglx->glXReleaseTexImageProc(ps->dpy, ptex->glpixmap,
|
|
GLX_FRONT_LEFT_EXT);
|
|
|
|
ps->psglx->glXBindTexImageProc(ps->dpy, ptex->glpixmap, GLX_FRONT_LEFT_EXT, NULL);
|
|
|
|
// Cleanup
|
|
glBindTexture(ptex->target, 0);
|
|
glDisable(ptex->target);
|
|
|
|
glx_check_err(ps);
|
|
|
|
return true;
|
|
}
|
|
|
|
#if 0
|
|
/**
|
|
* Preprocess function before start painting.
|
|
*/
|
|
void
|
|
glx_paint_pre(session_t *ps, region_t *preg) {
|
|
ps->psglx->z = 0.0;
|
|
// 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;
|
|
}
|
|
|
|
glx_set_clip(ps, preg);
|
|
|
|
#ifdef DEBUG_GLX_PAINTREG
|
|
glx_render_color(ps, 0, 0, ps->root_width, ps->root_height, 0, *preg, NULL);
|
|
#endif
|
|
|
|
glx_check_err(ps);
|
|
}
|
|
#endif
|
|
|
|
backend_info_t glx_backend = {
|
|
.init = glx_init,
|
|
|
|
};
|