// SPDX-License-Identifier: MPL-2.0 // Copyright (c) Yuxuan Shui #include #include "common.h" #include "options.h" #ifdef CONFIG_OPENGL #include "opengl.h" #endif #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 attr_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 = XCB_NONE; } } /** * Free paint_t. */ void free_paint(session_t *ps, paint_t *ppaint) { #ifdef CONFIG_OPENGL free_paint_glx(ps, ppaint); #endif 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, #ifdef CONFIG_OPENGL w ? &ps->glx_prog_win : NULL #else NULL #endif ); } /** * 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, XCB_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)); } xcb_drawable_t 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); } // 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))) { log_error("Failed to bind texture for window %#010x.", w->id); } w->pixmap_damaged = false; if (!paint_isvalid(ps, &w->paint)) { log_error("Window %#010x is missing painting data.", 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_with_pictfmt(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, XCB_NONE, newpict, 0, 0, 0, 0, 0, 0, wid, hei); xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_DIFFERENCE, ps->white_picture, XCB_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, XCB_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 = XCB_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 = XCB_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) { log_error("Failed to create pixmaps for root tile."); return false; } 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 there is no root tile pixmap, try getting one. // If that fails, give up. if (!ps->root_tile_paint.pixmap && !get_root_tile(ps)) return; paint_region(ps, NULL, 0, 0, ps->root_width, ps->root_height, 1.0, reg_paint, ps->root_tile_paint.pict); } 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) { log_error("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; // log_trace("(): building shadow for %s %d %d", w->name, width, height); xcb_image_t *shadow_image = NULL; xcb_pixmap_t shadow_pixmap = XCB_NONE, shadow_pixmap_argb = XCB_NONE; xcb_render_picture_t shadow_picture = XCB_NONE, shadow_picture_argb = XCB_NONE; xcb_gcontext_t gc = XCB_NONE; shadow_image = make_shadow(ps, opacity, width, height); if (!shadow_image) { log_error("failed to make shadow"); return XCB_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) { log_error("failed to create shadow pixmaps"); goto shadow_picture_err; } shadow_picture = x_create_picture_with_standard_and_pixmap( ps, XCB_PICT_STANDARD_A_8, shadow_pixmap, 0, NULL); shadow_picture_argb = x_create_picture_with_standard_and_pixmap( ps, XCB_PICT_STANDARD_ARGB_32, shadow_pixmap_argb, 0, NULL); if (!shadow_picture || !shadow_picture_argb) goto shadow_picture_err; gc = xcb_generate_id(ps->c); xcb_create_gc(ps->c, gc, shadow_pixmap, 0, NULL); xcb_image_put(ps->c, shadow_pixmap, gc, shadow_image, 0, 0, 0); xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_SRC, 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; 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)) { log_error("Window %#010x is 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_with_pictfmt(ps, wid, hei, NULL, 0, NULL); if (!tmp_picture) { log_error("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, XCB_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, XCB_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); 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 (ps->o.xrender_sync_fence) { if (!x_fence_sync(ps, ps->sync_fence)) { log_error("x_fence_sync failed, xrender-sync-fence will be disabled from now on."); xcb_sync_destroy_fence(ps->c, ps->sync_fence); ps->o.xrender_sync_fence = false; } } 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) { log_fatal("Failed to allocate a screen-sized pixmap for" "painting"); 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)) log_error("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_with_pictfmt( ps, ps->root_width, ps->root_height, pictfmt, 0, NULL); xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_SRC, ps->tgt_buffer.pict, XCB_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, XCB_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, XCB_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); paint_bind_tex(ps, &ps->tgt_buffer, ps->root_width, ps->root_height, ps->depth, !ps->o.glx_no_rebind_pixmap); 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 struct timespec now = get_time_timespec(); struct timespec diff = {0}; timespec_subtract(&diff, &now, &last_paint); log_trace("[ %5ld:%09ld ] ", diff.tv_sec, diff.tv_nsec); last_paint = now; log_trace("paint:"); for (win *w = t; w; w = w->prev_trans) log_trace(" %#010lx", w->id); #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); } } } /** * Query needed X Render / OpenGL filters to check for their existence. */ static bool xr_init_blur(session_t *ps) { // Query filters xcb_render_query_filters_reply_t *pf = xcb_render_query_filters_reply( ps->c, xcb_render_query_filters(ps->c, get_tgt_window(ps)), NULL); if (pf) { xcb_str_iterator_t iter = xcb_render_query_filters_filters_iterator(pf); for (; iter.rem; xcb_str_next(&iter)) { int len = xcb_str_name_length(iter.data); char *name = xcb_str_name(iter.data); // Check for the convolution filter if (strlen(XRFILTER_CONVOLUTION) == len && !memcmp(XRFILTER_CONVOLUTION, name, strlen(XRFILTER_CONVOLUTION))) ps->xrfilter_convolution_exists = true; } free(pf); } // Turn features off if any required filter is not present if (!ps->xrfilter_convolution_exists) { log_error("Xrender convolution filter " "unsupported by your X server. " "Background blur is not possible."); return false; } return true; } /** * Generate a 1x1 Picture of a particular color. */ static xcb_render_picture_t solid_picture(session_t *ps, bool argb, double a, double r, double g, double b) { xcb_pixmap_t pixmap; xcb_render_picture_t picture; xcb_render_create_picture_value_list_t pa; xcb_render_color_t col; xcb_rectangle_t rect; pixmap = x_create_pixmap(ps, argb ? 32 : 8, ps->root, 1, 1); if (!pixmap) return XCB_NONE; pa.repeat = True; picture = x_create_picture_with_standard_and_pixmap( ps, argb ? XCB_PICT_STANDARD_ARGB_32 : XCB_PICT_STANDARD_A_8, pixmap, XCB_RENDER_CP_REPEAT, &pa); if (!picture) { xcb_free_pixmap(ps->c, pixmap); return XCB_NONE; } col.alpha = a * 0xffff; col.red = r * 0xffff; col.green = g * 0xffff; col.blue = b * 0xffff; rect.x = 0; rect.y = 0; rect.width = 1; rect.height = 1; xcb_render_fill_rectangles(ps->c, XCB_RENDER_PICT_OP_SRC, picture, col, 1, &rect); xcb_free_pixmap(ps->c, pixmap); return picture; } /** * Pregenerate alpha pictures. */ static bool init_alpha_picts(session_t *ps) { ps->alpha_picts = ccalloc(MAX_ALPHA + 1, xcb_render_picture_t); for (int i = 0; i <= MAX_ALPHA; ++i) { double o = (double)i / MAX_ALPHA; ps->alpha_picts[i] = solid_picture(ps, false, o, 0, 0, 0); if (ps->alpha_picts[i] == XCB_NONE) return false; } return true; } /// precompute shadow corners and sides to save time for large windows static void presum_gaussian(session_t *ps, conv *map) { ps->cgsize = map->size; const int center = map->size / 2; const int r = ps->cgsize + 1; // radius of the kernel const int width = ps->cgsize * 2, height = ps->cgsize * 2; if (ps->shadow_corner) free(ps->shadow_corner); if (ps->shadow_top) free(ps->shadow_top); // clang-format off ps->shadow_corner = cvalloc(r*r*26); ps->shadow_top = cvalloc(r*26); for (int x = 0; x < r; x++) { double sum = sum_kernel(map, x-center, center, width, height); int tmp = ps->shadow_top[25*r+x] = (unsigned char)(sum*255.0); for (int opacity = 0; opacity < 25; opacity++) { ps->shadow_top[opacity*r+x] = tmp*opacity/25; } } for (int x = 0; x < r; x++) { for (int y = 0; y <= x; y++) { double sum = sum_kernel(map, x-center, y-center, width, height); ps->shadow_corner[25*r*r+y*r+x] = (unsigned char)(sum*255.0); ps->shadow_corner[25*r*r+x*r+y] = ps->shadow_corner[25*r*r+y*r+x]; for (int opacity = 0; opacity < 25; opacity++) { ps->shadow_corner[opacity*r*r+y*r+x] = ps->shadow_corner[opacity*r*r+x*r+y] = ps->shadow_corner[25*r*r+y*r+x]*opacity/25; } } } // clang-format on } bool init_render(session_t *ps) { // Initialize OpenGL as early as possible if (bkend_use_glx(ps)) { #ifdef CONFIG_OPENGL if (!glx_init(ps, true)) return false; #else log_error("GLX backend support not compiled in."); return false; #endif } // Initialize VSync if (!vsync_init(ps)) { return false; } // Initialize window GL shader if (BKEND_GLX == ps->o.backend && ps->o.glx_fshader_win_str) { #ifdef CONFIG_OPENGL if (!glx_load_prog_main(ps, NULL, ps->o.glx_fshader_win_str, &ps->glx_prog_win)) return false; #else log_error("GLSL supported not compiled in, can't load " "shader."); return false; #endif } if (!init_alpha_picts(ps)) { log_error("Failed to init alpha pictures."); return false; } // Blur filter if (ps->o.blur_background || ps->o.blur_background_frame) { bool ret; if (ps->o.backend == BKEND_GLX) { #ifdef CONFIG_OPENGL ret = glx_init_blur(ps); #else assert(false); #endif } else ret = xr_init_blur(ps); if (!ret) return false; } ps->gaussian_map = gaussian_kernel(ps->o.shadow_radius); presum_gaussian(ps, ps->gaussian_map); ps->black_picture = solid_picture(ps, true, 1, 0, 0, 0); ps->white_picture = solid_picture(ps, true, 1, 1, 1, 1); if (ps->black_picture == XCB_NONE || ps->white_picture == XCB_NONE) { log_error("Failed to create solid xrender pictures."); return false; } // Generates another Picture for shadows if the color is modified by // user if (!ps->o.shadow_red && !ps->o.shadow_green && !ps->o.shadow_blue) { ps->cshadow_picture = ps->black_picture; } else { ps->cshadow_picture = solid_picture( ps, true, 1, ps->o.shadow_red, ps->o.shadow_green, ps->o.shadow_blue); if (ps->cshadow_picture == XCB_NONE) { log_error("Failed to create shadow picture."); return false; } } return true; } /** * Free root tile related things. */ void free_root_tile(session_t *ps) { free_picture(ps->c, &ps->root_tile_paint.pict); #ifdef CONFIG_OPENGL free_texture(ps, &ps->root_tile_paint.ptex); #else assert(!ps->root_tile_paint.ptex); #endif if (ps->root_tile_fill) { xcb_free_pixmap(ps->c, ps->root_tile_paint.pixmap); ps->root_tile_paint.pixmap = XCB_NONE; } ps->root_tile_paint.pixmap = XCB_NONE; ps->root_tile_fill = false; } void deinit_render(session_t *ps) { // Free alpha_picts for (int i = 0; i <= MAX_ALPHA; ++i) free_picture(ps->c, &ps->alpha_picts[i]); free(ps->alpha_picts); ps->alpha_picts = NULL; // Free cshadow_picture and black_picture if (ps->cshadow_picture == ps->black_picture) ps->cshadow_picture = XCB_NONE; else free_picture(ps->c, &ps->cshadow_picture); free_picture(ps->c, &ps->black_picture); free_picture(ps->c, &ps->white_picture); free(ps->shadow_corner); free(ps->shadow_top); free(ps->gaussian_map); // Free other X resources free_root_tile(ps); #ifdef CONFIG_OPENGL glx_destroy(ps); #endif } // vim: set ts=8 sw=8 noet :