// 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 #include #include #include #include #include #include "backend/gl/gl_common.h" #include "backend/gl/glx.h" #include "common.h" #include "compiler.h" #include "config.h" #include "kernel.h" #include "log.h" #include "region.h" #include "string_utils.h" #include "uthash_extra.h" #include "utils.h" #include "win.h" #include "opengl.h" #ifndef GL_TEXTURE_RECTANGLE #define GL_TEXTURE_RECTANGLE 0x84F5 #endif static inline XVisualInfo *get_visualinfo_from_visual(session_t *ps, xcb_visualid_t visual) { XVisualInfo vreq = {.visualid = visual}; int nitems = 0; return XGetVisualInfo(ps->c.dpy, VisualIDMask, &vreq, &nitems); } /** * Initialize OpenGL. */ bool glx_init(session_t *ps, bool need_render) { bool success = false; XVisualInfo *pvis = NULL; // Check for GLX extension if (!ps->glx_exists) { log_error("No GLX extension."); goto glx_init_end; } // Get XVisualInfo pvis = get_visualinfo_from_visual(ps, ps->c.screen_info->root_visual); if (!pvis) { log_error("Failed to acquire XVisualInfo for current visual."); goto glx_init_end; } // Ensure the visual is double-buffered if (need_render) { int value = 0; if (Success != glXGetConfig(ps->c.dpy, pvis, GLX_USE_GL, &value) || !value) { log_error("Root visual is not a GL visual."); goto glx_init_end; } if (Success != glXGetConfig(ps->c.dpy, pvis, GLX_DOUBLEBUFFER, &value) || !value) { log_error("Root visual is not a double buffered GL visual."); goto glx_init_end; } } // Ensure GLX_EXT_texture_from_pixmap exists if (need_render && !glxext.has_GLX_EXT_texture_from_pixmap) { goto glx_init_end; } // Initialize GLX data structure if (!ps->psglx) { static const glx_session_t CGLX_SESSION_DEF = CGLX_SESSION_INIT; ps->psglx = cmalloc(glx_session_t); memcpy(ps->psglx, &CGLX_SESSION_DEF, sizeof(glx_session_t)); // +1 for the zero terminator ps->psglx->blur_passes = ccalloc(ps->o.blur_kernel_count, glx_blur_pass_t); for (int i = 0; i < ps->o.blur_kernel_count; ++i) { glx_blur_pass_t *ppass = &ps->psglx->blur_passes[i]; ppass->unifm_factor_center = -1; ppass->unifm_offset_x = -1; ppass->unifm_offset_y = -1; } ps->psglx->round_passes = ccalloc(1, glx_round_pass_t); glx_round_pass_t *ppass = ps->psglx->round_passes; ppass->unifm_radius = -1; ppass->unifm_texcoord = -1; ppass->unifm_texsize = -1; ppass->unifm_borderw = -1; ppass->unifm_borderc = -1; ppass->unifm_resolution = -1; ppass->unifm_tex_scr = -1; } glx_session_t *psglx = ps->psglx; if (!psglx->context) { // Get GLX context #ifndef DEBUG_GLX_DEBUG_CONTEXT psglx->context = glXCreateContext(ps->c.dpy, pvis, None, GL_TRUE); #else { GLXFBConfig fbconfig = get_fbconfig_from_visualinfo(ps, pvis); if (!fbconfig) { log_error("Failed to get GLXFBConfig for root visual " "%#lx.", pvis->visualid); goto glx_init_end; } f_glXCreateContextAttribsARB p_glXCreateContextAttribsARB = (f_glXCreateContextAttribsARB)glXGetProcAddress( (const GLubyte *)"glXCreateContextAttribsARB"); if (!p_glXCreateContextAttribsARB) { log_error("Failed to get glXCreateContextAttribsARB()."); goto glx_init_end; } static const int attrib_list[] = { GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_DEBUG_BIT_ARB, None}; psglx->context = p_glXCreateContextAttribsARB( ps->c.dpy, fbconfig, NULL, GL_TRUE, attrib_list); } #endif if (!psglx->context) { log_error("Failed to get GLX context."); goto glx_init_end; } // Attach GLX context if (!glXMakeCurrent(ps->c.dpy, get_tgt_window(ps), psglx->context)) { log_error("Failed to attach GLX context."); goto glx_init_end; } #ifdef DEBUG_GLX_DEBUG_CONTEXT { f_DebugMessageCallback p_DebugMessageCallback = (f_DebugMessageCallback)glXGetProcAddress( (const GLubyte *)"glDebugMessageCallback"); 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 (need_render && !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 glx_init_end; } } // Check GL_ARB_texture_non_power_of_two, requires a GLX context and // must precede FBConfig fetching if (need_render) { psglx->has_texture_non_power_of_two = epoxy_has_gl_extension("GL_ARB_texture_non_power_of_two"); } // Render preparations if (need_render) { glx_on_root_change(ps); 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->c.dpy, get_tgt_window(ps)); } success = true; glx_init_end: XFree(pvis); if (!success) { glx_destroy(ps); } return success; } static void glx_free_prog_main(glx_prog_main_t *pprogram) { if (!pprogram) { return; } if (pprogram->prog) { glDeleteProgram(pprogram->prog); pprogram->prog = 0; } pprogram->unifm_opacity = -1; pprogram->unifm_invert_color = -1; pprogram->unifm_tex = -1; } /** * Destroy GLX related resources. */ void glx_destroy(session_t *ps) { if (!ps->psglx) { return; } // Free all GLX resources of windows win_stack_foreach_managed(w, &ps->window_stack) { free_win_res_glx(ps, w); } // Free GLSL shaders/programs for (int i = 0; i < ps->o.blur_kernel_count; ++i) { glx_blur_pass_t *ppass = &ps->psglx->blur_passes[i]; if (ppass->frag_shader) { glDeleteShader(ppass->frag_shader); } if (ppass->prog) { glDeleteProgram(ppass->prog); } } free(ps->psglx->blur_passes); glx_round_pass_t *ppass = ps->psglx->round_passes; if (ppass->frag_shader) { glDeleteShader(ppass->frag_shader); } if (ppass->prog) { glDeleteProgram(ppass->prog); } free(ps->psglx->round_passes); glx_free_prog_main(&ps->glx_prog_win); gl_check_err(); // Destroy GLX context if (ps->psglx->context) { glXMakeCurrent(ps->c.dpy, None, NULL); glXDestroyContext(ps->c.dpy, ps->psglx->context); ps->psglx->context = NULL; } free(ps->psglx); ps->psglx = NULL; ps->argb_fbconfig = (struct glx_fbconfig_info){0}; } /** * Callback to run on root window size change. */ void glx_on_root_change(session_t *ps) { glViewport(0, 0, ps->root_width, ps->root_height); // Initialize matrix, copied from dcompmgr glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0, ps->root_width, 0, ps->root_height, -1000.0, 1000.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); } /** * Initialize GLX blur filter. */ bool glx_init_blur(session_t *ps) { assert(ps->o.blur_kernel_count > 0); assert(ps->o.blur_kerns); assert(ps->o.blur_kerns[0]); // Allocate PBO if more than one blur kernel is present if (ps->o.blur_kernel_count > 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_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 = NULL; if (use_texture_rect) { mstrextend(&extension, "#extension GL_ARB_texture_rectangle : " "require\n"); } if (!extension) { extension = strdup(""); } for (int i = 0; i < ps->o.blur_kernel_count; ++i) { auto kern = ps->o.blur_kerns[i]; glx_blur_pass_t *ppass = &ps->psglx->blur_passes[i]; // Build shader int width = kern->w, height = kern->h; int nele = width * height - 1; assert(nele >= 0); auto len = strlen(FRAG_SHADER_BLUR_PREFIX) + strlen(sampler_type) + strlen(extension) + (strlen(shader_add) + strlen(texture_func) + 42) * (uint)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 < height; ++j) { for (int k = 0; k < width; ++k) { if (height / 2 == j && width / 2 == k) { continue; } double val = kern->data[j * width + k]; if (val == 0) { continue; } sum += val; sprintf(pc, shader_add, val, texture_func, k - width / 2, j - height / 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 = gl_create_shader(GL_FRAGMENT_SHADER, shader_str); free(shader_str); if (!ppass->frag_shader) { log_error("Failed to create fragment shader %d.", i); free(extension); free(lc_numeric_old); return false; } // Build program ppass->prog = gl_create_program(&ppass->frag_shader, 1); if (!ppass->prog) { log_error("Failed to create GLSL program."); free(extension); free(lc_numeric_old); 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 \ "'. Might be troublesome.", \ i); \ } \ } P_GET_UNIFM_LOC("factor_center", unifm_factor_center); 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); } gl_check_err(); return true; } /** * Initialize GLX rounded corners filter. */ bool glx_init_rounded_corners(session_t *ps) { 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 = "#version 110\n" "%s" // extensions "uniform float u_radius;\n" "uniform float u_borderw;\n" "uniform vec4 u_borderc;\n" "uniform vec2 u_texcoord;\n" "uniform vec2 u_texsize;\n" "uniform vec2 u_resolution;\n" "uniform %s tex_scr;\n" // sampler2D | sampler2DRect "\n" "// https://www.shadertoy.com/view/ltS3zW\n" "float RectSDF(vec2 p, vec2 b, float r) {\n" " vec2 d = abs(p) - b + vec2(r);\n" " return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - r;\n" "}\n\n" "void main()\n" "{\n" " vec2 coord = vec2(u_texcoord.x, " "u_resolution.y-u_texsize.y-u_texcoord.y);\n" " vec4 u_v4WndBgColor = %s(tex_scr, vec2(gl_TexCoord[0].st));\n" " float u_fRadiusPx = u_radius;\n" " float u_fHalfBorderThickness = u_borderw / 2.0;\n" " vec4 u_v4BorderColor = u_borderc;\n" " vec4 u_v4FillColor = vec4(0.0, 0.0, 0.0, 0.0);\n" " vec4 v4FromColor = u_v4BorderColor; //Always the border " "color. If no border, this still should be set\n" " vec4 v4ToColor = u_v4WndBgColor; //Outside color is the " "background texture\n" "\n" " vec2 u_v2HalfShapeSizePx = u_texsize/2.0 - " "vec2(u_fHalfBorderThickness);\n" " vec2 v_v2CenteredPos = (gl_FragCoord.xy - u_texsize.xy / 2.0 - " "coord);\n" "\n" " float fDist = RectSDF(v_v2CenteredPos, u_v2HalfShapeSizePx, " "u_fRadiusPx - u_fHalfBorderThickness);\n" " if (u_fHalfBorderThickness > 0.0) {\n" " if (fDist < 0.0) {\n" " v4ToColor = u_v4FillColor;\n" " }\n" " fDist = abs(fDist) - u_fHalfBorderThickness;\n" " } else {\n" " v4FromColor = u_v4FillColor;\n" " }\n" " float fBlendAmount = smoothstep(-1.0, 1.0, fDist);\n" " vec4 c = mix(v4FromColor, v4ToColor, fBlendAmount);\n" "\n" " // final color\n" " gl_FragColor = c;\n" "\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"); char *extension = NULL; if (use_texture_rect) { mstrextend(&extension, "#extension GL_ARB_texture_rectangle : " "require\n"); } if (!extension) { extension = strdup(""); } bool success = false; // Build rounded corners shader auto ppass = ps->psglx->round_passes; auto len = strlen(FRAG_SHADER) + strlen(extension) + strlen(sampler_type) + strlen(texture_func) + 1; char *shader_str = ccalloc(len, char); sprintf(shader_str, FRAG_SHADER, extension, sampler_type, texture_func); assert(strlen(shader_str) < len); log_debug("Generated rounded corners shader:\n%s\n", shader_str); ppass->frag_shader = gl_create_shader(GL_FRAGMENT_SHADER, shader_str); free(shader_str); if (!ppass->frag_shader) { log_error("Failed to create rounded corners fragment shader."); goto out; } // Build program ppass->prog = gl_create_program(&ppass->frag_shader, 1); if (!ppass->prog) { log_error("Failed to create GLSL program."); goto out; } // Get uniform addresses #define P_GET_UNIFM_LOC(name, target) \ { \ ppass->target = glGetUniformLocation(ppass->prog, name); \ if (ppass->target < 0) { \ log_debug("Failed to get location of rounded corners uniform " \ "'" name "'. Might be troublesome."); \ } \ } P_GET_UNIFM_LOC("u_radius", unifm_radius); P_GET_UNIFM_LOC("u_texcoord", unifm_texcoord); P_GET_UNIFM_LOC("u_texsize", unifm_texsize); P_GET_UNIFM_LOC("u_borderw", unifm_borderw); P_GET_UNIFM_LOC("u_borderc", unifm_borderc); P_GET_UNIFM_LOC("u_resolution", unifm_resolution); P_GET_UNIFM_LOC("tex_scr", unifm_tex_scr); #undef P_GET_UNIFM_LOC success = true; out: free(extension); // Restore LC_NUMERIC setlocale(LC_NUMERIC, lc_numeric_old); free(lc_numeric_old); gl_check_err(); return success; } /** * Load a GLSL main program from shader strings. */ bool glx_load_prog_main(const char *vshader_str, const char *fshader_str, glx_prog_main_t *pprogram) { assert(pprogram); // Build program pprogram->prog = gl_create_program_from_str(vshader_str, fshader_str); if (!pprogram->prog) { log_error("Failed to create GLSL program."); return false; } // Get uniform addresses #define P_GET_UNIFM_LOC(name, target) \ { \ pprogram->target = glGetUniformLocation(pprogram->prog, name); \ if (pprogram->target < 0) { \ log_error("Failed to get location of uniform '" name \ "'. Might be troublesome."); \ } \ } P_GET_UNIFM_LOC("opacity", unifm_opacity); P_GET_UNIFM_LOC("invert_color", unifm_invert_color); P_GET_UNIFM_LOC("tex", unifm_tex); P_GET_UNIFM_LOC("time", unifm_time); #undef P_GET_UNIFM_LOC gl_check_err(); return true; } static inline void glx_copy_region_to_tex(session_t *ps, GLenum tex_tgt, int basex, int basey, int dx, int dy, int width, int height) { if (width > 0 && height > 0) { glCopyTexSubImage2D(tex_tgt, 0, dx - basex, dy - basey, dx, ps->root_height - dy - height, width, height); } } static inline GLuint glx_gen_texture(GLenum tex_tgt, int width, int height) { GLuint tex = 0; glGenTextures(1, &tex); if (!tex) { return 0; } glEnable(tex_tgt); glBindTexture(tex_tgt, tex); glTexParameteri(tex_tgt, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(tex_tgt, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(tex_tgt, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(tex_tgt, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexImage2D(tex_tgt, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); glBindTexture(tex_tgt, 0); return tex; } /** * Bind an OpenGL texture and fill it with pixel data from back buffer */ bool glx_bind_texture(session_t *ps attr_unused, glx_texture_t **pptex, int x, int y, int width, int height) { if (ps->o.backend != BKEND_GLX && ps->o.backend != BKEND_XR_GLX_HYBRID) { return true; } glx_texture_t *ptex = *pptex; // log_trace("Copying xy(%d %d) wh(%d %d) ptex(%p)", x, y, width, height, ptex); // Release texture if parameters are inconsistent if (ptex && ptex->texture && (ptex->width != width || ptex->height != height)) { free_texture(ps, &ptex); } // Allocate structure if (!ptex) { ptex = ccalloc(1, glx_texture_t); *pptex = ptex; ptex->width = width; ptex->height = height; ptex->target = GL_TEXTURE_RECTANGLE; if (ps->psglx->has_texture_non_power_of_two) { ptex->target = GL_TEXTURE_2D; } } // Create texture if (!ptex->texture) { ptex->texture = glx_gen_texture(ptex->target, width, height); } if (!ptex->texture) { log_error("Failed to allocate texture."); return false; } // Read destination pixels into a texture glEnable(ptex->target); glBindTexture(ptex->target, ptex->texture); if (width > 0 && height > 0) { glx_copy_region_to_tex(ps, ptex->target, x, y, x, y, width, height); } // Cleanup glBindTexture(ptex->target, 0); glDisable(ptex->target); gl_check_err(); return true; } /** * Bind a X pixmap to an OpenGL texture. */ bool glx_bind_pixmap(session_t *ps, glx_texture_t **pptex, xcb_pixmap_t pixmap, int width, int height, bool repeat, const struct glx_fbconfig_info *fbcfg) { 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; } assert(fbcfg); glx_texture_t *ptex = *pptex; bool need_release = true; // Release pixmap if parameters are inconsistent if (ptex && ptex->texture && ptex->pixmap != pixmap) { glx_release_pixmap(ps, ptex); } // Allocate structure if (!ptex) { static const glx_texture_t GLX_TEX_DEF = { .texture = 0, .glpixmap = 0, .pixmap = 0, .target = 0, .width = 0, .height = 0, .y_inverted = false, }; ptex = cmalloc(glx_texture_t); memcpy(ptex, &GLX_TEX_DEF, sizeof(glx_texture_t)); *pptex = ptex; } // Create GLX pixmap int depth = 0; if (!ptex->glpixmap) { need_release = false; // Retrieve pixmap parameters, if they aren't provided if (!width || !height) { auto r = xcb_get_geometry_reply( ps->c.c, xcb_get_geometry(ps->c.c, pixmap), NULL); if (!r) { log_error("Failed to query info of pixmap %#010x.", pixmap); return false; } if (r->depth > OPENGL_MAX_DEPTH) { log_error("Requested depth %d higher than %d.", depth, OPENGL_MAX_DEPTH); return false; } depth = r->depth; width = r->width; height = r->height; free(r); } // Determine texture target, copied from compiz // The assumption we made here is the target never changes based on any // pixmap-specific parameters, and this may change in the future GLenum tex_tgt = 0; if (GLX_TEXTURE_2D_BIT_EXT & fbcfg->texture_tgts && ps->psglx->has_texture_non_power_of_two) { tex_tgt = GLX_TEXTURE_2D_EXT; } else if (GLX_TEXTURE_RECTANGLE_BIT_EXT & fbcfg->texture_tgts) { tex_tgt = GLX_TEXTURE_RECTANGLE_EXT; } else if (!(GLX_TEXTURE_2D_BIT_EXT & fbcfg->texture_tgts)) { tex_tgt = GLX_TEXTURE_RECTANGLE_EXT; } else { tex_tgt = GLX_TEXTURE_2D_EXT; } log_debug("depth %d, tgt %#x, rgba %d", depth, tex_tgt, (GLX_TEXTURE_FORMAT_RGBA_EXT == fbcfg->texture_fmt)); GLint attrs[] = { GLX_TEXTURE_FORMAT_EXT, fbcfg->texture_fmt, GLX_TEXTURE_TARGET_EXT, (GLint)tex_tgt, 0, }; ptex->glpixmap = glXCreatePixmap(ps->c.dpy, fbcfg->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->y_inverted = fbcfg->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); if (repeat) { glTexParameteri(ptex->target, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(ptex->target, GL_TEXTURE_WRAP_T, GL_REPEAT); } else { 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) { glXReleaseTexImageEXT(ps->c.dpy, ptex->glpixmap, GLX_FRONT_LEFT_EXT); } glXBindTexImageEXT(ps->c.dpy, ptex->glpixmap, GLX_FRONT_LEFT_EXT, NULL); // Cleanup glBindTexture(ptex->target, 0); glDisable(ptex->target); gl_check_err(); return true; } /** * @brief Release binding of a texture. */ void glx_release_pixmap(session_t *ps, glx_texture_t *ptex) { // Release binding if (ptex->glpixmap && ptex->texture) { glBindTexture(ptex->target, ptex->texture); glXReleaseTexImageEXT(ps->c.dpy, ptex->glpixmap, GLX_FRONT_LEFT_EXT); glBindTexture(ptex->target, 0); } // Free GLX Pixmap if (ptex->glpixmap) { glXDestroyPixmap(ps->c.dpy, ptex->glpixmap); ptex->glpixmap = 0; } gl_check_err(); } /** * Set clipping region on the target window. */ void glx_set_clip(session_t *ps, const region_t *reg) { // Quit if we aren't using stencils if (ps->o.glx_no_stencil) { return; } glDisable(GL_STENCIL_TEST); glDisable(GL_SCISSOR_TEST); if (!reg) { return; } int nrects; const rect_t *rects = pixman_region32_rectangles((region_t *)reg, &nrects); if (nrects == 1) { glEnable(GL_SCISSOR_TEST); glScissor(rects[0].x1, ps->root_height - rects[0].y2, rects[0].x2 - rects[0].x1, rects[0].y2 - rects[0].y1); } gl_check_err(); } #define P_PAINTREG_START(var) \ region_t reg_new; \ int nrects; \ const rect_t *rects; \ assert(width >= 0 && height >= 0); \ pixman_region32_init_rect(®_new, dx, dy, (uint)width, (uint)height); \ pixman_region32_intersect(®_new, ®_new, (region_t *)reg_tgt); \ rects = pixman_region32_rectangles(®_new, &nrects); \ glBegin(GL_QUADS); \ \ for (int ri = 0; ri < nrects; ++ri) { \ rect_t var = rects[ri]; #define P_PAINTREG_END() \ } \ glEnd(); \ \ pixman_region32_fini(®_new); /** * Blur contents in a particular region. * * XXX seems to be way to complex for what it does */ bool glx_blur_dst(session_t *ps, int dx, int dy, int width, int height, float z, GLfloat factor_center, const region_t *reg_tgt, glx_blur_cache_t *pbc) { assert(ps->psglx->blur_passes[0].prog); const bool more_passes = ps->o.blur_kernel_count > 1; const bool have_scissors = glIsEnabled(GL_SCISSOR_TEST); const bool have_stencil = glIsEnabled(GL_STENCIL_TEST); bool ret = false; // Calculate copy region size glx_blur_cache_t ibc = {.width = 0, .height = 0}; if (!pbc) { pbc = &ibc; } int mdx = dx, mdy = dy, mwidth = width, mheight = height; // log_trace("%d, %d, %d, %d", mdx, mdy, mwidth, mheight); /* if (ps->o.resize_damage > 0) { int inc_x = 0, inc_y = 0; for (int i = 0; i < MAX_BLUR_PASS; ++i) { XFixed *kern = ps->o.blur_kerns[i]; if (!kern) break; inc_x += XFIXED_TO_DOUBLE(kern[0]) / 2; inc_y += XFIXED_TO_DOUBLE(kern[1]) / 2; } inc_x = min2(ps->o.resize_damage, inc_x); inc_y = min2(ps->o.resize_damage, inc_y); mdx = max2(dx - inc_x, 0); mdy = max2(dy - inc_y, 0); int mdx2 = min2(dx + width + inc_x, ps->root_width), mdy2 = min2(dy + height + inc_y, ps->root_height); mwidth = mdx2 - mdx; mheight = mdy2 - mdy; } */ GLenum tex_tgt = GL_TEXTURE_RECTANGLE; if (ps->psglx->has_texture_non_power_of_two) { tex_tgt = GL_TEXTURE_2D; } // Free textures if size inconsistency discovered if (mwidth != pbc->width || mheight != pbc->height) { free_glx_bc_resize(ps, pbc); } // Generate FBO and textures if needed if (!pbc->textures[0]) { pbc->textures[0] = glx_gen_texture(tex_tgt, mwidth, mheight); } GLuint tex_scr = pbc->textures[0]; if (more_passes && !pbc->textures[1]) { pbc->textures[1] = glx_gen_texture(tex_tgt, mwidth, mheight); } pbc->width = mwidth; pbc->height = mheight; GLuint tex_scr2 = pbc->textures[1]; if (more_passes && !pbc->fbo) { glGenFramebuffers(1, &pbc->fbo); } const GLuint fbo = pbc->fbo; if (!tex_scr || (more_passes && !tex_scr2)) { log_error("Failed to allocate texture."); goto glx_blur_dst_end; } if (more_passes && !fbo) { log_error("Failed to allocate framebuffer."); goto glx_blur_dst_end; } // Read destination pixels into a texture glEnable(tex_tgt); glBindTexture(tex_tgt, tex_scr); glx_copy_region_to_tex(ps, tex_tgt, mdx, mdy, mdx, mdy, mwidth, mheight); /* if (tex_scr2) { glBindTexture(tex_tgt, tex_scr2); glx_copy_region_to_tex(ps, tex_tgt, mdx, mdy, mdx, mdy, mwidth, dx - mdx); glx_copy_region_to_tex(ps, tex_tgt, mdx, mdy, mdx, dy + height, mwidth, mdy + mheight - dy - height); glx_copy_region_to_tex(ps, tex_tgt, mdx, mdy, mdx, dy, dx - mdx, height); glx_copy_region_to_tex(ps, tex_tgt, mdx, mdy, dx + width, dy, mdx + mwidth - dx - width, height); } */ // Texture scaling factor GLfloat texfac_x = 1.0F, texfac_y = 1.0F; if (tex_tgt == GL_TEXTURE_2D) { texfac_x /= (GLfloat)mwidth; texfac_y /= (GLfloat)mheight; } // Paint it back if (more_passes) { glDisable(GL_STENCIL_TEST); glDisable(GL_SCISSOR_TEST); } bool last_pass = false; for (int i = 0; i < ps->o.blur_kernel_count; ++i) { last_pass = (i == ps->o.blur_kernel_count - 1); const glx_blur_pass_t *ppass = &ps->psglx->blur_passes[i]; assert(ppass->prog); assert(tex_scr); glBindTexture(tex_tgt, tex_scr); if (!last_pass) { glBindFramebuffer(GL_FRAMEBUFFER, fbo); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex_scr2, 0); glDrawBuffer(GL_COLOR_ATTACHMENT0); if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { log_error("Framebuffer attachment failed."); goto glx_blur_dst_end; } } else { glBindFramebuffer(GL_FRAMEBUFFER, 0); glDrawBuffer(GL_BACK); if (have_scissors) { glEnable(GL_SCISSOR_TEST); } if (have_stencil) { glEnable(GL_STENCIL_TEST); } } // Color negation for testing... // glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); // glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_REPLACE); // glTexEnvf(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_ONE_MINUS_SRC_COLOR); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); glUseProgram(ppass->prog); if (ppass->unifm_offset_x >= 0) { glUniform1f(ppass->unifm_offset_x, texfac_x); } if (ppass->unifm_offset_y >= 0) { glUniform1f(ppass->unifm_offset_y, texfac_y); } if (ppass->unifm_factor_center >= 0) { glUniform1f(ppass->unifm_factor_center, factor_center); } P_PAINTREG_START(crect) { auto rx = (GLfloat)(crect.x1 - mdx) * texfac_x; auto ry = (GLfloat)(mheight - (crect.y1 - mdy)) * texfac_y; auto rxe = rx + (GLfloat)(crect.x2 - crect.x1) * texfac_x; auto rye = ry - (GLfloat)(crect.y2 - crect.y1) * texfac_y; auto rdx = (GLfloat)(crect.x1 - mdx); auto rdy = (GLfloat)(mheight - crect.y1 + mdy); if (last_pass) { rdx = (GLfloat)crect.x1; rdy = (GLfloat)(ps->root_height - crect.y1); } auto rdxe = rdx + (GLfloat)(crect.x2 - crect.x1); auto rdye = rdy - (GLfloat)(crect.y2 - crect.y1); // log_trace("%f, %f, %f, %f -> %f, %f, %f, %f", rx, ry, // rxe, rye, rdx, // rdy, rdxe, rdye); glTexCoord2f(rx, ry); glVertex3f(rdx, rdy, z); glTexCoord2f(rxe, ry); glVertex3f(rdxe, rdy, z); glTexCoord2f(rxe, rye); glVertex3f(rdxe, rdye, z); glTexCoord2f(rx, rye); glVertex3f(rdx, rdye, z); } P_PAINTREG_END(); glUseProgram(0); // Swap tex_scr and tex_scr2 { GLuint tmp = tex_scr2; tex_scr2 = tex_scr; tex_scr = tmp; } } ret = true; glx_blur_dst_end: glBindFramebuffer(GL_FRAMEBUFFER, 0); glBindTexture(tex_tgt, 0); glDisable(tex_tgt); if (have_scissors) { glEnable(GL_SCISSOR_TEST); } if (have_stencil) { glEnable(GL_STENCIL_TEST); } if (&ibc == pbc) { free_glx_bc(ps, pbc); } gl_check_err(); return ret; } // TODO(bhagwan) this is a mess and needs a more consistent way of getting the border // pixel I tried looking for a notify event for XCB_CW_BORDER_PIXEL (in // xcb_create_window()) or a way to get the pixels from xcb_render_picture_t but the // documentation for the xcb_xrender extension is literally non existent... // // NOTE(yshui) There is no consistent way to get the "border" color of a X window. From // the WM's perspective there are multiple ways to implement window borders. Using // glReadPixel is probably the most reliable way. void glx_read_border_pixel(int root_height, int root_width, int x, int y, int width, int height, float *ppixel) { assert(ppixel); // Reset the color so the shader doesn't use it ppixel[0] = ppixel[1] = ppixel[2] = ppixel[3] = -1.0F; // First try bottom left corner past the // circle radius (after the rounded corner ends) auto screen_x = x; auto screen_y = root_height - height - y; // X is out of bounds // move to the right side if (screen_x < 0) { screen_x += width; } // Y is out of bounds // move to to top part if (screen_y < 0) { screen_y += height; } // All corners are out of bounds, give up if (screen_x < 0 || screen_y < 0 || screen_x >= root_width || screen_y >= root_height) { return; } // Invert Y-axis so we can query border color from texture (0,0) glReadPixels(screen_x, screen_y, 1, 1, GL_RGBA, GL_FLOAT, (void *)ppixel); log_trace("xy(%d, %d), glxy(%d %d) wh(%d %d), border_col(%.2f, %.2f, %.2f, %.2f)", x, y, screen_x, screen_y, width, height, (float)ppixel[0], (float)ppixel[1], (float)ppixel[2], (float)ppixel[3]); gl_check_err(); } bool glx_round_corners_dst(session_t *ps, struct managed_win *w, const glx_texture_t *ptex, int dx, int dy, int width, int height, float z, float cr, const region_t *reg_tgt) { assert(ps->psglx->round_passes->prog); bool ret = false; // log_warn("dxy(%d, %d) wh(%d %d) rwh(%d %d) b(%d), f(%d)", // dx, dy, width, height, ps->root_width, ps->root_height, w->g.border_width, // w->focused); int mdx = dx, mdy = dy, mwidth = width, mheight = height; log_trace("%d, %d, %d, %d", mdx, mdy, mwidth, mheight); if (w->g.border_width > 0) { glx_read_border_pixel(ps->root_height, ps->root_width, dx, dy, width, height, &w->border_col[0]); } { const glx_round_pass_t *ppass = ps->psglx->round_passes; assert(ppass->prog); glEnable(GL_BLEND); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); glUseProgram(ppass->prog); // If caller specified a texture use it as source log_trace("ptex: %p wh(%d %d) %d %d", ptex, ptex->width, ptex->height, ptex->target, ptex->texture); glActiveTexture(GL_TEXTURE0); glBindTexture(ptex->target, ptex->texture); if (ppass->unifm_tex_scr >= 0) { glUniform1i(ppass->unifm_tex_scr, (GLint)0); } if (ppass->unifm_radius >= 0) { glUniform1f(ppass->unifm_radius, cr); } if (ppass->unifm_texcoord >= 0) { glUniform2f(ppass->unifm_texcoord, (float)dx, (float)dy); } if (ppass->unifm_texsize >= 0) { glUniform2f(ppass->unifm_texsize, (float)mwidth, (float)mheight); } if (ppass->unifm_borderw >= 0) { // Don't render rounded border if we don't know the border color glUniform1f(ppass->unifm_borderw, w->border_col[0] != -1. ? (GLfloat)w->g.border_width : 0); } if (ppass->unifm_borderc >= 0) { glUniform4f(ppass->unifm_borderc, w->border_col[0], w->border_col[1], w->border_col[2], w->border_col[3]); } if (ppass->unifm_resolution >= 0) { glUniform2f(ppass->unifm_resolution, (float)ps->root_width, (float)ps->root_height); } // Painting { P_PAINTREG_START(crect) { // texture-local coordinates auto rx = (GLfloat)(crect.x1 - dx); auto ry = (GLfloat)(crect.y1 - dy); auto rxe = rx + (GLfloat)(crect.x2 - crect.x1); auto rye = ry + (GLfloat)(crect.y2 - crect.y1); if (GL_TEXTURE_2D == ptex->target) { rx = rx / (GLfloat)width; ry = ry / (GLfloat)height; rxe = rxe / (GLfloat)width; rye = rye / (GLfloat)height; } // coordinates for the texture in the target auto rdx = (GLfloat)crect.x1; auto rdy = (GLfloat)(ps->root_height - crect.y1); auto rdxe = (GLfloat)rdx + (GLfloat)(crect.x2 - crect.x1); auto rdye = (GLfloat)rdy - (GLfloat)(crect.y2 - crect.y1); // Invert Y if needed, this may not work as expected, // though. I don't have such a FBConfig to test with. ry = 1.0F - ry; rye = 1.0F - rye; // log_trace("Rect %d (i:%d): %f, %f, %f, %f -> %f, %f, // %f, %f", ri ,ptex ? ptex->y_inverted : -1, rx, ry, // rxe, // rye, rdx, rdy, rdxe, rdye); glTexCoord2f(rx, ry); glVertex3f(rdx, rdy, z); glTexCoord2f(rxe, ry); glVertex3f(rdxe, rdy, z); glTexCoord2f(rxe, rye); glVertex3f(rdxe, rdye, z); glTexCoord2f(rx, rye); glVertex3f(rdx, rdye, z); } P_PAINTREG_END(); } glUseProgram(0); glDisable(GL_BLEND); } ret = true; glBindTexture(ptex->target, 0); glDisable(ptex->target); glDisable(GL_BLEND); gl_check_err(); return ret; } bool glx_dim_dst(session_t *ps, int dx, int dy, int width, int height, int z, GLfloat factor, const region_t *reg_tgt) { // It's possible to dim in glx_render(), but it would be over-complicated // considering all those mess in color negation and modulation glEnable(GL_BLEND); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); glColor4f(0.0F, 0.0F, 0.0F, factor); P_PAINTREG_START(crect) { // XXX what does all of these variables mean? GLint rdx = crect.x1; GLint rdy = ps->root_height - crect.y1; GLint rdxe = rdx + (crect.x2 - crect.x1); GLint rdye = rdy - (crect.y2 - crect.y1); glVertex3i(rdx, rdy, z); glVertex3i(rdxe, rdy, z); glVertex3i(rdxe, rdye, z); glVertex3i(rdx, rdye, z); } P_PAINTREG_END(); glColor4f(0.0F, 0.0F, 0.0F, 0.0F); glDisable(GL_BLEND); gl_check_err(); return true; } /** * @brief Render a region with texture data. */ bool glx_render(session_t *ps, const glx_texture_t *ptex, int x, int y, int dx, int dy, int width, int height, int z, double opacity, bool argb, bool neg, const region_t *reg_tgt, const glx_prog_main_t *pprogram) { if (!ptex || !ptex->texture) { log_error("Missing texture."); return false; } const bool has_prog = pprogram && pprogram->prog; bool dual_texture = false; // It's required by legacy versions of OpenGL to enable texture target // before specifying environment. Thanks to madsy for telling me. glEnable(ptex->target); // Enable blending if needed if (opacity < 1.0 || argb) { glEnable(GL_BLEND); // Needed for handling opacity of ARGB texture glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); // This is all weird, but X Render is using premultiplied ARGB format, and // we need to use those things to correct it. Thanks to derhass for help. glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); glColor4d(opacity, opacity, opacity, opacity); } if (!has_prog) { // The default, fixed-function path // Color negation if (neg) { // Simple color negation if (!glIsEnabled(GL_BLEND)) { glEnable(GL_COLOR_LOGIC_OP); glLogicOp(GL_COPY_INVERTED); } // ARGB texture color negation else if (argb) { dual_texture = true; // Use two texture stages because the calculation is too // complicated, thanks to madsy for providing code Texture // stage 0 glActiveTexture(GL_TEXTURE0); // Negation for premultiplied color: color = A - C glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_SUBTRACT); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_TEXTURE); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_ALPHA); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_TEXTURE); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR); // Pass texture alpha through glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_TEXTURE); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA); // Texture stage 1 glActiveTexture(GL_TEXTURE1); glEnable(ptex->target); glBindTexture(ptex->target, ptex->texture); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); // Modulation with constant factor glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_PREVIOUS); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_PRIMARY_COLOR); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_ALPHA); // Modulation with constant factor glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_MODULATE); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_PREVIOUS); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA, GL_PRIMARY_COLOR); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA, GL_SRC_ALPHA); glActiveTexture(GL_TEXTURE0); } // RGB blend color negation else { glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); // Modulation with constant factor glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_TEXTURE); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_ONE_MINUS_SRC_COLOR); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_PRIMARY_COLOR); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR); // Modulation with constant factor glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_MODULATE); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_TEXTURE); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA, GL_PRIMARY_COLOR); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA, GL_SRC_ALPHA); } } } else { // Programmable path assert(pprogram->prog); glUseProgram(pprogram->prog); struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); if (pprogram->unifm_opacity >= 0) { glUniform1f(pprogram->unifm_opacity, (float)opacity); } if (pprogram->unifm_invert_color >= 0) { glUniform1i(pprogram->unifm_invert_color, neg); } if (pprogram->unifm_tex >= 0) { glUniform1i(pprogram->unifm_tex, 0); } if (pprogram->unifm_time >= 0) { glUniform1f(pprogram->unifm_time, (float)ts.tv_sec * 1000.0F + (float)ts.tv_nsec / 1.0e6F); } } // log_trace("Draw: %d, %d, %d, %d -> %d, %d (%d, %d) z %d", x, y, width, height, // dx, dy, ptex->width, ptex->height, z); // Bind texture glBindTexture(ptex->target, ptex->texture); if (dual_texture) { glActiveTexture(GL_TEXTURE1); glBindTexture(ptex->target, ptex->texture); glActiveTexture(GL_TEXTURE0); } // Painting { P_PAINTREG_START(crect) { // texture-local coordinates auto rx = (GLfloat)(crect.x1 - dx + x); auto ry = (GLfloat)(crect.y1 - dy + y); auto rxe = rx + (GLfloat)(crect.x2 - crect.x1); auto rye = ry + (GLfloat)(crect.y2 - crect.y1); // Rectangle textures have [0-w] [0-h] while 2D texture has [0-1] // [0-1] Thanks to amonakov for pointing out! if (GL_TEXTURE_2D == ptex->target) { rx = rx / (GLfloat)ptex->width; ry = ry / (GLfloat)ptex->height; rxe = rxe / (GLfloat)ptex->width; rye = rye / (GLfloat)ptex->height; } // coordinates for the texture in the target GLint rdx = crect.x1; GLint rdy = ps->root_height - crect.y1; GLint rdxe = rdx + (crect.x2 - crect.x1); GLint rdye = rdy - (crect.y2 - crect.y1); // Invert Y if needed, this may not work as expected, though. I // don't have such a FBConfig to test with. if (!ptex->y_inverted) { ry = 1.0F - ry; rye = 1.0F - rye; } // log_trace("Rect %d: %f, %f, %f, %f -> %d, %d, %d, %d", ri, rx, // ry, rxe, rye, // rdx, rdy, rdxe, rdye); #define P_TEXCOORD(cx, cy) \ { \ if (dual_texture) { \ glMultiTexCoord2f(GL_TEXTURE0, cx, cy); \ glMultiTexCoord2f(GL_TEXTURE1, cx, cy); \ } else \ glTexCoord2f(cx, cy); \ } P_TEXCOORD(rx, ry); glVertex3i(rdx, rdy, z); P_TEXCOORD(rxe, ry); glVertex3i(rdxe, rdy, z); P_TEXCOORD(rxe, rye); glVertex3i(rdxe, rdye, z); P_TEXCOORD(rx, rye); glVertex3i(rdx, rdye, z); } P_PAINTREG_END(); } // Cleanup glBindTexture(ptex->target, 0); glColor4f(0.0F, 0.0F, 0.0F, 0.0F); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); glDisable(GL_BLEND); glDisable(GL_COLOR_LOGIC_OP); glDisable(ptex->target); if (dual_texture) { glActiveTexture(GL_TEXTURE1); glBindTexture(ptex->target, 0); glDisable(ptex->target); glActiveTexture(GL_TEXTURE0); } if (has_prog) { glUseProgram(0); } gl_check_err(); return true; }