From baeb4572ff013b34b704ab1be772da54579905bb Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Sat, 15 Dec 2018 21:11:41 +0000 Subject: [PATCH] Move rendering related functions out of compton.c Moved: * Blur kernel related functions to kernel.c * Vsync related functions to vsync.c * paint related functions to render.c This will make the `split-backend` branch easier to rebase. Signed-off-by: Yuxuan Shui --- src/common.h | 67 +- src/compton.c | 1492 +------------------------------------------- src/compton.h | 105 +--- src/kernel.c | 121 ++++ src/kernel.h | 19 + src/meson.build | 3 +- src/opengl.h | 64 ++ src/render.c | 1090 ++++++++++++++++++++++++++++++++ src/render.h | 32 + src/string_utils.c | 3 + src/vsync.c | 290 +++++++++ src/vsync.h | 9 + src/win.c | 32 + src/win.h | 26 +- src/x.c | 17 + src/x.h | 3 + 16 files changed, 1726 insertions(+), 1647 deletions(-) create mode 100644 src/kernel.c create mode 100644 src/kernel.h create mode 100644 src/render.c create mode 100644 src/render.h create mode 100644 src/vsync.c create mode 100644 src/vsync.h diff --git a/src/common.h b/src/common.h index 2577e06..60e7139 100644 --- a/src/common.h +++ b/src/common.h @@ -138,6 +138,7 @@ #include "log.h" #include "utils.h" #include "compiler.h" +#include "kernel.h" // === Constants === @@ -354,7 +355,7 @@ typedef struct { GLint unifm_factor_center; } glx_blur_pass_t; -typedef struct { +typedef struct glx_prog_main { /// GLSL program. GLuint prog; /// Location of uniform "opacity" in window GLSL program. @@ -379,11 +380,6 @@ typedef uint32_t glx_prog_main_t; #define PAINT_INIT { .pixmap = None, .pict = None } -typedef struct conv { - int size; - double data[]; -} conv; - /// Linked list type of atoms. typedef struct _latom { Atom atom; @@ -1308,18 +1304,6 @@ bkend_use_glx(session_t *ps) { || BKEND_XR_GLX_HYBRID == ps->o.backend; } -/** - * Check if there's a GLX context. - */ -static inline bool -glx_has_context(session_t *ps) { -#ifdef CONFIG_OPENGL - return ps->psglx && ps->psglx->context; -#else - return false; -#endif -} - /** * Check if a window is really focused. */ @@ -1483,53 +1467,6 @@ vsync_deinit(session_t *ps); */ ///@{ -/** - * Free a GLX texture. - */ -static inline void -free_texture_r(session_t *ps, GLuint *ptexture) { - if (*ptexture) { - assert(glx_has_context(ps)); - glDeleteTextures(1, ptexture); - *ptexture = 0; - } -} - -/** - * Free a GLX Framebuffer object. - */ -static inline void -free_glx_fbo(session_t *ps, GLuint *pfbo) { -#ifdef CONFIG_OPENGL - if (*pfbo) { - glDeleteFramebuffers(1, pfbo); - *pfbo = 0; - } -#endif - assert(!*pfbo); -} - -#ifdef CONFIG_OPENGL -/** - * Free data in glx_blur_cache_t on resize. - */ -static inline void -free_glx_bc_resize(session_t *ps, glx_blur_cache_t *pbc) { - free_texture_r(ps, &pbc->textures[0]); - free_texture_r(ps, &pbc->textures[1]); - pbc->width = 0; - pbc->height = 0; -} - -/** - * Free a glx_blur_cache_t - */ -static inline void -free_glx_bc(session_t *ps, glx_blur_cache_t *pbc) { - free_glx_fbo(ps, &pbc->fbo); - free_glx_bc_resize(ps, pbc); -} -#endif #endif /** diff --git a/src/compton.c b/src/compton.c index b9236ec..176792f 100644 --- a/src/compton.c +++ b/src/compton.c @@ -31,7 +31,10 @@ #include "config.h" #include "diagnostic.h" #include "string_utils.h" +#include "render.h" #include "utils.h" +#include "kernel.h" +#include "vsync.h" #include "log.h" #define auto __auto_type @@ -68,40 +71,6 @@ static void cxinerama_upd_scrs(session_t *ps); #endif -static bool -vsync_drm_init(session_t *ps); - -#ifdef CONFIG_VSYNC_DRM -static int -vsync_drm_wait(session_t *ps); -#endif - -static bool -vsync_opengl_init(session_t *ps); - -static bool -vsync_opengl_oml_init(session_t *ps); - -static bool -vsync_opengl_swc_init(session_t *ps); - -static bool -vsync_opengl_mswc_init(session_t *ps); - -#ifdef CONFIG_OPENGL -static int -vsync_opengl_wait(session_t *ps); - -static int -vsync_opengl_oml_wait(session_t *ps); - -static void -vsync_opengl_swc_deinit(session_t *ps); -#endif - -static void -vsync_wait(session_t *ps); - static void redir_start(session_t *ps); @@ -111,9 +80,6 @@ redir_stop(session_t *ps); static win * recheck_focus(session_t *ps); -static bool -get_root_tile(session_t *ps); - static double get_opacity_percent(win *w); @@ -126,12 +92,6 @@ update_ewmh_active_win(session_t *ps); static void draw_callback(EV_P_ ev_idle *w, int revents); -static void -render(session_t *ps, int x, int y, int dx, int dy, int wid, int hei, - double opacity, bool argb, bool neg, - xcb_render_picture_t pict, glx_texture_t *ptex, - const region_t *reg_paint, const glx_prog_main_t *pprogram); - // === Global constants === /// Name strings for window types. @@ -172,37 +132,9 @@ const char * const BACKEND_STRS[NUM_BKEND + 1] = { NULL }; -/// Function pointers to init VSync modes. -static bool (* const (VSYNC_FUNCS_INIT[NUM_VSYNC]))(session_t *ps) = { - [VSYNC_DRM ] = vsync_drm_init, - [VSYNC_OPENGL ] = vsync_opengl_init, - [VSYNC_OPENGL_OML ] = vsync_opengl_oml_init, - [VSYNC_OPENGL_SWC ] = vsync_opengl_swc_init, - [VSYNC_OPENGL_MSWC ] = vsync_opengl_mswc_init, -}; - -/// Function pointers to wait for VSync. -static int (* const (VSYNC_FUNCS_WAIT[NUM_VSYNC]))(session_t *ps) = { -#ifdef CONFIG_VSYNC_DRM - [VSYNC_DRM ] = vsync_drm_wait, -#endif -#ifdef CONFIG_OPENGL - [VSYNC_OPENGL ] = vsync_opengl_wait, - [VSYNC_OPENGL_OML ] = vsync_opengl_oml_wait, -#endif -}; - -/// Function pointers to deinitialize VSync. -static void (* const (VSYNC_FUNCS_DEINIT[NUM_VSYNC]))(session_t *ps) = { -#ifdef CONFIG_OPENGL - [VSYNC_OPENGL_SWC ] = vsync_opengl_swc_deinit, - [VSYNC_OPENGL_MSWC ] = vsync_opengl_swc_deinit, -#endif -}; - /// Names of root window properties that could point to a pixmap of /// background. -static const char *background_props_str[] = { +const char *background_props_str[] = { "_XROOTPMAP_ID", "_XSETROOT_ID", 0, @@ -259,7 +191,7 @@ free_win_res(session_t *ps, win *w) { */ static inline void free_root_tile(session_t *ps) { - free_picture(ps, &ps->root_tile_paint.pict); + free_picture(ps->c, &ps->root_tile_paint.pict); free_texture(ps, &ps->root_tile_paint.ptex); if (ps->root_tile_fill) { xcb_free_pixmap(ps->c, ps->root_tile_paint.pixmap); @@ -313,24 +245,6 @@ resize_region(session_t *ps, region_t *region, short mod) { free(newrects); } -static inline void -__attribute__((nonnull(1, 2))) -set_tgt_clip(session_t *ps, region_t *reg) { - switch (ps->o.backend) { - case BKEND_XRENDER: - case BKEND_XR_GLX_HYBRID: - x_set_picture_clip_region(ps, ps->tgt_buffer.pict, 0, 0, reg); - break; -#ifdef CONFIG_OPENGL - case BKEND_GLX: - glx_set_clip(ps, reg); - break; -#endif - default: - assert(false); - } -} - /** * Get the Xinerama screen a window is on. * @@ -417,19 +331,6 @@ void queue_redraw(session_t *ps) { ps->redraw_needed = true; } -static inline void -paint_region(session_t *ps, win *w, int x, int y, int wid, int hei, - double opacity, const region_t *reg_paint, xcb_render_picture_t pict) { - const int dx = (w ? w->g.x: 0) + x; - const int dy = (w ? w->g.y: 0) + y; - const bool argb = (w && (win_has_alpha(w) || ps->o.force_win_blend)); - const bool neg = (w && w->invert_color); - - render(ps, x, y, dx, dy, wid, hei, opacity, argb, neg, - pict, (w ? w->paint.ptex: ps->root_tile_paint.ptex), - reg_paint, (w ? &ps->o.glx_prog_win: NULL)); -} - /** * Get a region of the screen size. */ @@ -444,36 +345,6 @@ get_screen_region(session_t *ps, region_t *res) { pixman_region32_init_rects(res, &b, 1); } -/** - * Check if current backend uses XRender for rendering. - */ -static inline bool -bkend_use_xrender(session_t *ps) { - return BKEND_XRENDER == ps->o.backend - || BKEND_XR_GLX_HYBRID == ps->o.backend; -} - -/** - * Check whether a paint_t contains enough data. - */ -static inline bool -paint_isvalid(session_t *ps, const paint_t *ppaint) { - // Don't check for presence of Pixmap here, because older X Composite doesn't - // provide it - if (!ppaint) - return false; - - if (bkend_use_xrender(ps) && !ppaint->pict) - return false; - -#ifdef CONFIG_OPENGL - if (BKEND_GLX == ps->o.backend && !glx_tex_binded(ppaint->ptex, None)) - return false; -#endif - - return true; -} - void add_damage(session_t *ps, const region_t *damage) { // Ignore damage when screen isn't redirected if (!ps->redirected) @@ -532,151 +403,8 @@ run_fade(session_t *ps, win *w, unsigned steps) { } } -/** - * Set fade callback of a window, and possibly execute the previous - * callback. - * - * If a callback can cause rendering result to change, it should call - * `queue_redraw`. - * - * @param exec_callback whether the previous callback is to be executed - */ -void set_fade_callback(session_t *ps, win **_w, - void (*callback) (session_t *ps, win **w), bool exec_callback) { - win *w = *_w; - void (*old_callback) (session_t *ps, win **w) = w->fade_callback; - - w->fade_callback = callback; - // Must be the last line as the callback could destroy w! - if (exec_callback && old_callback) - old_callback(ps, _w); -} - -/** - * Execute fade callback of a window if fading finished. - * XXX should be in win.c - */ -static inline void -check_fade_fin(session_t *ps, win **_w) { - win *w = *_w; - if (w->fade_callback && w->opacity == w->opacity_tgt) { - // Must be the last line as the callback could destroy w! - set_fade_callback(ps, _w, NULL, true); - } -} - // === Shadows === -static double __attribute__((const)) -gaussian(double r, double x, double y) { - // Formula can be found here: - // https://en.wikipedia.org/wiki/Gaussian_blur#Mathematics - // Except a special case for r == 0 to produce sharp shadows - if (r == 0) - return 1; - return exp(-0.5*(x*x+y*y)/(r*r))/(2*M_PI*r*r); -} - -static conv * -make_gaussian_map(double r) { - conv *c; - int size = r*2+1; - int center = size/2; - double t; - - c = cvalloc(sizeof(conv)+size*size*sizeof(double)); - c->size = size; - t = 0.0; - - /*printf_errf("(): %f", r);*/ - for (int y = 0; y < size; y++) { - for (int x = 0; x < size; x++) { - double g = gaussian(r, x-center, y-center); - t += g; - c->data[y*size+x] = g; - /*printf("%f ", c->data[y*size+x]);*/ - } - /*printf("\n");*/ - } - - for (int y = 0; y < size; y++) { - for (int x = 0; x < size; x++) { - c->data[y*size+x] /= t; - /*printf("%f ", c->data[y*size+x]);*/ - } - /*printf("\n");*/ - } - - return c; -} - -/* - * A picture will help - * - * -center 0 width width+center - * -center +-----+-------------------+-----+ - * | | | | - * | | | | - * 0 +-----+-------------------+-----+ - * | | | | - * | | | | - * | | | | - * height +-----+-------------------+-----+ - * | | | | - * height+ | | | | - * center +-----+-------------------+-----+ - */ - -static unsigned char -sum_gaussian(conv *map, double opacity, - int x, int y, int width, int height) { - int fx, fy; - double *g_data; - double *g_line = map->data; - int g_size = map->size; - int center = g_size / 2; - int fx_start, fx_end; - int fy_start, fy_end; - double v; - - /* - * Compute set of filter values which are "in range", - * that's the set with: - * 0 <= x + (fx-center) && x + (fx-center) < width && - * 0 <= y + (fy-center) && y + (fy-center) < height - * - * 0 <= x + (fx - center) x + fx - center < width - * center - x <= fx fx < width + center - x - */ - - fx_start = center - x; - if (fx_start < 0) fx_start = 0; - fx_end = width + center - x; - if (fx_end > g_size) fx_end = g_size; - - fy_start = center - y; - if (fy_start < 0) fy_start = 0; - fy_end = height + center - y; - if (fy_end > g_size) fy_end = g_size; - - g_line = g_line + fy_start * g_size + fx_start; - - v = 0; - - for (fy = fy_start; fy < fy_end; fy++) { - g_data = g_line; - g_line += g_size; - - for (fx = fx_start; fx < fx_end; fx++) { - v += *g_data++; - } - } - - if (v > 1) v = 1; - - return ((unsigned char) (v * opacity * 255.0)); -} - /* precompute shadow corners and sides to save time for large windows */ @@ -696,8 +424,8 @@ presum_gaussian(session_t *ps, conv *map) { ps->shadow_top = cvalloc((ps->cgsize + 1) * 26); for (x = 0; x <= ps->cgsize; x++) { - ps->shadow_top[25 * (ps->cgsize + 1) + x] = - sum_gaussian(map, 1, x - center, center, ps->cgsize * 2, ps->cgsize * 2); + ps->shadow_top[25 * (ps->cgsize + 1) + x] = (unsigned char) + (sum_kernel(map, x - center, center, ps->cgsize * 2, ps->cgsize * 2) * 255.0); for (opacity = 0; opacity < 25; opacity++) { ps->shadow_top[opacity * (ps->cgsize + 1) + x] = @@ -706,7 +434,8 @@ presum_gaussian(session_t *ps, conv *map) { for (y = 0; y <= x; y++) { ps->shadow_corner[25 * (ps->cgsize + 1) * (ps->cgsize + 1) + y * (ps->cgsize + 1) + x] - = sum_gaussian(map, 1, x - center, y - center, ps->cgsize * 2, ps->cgsize * 2); + = (unsigned char) + (sum_kernel(map, x - center, y - center, ps->cgsize * 2, ps->cgsize * 2) * 255.0); ps->shadow_corner[25 * (ps->cgsize + 1) * (ps->cgsize + 1) + x * (ps->cgsize + 1) + y] = ps->shadow_corner[25 * (ps->cgsize + 1) * (ps->cgsize + 1) + y * (ps->cgsize + 1) + x]; @@ -722,189 +451,6 @@ presum_gaussian(session_t *ps, conv *map) { } } -static xcb_image_t * -make_shadow(session_t *ps, double opacity, - int width, int height) { - xcb_image_t *ximage; - int ylimit, xlimit; - int swidth = width + ps->cgsize; - int sheight = height + ps->cgsize; - int center = ps->cgsize / 2; - int x, y; - unsigned char d; - int x_diff; - int opacity_int = (int)(opacity * 25); - - ximage = xcb_image_create_native(ps->c, swidth, sheight, XCB_IMAGE_FORMAT_Z_PIXMAP, 8, - 0, 0, NULL); - - if (!ximage) { - printf_errf("(): failed to create an X image"); - return 0; - } - - unsigned char *data = ximage->data; - uint32_t sstride = ximage->stride; - - /* - * Build the gaussian in sections - */ - - /* - * center (fill the complete data array) - */ - - // XXX If the center part of the shadow would be entirely covered by - // the body of the window, we shouldn't need to fill the center here. - // XXX In general, we want to just fill the part that is not behind - // the window, in order to reduce CPU load and make transparent window - // look correct - if (ps->cgsize > 0) { - d = ps->shadow_top[opacity_int * (ps->cgsize + 1) + ps->cgsize]; - } else { - d = sum_gaussian(ps->gaussian_map, - opacity, center, center, width, height); - } - memset(data, d, sheight * swidth); - - /* - * corners - */ - - ylimit = ps->cgsize; - if (ylimit > sheight / 2) ylimit = (sheight + 1) / 2; - - xlimit = ps->cgsize; - if (xlimit > swidth / 2) xlimit = (swidth + 1) / 2; - - for (y = 0; y < ylimit; y++) { - for (x = 0; x < xlimit; x++) { - if (xlimit == ps->cgsize && ylimit == ps->cgsize) { - d = ps->shadow_corner[opacity_int * (ps->cgsize + 1) * (ps->cgsize + 1) - + y * (ps->cgsize + 1) + x]; - } else { - d = sum_gaussian(ps->gaussian_map, - opacity, x - center, y - center, width, height); - } - data[y * sstride + x] = d; - data[(sheight - y - 1) * sstride + x] = d; - data[(sheight - y - 1) * sstride + (swidth - x - 1)] = d; - data[y * sstride + (swidth - x - 1)] = d; - } - } - - /* - * top/bottom - */ - - x_diff = swidth - (ps->cgsize * 2); - if (x_diff > 0 && ylimit > 0) { - for (y = 0; y < ylimit; y++) { - if (ylimit == ps->cgsize) { - d = ps->shadow_top[opacity_int * (ps->cgsize + 1) + y]; - } else { - d = sum_gaussian(ps->gaussian_map, - opacity, center, y - center, width, height); - } - memset(&data[y * sstride + ps->cgsize], d, x_diff); - memset(&data[(sheight - y - 1) * sstride + ps->cgsize], d, x_diff); - } - } - - /* - * sides - */ - - for (x = 0; x < xlimit; x++) { - if (xlimit == ps->cgsize) { - d = ps->shadow_top[opacity_int * (ps->cgsize + 1) + x]; - } else { - d = sum_gaussian(ps->gaussian_map, - opacity, x - center, center, width, height); - } - for (y = ps->cgsize; y < sheight - ps->cgsize; y++) { - data[y * sstride + x] = d; - data[y * sstride + (swidth - x - 1)] = d; - } - } - - return ximage; -} - -/** - * Generate shadow Picture for a window. - */ -static bool -win_build_shadow(session_t *ps, win *w, double opacity) { - const int width = w->widthb; - const int height = w->heightb; - //printf_errf("(): building shadow for %s %d %d", w->name, width, height); - - 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) { - printf_errf("(): failed to make shadow"); - return None; - } - - 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) { - printf_errf("(): 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, ps->cshadow_picture, shadow_picture, - shadow_picture_argb, 0, 0, 0, 0, 0, 0, - shadow_image->width, shadow_image->height); - - assert(!w->shadow_paint.pixmap); - w->shadow_paint.pixmap = shadow_pixmap_argb; - assert(!w->shadow_paint.pict); - w->shadow_paint.pict = shadow_picture_argb; - - // Sync it once and only once - xr_sync(ps, w->shadow_paint.pixmap, NULL); - - 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; -} /** * Generate a 1x1 Picture of a particular color. @@ -1074,90 +620,6 @@ recheck_focus(session_t *ps) { return NULL; } -static bool -get_root_tile(session_t *ps) { - /* - if (ps->o.paint_on_overlay) { - return ps->root_picture; - } */ - - assert(!ps->root_tile_paint.pixmap); - ps->root_tile_fill = false; - - bool fill = false; - xcb_pixmap_t pixmap = None; - - // Get the values of background attributes - for (int p = 0; background_props_str[p]; p++) { - winprop_t prop = wid_get_prop(ps, ps->root, - get_atom(ps, background_props_str[p]), - 1L, XCB_ATOM_PIXMAP, 32); - if (prop.nitems) { - pixmap = *prop.p32; - fill = false; - free_winprop(&prop); - break; - } - free_winprop(&prop); - } - - // Make sure the pixmap we got is valid - if (pixmap && !validate_pixmap(ps, pixmap)) - pixmap = None; - - // Create a pixmap if there isn't any - if (!pixmap) { - pixmap = x_create_pixmap(ps, ps->depth, ps->root, 1, 1); - if (pixmap == XCB_NONE) { - fprintf(stderr, "Failed to create some pixmap\n"); - exit(1); - } - fill = true; - } - - // Create Picture - xcb_render_create_picture_value_list_t pa = { - .repeat = True, - }; - ps->root_tile_paint.pict = x_create_picture_with_visual_and_pixmap( - ps, ps->vis, pixmap, XCB_RENDER_CP_REPEAT, &pa); - - // Fill pixmap if needed - if (fill) { - xcb_render_color_t col; - xcb_rectangle_t rect; - - col.red = col.green = col.blue = 0x8080; - col.alpha = 0xffff; - - rect.x = rect.y = 0; - rect.width = rect.height = 1; - - xcb_render_fill_rectangles(ps->c, XCB_RENDER_PICT_OP_SRC, ps->root_tile_paint.pict, col, 1, &rect); - } - - ps->root_tile_fill = fill; - ps->root_tile_paint.pixmap = pixmap; -#ifdef CONFIG_OPENGL - if (BKEND_GLX == ps->o.backend) - return glx_bind_pixmap(ps, &ps->root_tile_paint.ptex, ps->root_tile_paint.pixmap, 0, 0, 0); -#endif - - return true; -} - -/** - * 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); -} - /** * Look for the client window of a particular window. */ @@ -1346,7 +808,7 @@ paint_preprocess(session_t *ps, win *list) { w->reg_ignore_valid = true; assert(w->destroyed == (w->fade_callback == finish_destroy_win)); - check_fade_fin(ps, &w); + win_check_fade_finished(ps, &w); // Avoid setting w->to_paint if w is freed if (w) { @@ -1389,91 +851,6 @@ paint_preprocess(session_t *ps, win *list) { return t; } -/** - * Paint the shadow of a window. - */ -static inline void -win_paint_shadow(session_t *ps, win *w, region_t *reg_paint) { - // Bind shadow pixmap to GLX texture if needed - paint_bind_tex(ps, &w->shadow_paint, 0, 0, 32, false); - - if (!paint_isvalid(ps, &w->shadow_paint)) { - printf_errf("(%#010lx): Missing shadow data.", w->id); - return; - } - - render(ps, 0, 0, w->g.x + w->shadow_dx, w->g.y + w->shadow_dy, - w->shadow_width, w->shadow_height, w->shadow_opacity, true, false, - w->shadow_paint.pict, w->shadow_paint.ptex, reg_paint, NULL); -} - -/** - * @brief Blur an area on a buffer. - * - * @param ps current session - * @param tgt_buffer a buffer as both source and destination - * @param x x pos - * @param y y pos - * @param wid width - * @param hei height - * @param blur_kerns blur kernels, ending with a NULL, guaranteed to have at - * least one kernel - * @param reg_clip a clipping region to be applied on intermediate buffers - * - * @return true if successful, false otherwise - */ -static bool -xr_blur_dst(session_t *ps, xcb_render_picture_t tgt_buffer, - int x, int y, int wid, int hei, xcb_render_fixed_t **blur_kerns, - const region_t *reg_clip) { - assert(blur_kerns[0]); - - // Directly copying from tgt_buffer to it does not work, so we create a - // Picture in the middle. - xcb_render_picture_t tmp_picture = x_create_picture(ps, wid, hei, NULL, 0, NULL); - - if (!tmp_picture) { - printf_errf("(): Failed to build intermediate Picture."); - return false; - } - - if (reg_clip && tmp_picture) - x_set_picture_clip_region(ps, tmp_picture, 0, 0, reg_clip); - - xcb_render_picture_t src_pict = tgt_buffer, dst_pict = tmp_picture; - for (int i = 0; blur_kerns[i]; ++i) { - assert(i < MAX_BLUR_PASS - 1); - xcb_render_fixed_t *convolution_blur = blur_kerns[i]; - int kwid = XFIXED_TO_DOUBLE(convolution_blur[0]), - khei = XFIXED_TO_DOUBLE(convolution_blur[1]); - bool rd_from_tgt = (tgt_buffer == src_pict); - - // 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); - xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_SRC, src_pict, None, dst_pict, - (rd_from_tgt ? x: 0), (rd_from_tgt ? y: 0), 0, 0, - (rd_from_tgt ? 0: x), (rd_from_tgt ? 0: y), wid, hei); - xrfilter_reset(ps, src_pict); - - { - xcb_render_picture_t tmp = src_pict; - src_pict = dst_pict; - dst_pict = tmp; - } - } - - if (src_pict != tgt_buffer) - xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_SRC, src_pict, None, tgt_buffer, - 0, 0, 0, 0, x, y, wid, hei); - - free_picture(ps, &tmp_picture); - - return true; -} - /* * WORK-IN-PROGRESS! static void @@ -1488,322 +865,6 @@ xr_take_screenshot(session_t *ps) { } */ -/** - * Blur the background of a window. - */ -static inline void -win_blur_background(session_t *ps, win *w, xcb_render_picture_t tgt_buffer, - const region_t *reg_paint) { - const int x = w->g.x; - const int y = w->g.y; - const int wid = w->widthb; - const int hei = w->heightb; - - double factor_center = 1.0; - // Adjust blur strength according to window opacity, to make it appear - // better during fading - if (!ps->o.blur_background_fixed) { - double pct = 1.0 - get_opacity_percent(w) * (1.0 - 1.0 / 9.0); - factor_center = pct * 8.0 / (1.1 - pct); - } - - switch (ps->o.backend) { - case BKEND_XRENDER: - case BKEND_XR_GLX_HYBRID: - { - // Normalize blur kernels - for (int i = 0; i < MAX_BLUR_PASS; ++i) { - xcb_render_fixed_t *kern_src = ps->o.blur_kerns[i]; - xcb_render_fixed_t *kern_dst = ps->blur_kerns_cache[i]; - assert(i < MAX_BLUR_PASS); - if (!kern_src) { - assert(!kern_dst); - break; - } - - assert(!kern_dst - || (kern_src[0] == kern_dst[0] && kern_src[1] == kern_dst[1])); - - // Skip for fixed factor_center if the cache exists already - if (ps->o.blur_background_fixed && kern_dst) continue; - - int kwid = XFIXED_TO_DOUBLE(kern_src[0]), - khei = XFIXED_TO_DOUBLE(kern_src[1]); - - // Allocate cache space if needed - if (!kern_dst) { - kern_dst = ccalloc(kwid * khei + 2, xcb_render_fixed_t); - if (!kern_dst) { - printf_errf("(): Failed to allocate memory for blur kernel."); - return; - } - ps->blur_kerns_cache[i] = kern_dst; - } - - // Modify the factor of the center pixel - kern_src[2 + (khei / 2) * kwid + kwid / 2] = - DOUBLE_TO_XFIXED(factor_center); - - // Copy over - memcpy(kern_dst, kern_src, (kwid * khei + 2) * sizeof(xcb_render_fixed_t)); - normalize_conv_kern(kwid, khei, kern_dst + 2); - } - - // Minimize the region we try to blur, if the window itself is not - // opaque, only the frame is. - region_t reg_blur = win_get_bounding_shape_global_by_val(w); - if (win_is_solid(ps, w)) { - region_t reg_noframe; - pixman_region32_init(®_noframe); - win_get_region_noframe_local(w, ®_noframe); - pixman_region32_translate(®_noframe, w->g.x, w->g.y); - pixman_region32_subtract(®_blur, ®_blur, ®_noframe); - pixman_region32_fini(®_noframe); - } - // Translate global coordinates to local ones - pixman_region32_translate(®_blur, -x, -y); - xr_blur_dst(ps, tgt_buffer, x, y, wid, hei, ps->blur_kerns_cache, - ®_blur); - pixman_region32_clear(®_blur); - } - break; -#ifdef CONFIG_OPENGL - case BKEND_GLX: - // TODO: Handle frame opacity - glx_blur_dst(ps, x, y, wid, hei, ps->psglx->z - 0.5, factor_center, - reg_paint, &w->glx_blur_cache); - break; -#endif - default: - assert(0); - } -} - -static void -render(session_t *ps, int x, int y, int dx, int dy, int wid, int hei, - double opacity, bool argb, bool neg, - xcb_render_picture_t pict, glx_texture_t *ptex, - const region_t *reg_paint, const glx_prog_main_t *pprogram) -{ - switch (ps->o.backend) { - case BKEND_XRENDER: - case BKEND_XR_GLX_HYBRID: - { - int alpha_step = opacity * MAX_ALPHA; - xcb_render_picture_t alpha_pict = ps->alpha_picts[alpha_step]; - if (alpha_step != 0) { - int op = ((!argb && !alpha_pict) ? XCB_RENDER_PICT_OP_SRC: XCB_RENDER_PICT_OP_OVER); - xcb_render_composite(ps->c, op, pict, alpha_pict, - ps->tgt_buffer.pict, x, y, 0, 0, dx, dy, wid, hei); - } - break; - } -#ifdef CONFIG_OPENGL - case BKEND_GLX: - glx_render(ps, ptex, x, y, dx, dy, wid, hei, - ps->psglx->z, opacity, argb, neg, reg_paint, pprogram); - ps->psglx->z += 1; - break; -#endif - default: - assert(0); - } -} - -/** - * Paint a window itself and dim it if asked. - */ -static inline void -paint_one(session_t *ps, win *w, const region_t *reg_paint) { - glx_mark(ps, w->id, true); - - // Fetch Pixmap - if (!w->paint.pixmap && ps->has_name_pixmap) { - w->paint.pixmap = xcb_generate_id(ps->c); - set_ignore_cookie(ps, - xcb_composite_name_window_pixmap(ps->c, w->id, w->paint.pixmap)); - if (w->paint.pixmap) - free_fence(ps, &w->fence); - } - - Drawable draw = w->paint.pixmap; - if (!draw) - draw = w->id; - - // XRender: Build picture - if (bkend_use_xrender(ps) && !w->paint.pict) { - xcb_render_create_picture_value_list_t pa = { - .subwindowmode = IncludeInferiors, - }; - - w->paint.pict = x_create_picture_with_pictfmt_and_pixmap(ps, w->pictfmt, - draw, XCB_RENDER_CP_SUBWINDOW_MODE, &pa); - } - - if (IsViewable == w->a.map_state) - xr_sync(ps, draw, &w->fence); - - // GLX: Build texture - // Let glx_bind_pixmap() determine pixmap size, because if the user - // is resizing windows, the width and height we get may not be up-to-date, - // causing the jittering issue M4he reported in #7. - if (!paint_bind_tex(ps, &w->paint, 0, 0, 0, - (!ps->o.glx_no_rebind_pixmap && w->pixmap_damaged))) { - printf_errf("(%#010lx): Failed to bind texture. Expect troubles.", w->id); - } - w->pixmap_damaged = false; - - if (!paint_isvalid(ps, &w->paint)) { - printf_errf("(%#010lx): Missing painting data. This is a bad sign.", w->id); - return; - } - - const int x = w->g.x; - const int y = w->g.y; - const int wid = w->widthb; - const int hei = w->heightb; - - xcb_render_picture_t pict = w->paint.pict; - - // Invert window color, if required - if (bkend_use_xrender(ps) && w->invert_color) { - xcb_render_picture_t newpict = x_create_picture(ps, wid, hei, w->pictfmt, 0, NULL); - if (newpict) { - // Apply clipping region to save some CPU - if (reg_paint) { - region_t reg; - pixman_region32_init(®); - pixman_region32_copy(®, (region_t *)reg_paint); - pixman_region32_translate(®, -x, -y); - // FIXME XFixesSetPictureClipRegion(ps->dpy, newpict, 0, 0, reg); - pixman_region32_fini(®); - } - - xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_SRC, pict, None, - newpict, 0, 0, 0, 0, 0, 0, wid, hei); - xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_DIFFERENCE, ps->white_picture, None, - newpict, 0, 0, 0, 0, 0, 0, wid, hei); - // 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, pict, None, - newpict, 0, 0, 0, 0, 0, 0, wid, hei); - pict = newpict; - } - } - - const double dopacity = get_opacity_percent(w); - - if (w->frame_opacity == 1) { - paint_region(ps, w, 0, 0, wid, hei, dopacity, reg_paint, pict); - } else { - // Painting parameters - const margin_t extents = win_calc_frame_extents(w); - const int t = extents.top; - const int l = extents.left; - const int b = extents.bottom; - const int r = extents.right; - -#define COMP_BDR(cx, cy, cwid, chei) \ -paint_region(ps, w, (cx), (cy), (cwid), (chei), w->frame_opacity * dopacity, \ - reg_paint, pict) - - // Sanitize the margins, in case some broken WM makes - // top_width + bottom_width > height in some cases. - - do { - // top - int body_height = hei; - // ctop = checked top - int ctop = min_i(body_height, t); // Make sure top margin is smaller than height - if (ctop > 0) - COMP_BDR(0, 0, wid, ctop); - - body_height -= ctop; - if (body_height <= 0) - break; - - // bottom - // cbot = checked bottom - int cbot = min_i(body_height, b); // Make sure bottom margin is not too large - if (cbot > 0) - COMP_BDR(0, hei-cbot, wid, cbot); - - body_height -= cbot; // Height of window exclude the margin - if (body_height <= 0) - break; - - // left - int body_width = wid; - int cleft = min_i(body_width, l); - if (cleft > 0) - COMP_BDR(0, ctop, cleft, body_height); - - body_width -= cleft; - if (body_width <= 0) - break; - - // right - int cright = min_i(body_width, r); - if (cright > 0) - COMP_BDR(wid - cright, ctop, cright, body_height); - - body_width -= cright; - if (body_width <= 0) - break; - - // body - paint_region(ps, w, cleft, ctop, body_width, body_height, dopacity, reg_paint, pict); - } while (0); - } - -#undef COMP_BDR - - if (pict != w->paint.pict) - free_picture(ps, &pict); - - // Dimming the window if needed - if (w->dim) { - double dim_opacity = ps->o.inactive_dim; - if (!ps->o.inactive_dim_fixed) - dim_opacity *= get_opacity_percent(w); - - switch (ps->o.backend) { - case BKEND_XRENDER: - case BKEND_XR_GLX_HYBRID: - { - unsigned short cval = 0xffff * dim_opacity; - - // Premultiply color - xcb_render_color_t color = { - .red = 0, .green = 0, .blue = 0, .alpha = cval, - }; - - xcb_rectangle_t rect = { - .x = x, - .y = y, - .width = wid, - .height = hei, - }; - - xcb_render_fill_rectangles(ps->c, XCB_RENDER_PICT_OP_OVER, ps->tgt_buffer.pict, - color, 1, &rect); - } - break; -#ifdef CONFIG_OPENGL - case BKEND_GLX: - glx_dim_dst(ps, x, y, wid, hei, ps->psglx->z - 0.7, dim_opacity, - reg_paint); - break; -#endif - default: - assert(false); - } - } - - glx_mark(ps, w->id, false); -} - /** * Rebuild cached screen_reg. */ @@ -1823,267 +884,6 @@ rebuild_shadow_exclude_reg(session_t *ps) { exit(1); } -/// paint all windows -/// region = ?? -/// region_real = the damage region -static void -paint_all(session_t *ps, region_t *region, const region_t *region_real, win * const t) { - if (!region_real) - region_real = region; - -#ifdef DEBUG_REPAINT - static struct timespec last_paint = { 0 }; -#endif - - if (!region) - region_real = region = &ps->screen_reg; - else - // Remove the damaged area out of screen - pixman_region32_intersect(region, region, &ps->screen_reg); - -#ifdef CONFIG_OPENGL - if (bkend_use_glx(ps)) - glx_paint_pre(ps, region); -#endif - - if (!paint_isvalid(ps, &ps->tgt_buffer)) { - if (!ps->tgt_buffer.pixmap) { - free_paint(ps, &ps->tgt_buffer); - ps->tgt_buffer.pixmap = x_create_pixmap(ps, ps->depth, ps->root, ps->root_width, ps->root_height); - if (ps->tgt_buffer.pixmap == XCB_NONE) { - fprintf(stderr, "Failed to allocate a screen-sized pixmap\n"); - exit(1); - } - } - - if (BKEND_GLX != ps->o.backend) - ps->tgt_buffer.pict = x_create_picture_with_visual_and_pixmap( - ps, ps->vis, ps->tgt_buffer.pixmap, 0, 0); - } - - if (BKEND_XRENDER == ps->o.backend) { - x_set_picture_clip_region(ps, ps->tgt_picture, 0, 0, region_real); - } - - region_t reg_tmp, *reg_paint; - pixman_region32_init(®_tmp); - if (t) { - // Calculate the region upon which the root window is to be painted - // based on the ignore region of the lowest window, if available - pixman_region32_subtract(®_tmp, region, t->reg_ignore); - reg_paint = ®_tmp; - } else { - reg_paint = region; - } - - set_tgt_clip(ps, reg_paint); - paint_root(ps, reg_paint); - - // Windows are sorted from bottom to top - // Each window has a reg_ignore, which is the region obscured by all the windows - // on top of that window. This is used to reduce the number of pixels painted. - // - // Whether this is beneficial is to be determined XXX - for (win *w = t; w; w = w->prev_trans) { - region_t bshape = win_get_bounding_shape_global_by_val(w); - // Painting shadow - if (w->shadow) { - // Lazy shadow building - if (!w->shadow_paint.pixmap) - if (!win_build_shadow(ps, w, 1)) - printf_errf("(): build shadow failed"); - - // Shadow doesn't need to be painted underneath the body of the window - // Because no one can see it - pixman_region32_subtract(®_tmp, region, 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); - - // Mask out the body of the window from the shadow if needed - // Doing it here instead of in make_shadow() for saving GPU - // power and handling shaped windows (XXX unconfirmed) - if (!ps->o.wintype_option[w->window_type].full_shadow) - pixman_region32_subtract(®_tmp, ®_tmp, &bshape); - -#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 - - // Detect if the region is empty before painting - if (pixman_region32_not_empty(®_tmp)) { - set_tgt_clip(ps, ®_tmp); - win_paint_shadow(ps, w, ®_tmp); - } - } - - // Calculate the region based on the reg_ignore of the next (higher) - // window and the bounding region - // XXX XXX - pixman_region32_subtract(®_tmp, region, w->reg_ignore); - pixman_region32_intersect(®_tmp, ®_tmp, &bshape); - pixman_region32_fini(&bshape); - - if (pixman_region32_not_empty(®_tmp)) { - set_tgt_clip(ps, ®_tmp); - // Blur window background - if (w->blur_background && (!win_is_solid(ps, w) - || (ps->o.blur_background_frame && w->frame_opacity != 1))) - win_blur_background(ps, w, ps->tgt_buffer.pict, ®_tmp); - - // Painting the window - paint_one(ps, w, ®_tmp); - } - } - - // Free up all temporary regions - pixman_region32_fini(®_tmp); - - // Do this as early as possible - set_tgt_clip(ps, &ps->screen_reg); - - if (ps->o.vsync) { - // Make sure all previous requests are processed to achieve best - // effect - x_sync(ps->c); -#ifdef CONFIG_OPENGL - if (glx_has_context(ps)) { - if (ps->o.vsync_use_glfinish) - glFinish(); - else - glFlush(); - glXWaitX(); - } -#endif - } - - // Wait for VBlank. We could do it aggressively (send the painting - // request and XFlush() on VBlank) or conservatively (send the request - // only on VBlank). - if (!ps->o.vsync_aggressive) - vsync_wait(ps); - - switch (ps->o.backend) { - case BKEND_XRENDER: - if (ps->o.monitor_repaint) { - // Copy the screen content to a new picture, and highlight the paint - // region. This is not very efficient, but since it's for debug only, - // we don't really care - - // First, we clear tgt_buffer.pict's clip region, since we want to copy - // everything - x_set_picture_clip_region(ps, ps->tgt_buffer.pict, 0, 0, &ps->screen_reg); - - // Then we create a new picture, and copy content to it - xcb_render_pictforminfo_t *pictfmt = x_get_pictform_for_visual(ps, ps->vis); - xcb_render_picture_t new_pict = x_create_picture( - ps, ps->root_width, ps->root_height, pictfmt, 0, NULL); - xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_SRC, ps->tgt_buffer.pict, - None, new_pict, 0, 0, 0, 0, 0, 0, ps->root_width, ps->root_height); - - // Next, we set the region of paint and highlight it - x_set_picture_clip_region(ps, new_pict, 0, 0, region_real); - xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_OVER, ps->white_picture, - ps->alpha_picts[MAX_ALPHA / 2], - new_pict, 0, 0, 0, 0, 0, 0, - ps->root_width, ps->root_height); - - // Finally, clear clip region and put the whole thing on screen - x_set_picture_clip_region(ps, new_pict, 0, 0, &ps->screen_reg); - xcb_render_composite( - ps->c, XCB_RENDER_PICT_OP_SRC, new_pict, None, - ps->tgt_picture, 0, 0, 0, 0, - 0, 0, ps->root_width, ps->root_height); - xcb_render_free_picture(ps->c, new_pict); - } else - xcb_render_composite( - ps->c, XCB_RENDER_PICT_OP_SRC, ps->tgt_buffer.pict, None, - ps->tgt_picture, 0, 0, 0, 0, - 0, 0, ps->root_width, ps->root_height); - break; -#ifdef CONFIG_OPENGL - case BKEND_XR_GLX_HYBRID: - x_sync(ps->c); - if (ps->o.vsync_use_glfinish) - glFinish(); - else - glFlush(); - glXWaitX(); - assert(ps->tgt_buffer.pixmap); - xr_sync(ps, ps->tgt_buffer.pixmap, &ps->tgt_buffer_fence); - paint_bind_tex(ps, &ps->tgt_buffer, - ps->root_width, ps->root_height, ps->depth, - !ps->o.glx_no_rebind_pixmap); - // See #163 - xr_sync(ps, ps->tgt_buffer.pixmap, &ps->tgt_buffer_fence); - if (ps->o.vsync_use_glfinish) - glFinish(); - else - glFlush(); - glXWaitX(); - glx_render(ps, ps->tgt_buffer.ptex, 0, 0, 0, 0, - ps->root_width, ps->root_height, 0, 1.0, false, false, - region_real, NULL); - // falls through - case BKEND_GLX: - glXSwapBuffers(ps->dpy, get_tgt_window(ps)); - break; -#endif - default: - assert(0); - } - glx_mark_frame(ps); - - if (ps->o.vsync_aggressive) - vsync_wait(ps); - - XFlush(ps->dpy); - -#ifdef CONFIG_OPENGL - if (glx_has_context(ps)) { - glFlush(); - glXWaitX(); - } -#endif - -#ifdef DEBUG_REPAINT - print_timestamp(ps); - struct timespec now = get_time_timespec(); - struct timespec diff = { 0 }; - timespec_subtract(&diff, &now, &last_paint); - printf("[ %5ld:%09ld ] ", diff.tv_sec, diff.tv_nsec); - last_paint = now; - printf("paint:"); - for (win *w = t; w; w = w->prev_trans) - printf(" %#010lx", w->id); - putchar('\n'); - fflush(stdout); -#endif - - // Check if fading is finished on all painted windows - { - win *pprev = NULL; - for (win *w = t; w; w = pprev) { - pprev = w->prev_trans; - check_fade_fin(ps, &w); - } - } -} - static void repair_win(session_t *ps, win *w) { if (w->a.map_state != XCB_MAP_STATE_VIEWABLE) @@ -2215,7 +1015,7 @@ map_win(session_t *ps, Window id) { // Set fading state w->in_openclose = true; - set_fade_callback(ps, &w, finish_map_win, true); + win_set_fade_callback(ps, &w, finish_map_win, true); win_determine_fade(ps, w); win_determine_blur_background(ps, w); @@ -2275,7 +1075,7 @@ unmap_win(session_t *ps, win **_w) { // Fading out w->flags |= WFLAG_OPCT_CHANGE; - set_fade_callback(ps, _w, finish_unmap_win, false); + win_set_fade_callback(ps, _w, finish_unmap_win, false); w->in_openclose = true; win_determine_fade(ps, w); @@ -2553,7 +1353,7 @@ destroy_win(session_t *ps, Window id) { win_determine_fade(ps, w); // Set fading callback - set_fade_callback(ps, &w, finish_destroy_win, false); + win_set_fade_callback(ps, &w, finish_destroy_win, false); #ifdef CONFIG_DBUS // Send D-Bus signal @@ -4412,258 +3212,6 @@ swopti_handle_timeout(session_t *ps) { return (ps->refresh_intv - offset) / 1e6; } -/** - * Initialize DRM VSync. - * - * @return true for success, false otherwise - */ -static bool -vsync_drm_init(session_t *ps) { -#ifdef CONFIG_VSYNC_DRM - // Should we always open card0? - if (ps->drm_fd < 0 && (ps->drm_fd = open("/dev/dri/card0", O_RDWR)) < 0) { - printf_errf("(): Failed to open device."); - return false; - } - - if (vsync_drm_wait(ps)) - return false; - - return true; -#else - printf_errf("(): Program not compiled with DRM VSync support."); - return false; -#endif -} - -#ifdef CONFIG_VSYNC_DRM -/** - * Wait for next VSync, DRM method. - * - * Stolen from: https://github.com/MythTV/mythtv/blob/master/mythtv/libs/libmythtv/vsync.cpp - */ -static int -vsync_drm_wait(session_t *ps) { - int ret = -1; - drm_wait_vblank_t vbl; - - vbl.request.type = _DRM_VBLANK_RELATIVE, - vbl.request.sequence = 1; - - do { - ret = ioctl(ps->drm_fd, DRM_IOCTL_WAIT_VBLANK, &vbl); - vbl.request.type &= ~_DRM_VBLANK_RELATIVE; - } while (ret && errno == EINTR); - - if (ret) - fprintf(stderr, "vsync_drm_wait(): VBlank ioctl did not work, " - "unimplemented in this drmver?\n"); - - return ret; - -} -#endif - -/** - * Initialize OpenGL VSync. - * - * Stolen from: http://git.tuxfamily.org/?p=ccm/cairocompmgr.git;a=commitdiff;h=efa4ceb97da501e8630ca7f12c99b1dce853c73e - * Possible original source: http://www.inb.uni-luebeck.de/~boehme/xvideo_sync.html - * - * @return true for success, false otherwise - */ -static bool -vsync_opengl_init(session_t *ps) { -#ifdef CONFIG_OPENGL - if (!ensure_glx_context(ps)) - return false; - - if (!glx_hasglxext(ps, "GLX_SGI_video_sync")) { - printf_errf("(): Your driver doesn't support SGI_video_sync, giving up."); - return false; - } - - // Get video sync functions - if (!ps->psglx->glXGetVideoSyncSGI) - ps->psglx->glXGetVideoSyncSGI = (f_GetVideoSync) - glXGetProcAddress((const GLubyte *) "glXGetVideoSyncSGI"); - if (!ps->psglx->glXWaitVideoSyncSGI) - ps->psglx->glXWaitVideoSyncSGI = (f_WaitVideoSync) - glXGetProcAddress((const GLubyte *) "glXWaitVideoSyncSGI"); - if (!ps->psglx->glXWaitVideoSyncSGI || !ps->psglx->glXGetVideoSyncSGI) { - printf_errf("(): Failed to get glXWait/GetVideoSyncSGI function."); - return false; - } - - return true; -#else - printf_errf("(): Program not compiled with OpenGL VSync support."); - return false; -#endif -} - -static bool -vsync_opengl_oml_init(session_t *ps) { -#ifdef CONFIG_OPENGL - if (!ensure_glx_context(ps)) - return false; - - if (!glx_hasglxext(ps, "GLX_OML_sync_control")) { - printf_errf("(): Your driver doesn't support OML_sync_control, giving up."); - return false; - } - - // Get video sync functions - if (!ps->psglx->glXGetSyncValuesOML) - ps->psglx->glXGetSyncValuesOML = (f_GetSyncValuesOML) - glXGetProcAddress ((const GLubyte *) "glXGetSyncValuesOML"); - if (!ps->psglx->glXWaitForMscOML) - ps->psglx->glXWaitForMscOML = (f_WaitForMscOML) - glXGetProcAddress ((const GLubyte *) "glXWaitForMscOML"); - if (!ps->psglx->glXGetSyncValuesOML || !ps->psglx->glXWaitForMscOML) { - printf_errf("(): Failed to get OML_sync_control functions."); - return false; - } - - return true; -#else - printf_errf("(): Program not compiled with OpenGL VSync support."); - return false; -#endif -} - -static bool -vsync_opengl_swc_swap_interval(session_t *ps, unsigned int interval) { -#ifdef CONFIG_OPENGL - if (!ensure_glx_context(ps)) - return false; - - if (!ps->psglx->glXSwapIntervalProc && !ps->psglx->glXSwapIntervalMESAProc) { - if (glx_hasglxext(ps, "GLX_MESA_swap_control")) { - ps->psglx->glXSwapIntervalMESAProc = (f_SwapIntervalMESA) - glXGetProcAddress ((const GLubyte *) "glXSwapIntervalMESA"); - } else if (glx_hasglxext(ps, "GLX_SGI_swap_control")) { - ps->psglx->glXSwapIntervalProc = (f_SwapIntervalSGI) - glXGetProcAddress ((const GLubyte *) "glXSwapIntervalSGI"); - } else { - printf_errf("(): Your driver doesn't support SGI_swap_control nor MESA_swap_control, giving up."); - return false; - } - } - - if (ps->psglx->glXSwapIntervalMESAProc) - ps->psglx->glXSwapIntervalMESAProc(interval); - else if (ps->psglx->glXSwapIntervalProc) - ps->psglx->glXSwapIntervalProc(interval); - else - return false; -#endif - - return true; -} - -static bool -vsync_opengl_swc_init(session_t *ps) { -#ifdef CONFIG_OPENGL - if (!bkend_use_glx(ps)) { - printf_errf("(): OpenGL swap control requires the GLX backend."); - return false; - } - - if (!vsync_opengl_swc_swap_interval(ps, 1)) { - printf_errf("(): Failed to load a swap control extension."); - return false; - } - - return true; -#else - printf_errf("(): Program not compiled with OpenGL VSync support."); - return false; -#endif -} - -static bool -vsync_opengl_mswc_init(session_t *ps) { - printf_errf("(): opengl-mswc is deprecated, please use opengl-swc instead."); - return vsync_opengl_swc_init(ps); -} - -#ifdef CONFIG_OPENGL -/** - * Wait for next VSync, OpenGL method. - */ -static int -vsync_opengl_wait(session_t *ps) { - unsigned vblank_count = 0; - - ps->psglx->glXGetVideoSyncSGI(&vblank_count); - ps->psglx->glXWaitVideoSyncSGI(2, (vblank_count + 1) % 2, &vblank_count); - // I see some code calling glXSwapIntervalSGI(1) afterwards, is it required? - - return 0; -} - -/** - * Wait for next VSync, OpenGL OML method. - * - * https://mail.gnome.org/archives/clutter-list/2012-November/msg00031.html - */ -static int -vsync_opengl_oml_wait(session_t *ps) { - int64_t ust = 0, msc = 0, sbc = 0; - - ps->psglx->glXGetSyncValuesOML(ps->dpy, ps->reg_win, &ust, &msc, &sbc); - ps->psglx->glXWaitForMscOML(ps->dpy, ps->reg_win, 0, 2, (msc + 1) % 2, - &ust, &msc, &sbc); - - return 0; -} - -static void -vsync_opengl_swc_deinit(session_t *ps) { - vsync_opengl_swc_swap_interval(ps, 0); -} -#endif - -/** - * Initialize current VSync method. - */ -bool -vsync_init(session_t *ps) { - // Mesa turns on swap control by default, undo that - if (bkend_use_glx(ps)) - vsync_opengl_swc_swap_interval(ps, 0); - - if (ps->o.vsync && VSYNC_FUNCS_INIT[ps->o.vsync] - && !VSYNC_FUNCS_INIT[ps->o.vsync](ps)) { - ps->o.vsync = VSYNC_NONE; - return false; - } - else - return true; -} - -/** - * Wait for next VSync. - */ -static void -vsync_wait(session_t *ps) { - if (!ps->o.vsync) - return; - - if (VSYNC_FUNCS_WAIT[ps->o.vsync]) - VSYNC_FUNCS_WAIT[ps->o.vsync](ps); -} - -/** - * Deinitialize current VSync method. - */ -void -vsync_deinit(session_t *ps) { - if (ps->o.vsync && VSYNC_FUNCS_DEINIT[ps->o.vsync]) - VSYNC_FUNCS_DEINIT[ps->o.vsync](ps); -} - /** * Pregenerate alpha pictures. */ @@ -5428,7 +3976,7 @@ session_init(session_t *ps_old, int argc, char **argv) { init_atoms(ps); init_alpha_picts(ps); - ps->gaussian_map = make_gaussian_map(ps->o.shadow_radius); + ps->gaussian_map = gaussian_kernel(ps->o.shadow_radius); presum_gaussian(ps, ps->gaussian_map); { @@ -5606,7 +4154,7 @@ session_destroy(session_t *ps) { // Free alpha_picts { for (int i = 0; i <= MAX_ALPHA; ++i) - free_picture(ps, &ps->alpha_picts[i]); + free_picture(ps->c, &ps->alpha_picts[i]); free(ps->alpha_picts); ps->alpha_picts = NULL; } @@ -5650,10 +4198,10 @@ session_destroy(session_t *ps) { if (ps->cshadow_picture == ps->black_picture) ps->cshadow_picture = None; else - free_picture(ps, &ps->cshadow_picture); + free_picture(ps->c, &ps->cshadow_picture); - free_picture(ps, &ps->black_picture); - free_picture(ps, &ps->white_picture); + free_picture(ps->c, &ps->black_picture); + free_picture(ps->c, &ps->white_picture); // Free tgt_{buffer,picture} and root_picture if (ps->tgt_buffer.pict == ps->tgt_picture) @@ -5662,10 +4210,10 @@ session_destroy(session_t *ps) { if (ps->tgt_picture == ps->root_picture) ps->tgt_picture = None; else - free_picture(ps, &ps->tgt_picture); + free_picture(ps->c, &ps->tgt_picture); free_fence(ps, &ps->tgt_buffer_fence); - free_picture(ps, &ps->root_picture); + free_picture(ps->c, &ps->root_picture); free_paint(ps, &ps->tgt_buffer); // Free other X resources diff --git a/src/compton.h b/src/compton.h index 62e68d4..db66bef 100644 --- a/src/compton.h +++ b/src/compton.h @@ -34,6 +34,7 @@ #include "x.h" #include "c2.h" #include "log.h" // XXX clean up +#include "render.h" // == Functions == // TODO move static inline functions that are only used in compton.c, into @@ -53,16 +54,6 @@ win *find_toplevel2(session_t *ps, Window wid); void map_win(session_t *ps, Window id); -/** - * Reset filter on a Picture. - */ -static inline void -xrfilter_reset(session_t *ps, xcb_render_picture_t p) { -#define FILTER "Nearest" - xcb_render_set_picture_filter(ps->c, p, strlen(FILTER), FILTER, 0, NULL); -#undef FILTER -} - /** * Subtract two unsigned long values. * @@ -103,17 +94,6 @@ array_wid_exists(const Window *arr, int count, Window wid) { return false; } -/** - * Destroy a Picture. - */ -inline static void -free_picture(session_t *ps, xcb_render_picture_t *p) { - if (*p) { - xcb_render_free_picture(ps->c, *p); - *p = None; - } -} - /** * Destroy a condition list. */ @@ -123,29 +103,7 @@ free_wincondlst(c2_lptr_t **pcondlst) { continue; } -#ifdef CONFIG_OPENGL -/** - * Bind texture in paint_t if we are using GLX backend. - */ -static inline bool -paint_bind_tex(session_t *ps, paint_t *ppaint, - unsigned wid, unsigned hei, unsigned depth, bool force) -{ - if (!ppaint->pixmap) - return false; - - if (force || !glx_tex_binded(ppaint->ptex, ppaint->pixmap)) - return glx_bind_pixmap(ps, &ppaint->ptex, ppaint->pixmap, wid, hei, depth); - - return true; -} -#else -static inline bool -paint_bind_tex(session_t *ps, paint_t *ppaint, - unsigned wid, unsigned hei, unsigned depth, bool force) -{ - return true; -} +#ifndef CONFIG_OPENGL static inline void free_paint_glx(session_t *ps, paint_t *p) {} static inline void @@ -156,18 +114,6 @@ free_texture(session_t *ps, glx_texture_t **t) { } #endif -/** - * Free paint_t. - */ -static inline void -free_paint(session_t *ps, paint_t *ppaint) { - free_paint_glx(ps, ppaint); - free_picture(ps, &ppaint->pict); - if (ppaint->pixmap) - xcb_free_pixmap(ps->c, ppaint->pixmap); - ppaint->pixmap = XCB_NONE; -} - /** * Create a XTextProperty of a single string. */ @@ -220,59 +166,14 @@ dump_drawable(session_t *ps, Drawable drawable) { } } - -/** - * Validate a pixmap. - * - * Detect whether the pixmap is valid with XGetGeometry. Well, maybe there - * are better ways. - */ -static inline bool -validate_pixmap(session_t *ps, xcb_pixmap_t pxmap) { - if (!pxmap) return false; - - Window rroot = None; - int rx = 0, ry = 0; - unsigned rwid = 0, rhei = 0, rborder = 0, rdepth = 0; - return XGetGeometry(ps->dpy, pxmap, &rroot, &rx, &ry, - &rwid, &rhei, &rborder, &rdepth) && rwid && rhei; -} - /** * Validate pixmap of a window, and destroy pixmap and picture if invalid. */ static inline void win_validate_pixmap(session_t *ps, win *w) { // Destroy pixmap and picture, if invalid - if (!validate_pixmap(ps, w->paint.pixmap)) + if (!x_validate_pixmap(ps, w->paint.pixmap)) free_paint(ps, &w->paint); } -/** - * Normalize a convolution kernel. - */ -static inline void -normalize_conv_kern(int wid, int hei, xcb_render_fixed_t *kern) { - double sum = 0.0; - for (int i = 0; i < wid * hei; ++i) - sum += XFIXED_TO_DOUBLE(kern[i]); - double factor = 1.0 / sum; - for (int i = 0; i < wid * hei; ++i) - kern[i] = DOUBLE_TO_XFIXED(XFIXED_TO_DOUBLE(kern[i]) * factor); -} - -#ifdef CONFIG_OPENGL -/** - * Ensure we have a GLX context. - */ -static inline bool -ensure_glx_context(session_t *ps) { - // Create GLX context - if (!glx_has_context(ps)) - glx_init(ps, false); - - return ps->psglx->context; -} -#endif - // vim: set et sw=2 : diff --git a/src/kernel.c b/src/kernel.c new file mode 100644 index 0000000..5c955ec --- /dev/null +++ b/src/kernel.c @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) Yuxuan Shui + +#include + +#include "utils.h" +#include "kernel.h" + +/* + * A picture will help + * + * -center 0 width width+center + * -center +-----+-------------------+-----+ + * | | | | + * | | | | + * 0 +-----+-------------------+-----+ + * | | | | + * | | | | + * | | | | + * height +-----+-------------------+-----+ + * | | | | + * height+ | | | | + * center +-----+-------------------+-----+ + */ + +double +sum_kernel(conv *map, int x, int y, int width, int height) { + int fx, fy; + double *g_data; + double *g_line = map->data; + int g_size = map->size; + int center = g_size / 2; + int fx_start, fx_end; + int fy_start, fy_end; + double v; + + /* + * Compute set of filter values which are "in range", + * that's the set with: + * 0 <= x + (fx-center) && x + (fx-center) < width && + * 0 <= y + (fy-center) && y + (fy-center) < height + * + * 0 <= x + (fx - center) x + fx - center < width + * center - x <= fx fx < width + center - x + */ + + fx_start = center - x; + if (fx_start < 0) + fx_start = 0; + fx_end = width + center - x; + if (fx_end > g_size) + fx_end = g_size; + + fy_start = center - y; + if (fy_start < 0) + fy_start = 0; + fy_end = height + center - y; + if (fy_end > g_size) + fy_end = g_size; + + g_line = g_line + fy_start * g_size + fx_start; + + v = 0; + + for (fy = fy_start; fy < fy_end; fy++) { + g_data = g_line; + g_line += g_size; + + for (fx = fx_start; fx < fx_end; fx++) { + v += *g_data++; + } + } + + if (v > 1) + v = 1; + + return v; +} + +static double __attribute__((const)) gaussian(double r, double x, double y) { + // Formula can be found here: + // https://en.wikipedia.org/wiki/Gaussian_blur#Mathematics + // Except a special case for r == 0 to produce sharp shadows + if (r == 0) + return 1; + return exp(-0.5 * (x * x + y * y) / (r * r)) / (2 * M_PI * r * r); +} + +conv *gaussian_kernel(double r) { + conv *c; + int size = r * 2 + 1; + int center = size / 2; + double t; + + c = cvalloc(sizeof(conv) + size * size * sizeof(double)); + c->size = size; + t = 0.0; + + /*printf_errf("(): %f", r);*/ + for (int y = 0; y < size; y++) { + for (int x = 0; x < size; x++) { + double g = gaussian(r, x - center, y - center); + t += g; + c->data[y * size + x] = g; + /*printf("%f ", c->data[y*size+x]);*/ + } + /*printf("\n");*/ + } + + for (int y = 0; y < size; y++) { + for (int x = 0; x < size; x++) { + c->data[y * size + x] /= t; + /*printf("%f ", c->data[y*size+x]);*/ + } + /*printf("\n");*/ + } + + return c; +} + +// vim: set noet sw=8 ts=8 : diff --git a/src/kernel.h b/src/kernel.h new file mode 100644 index 0000000..7881a1e --- /dev/null +++ b/src/kernel.h @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) Yuxuan Shui + +#pragma once + +/// Code for generating convolution kernels + +typedef struct conv { + int size; + double data[]; +} conv; + +/// Calculate the sum of a rectangle part of the convolution kernel +/// the rectangle is defined by top left (x, y), and a size (width x height) +double +sum_kernel(conv *map, int x, int y, int width, int height); + +/// Create a kernel with gaussian distribution of radius r +conv *gaussian_kernel(double r); diff --git a/src/meson.build b/src/meson.build index 58f323e..5404cf3 100644 --- a/src/meson.build +++ b/src/meson.build @@ -4,7 +4,8 @@ deps = [ dependency('xcb', version: '>=1.9.2'), ] -srcs = ['compton.c', 'win.c', 'c2.c', 'x.c', 'config.c', 'diagnostic.c', 'string_utils.c'] +srcs = [ files('compton.c', 'win.c', 'c2.c', 'x.c', 'config.c', 'vsync.c', + 'diagnostic.c', 'string_utils.c', 'render.c', 'kernel.c')] cflags = [] diff --git a/src/opengl.h b/src/opengl.h index 326b5e8..e6ad57d 100644 --- a/src/opengl.h +++ b/src/opengl.h @@ -199,6 +199,70 @@ glx_create_program_from_str(const char *vert_shader_str, unsigned char * glx_take_screenshot(session_t *ps, int *out_length); +/** + * Check if there's a GLX context. + */ +static inline bool +glx_has_context(session_t *ps) { + return ps->psglx && ps->psglx->context; +} + +/** + * Ensure we have a GLX context. + */ +static inline bool +ensure_glx_context(session_t *ps) { + // Create GLX context + if (!glx_has_context(ps)) + glx_init(ps, false); + + return ps->psglx->context; +} + +/** + * Free a GLX texture. + */ +static inline void +free_texture_r(session_t *ps, GLuint *ptexture) { + if (*ptexture) { + assert(glx_has_context(ps)); + glDeleteTextures(1, ptexture); + *ptexture = 0; + } +} + +/** + * Free a GLX Framebuffer object. + */ +static inline void +free_glx_fbo(session_t *ps, GLuint *pfbo) { + if (*pfbo) { + glDeleteFramebuffers(1, pfbo); + *pfbo = 0; + } + assert(!*pfbo); +} + +/** + * Free data in glx_blur_cache_t on resize. + */ +static inline void +free_glx_bc_resize(session_t *ps, glx_blur_cache_t *pbc) { + free_texture_r(ps, &pbc->textures[0]); + free_texture_r(ps, &pbc->textures[1]); + pbc->width = 0; + pbc->height = 0; +} + +/** + * Free a glx_blur_cache_t + */ +static inline void +free_glx_bc(session_t *ps, glx_blur_cache_t *pbc) { + free_glx_fbo(ps, &pbc->fbo); + free_glx_bc_resize(ps, pbc); +} + /** * Free a glx_texture_t. */ diff --git a/src/render.c b/src/render.c new file mode 100644 index 0000000..3ff4186 --- /dev/null +++ b/src/render.c @@ -0,0 +1,1090 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) Yuxuan Shui + +#include + +#include "common.h" +#include "opengl.h" +#include "vsync.h" +#include "win.h" + +#include "render.h" + +#ifdef CONFIG_OPENGL +/** + * Bind texture in paint_t if we are using GLX backend. + */ +static inline bool paint_bind_tex(session_t *ps, paint_t *ppaint, unsigned wid, + unsigned hei, unsigned depth, bool force) { + if (!ppaint->pixmap) + return false; + + if (force || !glx_tex_binded(ppaint->ptex, ppaint->pixmap)) + return glx_bind_pixmap(ps, &ppaint->ptex, ppaint->pixmap, wid, hei, + depth); + + return true; +} +#else +static inline bool paint_bind_tex(session_t *ps, paint_t *ppaint, unsigned wid, + unsigned hei, unsigned depth, bool force) { + return true; +} +#endif + +/** + * Check if current backend uses XRender for rendering. + */ +static inline bool bkend_use_xrender(session_t *ps) { + return BKEND_XRENDER == ps->o.backend || BKEND_XR_GLX_HYBRID == ps->o.backend; +} + +/** + * Reset filter on a Picture. + */ +static inline void xrfilter_reset(session_t *ps, xcb_render_picture_t p) { +#define FILTER "Nearest" + xcb_render_set_picture_filter(ps->c, p, strlen(FILTER), FILTER, 0, NULL); +#undef FILTER +} + +static inline void __attribute__((nonnull(1, 2))) +set_tgt_clip(session_t *ps, region_t *reg) { + switch (ps->o.backend) { + case BKEND_XRENDER: + case BKEND_XR_GLX_HYBRID: + x_set_picture_clip_region(ps, ps->tgt_buffer.pict, 0, 0, reg); + break; +#ifdef CONFIG_OPENGL + case BKEND_GLX: glx_set_clip(ps, reg); break; +#endif + default: assert(false); + } +} + +/** + * Destroy a Picture. + */ +void free_picture(xcb_connection_t *c, xcb_render_picture_t *p) { + if (*p) { + xcb_render_free_picture(c, *p); + *p = None; + } +} + +/** + * Free paint_t. + */ +void free_paint(session_t *ps, paint_t *ppaint) { + free_paint_glx(ps, ppaint); + free_picture(ps->c, &ppaint->pict); + if (ppaint->pixmap) + xcb_free_pixmap(ps->c, ppaint->pixmap); + ppaint->pixmap = XCB_NONE; +} + +void render(session_t *ps, int x, int y, int dx, int dy, int wid, int hei, + double opacity, bool argb, bool neg, xcb_render_picture_t pict, + glx_texture_t *ptex, const region_t *reg_paint, + const glx_prog_main_t *pprogram) { + switch (ps->o.backend) { + case BKEND_XRENDER: + case BKEND_XR_GLX_HYBRID: { + int alpha_step = opacity * MAX_ALPHA; + xcb_render_picture_t alpha_pict = ps->alpha_picts[alpha_step]; + if (alpha_step != 0) { + int op = ((!argb && !alpha_pict) ? XCB_RENDER_PICT_OP_SRC + : XCB_RENDER_PICT_OP_OVER); + xcb_render_composite(ps->c, op, pict, alpha_pict, + ps->tgt_buffer.pict, x, y, 0, 0, dx, dy, + wid, hei); + } + break; + } +#ifdef CONFIG_OPENGL + case BKEND_GLX: + glx_render(ps, ptex, x, y, dx, dy, wid, hei, ps->psglx->z, opacity, + argb, neg, reg_paint, pprogram); + ps->psglx->z += 1; + break; +#endif + default: assert(0); + } +} + +static inline void +paint_region(session_t *ps, win *w, int x, int y, int wid, int hei, double opacity, + const region_t *reg_paint, xcb_render_picture_t pict) { + const int dx = (w ? w->g.x : 0) + x; + const int dy = (w ? w->g.y : 0) + y; + const bool argb = (w && (win_has_alpha(w) || ps->o.force_win_blend)); + const bool neg = (w && w->invert_color); + + render(ps, x, y, dx, dy, wid, hei, opacity, argb, neg, pict, + (w ? w->paint.ptex : ps->root_tile_paint.ptex), reg_paint, + (w ? &ps->o.glx_prog_win : NULL)); +} + +/** + * Check whether a paint_t contains enough data. + */ +static inline bool paint_isvalid(session_t *ps, const paint_t *ppaint) { + // Don't check for presence of Pixmap here, because older X Composite doesn't + // provide it + if (!ppaint) + return false; + + if (bkend_use_xrender(ps) && !ppaint->pict) + return false; + +#ifdef CONFIG_OPENGL + if (BKEND_GLX == ps->o.backend && !glx_tex_binded(ppaint->ptex, None)) + return false; +#endif + + return true; +} + +/** + * Paint a window itself and dim it if asked. + */ +void paint_one(session_t *ps, win *w, const region_t *reg_paint) { + glx_mark(ps, w->id, true); + + // Fetch Pixmap + if (!w->paint.pixmap && ps->has_name_pixmap) { + w->paint.pixmap = xcb_generate_id(ps->c); + set_ignore_cookie(ps, xcb_composite_name_window_pixmap( + ps->c, w->id, w->paint.pixmap)); + if (w->paint.pixmap) + free_fence(ps, &w->fence); + } + + Drawable draw = w->paint.pixmap; + if (!draw) + draw = w->id; + + // XRender: Build picture + if (bkend_use_xrender(ps) && !w->paint.pict) { + xcb_render_create_picture_value_list_t pa = { + .subwindowmode = IncludeInferiors, + }; + + w->paint.pict = x_create_picture_with_pictfmt_and_pixmap( + ps, w->pictfmt, draw, XCB_RENDER_CP_SUBWINDOW_MODE, &pa); + } + + if (IsViewable == w->a.map_state) + xr_sync(ps, draw, &w->fence); + + // GLX: Build texture + // Let glx_bind_pixmap() determine pixmap size, because if the user + // is resizing windows, the width and height we get may not be up-to-date, + // causing the jittering issue M4he reported in #7. + if (!paint_bind_tex(ps, &w->paint, 0, 0, 0, + (!ps->o.glx_no_rebind_pixmap && w->pixmap_damaged))) { + printf_errf("(%#010lx): Failed to bind texture. Expect troubles.", + w->id); + } + w->pixmap_damaged = false; + + if (!paint_isvalid(ps, &w->paint)) { + printf_errf("(%#010lx): Missing painting data. This is a bad sign.", + w->id); + return; + } + + const int x = w->g.x; + const int y = w->g.y; + const int wid = w->widthb; + const int hei = w->heightb; + + xcb_render_picture_t pict = w->paint.pict; + + // Invert window color, if required + if (bkend_use_xrender(ps) && w->invert_color) { + xcb_render_picture_t newpict = + x_create_picture(ps, wid, hei, w->pictfmt, 0, NULL); + if (newpict) { + // Apply clipping region to save some CPU + if (reg_paint) { + region_t reg; + pixman_region32_init(®); + pixman_region32_copy(®, (region_t *)reg_paint); + pixman_region32_translate(®, -x, -y); + // FIXME XFixesSetPictureClipRegion(ps->dpy, newpict, 0, 0, reg); + pixman_region32_fini(®); + } + + xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_SRC, pict, + None, newpict, 0, 0, 0, 0, 0, 0, wid, + hei); + xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_DIFFERENCE, + ps->white_picture, None, newpict, 0, 0, + 0, 0, 0, 0, wid, hei); + // 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, pict, None, + newpict, 0, 0, 0, 0, 0, 0, wid, hei); + pict = newpict; + } + } + + const double dopacity = get_opacity_percent(w); + + if (w->frame_opacity == 1) { + paint_region(ps, w, 0, 0, wid, hei, dopacity, reg_paint, pict); + } else { + // Painting parameters + const margin_t extents = win_calc_frame_extents(w); + const int t = extents.top; + const int l = extents.left; + const int b = extents.bottom; + const int r = extents.right; + +#define COMP_BDR(cx, cy, cwid, chei) \ + paint_region(ps, w, (cx), (cy), (cwid), (chei), w->frame_opacity *dopacity, \ + reg_paint, pict) + + // Sanitize the margins, in case some broken WM makes + // top_width + bottom_width > height in some cases. + + do { + // top + int body_height = hei; + // ctop = checked top + int ctop = min_i( + body_height, + t); // Make sure top margin is smaller than height + if (ctop > 0) + COMP_BDR(0, 0, wid, ctop); + + body_height -= ctop; + if (body_height <= 0) + break; + + // bottom + // cbot = checked bottom + int cbot = min_i( + body_height, + b); // Make sure bottom margin is not too large + if (cbot > 0) + COMP_BDR(0, hei - cbot, wid, cbot); + + body_height -= + cbot; // Height of window exclude the margin + if (body_height <= 0) + break; + + // left + int body_width = wid; + int cleft = min_i(body_width, l); + if (cleft > 0) + COMP_BDR(0, ctop, cleft, body_height); + + body_width -= cleft; + if (body_width <= 0) + break; + + // right + int cright = min_i(body_width, r); + if (cright > 0) + COMP_BDR(wid - cright, ctop, cright, body_height); + + body_width -= cright; + if (body_width <= 0) + break; + + // body + paint_region(ps, w, cleft, ctop, body_width, body_height, + dopacity, reg_paint, pict); + } while (0); + } + +#undef COMP_BDR + + if (pict != w->paint.pict) + free_picture(ps->c, &pict); + + // Dimming the window if needed + if (w->dim) { + double dim_opacity = ps->o.inactive_dim; + if (!ps->o.inactive_dim_fixed) + dim_opacity *= get_opacity_percent(w); + + switch (ps->o.backend) { + case BKEND_XRENDER: + case BKEND_XR_GLX_HYBRID: { + unsigned short cval = 0xffff * dim_opacity; + + // Premultiply color + xcb_render_color_t color = { + .red = 0, + .green = 0, + .blue = 0, + .alpha = cval, + }; + + xcb_rectangle_t rect = { + .x = x, + .y = y, + .width = wid, + .height = hei, + }; + + xcb_render_fill_rectangles(ps->c, XCB_RENDER_PICT_OP_OVER, + ps->tgt_buffer.pict, color, 1, + &rect); + } break; +#ifdef CONFIG_OPENGL + case BKEND_GLX: + glx_dim_dst(ps, x, y, wid, hei, ps->psglx->z - 0.7, + dim_opacity, reg_paint); + break; +#endif + default: assert(false); + } + } + + glx_mark(ps, w->id, false); +} + +extern const char *background_props_str[]; + +static bool get_root_tile(session_t *ps) { + /* + if (ps->o.paint_on_overlay) { + return ps->root_picture; + } */ + + assert(!ps->root_tile_paint.pixmap); + ps->root_tile_fill = false; + + bool fill = false; + xcb_pixmap_t pixmap = None; + + // Get the values of background attributes + for (int p = 0; background_props_str[p]; p++) { + winprop_t prop = + wid_get_prop(ps, ps->root, get_atom(ps, background_props_str[p]), + 1L, XCB_ATOM_PIXMAP, 32); + if (prop.nitems) { + pixmap = *prop.p32; + fill = false; + free_winprop(&prop); + break; + } + free_winprop(&prop); + } + + // Make sure the pixmap we got is valid + if (pixmap && !x_validate_pixmap(ps, pixmap)) + pixmap = None; + + // Create a pixmap if there isn't any + if (!pixmap) { + pixmap = x_create_pixmap(ps, ps->depth, ps->root, 1, 1); + if (pixmap == XCB_NONE) { + fprintf(stderr, "Failed to create some pixmap\n"); + exit(1); + } + fill = true; + } + + // Create Picture + xcb_render_create_picture_value_list_t pa = { + .repeat = True, + }; + ps->root_tile_paint.pict = x_create_picture_with_visual_and_pixmap( + ps, ps->vis, pixmap, XCB_RENDER_CP_REPEAT, &pa); + + // Fill pixmap if needed + if (fill) { + xcb_render_color_t col; + xcb_rectangle_t rect; + + col.red = col.green = col.blue = 0x8080; + col.alpha = 0xffff; + + rect.x = rect.y = 0; + rect.width = rect.height = 1; + + xcb_render_fill_rectangles(ps->c, XCB_RENDER_PICT_OP_SRC, + ps->root_tile_paint.pict, col, 1, &rect); + } + + ps->root_tile_fill = fill; + ps->root_tile_paint.pixmap = pixmap; +#ifdef CONFIG_OPENGL + if (BKEND_GLX == ps->o.backend) + return glx_bind_pixmap(ps, &ps->root_tile_paint.ptex, + ps->root_tile_paint.pixmap, 0, 0, 0); +#endif + + return true; +} + +/** + * 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); +} + +static xcb_image_t *make_shadow(session_t *ps, double opacity, int width, int height) { + xcb_image_t *ximage; + int ylimit, xlimit; + int swidth = width + ps->cgsize; + int sheight = height + ps->cgsize; + int center = ps->cgsize / 2; + int x, y; + unsigned char d; + int x_diff; + int opacity_int = (int)(opacity * 25); + + ximage = xcb_image_create_native(ps->c, swidth, sheight, + XCB_IMAGE_FORMAT_Z_PIXMAP, 8, 0, 0, NULL); + + if (!ximage) { + printf_errf("(): failed to create an X image"); + return 0; + } + + unsigned char *data = ximage->data; + uint32_t sstride = ximage->stride; + + /* + * Build the gaussian in sections + */ + + /* + * center (fill the complete data array) + */ + + // XXX If the center part of the shadow would be entirely covered by + // the body of the window, we shouldn't need to fill the center here. + // XXX In general, we want to just fill the part that is not behind + // the window, in order to reduce CPU load and make transparent window + // look correct + if (ps->cgsize > 0) { + d = ps->shadow_top[opacity_int * (ps->cgsize + 1) + ps->cgsize]; + } else { + d = (unsigned char)(sum_kernel(ps->gaussian_map, center, center, + width, height) * + opacity * 255.0); + } + memset(data, d, sheight * swidth); + + /* + * corners + */ + + ylimit = ps->cgsize; + if (ylimit > sheight / 2) + ylimit = (sheight + 1) / 2; + + xlimit = ps->cgsize; + if (xlimit > swidth / 2) + xlimit = (swidth + 1) / 2; + + for (y = 0; y < ylimit; y++) { + for (x = 0; x < xlimit; x++) { + if (xlimit == ps->cgsize && ylimit == ps->cgsize) { + d = ps->shadow_corner[opacity_int * (ps->cgsize + 1) * + (ps->cgsize + 1) + + y * (ps->cgsize + 1) + x]; + } else { + d = (unsigned char)(sum_kernel(ps->gaussian_map, + x - center, y - center, + width, height) * + opacity * 255.0); + } + data[y * sstride + x] = d; + data[(sheight - y - 1) * sstride + x] = d; + data[(sheight - y - 1) * sstride + (swidth - x - 1)] = d; + data[y * sstride + (swidth - x - 1)] = d; + } + } + + /* + * top/bottom + */ + + x_diff = swidth - (ps->cgsize * 2); + if (x_diff > 0 && ylimit > 0) { + for (y = 0; y < ylimit; y++) { + if (ylimit == ps->cgsize) { + d = ps->shadow_top[opacity_int * (ps->cgsize + 1) + y]; + } else { + d = (unsigned char)(sum_kernel(ps->gaussian_map, + center, y - center, + width, height) * + opacity * 255.0); + } + memset(&data[y * sstride + ps->cgsize], d, x_diff); + memset(&data[(sheight - y - 1) * sstride + ps->cgsize], d, + x_diff); + } + } + + /* + * sides + */ + + for (x = 0; x < xlimit; x++) { + if (xlimit == ps->cgsize) { + d = ps->shadow_top[opacity_int * (ps->cgsize + 1) + x]; + } else { + d = (unsigned char)(sum_kernel(ps->gaussian_map, x - center, + center, width, height) * + opacity * 255.0); + } + for (y = ps->cgsize; y < sheight - ps->cgsize; y++) { + data[y * sstride + x] = d; + data[y * sstride + (swidth - x - 1)] = d; + } + } + + return ximage; +} + +/** + * Generate shadow Picture for a window. + */ +static bool win_build_shadow(session_t *ps, win *w, double opacity) { + const int width = w->widthb; + const int height = w->heightb; + // printf_errf("(): building shadow for %s %d %d", w->name, width, height); + + 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) { + printf_errf("(): failed to make shadow"); + return None; + } + + 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) { + printf_errf("(): 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, ps->cshadow_picture, + shadow_picture, shadow_picture_argb, 0, 0, 0, 0, 0, 0, + shadow_image->width, shadow_image->height); + + assert(!w->shadow_paint.pixmap); + w->shadow_paint.pixmap = shadow_pixmap_argb; + assert(!w->shadow_paint.pict); + w->shadow_paint.pict = shadow_picture_argb; + + // Sync it once and only once + xr_sync(ps, w->shadow_paint.pixmap, NULL); + + 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; +} + +/** + * Paint the shadow of a window. + */ +static inline void win_paint_shadow(session_t *ps, win *w, region_t *reg_paint) { + // Bind shadow pixmap to GLX texture if needed + paint_bind_tex(ps, &w->shadow_paint, 0, 0, 32, false); + + if (!paint_isvalid(ps, &w->shadow_paint)) { + printf_errf("(%#010lx): Missing shadow data.", w->id); + return; + } + + render(ps, 0, 0, w->g.x + w->shadow_dx, w->g.y + w->shadow_dy, + w->shadow_width, w->shadow_height, w->shadow_opacity, true, false, + w->shadow_paint.pict, w->shadow_paint.ptex, reg_paint, NULL); +} + +/** + * Normalize a convolution kernel. + */ +static inline void normalize_conv_kern(int wid, int hei, xcb_render_fixed_t *kern) { + double sum = 0.0; + for (int i = 0; i < wid * hei; ++i) + sum += XFIXED_TO_DOUBLE(kern[i]); + double factor = 1.0 / sum; + for (int i = 0; i < wid * hei; ++i) + kern[i] = DOUBLE_TO_XFIXED(XFIXED_TO_DOUBLE(kern[i]) * factor); +} + +/** + * @brief Blur an area on a buffer. + * + * @param ps current session + * @param tgt_buffer a buffer as both source and destination + * @param x x pos + * @param y y pos + * @param wid width + * @param hei height + * @param blur_kerns blur kernels, ending with a NULL, guaranteed to have at + * least one kernel + * @param reg_clip a clipping region to be applied on intermediate buffers + * + * @return true if successful, false otherwise + */ +static bool +xr_blur_dst(session_t *ps, xcb_render_picture_t tgt_buffer, int x, int y, int wid, + int hei, xcb_render_fixed_t **blur_kerns, const region_t *reg_clip) { + assert(blur_kerns[0]); + + // Directly copying from tgt_buffer to it does not work, so we create a + // Picture in the middle. + xcb_render_picture_t tmp_picture = + x_create_picture(ps, wid, hei, NULL, 0, NULL); + + if (!tmp_picture) { + printf_errf("(): Failed to build intermediate Picture."); + return false; + } + + if (reg_clip && tmp_picture) + x_set_picture_clip_region(ps, tmp_picture, 0, 0, reg_clip); + + xcb_render_picture_t src_pict = tgt_buffer, dst_pict = tmp_picture; + for (int i = 0; blur_kerns[i]; ++i) { + assert(i < MAX_BLUR_PASS - 1); + xcb_render_fixed_t *convolution_blur = blur_kerns[i]; + int kwid = XFIXED_TO_DOUBLE(convolution_blur[0]), + khei = XFIXED_TO_DOUBLE(convolution_blur[1]); + bool rd_from_tgt = (tgt_buffer == src_pict); + + // 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); + xcb_render_composite( + ps->c, XCB_RENDER_PICT_OP_SRC, src_pict, None, dst_pict, + (rd_from_tgt ? x : 0), (rd_from_tgt ? y : 0), 0, 0, + (rd_from_tgt ? 0 : x), (rd_from_tgt ? 0 : y), wid, hei); + xrfilter_reset(ps, src_pict); + + { + xcb_render_picture_t tmp = src_pict; + src_pict = dst_pict; + dst_pict = tmp; + } + } + + if (src_pict != tgt_buffer) + xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_SRC, src_pict, None, + tgt_buffer, 0, 0, 0, 0, x, y, wid, hei); + + free_picture(ps->c, &tmp_picture); + + return true; +} + +/** + * Blur the background of a window. + */ +static inline void +win_blur_background(session_t *ps, win *w, xcb_render_picture_t tgt_buffer, + const region_t *reg_paint) { + const int x = w->g.x; + const int y = w->g.y; + const int wid = w->widthb; + const int hei = w->heightb; + + double factor_center = 1.0; + // Adjust blur strength according to window opacity, to make it appear + // better during fading + if (!ps->o.blur_background_fixed) { + double pct = 1.0 - get_opacity_percent(w) * (1.0 - 1.0 / 9.0); + factor_center = pct * 8.0 / (1.1 - pct); + } + + switch (ps->o.backend) { + case BKEND_XRENDER: + case BKEND_XR_GLX_HYBRID: { + // Normalize blur kernels + for (int i = 0; i < MAX_BLUR_PASS; ++i) { + xcb_render_fixed_t *kern_src = ps->o.blur_kerns[i]; + xcb_render_fixed_t *kern_dst = ps->blur_kerns_cache[i]; + assert(i < MAX_BLUR_PASS); + if (!kern_src) { + assert(!kern_dst); + break; + } + + assert(!kern_dst || (kern_src[0] == kern_dst[0] && + kern_src[1] == kern_dst[1])); + + // Skip for fixed factor_center if the cache exists already + if (ps->o.blur_background_fixed && kern_dst) + continue; + + int kwid = XFIXED_TO_DOUBLE(kern_src[0]), + khei = XFIXED_TO_DOUBLE(kern_src[1]); + + // Allocate cache space if needed + if (!kern_dst) { + kern_dst = + ccalloc(kwid * khei + 2, xcb_render_fixed_t); + if (!kern_dst) { + printf_errf("(): Failed to allocate memory " + "for blur kernel."); + return; + } + ps->blur_kerns_cache[i] = kern_dst; + } + + // Modify the factor of the center pixel + kern_src[2 + (khei / 2) * kwid + kwid / 2] = + DOUBLE_TO_XFIXED(factor_center); + + // Copy over + memcpy(kern_dst, kern_src, + (kwid * khei + 2) * sizeof(xcb_render_fixed_t)); + normalize_conv_kern(kwid, khei, kern_dst + 2); + } + + // Minimize the region we try to blur, if the window itself is not + // opaque, only the frame is. + region_t reg_blur = win_get_bounding_shape_global_by_val(w); + if (win_is_solid(ps, w)) { + region_t reg_noframe; + pixman_region32_init(®_noframe); + win_get_region_noframe_local(w, ®_noframe); + pixman_region32_translate(®_noframe, w->g.x, w->g.y); + pixman_region32_subtract(®_blur, ®_blur, ®_noframe); + pixman_region32_fini(®_noframe); + } + // Translate global coordinates to local ones + pixman_region32_translate(®_blur, -x, -y); + xr_blur_dst(ps, tgt_buffer, x, y, wid, hei, ps->blur_kerns_cache, + ®_blur); + pixman_region32_clear(®_blur); + } break; +#ifdef CONFIG_OPENGL + case BKEND_GLX: + // TODO: Handle frame opacity + glx_blur_dst(ps, x, y, wid, hei, ps->psglx->z - 0.5, factor_center, + reg_paint, &w->glx_blur_cache); + break; +#endif + default: assert(0); + } +} + +/// paint all windows +/// region = ?? +/// region_real = the damage region +void paint_all(session_t *ps, region_t *region, const region_t *region_real, + win *const t) { + if (!region_real) + region_real = region; + +#ifdef DEBUG_REPAINT + static struct timespec last_paint = {0}; +#endif + + if (!region) + region_real = region = &ps->screen_reg; + else + // Remove the damaged area out of screen + pixman_region32_intersect(region, region, &ps->screen_reg); + +#ifdef CONFIG_OPENGL + if (bkend_use_glx(ps)) + glx_paint_pre(ps, region); +#endif + + if (!paint_isvalid(ps, &ps->tgt_buffer)) { + if (!ps->tgt_buffer.pixmap) { + free_paint(ps, &ps->tgt_buffer); + ps->tgt_buffer.pixmap = x_create_pixmap( + ps, ps->depth, ps->root, ps->root_width, ps->root_height); + if (ps->tgt_buffer.pixmap == XCB_NONE) { + fprintf(stderr, "Failed to allocate a screen-sized " + "pixmap\n"); + exit(1); + } + } + + if (BKEND_GLX != ps->o.backend) + ps->tgt_buffer.pict = x_create_picture_with_visual_and_pixmap( + ps, ps->vis, ps->tgt_buffer.pixmap, 0, 0); + } + + if (BKEND_XRENDER == ps->o.backend) { + x_set_picture_clip_region(ps, ps->tgt_picture, 0, 0, region_real); + } + + region_t reg_tmp, *reg_paint; + pixman_region32_init(®_tmp); + if (t) { + // Calculate the region upon which the root window is to be painted + // based on the ignore region of the lowest window, if available + pixman_region32_subtract(®_tmp, region, t->reg_ignore); + reg_paint = ®_tmp; + } else { + reg_paint = region; + } + + set_tgt_clip(ps, reg_paint); + paint_root(ps, reg_paint); + + // Windows are sorted from bottom to top + // Each window has a reg_ignore, which is the region obscured by all the windows + // on top of that window. This is used to reduce the number of pixels painted. + // + // Whether this is beneficial is to be determined XXX + for (win *w = t; w; w = w->prev_trans) { + region_t bshape = win_get_bounding_shape_global_by_val(w); + // Painting shadow + if (w->shadow) { + // Lazy shadow building + if (!w->shadow_paint.pixmap) + if (!win_build_shadow(ps, w, 1)) + printf_errf("(): build shadow failed"); + + // Shadow doesn't need to be painted underneath the body of + // the window Because no one can see it + pixman_region32_subtract(®_tmp, region, 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); + + // Mask out the body of the window from the shadow if needed + // Doing it here instead of in make_shadow() for saving GPU + // power and handling shaped windows (XXX unconfirmed) + if (!ps->o.wintype_option[w->window_type].full_shadow) + pixman_region32_subtract(®_tmp, ®_tmp, &bshape); + +#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 + + // Detect if the region is empty before painting + if (pixman_region32_not_empty(®_tmp)) { + set_tgt_clip(ps, ®_tmp); + win_paint_shadow(ps, w, ®_tmp); + } + } + + // Calculate the region based on the reg_ignore of the next (higher) + // window and the bounding region + // XXX XXX + pixman_region32_subtract(®_tmp, region, w->reg_ignore); + pixman_region32_intersect(®_tmp, ®_tmp, &bshape); + pixman_region32_fini(&bshape); + + if (pixman_region32_not_empty(®_tmp)) { + set_tgt_clip(ps, ®_tmp); + // Blur window background + if (w->blur_background && + (!win_is_solid(ps, w) || + (ps->o.blur_background_frame && w->frame_opacity != 1))) + win_blur_background(ps, w, ps->tgt_buffer.pict, + ®_tmp); + + // Painting the window + paint_one(ps, w, ®_tmp); + } + } + + // Free up all temporary regions + pixman_region32_fini(®_tmp); + + // Do this as early as possible + set_tgt_clip(ps, &ps->screen_reg); + + if (ps->o.vsync) { + // Make sure all previous requests are processed to achieve best + // effect + x_sync(ps->c); +#ifdef CONFIG_OPENGL + if (glx_has_context(ps)) { + if (ps->o.vsync_use_glfinish) + glFinish(); + else + glFlush(); + glXWaitX(); + } +#endif + } + + // Wait for VBlank. We could do it aggressively (send the painting + // request and XFlush() on VBlank) or conservatively (send the request + // only on VBlank). + if (!ps->o.vsync_aggressive) + vsync_wait(ps); + + switch (ps->o.backend) { + case BKEND_XRENDER: + if (ps->o.monitor_repaint) { + // Copy the screen content to a new picture, and highlight + // the paint region. This is not very efficient, but since + // it's for debug only, we don't really care + + // First, we clear tgt_buffer.pict's clip region, since we + // want to copy everything + x_set_picture_clip_region(ps, ps->tgt_buffer.pict, 0, 0, + &ps->screen_reg); + + // Then we create a new picture, and copy content to it + xcb_render_pictforminfo_t *pictfmt = + x_get_pictform_for_visual(ps, ps->vis); + xcb_render_picture_t new_pict = x_create_picture( + ps, ps->root_width, ps->root_height, pictfmt, 0, NULL); + xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_SRC, + ps->tgt_buffer.pict, None, new_pict, 0, + 0, 0, 0, 0, 0, ps->root_width, + ps->root_height); + + // Next, we set the region of paint and highlight it + x_set_picture_clip_region(ps, new_pict, 0, 0, region_real); + xcb_render_composite( + ps->c, XCB_RENDER_PICT_OP_OVER, ps->white_picture, + ps->alpha_picts[MAX_ALPHA / 2], new_pict, 0, 0, 0, 0, 0, + 0, ps->root_width, ps->root_height); + + // Finally, clear clip region and put the whole thing on screen + x_set_picture_clip_region(ps, new_pict, 0, 0, &ps->screen_reg); + xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_SRC, new_pict, + None, ps->tgt_picture, 0, 0, 0, 0, 0, 0, + ps->root_width, ps->root_height); + xcb_render_free_picture(ps->c, new_pict); + } else + xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_SRC, + ps->tgt_buffer.pict, None, + ps->tgt_picture, 0, 0, 0, 0, 0, 0, + ps->root_width, ps->root_height); + break; +#ifdef CONFIG_OPENGL + case BKEND_XR_GLX_HYBRID: + x_sync(ps->c); + if (ps->o.vsync_use_glfinish) + glFinish(); + else + glFlush(); + glXWaitX(); + assert(ps->tgt_buffer.pixmap); + xr_sync(ps, ps->tgt_buffer.pixmap, &ps->tgt_buffer_fence); + paint_bind_tex(ps, &ps->tgt_buffer, ps->root_width, ps->root_height, + ps->depth, !ps->o.glx_no_rebind_pixmap); + // See #163 + xr_sync(ps, ps->tgt_buffer.pixmap, &ps->tgt_buffer_fence); + if (ps->o.vsync_use_glfinish) + glFinish(); + else + glFlush(); + glXWaitX(); + glx_render(ps, ps->tgt_buffer.ptex, 0, 0, 0, 0, ps->root_width, + ps->root_height, 0, 1.0, false, false, region_real, NULL); + // falls through + case BKEND_GLX: glXSwapBuffers(ps->dpy, get_tgt_window(ps)); break; +#endif + default: assert(0); + } + glx_mark_frame(ps); + + if (ps->o.vsync_aggressive) + vsync_wait(ps); + + XFlush(ps->dpy); + +#ifdef CONFIG_OPENGL + if (glx_has_context(ps)) { + glFlush(); + glXWaitX(); + } +#endif + +#ifdef DEBUG_REPAINT + print_timestamp(ps); + struct timespec now = get_time_timespec(); + struct timespec diff = {0}; + timespec_subtract(&diff, &now, &last_paint); + printf("[ %5ld:%09ld ] ", diff.tv_sec, diff.tv_nsec); + last_paint = now; + printf("paint:"); + for (win *w = t; w; w = w->prev_trans) + printf(" %#010lx", w->id); + putchar('\n'); + fflush(stdout); +#endif + + // Check if fading is finished on all painted windows + { + win *pprev = NULL; + for (win *w = t; w; w = pprev) { + pprev = w->prev_trans; + win_check_fade_finished(ps, &w); + } + } +} + +// vim: set ts=8 sw=8 noet : diff --git a/src/render.h b/src/render.h new file mode 100644 index 0000000..1709d6e --- /dev/null +++ b/src/render.h @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) Yuxuan Shui +#pragma once + +#include +#include "region.h" + +typedef struct _glx_texture glx_texture_t; +typedef struct glx_prog_main glx_prog_main_t; +typedef struct win win; +typedef struct session session_t; + +typedef struct paint { + xcb_pixmap_t pixmap; + xcb_render_picture_t pict; + glx_texture_t *ptex; +} paint_t; + +void +render(session_t *ps, int x, int y, int dx, int dy, int wid, int hei, + double opacity, bool argb, bool neg, + xcb_render_picture_t pict, glx_texture_t *ptex, + const region_t *reg_paint, const glx_prog_main_t *pprogram); +void +paint_one(session_t *ps, win *w, const region_t *reg_paint); + +void +paint_all(session_t *ps, region_t *region, const region_t *region_real, win * const t); + +void free_picture(xcb_connection_t *c, xcb_render_picture_t *p); + +void free_paint(session_t *ps, paint_t *ppaint); diff --git a/src/string_utils.c b/src/string_utils.c index 8dca93d..064db9c 100644 --- a/src/string_utils.c +++ b/src/string_utils.c @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) Yuxuan Shui + #include #include "compiler.h" diff --git a/src/vsync.c b/src/vsync.c new file mode 100644 index 0000000..a1ae076 --- /dev/null +++ b/src/vsync.c @@ -0,0 +1,290 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) Yuxuan Shui + +/// Function pointers to init VSync modes. + +#include "common.h" +#include "log.h" +#include "opengl.h" + +#include "vsync.h" + +/** + * Initialize DRM VSync. + * + * @return true for success, false otherwise + */ +static bool +vsync_drm_init(session_t *ps) { +#ifdef CONFIG_VSYNC_DRM + // Should we always open card0? + if (ps->drm_fd < 0 && (ps->drm_fd = open("/dev/dri/card0", O_RDWR)) < 0) { + printf_errf("(): Failed to open device."); + return false; + } + + if (vsync_drm_wait(ps)) + return false; + + return true; +#else + printf_errf("(): Program not compiled with DRM VSync support."); + return false; +#endif +} + +/** + * Initialize OpenGL VSync. + * + * Stolen from: http://git.tuxfamily.org/?p=ccm/cairocompmgr.git;a=commitdiff;h=efa4ceb97da501e8630ca7f12c99b1dce853c73e + * Possible original source: http://www.inb.uni-luebeck.de/~boehme/xvideo_sync.html + * + * @return true for success, false otherwise + */ +static bool +vsync_opengl_init(session_t *ps) { +#ifdef CONFIG_OPENGL + if (!ensure_glx_context(ps)) + return false; + + if (!glx_hasglxext(ps, "GLX_SGI_video_sync")) { + printf_errf("(): Your driver doesn't support SGI_video_sync, giving up."); + return false; + } + + // Get video sync functions + if (!ps->psglx->glXGetVideoSyncSGI) + ps->psglx->glXGetVideoSyncSGI = (f_GetVideoSync) + glXGetProcAddress((const GLubyte *) "glXGetVideoSyncSGI"); + if (!ps->psglx->glXWaitVideoSyncSGI) + ps->psglx->glXWaitVideoSyncSGI = (f_WaitVideoSync) + glXGetProcAddress((const GLubyte *) "glXWaitVideoSyncSGI"); + if (!ps->psglx->glXWaitVideoSyncSGI || !ps->psglx->glXGetVideoSyncSGI) { + printf_errf("(): Failed to get glXWait/GetVideoSyncSGI function."); + return false; + } + + return true; +#else + printf_errf("(): Program not compiled with OpenGL VSync support."); + return false; +#endif +} + +static bool +vsync_opengl_oml_init(session_t *ps) { +#ifdef CONFIG_OPENGL + if (!ensure_glx_context(ps)) + return false; + + if (!glx_hasglxext(ps, "GLX_OML_sync_control")) { + printf_errf("(): Your driver doesn't support OML_sync_control, giving up."); + return false; + } + + // Get video sync functions + if (!ps->psglx->glXGetSyncValuesOML) + ps->psglx->glXGetSyncValuesOML = (f_GetSyncValuesOML) + glXGetProcAddress ((const GLubyte *) "glXGetSyncValuesOML"); + if (!ps->psglx->glXWaitForMscOML) + ps->psglx->glXWaitForMscOML = (f_WaitForMscOML) + glXGetProcAddress ((const GLubyte *) "glXWaitForMscOML"); + if (!ps->psglx->glXGetSyncValuesOML || !ps->psglx->glXWaitForMscOML) { + printf_errf("(): Failed to get OML_sync_control functions."); + return false; + } + + return true; +#else + printf_errf("(): Program not compiled with OpenGL VSync support."); + return false; +#endif +} + +static bool +vsync_opengl_swc_swap_interval(session_t *ps, unsigned int interval) { +#ifdef CONFIG_OPENGL + if (!ensure_glx_context(ps)) + return false; + + if (!ps->psglx->glXSwapIntervalProc && !ps->psglx->glXSwapIntervalMESAProc) { + if (glx_hasglxext(ps, "GLX_MESA_swap_control")) { + ps->psglx->glXSwapIntervalMESAProc = (f_SwapIntervalMESA) + glXGetProcAddress ((const GLubyte *) "glXSwapIntervalMESA"); + } else if (glx_hasglxext(ps, "GLX_SGI_swap_control")) { + ps->psglx->glXSwapIntervalProc = (f_SwapIntervalSGI) + glXGetProcAddress ((const GLubyte *) "glXSwapIntervalSGI"); + } else { + printf_errf("(): Your driver doesn't support SGI_swap_control nor MESA_swap_control, giving up."); + return false; + } + } + + if (ps->psglx->glXSwapIntervalMESAProc) + ps->psglx->glXSwapIntervalMESAProc(interval); + else if (ps->psglx->glXSwapIntervalProc) + ps->psglx->glXSwapIntervalProc(interval); + else + return false; +#endif + + return true; +} + +static bool +vsync_opengl_swc_init(session_t *ps) { +#ifdef CONFIG_OPENGL + if (!bkend_use_glx(ps)) { + printf_errf("(): OpenGL swap control requires the GLX backend."); + return false; + } + + if (!vsync_opengl_swc_swap_interval(ps, 1)) { + printf_errf("(): Failed to load a swap control extension."); + return false; + } + + return true; +#else + printf_errf("(): Program not compiled with OpenGL VSync support."); + return false; +#endif +} + +static bool +vsync_opengl_mswc_init(session_t *ps) { + printf_errf("(): opengl-mswc is deprecated, please use opengl-swc instead."); + return vsync_opengl_swc_init(ps); +} + +bool (*const VSYNC_FUNCS_INIT[])(session_t *ps) = { + [VSYNC_DRM ] = vsync_drm_init, + [VSYNC_OPENGL ] = vsync_opengl_init, + [VSYNC_OPENGL_OML ] = vsync_opengl_oml_init, + [VSYNC_OPENGL_SWC ] = vsync_opengl_swc_init, + [VSYNC_OPENGL_MSWC ] = vsync_opengl_mswc_init, +}; + +#ifdef CONFIG_VSYNC_DRM +/** + * Wait for next VSync, DRM method. + * + * Stolen from: https://github.com/MythTV/mythtv/blob/master/mythtv/libs/libmythtv/vsync.cpp + */ +static int +vsync_drm_wait(session_t *ps) { + int ret = -1; + drm_wait_vblank_t vbl; + + vbl.request.type = _DRM_VBLANK_RELATIVE, + vbl.request.sequence = 1; + + do { + ret = ioctl(ps->drm_fd, DRM_IOCTL_WAIT_VBLANK, &vbl); + vbl.request.type &= ~_DRM_VBLANK_RELATIVE; + } while (ret && errno == EINTR); + + if (ret) + fprintf(stderr, "vsync_drm_wait(): VBlank ioctl did not work, " + "unimplemented in this drmver?\n"); + + return ret; + +} +#endif + +#ifdef CONFIG_OPENGL +/** + * Wait for next VSync, OpenGL method. + */ +static int +vsync_opengl_wait(session_t *ps) { + unsigned vblank_count = 0; + + ps->psglx->glXGetVideoSyncSGI(&vblank_count); + ps->psglx->glXWaitVideoSyncSGI(2, (vblank_count + 1) % 2, &vblank_count); + // I see some code calling glXSwapIntervalSGI(1) afterwards, is it required? + + return 0; +} + +/** + * Wait for next VSync, OpenGL OML method. + * + * https://mail.gnome.org/archives/clutter-list/2012-November/msg00031.html + */ +static int +vsync_opengl_oml_wait(session_t *ps) { + int64_t ust = 0, msc = 0, sbc = 0; + + ps->psglx->glXGetSyncValuesOML(ps->dpy, ps->reg_win, &ust, &msc, &sbc); + ps->psglx->glXWaitForMscOML(ps->dpy, ps->reg_win, 0, 2, (msc + 1) % 2, + &ust, &msc, &sbc); + + return 0; +} + +#endif + +/// Function pointers to wait for VSync. +int (*const VSYNC_FUNCS_WAIT[])(session_t *ps) = { +#ifdef CONFIG_VSYNC_DRM + [VSYNC_DRM ] = vsync_drm_wait, +#endif +#ifdef CONFIG_OPENGL + [VSYNC_OPENGL ] = vsync_opengl_wait, + [VSYNC_OPENGL_OML ] = vsync_opengl_oml_wait, +#endif +}; + +#ifdef CONFIG_OPENGL +static void +vsync_opengl_swc_deinit(session_t *ps) { + vsync_opengl_swc_swap_interval(ps, 0); +} +#endif + + +/// Function pointers to deinitialize VSync. +void (*const VSYNC_FUNCS_DEINIT[])(session_t *ps) = { +#ifdef CONFIG_OPENGL + [VSYNC_OPENGL_SWC ] = vsync_opengl_swc_deinit, + [VSYNC_OPENGL_MSWC ] = vsync_opengl_swc_deinit, +#endif +}; + +/** + * Initialize current VSync method. + */ +bool vsync_init(session_t *ps) { + // Mesa turns on swap control by default, undo that + if (bkend_use_glx(ps)) + vsync_opengl_swc_swap_interval(ps, 0); + + if (ps->o.vsync && VSYNC_FUNCS_INIT[ps->o.vsync] + && !VSYNC_FUNCS_INIT[ps->o.vsync](ps)) { + ps->o.vsync = VSYNC_NONE; + return false; + } + else + return true; +} + +/** + * Wait for next VSync. + */ +void vsync_wait(session_t *ps) { + if (!ps->o.vsync) + return; + + if (VSYNC_FUNCS_WAIT[ps->o.vsync]) + VSYNC_FUNCS_WAIT[ps->o.vsync](ps); +} + +/** + * Deinitialize current VSync method. + */ +void vsync_deinit(session_t *ps) { + if (ps->o.vsync && VSYNC_FUNCS_DEINIT[ps->o.vsync]) + VSYNC_FUNCS_DEINIT[ps->o.vsync](ps); +} diff --git a/src/vsync.h b/src/vsync.h new file mode 100644 index 0000000..e50f6d0 --- /dev/null +++ b/src/vsync.h @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) Yuxuan Shui +#include + +typedef struct session session_t; + +bool vsync_init(session_t *ps); +void vsync_wait(session_t *ps); +void vsync_deinit(session_t *ps); diff --git a/src/win.c b/src/win.c index 33261ce..d0845ad 100644 --- a/src/win.c +++ b/src/win.c @@ -1289,3 +1289,35 @@ void win_ev_stop(session_t *ps, win *w) { xcb_shape_select_input(ps->c, w->id, 0)); } } + +/** + * Set fade callback of a window, and possibly execute the previous + * callback. + * + * If a callback can cause rendering result to change, it should call + * `queue_redraw`. + * + * @param exec_callback whether the previous callback is to be executed + */ +void win_set_fade_callback(session_t *ps, win **_w, + void (*callback) (session_t *ps, win **w), bool exec_callback) { + win *w = *_w; + void (*old_callback) (session_t *ps, win **w) = w->fade_callback; + + w->fade_callback = callback; + // Must be the last line as the callback could destroy w! + if (exec_callback && old_callback) + old_callback(ps, _w); +} + +/** + * Execute fade callback of a window if fading finished. + */ +void +win_check_fade_finished(session_t *ps, win **_w) { + win *w = *_w; + if (w->fade_callback && w->opacity == w->opacity_tgt) { + // Must be the last line as the callback could destroy w! + win_set_fade_callback(ps, _w, NULL, true); + } +} diff --git a/src/win.h b/src/win.h index 75a205b..787e5fc 100644 --- a/src/win.h +++ b/src/win.h @@ -16,17 +16,11 @@ #include "x.h" #include "types.h" #include "c2.h" +#include "render.h" typedef struct session session_t; typedef struct _glx_texture glx_texture_t; -// FIXME not the best place for this type -typedef struct { - xcb_pixmap_t pixmap; - xcb_render_picture_t pict; - glx_texture_t *ptex; -} paint_t; - #ifdef CONFIG_OPENGL // FIXME this type should be in opengl.h // it is very unideal for it to be here @@ -335,6 +329,24 @@ void win_update_frame_extents(session_t *ps, win *w, Window client); bool add_win(session_t *ps, Window id, Window prev); +/** + * Set fade callback of a window, and possibly execute the previous + * callback. + * + * If a callback can cause rendering result to change, it should call + * `queue_redraw`. + * + * @param exec_callback whether the previous callback is to be executed + */ +void win_set_fade_callback(session_t *ps, win **_w, + void (*callback) (session_t *ps, win **w), bool exec_callback); + +/** + * Execute fade callback of a window if fading finished. + */ +void +win_check_fade_finished(session_t *ps, win **_w); + // Stop receiving events (except ConfigureNotify, XXX why?) from a window void win_ev_stop(session_t *ps, win *w); diff --git a/src/x.c b/src/x.c index 47f17a1..22f1273 100644 --- a/src/x.c +++ b/src/x.c @@ -377,3 +377,20 @@ x_create_pixmap(session_t *ps, uint8_t depth, xcb_drawable_t drawable, uint16_t free(err); return XCB_NONE; } + +/** + * Validate a pixmap. + * + * Detect whether the pixmap is valid with XGetGeometry. Well, maybe there + * are better ways. + */ +bool +x_validate_pixmap(session_t *ps, xcb_pixmap_t pxmap) { + if (!pxmap) return false; + + Window rroot = None; + int rx = 0, ry = 0; + unsigned rwid = 0, rhei = 0, rborder = 0, rdepth = 0; + return XGetGeometry(ps->dpy, pxmap, &rroot, &rx, &ry, + &rwid, &rhei, &rborder, &rdepth) && rwid && rhei; +} diff --git a/src/x.h b/src/x.h index 3e1cde3..9e22bab 100644 --- a/src/x.h +++ b/src/x.h @@ -137,6 +137,9 @@ x_print_error(unsigned long serial, uint8_t major, uint8_t minor, uint8_t error_ xcb_pixmap_t x_create_pixmap(session_t *ps, uint8_t depth, xcb_drawable_t drawable, uint16_t width, uint16_t height); +bool +x_validate_pixmap(session_t *ps, xcb_pixmap_t pxmap); + /** * Free a winprop_t. *