diff --git a/.circleci/config.yml b/.circleci/config.yml index 5d8af5ec..cb5df4e2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -27,7 +27,7 @@ commands: - ".git" - run: name: config - command: CC=<< parameters.cc >> meson << parameters.build-config >> --werror . build + command: CC=<< parameters.cc >> meson << parameters.build-config >> -Dnew_backends=true --werror . build - run: name: build command: ninja -C build diff --git a/meson_options.txt b/meson_options.txt index 7748df66..66f924ba 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -5,9 +5,11 @@ option('regex', type: 'boolean', value: true, description: 'Enable regex support option('vsync_drm', type: 'boolean', value: false, description: 'Enable support for using drm for vsync') -option('opengl', type: 'boolean', value: true, description: 'Enable features that require opengl (opengl backend, and opengl vsync methods)') +option('opengl', type: 'boolean', value: false, description: 'Enable features that require opengl (opengl backend, and opengl vsync methods)') option('dbus', type: 'boolean', value: true, description: 'Enable suport for D-Bus remote control') option('xrescheck', type: 'boolean', value: false, description: 'Enable X resource leak checker (for debug only)') option('build_docs', type: 'boolean', value: false, description: 'Build documentation and man pages') + +option('new_backends', type: 'boolean', value: false, description: 'Does not really do anything right now') diff --git a/src/backend/backend.c b/src/backend/backend.c new file mode 100644 index 00000000..1b2baa2c --- /dev/null +++ b/src/backend/backend.c @@ -0,0 +1,11 @@ +#include "backend.h" + +backend_info_t *backend_list[NUM_BKEND] = {[BKEND_XRENDER] = &xrender_backend}; + +bool default_is_win_transparent(void *backend_data, win *w, void *win_data) { + return w->mode != WMODE_SOLID; +} + +bool default_is_frame_transparent(void *backend_data, win *w, void *win_data) { + return w->frame_opacity != 1; +} diff --git a/src/backend/backend.h b/src/backend/backend.h new file mode 100644 index 00000000..4d553046 --- /dev/null +++ b/src/backend/backend.h @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) 2018, Yuxuan Shui + +#pragma once +#include "common.h" +#include "region.h" + +typedef struct backend_info { + + // =========== Initialization =========== + + /// Initialize the backend, prepare for rendering to the target window. + /// Here is how you should choose target window: + /// 1) if ps->overlay is not XCB_NONE, use that + /// 2) use ps->root otherwise + /// XXX make the target window a parameter + void *(*init)(session_t *ps) __attribute__((nonnull(1))); + void (*deinit)(void *backend_data, session_t *ps) __attribute__((nonnull(1, 2))); + + /// Called when rendering will be stopped for an unknown amount of + /// time (e.g. screen is unredirected). Free some resources. + void (*pause)(void *backend_data, session_t *ps); + + /// Called before rendering is resumed + void (*resume)(void *backend_data, session_t *ps); + + /// Called when root property changed, returns the new + /// backend_data. Even if the backend_data changed, all + /// the existing win_data returned by prepare_win should + /// remain valid. + /// + /// Optional + void *(*root_change)(void *backend_data, session_t *ps); + + // =========== Rendering ============ + + /// Called before any compose() calls. + /// + /// Usually the backend should clear the buffer, or paint a background + /// on the buffer (usually the wallpaper). + /// + /// Optional? + void (*prepare)(void *backend_data, session_t *ps, const region_t *reg_paint); + + /// Paint the content of the window onto the (possibly buffered) + /// target picture. Always called after render_win(). Maybe called + /// multiple times between render_win() and finish_render_win(). + /// The origin is the top left of the window, exclude the shadow, + /// (dst_x, dst_y) refers to where the origin should be in the target + /// buffer. + void (*compose)(void *backend_data, session_t *ps, win *w, void *win_data, + int dst_x, int dst_y, const region_t *reg_paint); + + /// Blur a given region on of the target. + bool (*blur)(void *backend_data, session_t *ps, double opacity, const region_t *) + __attribute__((nonnull(1, 2, 4))); + + /// Present the buffered target picture onto the screen. If target + /// is not buffered, this should be NULL. + /// + /// Optional + void (*present)(void *backend_data, session_t *ps) __attribute__((nonnull(1, 2))); + + /** + * Render the content of a window into an opaque + * data structure. Dimming, shadow and color inversion is handled + * here. + * + * This function is allowed to allocate additional resource needed + * for rendering. + * + * Params: + * reg_paint = the paint region, meaning painting should only + * be happening within that region. It's in global + * coordinates. If NULL, the region of paint is the + * whole screen. + */ + void (*render_win)(void *backend_data, session_t *ps, win *w, void *win_data, + const region_t *reg_paint); + + /// Free resource allocated for rendering. After this function is + /// called, compose() won't be called before render_win is called + /// another time. + /// + /// Optional + void (*finish_render_win)(void *backend_data, session_t *ps, win *w, + void *win_data); + + // ============ Resource management =========== + + // XXX Thoughts: calling release_win and prepare_win for every config notify + // is wasteful, since there can be multiple such notifies per drawing. + // But if we don't, it can mean there will be a state where is window is + // mapped and visible, but there is no win_data attached to it. We don't + // want to break that assumption. + + /// Create a structure to stored additional data needed for rendering a + /// window, later used for render() and compose(). + /// + /// Backend can assume this function will only be called with visible + /// InputOutput windows, and only be called when screen is redirected. + /// + /// Backend can assume size, shape and visual of the window won't change between + /// prepare_win() and release_win(). + void *(*prepare_win)(void *backend_data, session_t *ps, win *w) + __attribute__((nonnull(1, 2, 3))); + + /// Free resources allocated by prepare() + void (*release_win)(void *backend_data, session_t *ps, win *w, void *win_data) + __attribute__((nonnull(1, 2, 3))); + + // =========== Query =========== + + /// Return if a window has transparent content. Guaranteed to only + /// be called after render_win is called. + bool (*is_win_transparent)(void *backend_data, win *w, void *win_data) + __attribute__((nonnull(1, 2))); + + /// Return if the frame window has transparent content. Guaranteed to + /// only be called after render_win is called. + bool (*is_frame_transparent)(void *backend_data, win *w, void *win_data) + __attribute__((nonnull(1, 2))); +} backend_info_t; + +extern backend_info_t xrender_backend; +extern backend_info_t glx_backend; +extern backend_info_t *backend_list[NUM_BKEND]; + +bool default_is_win_transparent(void *, win *, void *); +bool default_is_frame_transparent(void *, win *, void *); diff --git a/src/backend/backend_common.c b/src/backend/backend_common.c new file mode 100644 index 00000000..f0584d4e --- /dev/null +++ b/src/backend/backend_common.c @@ -0,0 +1,116 @@ +#include + +#include "render.h" +#include "backend_common.h" + +/** + * Generate a 1x1 Picture of a particular color. + */ +xcb_render_picture_t +solid_picture(session_t *ps, bool argb, double a, double r, double g, double b) { + xcb_pixmap_t pixmap; + xcb_render_picture_t picture; + xcb_render_create_picture_value_list_t pa; + xcb_render_color_t col; + xcb_rectangle_t rect; + + pixmap = x_create_pixmap(ps, argb ? 32 : 8, ps->root, 1, 1); + if (!pixmap) + return None; + + pa.repeat = True; + picture = x_create_picture_with_standard_and_pixmap( + ps, argb ? XCB_PICT_STANDARD_ARGB_32 : XCB_PICT_STANDARD_A_8, pixmap, + XCB_RENDER_CP_REPEAT, &pa); + + if (!picture) { + xcb_free_pixmap(ps->c, pixmap); + return None; + } + + col.alpha = a * 0xffff; + col.red = r * 0xffff; + col.green = g * 0xffff; + col.blue = b * 0xffff; + + rect.x = 0; + rect.y = 0; + rect.width = 1; + rect.height = 1; + + xcb_render_fill_rectangles(ps->c, XCB_RENDER_PICT_OP_SRC, picture, col, 1, &rect); + xcb_free_pixmap(ps->c, pixmap); + + return picture; +} + +/** + * Generate shadow Picture for a window. + */ +bool build_shadow(session_t *ps, double opacity, const int width, const int height, + xcb_render_picture_t shadow_pixel, xcb_pixmap_t *pixmap, + xcb_render_picture_t *pict) { + xcb_image_t *shadow_image = NULL; + xcb_pixmap_t shadow_pixmap = None, shadow_pixmap_argb = None; + xcb_render_picture_t shadow_picture = None, shadow_picture_argb = None; + xcb_gcontext_t gc = None; + + shadow_image = make_shadow(ps, opacity, width, height); + if (!shadow_image) { + log_error("Failed to make shadow"); + return false; + } + + shadow_pixmap = + x_create_pixmap(ps, 8, ps->root, shadow_image->width, shadow_image->height); + shadow_pixmap_argb = + x_create_pixmap(ps, 32, ps->root, shadow_image->width, shadow_image->height); + + if (!shadow_pixmap || !shadow_pixmap_argb) { + log_error("Failed to create shadow pixmaps"); + goto shadow_picture_err; + } + + shadow_picture = x_create_picture_with_standard_and_pixmap( + ps, XCB_PICT_STANDARD_A_8, shadow_pixmap, 0, NULL); + shadow_picture_argb = x_create_picture_with_standard_and_pixmap( + ps, XCB_PICT_STANDARD_ARGB_32, shadow_pixmap_argb, 0, NULL); + if (!shadow_picture || !shadow_picture_argb) + goto shadow_picture_err; + + gc = xcb_generate_id(ps->c); + xcb_create_gc(ps->c, gc, shadow_pixmap, 0, NULL); + + xcb_image_put(ps->c, shadow_pixmap, gc, shadow_image, 0, 0, 0); + xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_SRC, shadow_pixel, shadow_picture, + shadow_picture_argb, 0, 0, 0, 0, 0, 0, shadow_image->width, + shadow_image->height); + + *pixmap = shadow_pixmap_argb; + *pict = shadow_picture_argb; + + xcb_free_gc(ps->c, gc); + xcb_image_destroy(shadow_image); + xcb_free_pixmap(ps->c, shadow_pixmap); + xcb_render_free_picture(ps->c, shadow_picture); + + return true; + +shadow_picture_err: + if (shadow_image) + xcb_image_destroy(shadow_image); + if (shadow_pixmap) + xcb_free_pixmap(ps->c, shadow_pixmap); + if (shadow_pixmap_argb) + xcb_free_pixmap(ps->c, shadow_pixmap_argb); + if (shadow_picture) + xcb_render_free_picture(ps->c, shadow_picture); + if (shadow_picture_argb) + xcb_render_free_picture(ps->c, shadow_picture_argb); + if (gc) + xcb_free_gc(ps->c, gc); + + return false; +} + +// vim: set noet sw=8 ts=8 : diff --git a/src/backend/backend_common.h b/src/backend/backend_common.h new file mode 100644 index 00000000..2cd8ea2d --- /dev/null +++ b/src/backend/backend_common.h @@ -0,0 +1,10 @@ +#pragma once +#include +#include "common.h" + +bool build_shadow(session_t *ps, double opacity, const int width, const int height, + xcb_render_picture_t shadow_pixel, xcb_pixmap_t *pixmap, + xcb_render_picture_t *pict); + +xcb_render_picture_t +solid_picture(session_t *ps, bool argb, double a, double r, double g, double b); diff --git a/src/backend/gl/gl_common.c b/src/backend/gl/gl_common.c new file mode 100644 index 00000000..7c37e0be --- /dev/null +++ b/src/backend/gl/gl_common.c @@ -0,0 +1,575 @@ +#include +#include +#include + +#include "common.h" +#include "log.h" + +#include "backend/gl/gl_common.h" + +GLuint gl_create_shader(GLenum shader_type, const char *shader_str) { + log_trace("===\n%s\n===", shader_str); + + bool success = false; + GLuint shader = glCreateShader(shader_type); + if (!shader) { + log_error("Failed to create shader with type %#x.", shader_type); + goto end; + } + glShaderSource(shader, 1, &shader_str, NULL); + glCompileShader(shader); + + // Get shader status + { + GLint status = GL_FALSE; + glGetShaderiv(shader, GL_COMPILE_STATUS, &status); + if (GL_FALSE == status) { + GLint log_len = 0; + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_len); + if (log_len) { + char log[log_len + 1]; + glGetShaderInfoLog(shader, log_len, NULL, log); + log_error("Failed to compile shader with type %d: %s", + shader_type, log); + } + goto end; + } + } + + success = true; + +end: + if (shader && !success) { + glDeleteShader(shader); + shader = 0; + } + + return shader; +} + +GLuint gl_create_program(const GLuint *const shaders, int nshaders) { + bool success = false; + GLuint program = glCreateProgram(); + if (!program) { + log_error("Failed to create program."); + goto end; + } + + for (int i = 0; i < nshaders; ++i) + glAttachShader(program, shaders[i]); + glLinkProgram(program); + + // Get program status + { + GLint status = GL_FALSE; + glGetProgramiv(program, GL_LINK_STATUS, &status); + if (GL_FALSE == status) { + GLint log_len = 0; + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &log_len); + if (log_len) { + char log[log_len + 1]; + glGetProgramInfoLog(program, log_len, NULL, log); + log_error("Failed to link program: %s", log); + } + goto end; + } + } + success = true; + +end: + if (program) { + for (int i = 0; i < nshaders; ++i) + glDetachShader(program, shaders[i]); + } + if (program && !success) { + glDeleteProgram(program); + program = 0; + } + + return program; +} + +/** + * @brief Create a program from vertex and fragment shader strings. + */ +GLuint +gl_create_program_from_str(const char *vert_shader_str, const char *frag_shader_str) { + GLuint vert_shader = 0; + GLuint frag_shader = 0; + GLuint prog = 0; + + if (vert_shader_str) + vert_shader = gl_create_shader(GL_VERTEX_SHADER, vert_shader_str); + if (frag_shader_str) + frag_shader = gl_create_shader(GL_FRAGMENT_SHADER, frag_shader_str); + + { + GLuint shaders[2]; + unsigned int count = 0; + if (vert_shader) + shaders[count++] = vert_shader; + if (frag_shader) + shaders[count++] = frag_shader; + assert(count <= sizeof(shaders) / sizeof(shaders[0])); + if (count) + prog = gl_create_program(shaders, count); + } + + if (vert_shader) + glDeleteShader(vert_shader); + if (frag_shader) + glDeleteShader(frag_shader); + + return prog; +} + +/** + * @brief Get tightly packed RGB888 data from GL front buffer. + * + * Don't expect any sort of decent performance. + * + * @returns tightly packed RGB888 data of the size of the screen, + * to be freed with `free()` + */ +unsigned char *gl_take_screenshot(session_t *ps, int *out_length) { + int length = 3 * ps->root_width * ps->root_height; + GLint unpack_align_old = 0; + glGetIntegerv(GL_UNPACK_ALIGNMENT, &unpack_align_old); + assert(unpack_align_old > 0); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + unsigned char *buf = ccalloc(length, unsigned char); + glReadBuffer(GL_FRONT); + glReadPixels(0, 0, ps->root_width, ps->root_height, GL_RGB, GL_UNSIGNED_BYTE, buf); + glReadBuffer(GL_BACK); + glPixelStorei(GL_UNPACK_ALIGNMENT, unpack_align_old); + if (out_length) + *out_length = sizeof(unsigned char) * length; + return buf; +} + +/** + * @brief Render a region with texture data. + */ +bool gl_compose(const gl_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 gl_win_shader_t *shader) { + if (!ptex || !ptex->texture) { + log_error("Missing texture."); + return false; + } + + // argb = argb || (GLX_TEXTURE_FORMAT_RGBA_EXT == + // ps->psglx->fbconfigs[ptex->depth]->texture_fmt); + 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); + glColor4f(opacity, opacity, opacity, opacity); + } + + // Programmable path + assert(shader->prog); + glUseProgram(shader->prog); + if (shader->unifm_opacity >= 0) + glUniform1f(shader->unifm_opacity, opacity); + if (shader->unifm_invert_color >= 0) + glUniform1i(shader->unifm_invert_color, neg); + if (shader->unifm_tex >= 0) + glUniform1i(shader->unifm_tex, 0); + + // log_trace("Draw: %d, %d, %d, %d -> %d, %d (%d, %d) z %d\n", + // 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) { + // Calculate texture coordinates + GLfloat texture_x1 = (double)(crect.x1 - dx + x); + GLfloat texture_y1 = (double)(crect.y1 - dy + y); + GLfloat texture_x2 = texture_x1 + (double)(crect.x2 - crect.x1); + GLfloat texture_y2 = texture_y1 + (double)(crect.y2 - crect.y1); + + if (GL_TEXTURE_2D == ptex->target) { + // GL_TEXTURE_2D coordinates are 0-1 + texture_x1 /= ptex->width; + texture_y1 /= ptex->height; + texture_x2 /= ptex->width; + texture_y2 /= ptex->height; + } + + // Vertex coordinates + GLint vx1 = crect.x1; + GLint vy1 = crect.y1; + GLint vx2 = crect.x2; + GLint vy2 = crect.y2; + + // X pixmaps might be Y inverted, invert the texture coordinates + if (ptex->y_inverted) { + texture_y1 = 1.0 - texture_y1; + texture_y2 = 1.0 - texture_y2; + } + + // log_trace("Rect %d: %f, %f, %f, %f -> %d, %d, %d, %d", + // ri, rx, ry, rxe, rye, rdx, rdy, rdxe, rdye); + + GLfloat texture_x[] = {texture_x1, texture_x2, texture_x2, texture_x1}; + GLfloat texture_y[] = {texture_y1, texture_y1, texture_y2, texture_y2}; + GLint vx[] = {vx1, vx2, vx2, vx1}; + GLint vy[] = {vy1, vy1, vy2, vy2}; + + for (int i = 0; i < 4; i++) { + glTexCoord2f(texture_x[i], texture_y[i]); + glVertex3i(vx[i], vy[i], 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); + } + + glUseProgram(0); + + gl_check_err(); + + return true; +} + +bool gl_dim_reg(session_t *ps, int dx, int dy, int width, int height, float 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) { + glVertex3i(crect.x1, crect.y1, z); + glVertex3i(crect.x2, crect.y1, z); + glVertex3i(crect.x2, crect.y2, z); + glVertex3i(crect.x1, crect.y2, z); + } + P_PAINTREG_END(); + } + + glEnd(); + + glColor4f(0.0f, 0.0f, 0.0f, 0.0f); + glDisable(GL_BLEND); + + gl_check_err(); + + return true; +} + +static inline int gl_gen_texture(GLenum tex_tgt, int width, int height, GLuint *tex) { + glGenTextures(1, tex); + if (!*tex) + return -1; + 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 0; +} + +/** + * Blur contents in a particular region. + * + * XXX seems to be way to complex for what it does + */ + +// Blur the area sized width x height starting at dx x dy +bool gl_blur_dst(session_t *ps, const gl_cap_t *cap, int dx, int dy, int width, + int height, float z, GLfloat factor_center, const region_t *reg_tgt, + gl_blur_cache_t *pbc, const gl_blur_shader_t *pass, int npasses) { + const bool more_passes = npasses > 1; + + // these should be arguments + const bool have_scissors = glIsEnabled(GL_SCISSOR_TEST); + const bool have_stencil = glIsEnabled(GL_STENCIL_TEST); + bool ret = false; + + // Calculate copy region size + gl_blur_cache_t ibc = {.width = 0, .height = 0}; + if (!pbc) + pbc = &ibc; + + // log_trace("(): %d, %d, %d, %d\n", dx, dy, width, height); + + GLenum tex_tgt = GL_TEXTURE_RECTANGLE; + if (cap->non_power_of_two_texture) + tex_tgt = GL_TEXTURE_2D; + + // Free textures if size inconsistency discovered + if (width != pbc->width || height != pbc->height) { + glDeleteTextures(1, &pbc->textures[0]); + glDeleteTextures(1, &pbc->textures[1]); + pbc->width = pbc->height = 0; + pbc->textures[0] = pbc->textures[1] = 0; + } + + // Generate FBO and textures if needed + if (!pbc->textures[0]) + gl_gen_texture(tex_tgt, width, height, &pbc->textures[0]); + GLuint tex_scr = pbc->textures[0]; + if (npasses > 1 && !pbc->textures[1]) + gl_gen_texture(tex_tgt, width, height, &pbc->textures[1]); + pbc->width = width; + pbc->height = height; + GLuint tex_scr2 = pbc->textures[1]; + if (npasses > 1 && !pbc->fbo) + glGenFramebuffers(1, &pbc->fbo); + const GLuint fbo = pbc->fbo; + + if (!tex_scr || (npasses > 1 && !tex_scr2)) { + log_error("Failed to allocate texture."); + goto end; + } + if (npasses > 1 && !fbo) { + log_error("Failed to allocate framebuffer."); + goto end; + } + + // Read destination pixels into a texture + glEnable(tex_tgt); + glBindTexture(tex_tgt, tex_scr); + + // Copy the area to be blurred into tmp buffer + glCopyTexSubImage2D(tex_tgt, 0, 0, 0, dx, dy, width, height); + + // Texture scaling factor + GLfloat texfac_x = 1.0f, texfac_y = 1.0f; + if (tex_tgt == GL_TEXTURE_2D) { + texfac_x /= width; + texfac_y /= height; + } + + // Paint it back + if (more_passes) { + glDisable(GL_STENCIL_TEST); + glDisable(GL_SCISSOR_TEST); + } + + for (int i = 0; i < npasses; ++i) { + assert(i < MAX_BLUR_PASS - 1); + const gl_blur_shader_t *curr = &pass[i]; + assert(curr->prog); + + assert(tex_scr); + glBindTexture(tex_tgt, tex_scr); + + if (i < npasses - 1) { + // not last pass, draw into framebuffer + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + + // XXX not fixing bug during porting + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, tex_scr2, + 0); // XXX wrong, should use tex_tgt + glDrawBuffers(1, (GLenum[]){GL_COLOR_ATTACHMENT0}); + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != + GL_FRAMEBUFFER_COMPLETE) { + log_error("Framebuffer attachment failed."); + goto end; + } + } else { + // last pass, draw directly into the back buffer + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glDrawBuffers(1, (GLenum[]){GL_BACK}); + if (have_scissors) + glEnable(GL_SCISSOR_TEST); + if (have_stencil) + glEnable(GL_STENCIL_TEST); + } + + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glUseProgram(curr->prog); + if (curr->unifm_offset_x >= 0) + glUniform1f(curr->unifm_offset_x, texfac_x); + if (curr->unifm_offset_y >= 0) + glUniform1f(curr->unifm_offset_y, texfac_y); + if (curr->unifm_factor_center >= 0) + glUniform1f(curr->unifm_factor_center, factor_center); + + // XXX use multiple draw calls is probably going to be slow than + // just simply blur the whole area. + + P_PAINTREG_START(crect) { + // Texture coordinates + const GLfloat texture_x1 = (crect.x1 - dx) * texfac_x; + const GLfloat texture_y1 = (crect.y1 - dy) * texfac_y; + const GLfloat texture_x2 = + texture_x1 + (crect.x2 - crect.x1) * texfac_x; + const GLfloat texture_y2 = + texture_y1 + (crect.y2 - crect.y1) * texfac_y; + + // Vertex coordinates + // For passes before the last one, we are drawing into a buffer, + // so (dx, dy) from source maps to (0, 0) + GLfloat vx1 = crect.x1 - dx; + GLfloat vy1 = crect.y1 - dy; + if (i == npasses - 1) { + // For last pass, we are drawing back to source, so we + // don't need to map + vx1 = crect.x1; + vy1 = crect.y1; + } + GLfloat vx2 = vx1 + (crect.x2 - crect.x1); + GLfloat vy2 = vy1 + (crect.y2 - crect.y1); + + GLfloat texture_x[] = {texture_x1, texture_x2, texture_x2, + texture_x1}; + GLfloat texture_y[] = {texture_y1, texture_y1, texture_y2, + texture_y2}; + GLint vx[] = {vx1, vx2, vx2, vx1}; + GLint vy[] = {vy1, vy1, vy2, vy2}; + + for (int j = 0; j < 4; j++) { + glTexCoord2f(texture_x[j], texture_y[j]); + glVertex3i(vx[j], vy[j], 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; + +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) { + glDeleteTextures(1, &pbc->textures[0]); + glDeleteTextures(1, &pbc->textures[1]); + glDeleteFramebuffers(1, &pbc->fbo); + } + + gl_check_err(); + + return ret; +} + +/** + * Set clipping region on the target window. + */ +void gl_set_clip(const region_t *reg) { + 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, rects[0].y2, rects[0].x2 - rects[0].x1, + rects[0].y2 - rects[0].y1); + } + + gl_check_err(); +} + +static GLint glGetUniformLocationChecked(GLint prog, const char *name) { + GLint ret = glGetUniformLocation(prog, name); + if (ret < 0) + log_error("Failed to get location of uniform '%s'. Might be troublesome.", + name); + return ret; +} + +/** + * Load a GLSL main program from shader strings. + */ +int gl_win_shader_from_string(session_t *ps, const char *vshader_str, + const char *fshader_str, gl_win_shader_t *ret) { + // Build program + ret->prog = gl_create_program_from_str(vshader_str, fshader_str); + if (!ret->prog) { + log_error("Failed to create GLSL program."); + return -1; + } + + // Get uniform addresses + ret->unifm_opacity = glGetUniformLocationChecked(ret->prog, "opacity"); + ret->unifm_invert_color = glGetUniformLocationChecked(ret->prog, "invert_color"); + ret->unifm_tex = glGetUniformLocationChecked(ret->prog, "tex"); + + gl_check_err(); + + return true; +} + +/** + * Callback to run on root window size change. + */ +void gl_resize(int width, int height) { + glViewport(0, 0, width, height); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0, width, 0, height, -1000.0, 1000.0); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); +} + +static void attr_unused gl_destroy_win_shader(session_t *ps, gl_win_shader_t *shader) { + assert(shader); + assert(shader->prog); + glDeleteProgram(shader->prog); + shader->prog = 0; + shader->unifm_opacity = -1; + shader->unifm_invert_color = -1; + shader->unifm_tex = -1; +} diff --git a/src/backend/gl/gl_common.h b/src/backend/gl/gl_common.h new file mode 100644 index 00000000..88628760 --- /dev/null +++ b/src/backend/gl/gl_common.h @@ -0,0 +1,160 @@ +#pragma once +#include +#include + +#include "common.h" + +// Program and uniforms for window shader +typedef struct { + /// GLSL program. + GLuint prog; + /// Location of uniform "opacity" in window GLSL program. + GLint unifm_opacity; + /// Location of uniform "invert_color" in blur GLSL program. + GLint unifm_invert_color; + /// Location of uniform "tex" in window GLSL program. + GLint unifm_tex; +} gl_win_shader_t; + +// Program and uniforms for blur shader +typedef struct { + /// Fragment shader for blur. + GLuint frag_shader; + /// GLSL program for blur. + GLuint prog; + /// Location of uniform "offset_x" in blur GLSL program. + GLint unifm_offset_x; + /// Location of uniform "offset_y" in blur GLSL program. + GLint unifm_offset_y; + /// Location of uniform "factor_center" in blur GLSL program. + GLint unifm_factor_center; +} gl_blur_shader_t; + +/// @brief Wrapper of a binded GLX texture. +typedef struct gl_texture { + GLuint texture; + GLenum target; + unsigned width; + unsigned height; + unsigned depth; + bool y_inverted; +} gl_texture_t; + +// OpenGL capabilities +typedef struct gl_cap { + bool non_power_of_two_texture; +} gl_cap_t; + +typedef struct { + /// Framebuffer used for blurring. + GLuint fbo; + /// Textures used for blurring. + GLuint textures[2]; + /// Width of the textures. + int width; + /// Height of the textures. + int height; +} gl_blur_cache_t; + +#define GL_PROG_MAIN_INIT \ + { .prog = 0, .unifm_opacity = -1, .unifm_invert_color = -1, .unifm_tex = -1, } + +GLuint gl_create_shader(GLenum shader_type, const char *shader_str); +GLuint gl_create_program(const GLuint *const shaders, int nshaders); +GLuint +gl_create_program_from_str(const char *vert_shader_str, const char *frag_shader_str); + +bool gl_load_prog_main(session_t *ps, const char *vshader_str, const char *fshader_str, + gl_win_shader_t *pprogram); + +unsigned char *gl_take_screenshot(session_t *ps, int *out_length); +void gl_resize(int width, int height); + +/** + * Get a textual representation of an OpenGL error. + */ +static inline const char *gl_get_err_str(GLenum err) { + switch (err) { + CASESTRRET(GL_NO_ERROR); + CASESTRRET(GL_INVALID_ENUM); + CASESTRRET(GL_INVALID_VALUE); + CASESTRRET(GL_INVALID_OPERATION); + CASESTRRET(GL_INVALID_FRAMEBUFFER_OPERATION); + CASESTRRET(GL_OUT_OF_MEMORY); + CASESTRRET(GL_STACK_UNDERFLOW); + CASESTRRET(GL_STACK_OVERFLOW); + } + return NULL; +} + +/** + * Check for GLX error. + * + * http://blog.nobel-joergensen.com/2013/01/29/debugging-opengl-using-glgeterror/ + */ +static inline void gl_check_err_(const char *func, int line) { + GLenum err = GL_NO_ERROR; + + while (GL_NO_ERROR != (err = glGetError())) { + const char *errtext = gl_get_err_str(err); + if (errtext) { + log_printf(tls_logger, LOG_LEVEL_ERROR, func, + "GLX error at line %d: %s", line, errtext); + } else { + log_printf(tls_logger, LOG_LEVEL_ERROR, func, + "GLX error at line %d: %d", line, err); + } + } +} + +#define gl_check_err() gl_check_err_(__func__, __LINE__) + +/** + * Check if a GLX extension exists. + */ +static inline bool gl_has_extension(session_t *ps, const char *ext) { + GLint nexts = 0; + glGetIntegerv(GL_NUM_EXTENSIONS, &nexts); + if (!nexts) { + log_error("Failed get GL extension list."); + return false; + } + + for (int i = 0; i < nexts; i++) { + const char *exti = (const char *)glGetStringi(GL_EXTENSIONS, i); + if (strcmp(ext, exti) == 0) + return true; + } + log_info("Missing GL extension %s.", ext); + return false; +} + +static inline void gl_free_blur_shader(gl_blur_shader_t *shader) { + if (shader->prog) + glDeleteShader(shader->prog); + if (shader->frag_shader) + glDeleteShader(shader->frag_shader); + + shader->prog = 0; + shader->frag_shader = 0; +} + +#define P_PAINTREG_START(var) \ + do { \ + region_t reg_new; \ + int nrects; \ + const rect_t *rects; \ + pixman_region32_init_rect(®_new, dx, dy, width, 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); \ + } \ + while (0) diff --git a/src/backend/gl/glx.c b/src/backend/gl/glx.c new file mode 100644 index 00000000..59e565f3 --- /dev/null +++ b/src/backend/gl/glx.c @@ -0,0 +1,882 @@ +// 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 "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, + +}; diff --git a/src/backend/gl/glx.h b/src/backend/gl/glx.h new file mode 100644 index 00000000..2fa2f2f2 --- /dev/null +++ b/src/backend/gl/glx.h @@ -0,0 +1,50 @@ +// 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. + * + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "common.h" + +void +glx_destroy(session_t *ps); + +bool +glx_reinit(session_t *ps, bool need_render); + +void +glx_on_root_change(session_t *ps); + +bool +glx_bind_pixmap(session_t *ps, glx_texture_t **pptex, xcb_pixmap_t pixmap, + unsigned width, unsigned height, unsigned depth); + +void +glx_release_pixmap(session_t *ps, glx_texture_t *ptex); + +void glx_paint_pre(session_t *ps, region_t *preg) +__attribute__((nonnull(1, 2))); + +/** + * Check if a texture is binded, or is binded to the given pixmap. + */ +static inline bool +glx_tex_binded(const glx_texture_t *ptex, xcb_pixmap_t pixmap) { + return ptex && ptex->glpixmap && ptex->texture + && (!pixmap || pixmap == ptex->pixmap); +} + diff --git a/src/backend/meson.build b/src/backend/meson.build new file mode 100644 index 00000000..a8143459 --- /dev/null +++ b/src/backend/meson.build @@ -0,0 +1,12 @@ +# enable xrender + +if get_option('new_backends') + srcs += [ files('xrender.c', 'backend.c', 'backend_common.c') ] + + # enable opengl + if get_option('opengl') + srcs += [ files('gl/gl_common.c') ] + deps += [ dependency('gl', required: true) ] + cflags += [ '-DGL_GLEXT_PROTOTYPES' ] + endif +endif diff --git a/src/backend/xrender.c b/src/backend/xrender.c new file mode 100644 index 00000000..5c388eb2 --- /dev/null +++ b/src/backend/xrender.c @@ -0,0 +1,464 @@ +#include +#include +#include "backend/backend.h" +#include "backend_common.h" +#include "utils.h" +#include "win.h" + +#define auto __auto_type + +typedef struct _xrender_data { + /// The painting target drawable + xcb_drawable_t target_draw; + /// The painting target, it is either the root or the overlay + xcb_render_picture_t target; + /// A buffer of the image to paint + xcb_render_picture_t target_buffer; + /// The original root window content, usually the wallpaper. + /// We save it so we don't loss the wallpaper when we paint over + /// it. + xcb_render_picture_t root_pict; + /// Pictures of pixel of different alpha value, used as a mask to + /// paint transparent images + xcb_render_picture_t alpha_pict[256]; + + // XXX don't know if these are really needed + + /// 1x1 white picture + xcb_render_picture_t white_pixel; + /// 1x1 black picture + xcb_render_picture_t black_pixel; + + /// 1x1 picture of the shadow color + xcb_render_picture_t shadow_pixel; +} xrender_data; + +#if 0 +/** + * Paint root window content. + */ +static void +paint_root(session_t *ps, const region_t *reg_paint) { + if (!ps->root_tile_paint.pixmap) + get_root_tile(ps); + + paint_region(ps, NULL, 0, 0, ps->root_width, ps->root_height, 1.0, reg_paint, + ps->root_tile_paint.pict); +} +#endif + +struct _xrender_win_data { + // Pixmap that the client window draws to, + // it will contain the content of client window. + xcb_pixmap_t pixmap; + // A Picture links to the Pixmap + xcb_render_picture_t pict; + // A buffer used for rendering + xcb_render_picture_t buffer; + // The rendered content of the window (dimmed, inverted + // color, etc.). This is either `buffer` or `pict` + xcb_render_picture_t rendered_pict; + xcb_pixmap_t shadow_pixmap; + xcb_render_picture_t shadow_pict; +}; + +static void compose(void *backend_data, session_t *ps, win *w, void *win_data, int dst_x, + int dst_y, const region_t *reg_paint) { + struct _xrender_data *xd = backend_data; + struct _xrender_win_data *wd = win_data; + bool blend = default_is_frame_transparent(NULL, w, win_data) || + default_is_win_transparent(NULL, w, win_data); + int op = (blend ? XCB_RENDER_PICT_OP_OVER : XCB_RENDER_PICT_OP_SRC); + auto alpha_pict = xd->alpha_pict[(int)(((double)w->opacity / OPAQUE) * 255.0)]; + + // XXX Move shadow drawing into a separate function, + // also do shadow excluding outside of backend + // XXX This is needed to implement full-shadow + if (w->shadow) { + // Put shadow on background + region_t shadow_reg = win_extents_by_val(w); + region_t bshape = win_get_bounding_shape_global_by_val(w); + region_t reg_tmp; + pixman_region32_init(®_tmp); + // Shadow doesn't need to be painted underneath the body of the window + // Because no one can see it + pixman_region32_subtract(®_tmp, &shadow_reg, w->reg_ignore); + + // Mask out the region we don't want shadow on + if (pixman_region32_not_empty(&ps->shadow_exclude_reg)) + pixman_region32_subtract(®_tmp, ®_tmp, + &ps->shadow_exclude_reg); + + // Might be worth while to crop the region to shadow border + pixman_region32_intersect_rect(®_tmp, ®_tmp, w->g.x + w->shadow_dx, + w->g.y + w->shadow_dy, w->shadow_width, + w->shadow_height); + + // Crop the shadow to the damage region. If we draw out side of + // the damage region, we could be drawing over perfectly good + // content, and destroying it. + pixman_region32_intersect(®_tmp, ®_tmp, (region_t *)reg_paint); + +#ifdef CONFIG_XINERAMA + if (ps->o.xinerama_shadow_crop && w->xinerama_scr >= 0 && + w->xinerama_scr < ps->xinerama_nscrs) + // There can be a window where number of screens is updated, + // but the screen number attached to the windows have not. + // + // Window screen number will be updated eventually, so here we + // just check to make sure we don't access out of bounds. + pixman_region32_intersect( + ®_tmp, ®_tmp, &ps->xinerama_scr_regs[w->xinerama_scr]); +#endif + + // Mask out the body of the window from the shadow + // Doing it here instead of in make_shadow() for saving GPU + // power and handling shaped windows (XXX unconfirmed) + pixman_region32_subtract(®_tmp, ®_tmp, &bshape); + pixman_region32_fini(&bshape); + + // Detect if the region is empty before painting + if (pixman_region32_not_empty(®_tmp)) { + x_set_picture_clip_region(ps, xd->target_buffer, 0, 0, ®_tmp); + xcb_render_composite( + ps->c, XCB_RENDER_PICT_OP_OVER, wd->shadow_pict, alpha_pict, + xd->target_buffer, 0, 0, 0, 0, dst_x + w->shadow_dx, + dst_y + w->shadow_dy, w->shadow_width, w->shadow_height); + } + pixman_region32_fini(®_tmp); + pixman_region32_fini(&shadow_reg); + } + + // Clip region of rendered_pict might be set during rendering, clear it to make + // sure we get everything into the buffer + x_clear_picture_clip_region(ps, wd->rendered_pict); + + x_set_picture_clip_region(ps, xd->target_buffer, 0, 0, reg_paint); + xcb_render_composite(ps->c, op, wd->rendered_pict, alpha_pict, xd->target_buffer, + 0, 0, 0, 0, dst_x, dst_y, w->widthb, w->heightb); +} + +/** + * Reset filter on a Picture. + */ +static inline void xrfilter_reset(session_t *ps, xcb_render_picture_t p) { + const char *filter = "Nearest"; + xcb_render_set_picture_filter(ps->c, p, strlen(filter), filter, 0, NULL); +} + +static bool +blur(void *backend_data, session_t *ps, double opacity, const region_t *reg_paint) { + struct _xrender_data *xd = backend_data; + const pixman_box32_t *reg = pixman_region32_extents((region_t *)reg_paint); + const int height = reg->y2 - reg->y1; + const int width = reg->x2 - reg->x1; + + // Create a buffer for storing blurred picture, make it just big enough + // for the blur region + xcb_render_picture_t tmp_picture[2] = { + x_create_picture_with_visual(ps, width, height, ps->vis, 0, NULL), + x_create_picture_with_visual(ps, width, height, ps->vis, 0, NULL)}; + + region_t clip; + pixman_region32_init(&clip); + pixman_region32_copy(&clip, (region_t *)reg_paint); + pixman_region32_translate(&clip, -reg->x1, -reg->y1); + + if (!tmp_picture[0] || !tmp_picture[1]) { + log_error("Failed to build intermediate Picture."); + return false; + } + + x_set_picture_clip_region(ps, tmp_picture[0], 0, 0, &clip); + x_set_picture_clip_region(ps, tmp_picture[1], 0, 0, &clip); + + // The multipass blur implemented here is not correct, but this is what old + // compton did anyway. XXX + xcb_render_picture_t src_pict = xd->target_buffer, dst_pict = tmp_picture[0]; + auto alpha_pict = xd->alpha_pict[(int)(opacity * 255)]; + int current = 0; + int src_x = reg->x1, src_y = reg->y1; + + // For more than 1 pass, we do: + // target_buffer -(pass 1)-> tmp0 -(pass 2)-> tmp1 ... + // -(pass n-1)-> tmp0 or tmp1 -(pass n)-> target_buffer + // For 1 pass, we do + // target_buffer -(pass 1)-> tmp0 -(copy)-> target_buffer + int i; + for (i = 0; ps->o.blur_kerns[i]; i++) { + assert(i < MAX_BLUR_PASS - 1); + xcb_render_fixed_t *convolution_blur = ps->o.blur_kerns[i]; + int kwid = XFIXED_TO_DOUBLE(convolution_blur[0]), + khei = XFIXED_TO_DOUBLE(convolution_blur[1]); + + // Copy from source picture to destination. The filter must + // be applied on source picture, to get the nearby pixels outside the + // window. + xcb_render_set_picture_filter( + ps->c, src_pict, strlen(XRFILTER_CONVOLUTION), XRFILTER_CONVOLUTION, + kwid * khei + 2, convolution_blur); + + if (ps->o.blur_kerns[i + 1] || i == 0) { + // This is not the last pass, or this is the first pass + xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_SRC, src_pict, + XCB_NONE, dst_pict, src_x, src_y, 0, 0, 0, 0, + width, height); + } else { + // This is the last pass, and this is also not the first + xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_OVER, src_pict, + alpha_pict, xd->target_buffer, 0, 0, 0, 0, + reg->x1, reg->y1, width, height); + } + + xrfilter_reset(ps, src_pict); + + src_pict = tmp_picture[current]; + dst_pict = tmp_picture[!current]; + src_x = 0; + src_y = 0; + current = !current; + } + + // There is only 1 pass + if (i == 1) { + xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_OVER, src_pict, alpha_pict, + xd->target_buffer, 0, 0, 0, 0, reg->x1, reg->y1, + width, height); + } + + xcb_render_free_picture(ps->c, tmp_picture[0]); + xcb_render_free_picture(ps->c, tmp_picture[1]); + return true; +} + +static void render_win(void *backend_data, session_t *ps, win *w, void *win_data, + const region_t *reg_paint) { + struct _xrender_data *xd = backend_data; + struct _xrender_win_data *wd = win_data; + xcb_drawable_t draw = wd->pixmap; + if (!draw) + draw = w->id; + + w->pixmap_damaged = false; + + region_t reg_paint_local; + pixman_region32_init(®_paint_local); + pixman_region32_copy(®_paint_local, (region_t *)reg_paint); + pixman_region32_translate(®_paint_local, -w->g.x, -w->g.y); + + if (!w->invert_color && w->frame_opacity == 1 && !w->dim) { + // No extra processing needed + wd->rendered_pict = wd->pict; + return; + } + + // We don't want to modify the content of the original window when we process + // it, so we create a buffer. + if (wd->buffer == XCB_NONE) { + wd->buffer = x_create_picture_with_pictfmt(ps, w->widthb, w->heightb, + w->pictfmt, 0, NULL); + } + + // Copy the content of the window over to the buffer + x_clear_picture_clip_region(ps, wd->buffer); + wd->rendered_pict = wd->buffer; + xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_SRC, wd->pict, None, + wd->rendered_pict, 0, 0, 0, 0, 0, 0, w->widthb, w->heightb); + + if (w->invert_color) { + // Handle invert color + x_set_picture_clip_region(ps, wd->rendered_pict, 0, 0, ®_paint_local); + + xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_DIFFERENCE, + xd->white_pixel, None, wd->rendered_pict, 0, 0, 0, 0, + 0, 0, w->widthb, w->heightb); + // We use an extra PictOpInReverse operation to get correct pixel + // alpha. There could be a better solution. + if (win_has_alpha(w)) + xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_IN_REVERSE, + wd->pict, None, wd->rendered_pict, 0, 0, 0, + 0, 0, 0, w->widthb, w->heightb); + } + + const double dopacity = get_opacity_percent(w); + if (w->frame_opacity != 1) { + // Handle transparent frame + // Step 1: clip paint area to frame + region_t frame_reg; + pixman_region32_init(&frame_reg); + pixman_region32_copy(&frame_reg, &w->bounding_shape); + + region_t body_reg = win_get_region_noframe_local_by_val(w); + pixman_region32_subtract(&frame_reg, &frame_reg, &body_reg); + + // Draw the frame with frame opacity + xcb_render_picture_t alpha_pict = + xd->alpha_pict[(int)(w->frame_opacity * dopacity * 255)]; + x_set_picture_clip_region(ps, wd->rendered_pict, 0, 0, &frame_reg); + + // Step 2: multiply alpha value + // XXX test + xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_SRC, xd->white_pixel, + alpha_pict, wd->rendered_pict, 0, 0, 0, 0, 0, 0, + w->widthb, w->heightb); + } + + if (w->dim) { + // Handle dimming + + double dim_opacity = ps->o.inactive_dim; + if (!ps->o.inactive_dim_fixed) + dim_opacity *= get_opacity_percent(w); + + xcb_render_color_t color = { + .red = 0, .green = 0, .blue = 0, .alpha = 0xffff * dim_opacity}; + + // Dim the actually content of window + xcb_rectangle_t rect = { + .x = 0, + .y = 0, + .width = w->widthb, + .height = w->heightb, + }; + + x_clear_picture_clip_region(ps, wd->rendered_pict); + xcb_render_fill_rectangles(ps->c, XCB_RENDER_PICT_OP_OVER, + wd->rendered_pict, color, 1, &rect); + } +} + +static void *prepare_win(void *backend_data, session_t *ps, win *w) { + auto wd = ccalloc(1, struct _xrender_win_data); + struct _xrender_data *xd = backend_data; + assert(w->a.map_state == XCB_MAP_STATE_VIEWABLE); + if (ps->has_name_pixmap) { + wd->pixmap = xcb_generate_id(ps->c); + xcb_composite_name_window_pixmap_checked(ps->c, w->id, wd->pixmap); + } + + xcb_drawable_t draw = wd->pixmap; + if (!draw) + draw = w->id; + + log_trace("%s %x", w->name, wd->pixmap); + wd->pict = x_create_picture_with_pictfmt_and_pixmap(ps, w->pictfmt, draw, 0, NULL); + wd->buffer = XCB_NONE; + + // XXX delay allocating shadow pict until compose() will dramatical + // improve performance, probably because otherwise shadow pict + // can be created and destroyed multiple times per draw. + // + // However doing that breaks a assumption the backend API makes (i.e. + // either all needed data is here, or none is), therefore we will + // leave this here until we have chance to re-think the backend API + if (w->shadow) { + xcb_pixmap_t pixmap; + build_shadow(ps, 1, w->widthb, w->heightb, xd->shadow_pixel, &pixmap, + &wd->shadow_pict); + xcb_free_pixmap(ps->c, pixmap); + } + return wd; +} + +static void release_win(void *backend_data, session_t *ps, win *w, void *win_data) { + struct _xrender_win_data *wd = win_data; + xcb_free_pixmap(ps->c, wd->pixmap); + // xcb_free_pixmap(ps->c, wd->shadow_pixmap); + xcb_render_free_picture(ps->c, wd->pict); + xcb_render_free_picture(ps->c, wd->shadow_pict); + if (wd->buffer != XCB_NONE) + xcb_render_free_picture(ps->c, wd->buffer); + free(wd); +} + +static void *init(session_t *ps) { + auto xd = ccalloc(1, struct _xrender_data); + + for (int i = 0; i < 256; ++i) { + double o = (double)i / 255.0; + xd->alpha_pict[i] = solid_picture(ps, false, o, 0, 0, 0); + assert(xd->alpha_pict[i] != None); + } + + xd->black_pixel = solid_picture(ps, true, 1, 0, 0, 0); + xd->white_pixel = solid_picture(ps, true, 1, 1, 1, 1); + xd->shadow_pixel = solid_picture(ps, true, 1, ps->o.shadow_red, + ps->o.shadow_green, ps->o.shadow_blue); + + if (ps->overlay != XCB_NONE) { + xd->target = x_create_picture_with_visual_and_pixmap( + ps, ps->vis, ps->overlay, 0, NULL); + } else { + xcb_render_create_picture_value_list_t pa = { + .subwindowmode = XCB_SUBWINDOW_MODE_INCLUDE_INFERIORS, + }; + xd->target = x_create_picture_with_visual_and_pixmap( + ps, ps->vis, ps->root, XCB_RENDER_CP_SUBWINDOW_MODE, &pa); + } + + xd->target_buffer = x_create_picture_with_visual( + ps, ps->root_width, ps->root_height, ps->vis, 0, NULL); + + xcb_pixmap_t root_pixmap = x_get_root_back_pixmap(ps); + if (root_pixmap == XCB_NONE) { + xd->root_pict = solid_picture(ps, false, 1, 0.5, 0.5, 0.5); + } else { + xd->root_pict = x_create_picture_with_visual_and_pixmap( + ps, ps->vis, root_pixmap, 0, NULL); + } + return xd; +} + +static void deinit(void *backend_data, session_t *ps) { + struct _xrender_data *xd = backend_data; + for (int i = 0; i < 256; i++) + xcb_render_free_picture(ps->c, xd->alpha_pict[i]); + xcb_render_free_picture(ps->c, xd->white_pixel); + xcb_render_free_picture(ps->c, xd->black_pixel); + free(xd); +} + +static void *root_change(void *backend_data, session_t *ps) { + deinit(backend_data, ps); + return init(ps); +} + +static void paint_root(void *backend_data, session_t *ps, const region_t *reg_paint) { + struct _xrender_data *xd = backend_data; + + // Limit the paint area + x_set_picture_clip_region(ps, xd->target_buffer, 0, 0, reg_paint); + + xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_SRC, xd->root_pict, XCB_NONE, + xd->target_buffer, 0, 0, 0, 0, 0, 0, ps->root_width, + ps->root_height); +} + +static void present(void *backend_data, session_t *ps) { + struct _xrender_data *xd = backend_data; + + // compose() sets clip region, so clear it first to make + // sure we update the whole screen. + x_clear_picture_clip_region(ps, xd->target_buffer); + + // TODO buffer-age-like optimization might be possible here. + // but that will require a different backend API + xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_SRC, xd->target_buffer, None, + xd->target, 0, 0, 0, 0, 0, 0, ps->root_width, + ps->root_height); +} + +struct backend_info xrender_backend = { + .init = init, + .deinit = deinit, + .blur = blur, + .present = present, + .prepare = paint_root, + .compose = compose, + .root_change = root_change, + .render_win = render_win, + .prepare_win = prepare_win, + .release_win = release_win, + .is_win_transparent = default_is_win_transparent, + .is_frame_transparent = default_is_frame_transparent, +}; diff --git a/src/common.h b/src/common.h index 9a7db4ed..6454f159 100644 --- a/src/common.h +++ b/src/common.h @@ -83,8 +83,6 @@ #ifdef CONFIG_OPENGL // libGL -#define GL_GLEXT_PROTOTYPES - #include // Workarounds for missing definitions in some broken GL drivers, thanks to @@ -423,6 +421,8 @@ typedef struct session { ev_prepare event_check; /// Signal handler for SIGUSR1 ev_signal usr1_signal; + /// backend data + void *backend_data; /// libev mainloop struct ev_loop *loop; // === Display related === diff --git a/src/compton.c b/src/compton.c index 8c4ef72c..071d1ad2 100644 --- a/src/compton.c +++ b/src/compton.c @@ -2536,6 +2536,7 @@ reset_enable(EV_P_ ev_signal *w, int revents) { static session_t * session_init(session_t *ps_old, int argc, char **argv) { static const session_t s_def = { + .backend_data = NULL, .dpy = NULL, .scr = 0, .c = NULL, diff --git a/src/meson.build b/src/meson.build index da5f49bf..7478213f 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,4 +1,4 @@ -deps = [ +base_deps = [ cc.find_library('m'), cc.find_library('ev'), dependency('xcb', version: '>=1.9.2'), @@ -6,10 +6,12 @@ deps = [ srcs = [ files('compton.c', 'win.c', 'c2.c', 'x.c', 'config.c', 'vsync.c', 'utils.c', 'diagnostic.c', 'string_utils.c', 'render.c', 'kernel.c', 'log.c', - 'options.c')] + 'options.c') ] +compton_inc = include_directories('.') cflags = [] + required_package = [ 'x11', 'x11-xcb', 'xcb-renderutil', 'xcb-render', 'xcb-damage', 'xcb-randr', @@ -18,9 +20,11 @@ required_package = [ ] foreach i : required_package - deps += [dependency(i, required: true)] + base_deps += [dependency(i, required: true)] endforeach +deps = [] + if get_option('xinerama') deps += [dependency('xcb-xinerama', required: true)] cflags += ['-DCONFIG_XINERAMA'] @@ -47,7 +51,7 @@ if get_option('vsync_drm') endif if get_option('opengl') - cflags += ['-DCONFIG_OPENGL'] + cflags += ['-DCONFIG_OPENGL', '-DGL_GLEXT_PROTOTYPES'] deps += [dependency('gl', required: true)] srcs += [ 'opengl.c' ] endif @@ -63,4 +67,8 @@ if get_option('xrescheck') srcs += [ 'xrescheck.c' ] endif -executable('compton', srcs, c_args: cflags, dependencies: deps, install: true) +subdir('backend') + +executable('compton', srcs, c_args: cflags, + dependencies: [ base_deps, deps ], + install: true, include_directories: compton_inc) diff --git a/src/win.c b/src/win.c index 5129bafd..4e241966 100644 --- a/src/win.c +++ b/src/win.c @@ -725,6 +725,7 @@ void win_recheck_client(session_t *ps, win *w) { // TODO: probably split into win_new (in win.c) and add_win (in compton.c) bool add_win(session_t *ps, Window id, Window prev) { static const win win_def = { + .win_data = NULL, .next = NULL, .prev_trans = NULL, diff --git a/src/win.h b/src/win.h index cb02da03..c008adea 100644 --- a/src/win.h +++ b/src/win.h @@ -9,7 +9,6 @@ // FIXME shouldn't need this #ifdef CONFIG_OPENGL -#define GL_GLEXT_PROTOTYPES #include #endif @@ -75,6 +74,9 @@ typedef enum { /// Structure representing a top-level window compton manages. typedef struct win win; struct win { + /// backend data attached to this window. Only available when + /// `state` is not UNMAPPED + void *win_data; /// Pointer to the next lower window in window stack. win *next; /// Pointer to the next higher window to paint.