From 0c4b690b2b4ea5374bd9659fc4eb2c141c57b986 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Wed, 3 Oct 2018 22:46:18 +0100 Subject: [PATCH] First step of split backend into modules This commit introduced a new, modular backend interface. The interface is not very good, since I don't think I fully understand all the requirements writing a backend have. But this is a good first step. This commit also includes an initial xrender backend written using the new interface, and some opengl backend related helper functions, which are taken from the old opengl backend. However, there is not integration with the core compton yet. compton will still use the old backend code. This commit is here so we can get the automated build test. What is implemented in the new xrender backend: * Windows with transparency * Shadow * Opacity * Wallpaper (getting the root pixmap) * Blur Known problem with the xrender backend: * It is slower Things that still need to be figured out: * What is the better way to add vsync to the new backends Signed-off-by: Yuxuan Shui --- .circleci/config.yml | 2 +- meson_options.txt | 4 +- src/backend/backend.c | 11 + src/backend/backend.h | 130 ++++++ src/backend/backend_common.c | 116 +++++ src/backend/backend_common.h | 10 + src/backend/gl/gl_common.c | 575 +++++++++++++++++++++++ src/backend/gl/gl_common.h | 160 +++++++ src/backend/gl/glx.c | 882 +++++++++++++++++++++++++++++++++++ src/backend/gl/glx.h | 50 ++ src/backend/meson.build | 12 + src/backend/xrender.c | 464 ++++++++++++++++++ src/common.h | 4 +- src/compton.c | 1 + src/meson.build | 18 +- src/win.c | 1 + src/win.h | 4 +- 17 files changed, 2434 insertions(+), 10 deletions(-) create mode 100644 src/backend/backend.c create mode 100644 src/backend/backend.h create mode 100644 src/backend/backend_common.c create mode 100644 src/backend/backend_common.h create mode 100644 src/backend/gl/gl_common.c create mode 100644 src/backend/gl/gl_common.h create mode 100644 src/backend/gl/glx.c create mode 100644 src/backend/gl/glx.h create mode 100644 src/backend/meson.build create mode 100644 src/backend/xrender.c diff --git a/.circleci/config.yml b/.circleci/config.yml index 5d8af5e..cb5df4e 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 7748df6..66f924b 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 0000000..1b2baa2 --- /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 0000000..4d55304 --- /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 0000000..f0584d4 --- /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 0000000..2cd8ea2 --- /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 0000000..7c37e0b --- /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 0000000..8862876 --- /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 0000000..59e565f --- /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 0000000..2fa2f2f --- /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 0000000..a814345 --- /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 0000000..5c388eb --- /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 9a7db4e..6454f15 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 8c4ef72..071d1ad 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 da5f49b..7478213 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 5129baf..4e24196 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 cb02da0..c008ade 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.