diff options
| author | allusive-dev <[email protected]> | 2023-09-19 17:46:20 +1000 |
|---|---|---|
| committer | allusive-dev <[email protected]> | 2023-09-19 17:46:20 +1000 |
| commit | 5650d887357bf2a3fac8c5fd4f467bf8795b5fc4 (patch) | |
| tree | 4b825dc642cb6eb9a060e54bf8d69288fbee4904 /src/backend | |
| parent | Update picom.sample.conf (diff) | |
| download | compfy-5650d887357bf2a3fac8c5fd4f467bf8795b5fc4.tar.xz compfy-5650d887357bf2a3fac8c5fd4f467bf8795b5fc4.zip | |
reset
Diffstat (limited to 'src/backend')
| -rw-r--r-- | src/backend/backend.c | 494 | ||||
| -rw-r--r-- | src/backend/backend.h | 290 | ||||
| -rw-r--r-- | src/backend/backend_common.c | 480 | ||||
| -rw-r--r-- | src/backend/backend_common.h | 78 | ||||
| -rw-r--r-- | src/backend/driver.c | 82 | ||||
| -rw-r--r-- | src/backend/driver.h | 62 | ||||
| -rw-r--r-- | src/backend/dummy/dummy.c | 174 | ||||
| -rw-r--r-- | src/backend/gl/gl_common.c | 1922 | ||||
| -rw-r--r-- | src/backend/gl/gl_common.h | 220 | ||||
| -rw-r--r-- | src/backend/gl/glx.c | 648 | ||||
| -rw-r--r-- | src/backend/gl/glx.h | 77 | ||||
| -rw-r--r-- | src/backend/meson.build | 7 | ||||
| -rw-r--r-- | src/backend/xrender/xrender.c | 779 |
13 files changed, 0 insertions, 5313 deletions
diff --git a/src/backend/backend.c b/src/backend/backend.c deleted file mode 100644 index b0e562a..0000000 --- a/src/backend/backend.c +++ /dev/null @@ -1,494 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Yuxuan Shui <[email protected]> -#include <xcb/sync.h> -#include <xcb/xcb.h> - -#include "backend/backend.h" -#include "common.h" -#include "compiler.h" -#include "config.h" -#include "log.h" -#include "region.h" -#include "types.h" -#include "win.h" -#include "x.h" - -extern struct backend_operations xrender_ops, dummy_ops; -#ifdef CONFIG_OPENGL -extern struct backend_operations glx_ops; -#endif - -struct backend_operations *backend_list[NUM_BKEND] = { - [BKEND_XRENDER] = &xrender_ops, - [BKEND_DUMMY] = &dummy_ops, -#ifdef CONFIG_OPENGL - [BKEND_GLX] = &glx_ops, -#endif -}; - -/** - * @param all_damage if true ignore damage and repaint the whole screen - */ -region_t get_damage(session_t *ps, bool all_damage) { - region_t region; - auto buffer_age_fn = ps->backend_data->ops->buffer_age; - int buffer_age = buffer_age_fn ? buffer_age_fn(ps->backend_data) : -1; - - if (all_damage) { - buffer_age = -1; - } - - pixman_region32_init(®ion); - if (buffer_age == -1 || buffer_age > ps->ndamage) { - pixman_region32_copy(®ion, &ps->screen_reg); - } else { - for (int i = 0; i < buffer_age; i++) { - auto curr = ((ps->damage - ps->damage_ring) + i) % ps->ndamage; - log_trace("damage index: %d, damage ring offset: %ld", i, curr); - dump_region(&ps->damage_ring[curr]); - pixman_region32_union(®ion, ®ion, &ps->damage_ring[curr]); - } - pixman_region32_intersect(®ion, ®ion, &ps->screen_reg); - } - return region; -} - -static void process_window_for_painting(session_t *ps, struct managed_win* w, void* win_image, - double additional_alpha, - region_t* reg_bound, region_t* reg_visible, - region_t* reg_paint, region_t* reg_paint_in_bound) { - // For window image processing, we don't have to limit the process - // region to damage for correctness. (see <damager-note> for - // details) - - // The visible region, in window local coordinates Although we - // don't limit process region to damage, we provide that info in - // reg_visible as a hint. Since window image data outside of the - // damage region won't be painted onto target - region_t reg_visible_local; - { - // The bounding shape, in window local coordinates - region_t reg_bound_local; - pixman_region32_init(®_bound_local); - pixman_region32_copy(®_bound_local, reg_bound); - pixman_region32_translate(®_bound_local, -w->g.x, -w->g.y); - - pixman_region32_init(®_visible_local); - pixman_region32_intersect(®_visible_local, - reg_visible, reg_paint); - pixman_region32_translate(®_visible_local, -w->g.x, - -w->g.y); - // Data outside of the bounding shape won't be visible, - // but it is not necessary to limit the image operations - // to the bounding shape yet. So pass that as the visible - // region, not the clip region. - pixman_region32_intersect( - ®_visible_local, ®_visible_local, ®_bound_local); - pixman_region32_fini(®_bound_local); - } - - auto new_img = ps->backend_data->ops->clone_image( - ps->backend_data, win_image, ®_visible_local); - auto reg_frame = win_get_region_frame_local_by_val(w); - double alpha = additional_alpha*w->opacity; - ps->backend_data->ops->set_image_property( - ps->backend_data, IMAGE_PROPERTY_OPACITY, new_img, &alpha); - ps->backend_data->ops->image_op( - ps->backend_data, IMAGE_OP_APPLY_ALPHA, new_img, ®_frame, - ®_visible_local, (double[]){w->frame_opacity}); - pixman_region32_fini(®_frame); - ps->backend_data->ops->compose(ps->backend_data, new_img, - w->g.x, w->g.y, - w->g.x + w->widthb, w->g.y + w->heightb, - reg_paint_in_bound, reg_visible); - ps->backend_data->ops->release_image(ps->backend_data, new_img); - pixman_region32_fini(®_visible_local); -} - -/// paint all windows -void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { - if (ps->o.xrender_sync_fence) { - if (ps->xsync_exists && !x_fence_sync(ps->c, 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->sync_fence = XCB_NONE; - ps->o.xrender_sync_fence = false; - ps->xsync_exists = false; - } - } - // All painting will be limited to the damage, if _some_ of - // the paints bleed out of the damage region, it will destroy - // part of the image we want to reuse - region_t reg_damage; - if (!ignore_damage) { - reg_damage = get_damage(ps, ps->o.monitor_repaint || !ps->o.use_damage); - } else { - pixman_region32_init(®_damage); - pixman_region32_copy(®_damage, &ps->screen_reg); - } - - if (!pixman_region32_not_empty(®_damage)) { - pixman_region32_fini(®_damage); - return; - } - -#ifdef DEBUG_REPAINT - static struct timespec last_paint = {0}; -#endif - - // <damage-note> - // If use_damage is enabled, we MUST make sure only the damaged regions of the - // screen are ever touched by the compositor. The reason is that at the beginning - // of each render, we clear the damaged regions with the wallpaper, and nothing - // else. If later during the render we changed anything outside the damaged - // region, that won't be cleared by the next render, and will thus accumulate. - // (e.g. if shadow is drawn outside the damaged region, it will become thicker and - // thicker over time.) - - /// The adjusted damaged regions - region_t reg_paint; - assert(ps->o.blur_method != BLUR_METHOD_INVALID); - if (ps->o.blur_method != BLUR_METHOD_NONE && ps->backend_data->ops->get_blur_size) { - int blur_width, blur_height; - ps->backend_data->ops->get_blur_size(ps->backend_blur_context, - &blur_width, &blur_height); - - // The region of screen a given window influences will be smeared - // out by blur. With more windows on top of the given window, the - // influences region will be smeared out more. - // - // Also, blurring requires data slightly outside the area that needs - // to be blurred. The more semi-transparent windows are stacked on top - // of each other, the larger the area will be. - // - // Instead of accurately calculate how much bigger the damage - // region will be because of blur, we assume the worst case here. - // That is, the damaged window is at the bottom of the stack, and - // all other windows have semi-transparent background - int resize_factor = 1; - if (t) { - resize_factor = t->stacking_rank; - } - resize_region_in_place(®_damage, blur_width * resize_factor, - blur_height * resize_factor); - reg_paint = resize_region(®_damage, blur_width * resize_factor, - blur_height * resize_factor); - pixman_region32_intersect(®_paint, ®_paint, &ps->screen_reg); - pixman_region32_intersect(®_damage, ®_damage, &ps->screen_reg); - } else { - pixman_region32_init(®_paint); - pixman_region32_copy(®_paint, ®_damage); - } - - // A hint to backend, the region that will be visible on screen - // backend can optimize based on this info - region_t reg_visible; - pixman_region32_init(®_visible); - pixman_region32_copy(®_visible, &ps->screen_reg); - if (t && !ps->o.transparent_clipping) { - // Calculate the region upon which the root window (wallpaper) is to be - // painted based on the ignore region of the lowest window, if available - // - // NOTE If transparent_clipping is enabled, transparent windows are - // included in the reg_ignore, but we still want to have the wallpaper - // beneath them, so we don't use reg_ignore for wallpaper in that case. - pixman_region32_subtract(®_visible, ®_visible, t->reg_ignore); - } - - // Region on screen we don't want any shadows on - region_t reg_shadow_clip; - pixman_region32_init(®_shadow_clip); - - if (ps->backend_data->ops->prepare) { - ps->backend_data->ops->prepare(ps->backend_data, ®_paint); - } - - if (ps->root_image) { - ps->backend_data->ops->compose(ps->backend_data, ps->root_image, - 0, 0, ps->root_width, ps->root_height, - ®_paint, ®_visible); - } else { - ps->backend_data->ops->fill(ps->backend_data, (struct color){0, 0, 0, 1}, - ®_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 (auto w = t; w; w = w->prev_trans) { - pixman_region32_subtract(®_visible, &ps->screen_reg, w->reg_ignore); - assert(!(w->flags & WIN_FLAGS_IMAGE_ERROR)); - assert(!(w->flags & WIN_FLAGS_PIXMAP_STALE)); - assert(!(w->flags & WIN_FLAGS_PIXMAP_NONE)); - - // The bounding shape of the window, in global/target coordinates - // reminder: bounding shape contains the WM frame - auto reg_bound = win_get_bounding_shape_global_by_val(w); - - // The clip region for the current window, in global/target coordinates - // reg_paint_in_bound \in reg_paint - region_t reg_paint_in_bound; - pixman_region32_init(®_paint_in_bound); - pixman_region32_intersect(®_paint_in_bound, ®_bound, ®_paint); - if (ps->o.transparent_clipping) { - // <transparent-clipping-note> - // If transparent_clipping is enabled, we need to be SURE that - // things are not drawn inside reg_ignore, because otherwise they - // will appear underneath transparent windows. - // So here we have make sure reg_paint_in_bound \in reg_visible - // There are a few other places below where this is needed as - // well. - pixman_region32_intersect(®_paint_in_bound, - ®_paint_in_bound, ®_visible); - } - - // Blur window background - /* TODO(yshui) since the backend might change the content of the window - * (e.g. with shaders), we should consult the backend whether the window - * is transparent or not. for now we will just rely on the force_win_blend - * option */ - auto real_win_mode = w->mode; - - if (w->blur_background && - (ps->o.force_win_blend || real_win_mode == WMODE_TRANS || - (ps->o.blur_background_frame && real_win_mode == WMODE_FRAME_TRANS))) { - // Minimize the region we try to blur, if the window - // itself is not opaque, only the frame is. - - double blur_opacity = 1; - if (w->opacity < (1.0 / MAX_ALPHA)) { - // Hide blur for fully transparent windows. - blur_opacity = 0; - } else if (w->state == WSTATE_MAPPING) { - // Gradually increase the blur intensity during - // fading in. - assert(w->opacity <= w->opacity_target); - blur_opacity = w->opacity / w->opacity_target; - } else if (w->state == WSTATE_UNMAPPING || - w->state == WSTATE_DESTROYING) { - // Gradually decrease the blur intensity during - // fading out. - assert(w->opacity <= w->opacity_target_old); - blur_opacity = w->opacity / w->opacity_target_old; - } else if (w->state == WSTATE_FADING) { - if (w->opacity < w->opacity_target && - w->opacity_target_old < (1.0 / MAX_ALPHA)) { - // Gradually increase the blur intensity during - // fading in. - assert(w->opacity <= w->opacity_target); - blur_opacity = w->opacity / w->opacity_target; - } else if (w->opacity > w->opacity_target && - w->opacity_target < (1.0 / MAX_ALPHA)) { - // Gradually decrease the blur intensity during - // fading out. - assert(w->opacity <= w->opacity_target_old); - blur_opacity = w->opacity / w->opacity_target_old; - } - } - assert(blur_opacity >= 0 && blur_opacity <= 1); - - if (real_win_mode == WMODE_TRANS || ps->o.force_win_blend) { - // We need to blur the bounding shape of the window - // (reg_paint_in_bound = reg_bound \cap reg_paint) - ps->backend_data->ops->blur( - ps->backend_data, blur_opacity, ps->backend_blur_context, - ®_paint_in_bound, ®_visible); - } else { - // Window itself is solid, we only need to blur the frame - // region - - // Readability assertions - assert(ps->o.blur_background_frame); - assert(real_win_mode == WMODE_FRAME_TRANS); - - auto reg_blur = win_get_region_frame_local_by_val(w); - pixman_region32_translate(®_blur, w->g.x, w->g.y); - // make sure reg_blur \in reg_paint - pixman_region32_intersect(®_blur, ®_blur, ®_paint); - if (ps->o.transparent_clipping) { - // ref: <transparent-clipping-note> - pixman_region32_intersect(®_blur, ®_blur, - ®_visible); - } - ps->backend_data->ops->blur(ps->backend_data, blur_opacity, - ps->backend_blur_context, - ®_blur, ®_visible); - pixman_region32_fini(®_blur); - } - } - - // Draw shadow on target - if (w->shadow) { - assert(!(w->flags & WIN_FLAGS_SHADOW_NONE)); - // Clip region for the shadow - // reg_shadow \in reg_paint - auto reg_shadow = win_extents_by_val(w); - pixman_region32_intersect(®_shadow, ®_shadow, ®_paint); - if (!ps->o.wintype_option[w->window_type].full_shadow) { - pixman_region32_subtract(®_shadow, ®_shadow, ®_bound); - } - - // Mask out the region we don't want shadow on - if (pixman_region32_not_empty(&ps->shadow_exclude_reg)) { - pixman_region32_subtract(®_shadow, ®_shadow, - &ps->shadow_exclude_reg); - } - if (pixman_region32_not_empty(®_shadow_clip)) { - pixman_region32_subtract(®_shadow, ®_shadow, - ®_shadow_clip); - } - - 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( - ®_shadow, ®_shadow, - &ps->xinerama_scr_regs[w->xinerama_scr]); - } - - if (ps->o.transparent_clipping) { - // ref: <transparent-clipping-note> - pixman_region32_intersect(®_shadow, ®_shadow, - ®_visible); - } - - assert(w->shadow_image); - ps->backend_data->ops->set_image_property( - ps->backend_data, IMAGE_PROPERTY_OPACITY, w->shadow_image, - &w->opacity); - ps->backend_data->ops->compose( - ps->backend_data, w->shadow_image, - w->g.x + w->shadow_dx, w->g.y + w->shadow_dy, - w->g.x + w->shadow_dx + w->shadow_width, - w->g.y + w->shadow_dy + w->shadow_height, - ®_shadow, ®_visible); - pixman_region32_fini(®_shadow); - } - - // Update image properties - { - double dim_opacity = 0.0; - if (w->dim) { - dim_opacity = ps->o.inactive_dim; - if (!ps->o.inactive_dim_fixed) { - dim_opacity *= w->opacity; - } - } - - ps->backend_data->ops->set_image_property( - ps->backend_data, IMAGE_PROPERTY_MAX_BRIGHTNESS, w->win_image, - &ps->o.max_brightness); - ps->backend_data->ops->set_image_property( - ps->backend_data, IMAGE_PROPERTY_INVERTED, w->win_image, - &w->invert_color); - ps->backend_data->ops->set_image_property( - ps->backend_data, IMAGE_PROPERTY_DIM_LEVEL, w->win_image, - &dim_opacity); - ps->backend_data->ops->set_image_property( - ps->backend_data, IMAGE_PROPERTY_OPACITY, w->win_image, &w->opacity); - } - - if (w->opacity * MAX_ALPHA < 1) { - // We don't need to paint the window body itself if it's - // completely transparent. - goto skip; - } - - if (w->clip_shadow_above) { - // Add window bounds to shadow-clip region - pixman_region32_union(®_shadow_clip, ®_shadow_clip, ®_bound); - } else { - // Remove overlapping window bounds from shadow-clip region - pixman_region32_subtract(®_shadow_clip, ®_shadow_clip, ®_bound); - } - - // Draw window on target - bool is_animating = 0 <= w->animation_progress && w->animation_progress < 1.0; - if (w->frame_opacity == 1 && !is_animating) { - ps->backend_data->ops->compose(ps->backend_data, w->win_image, - w->g.x, w->g.y, - w->g.x + w->widthb, w->g.y + w->heightb, - ®_paint_in_bound, ®_visible); - } else { - if (is_animating && w->old_win_image) { - assert(w->old_win_image); - - bool resizing = - w->g.width != w->pending_g.width || - w->g.height != w->pending_g.height; - - // Only animate opacity here if we are resizing - // a transparent window - process_window_for_painting(ps, w, w->win_image, - 1, - ®_bound, ®_visible, - ®_paint, ®_paint_in_bound); - - // Only do this if size changes as otherwise moving - // transparent windows will flicker and if you just - // move so slightly they will keep flickering - if (resizing) { - process_window_for_painting(ps, w, w->old_win_image, - 1.0 - w->animation_progress, - ®_bound, ®_visible, - ®_paint, ®_paint_in_bound); - } - - } else { - process_window_for_painting(ps, w, w->win_image, - 1, - ®_bound, ®_visible, - ®_paint, ®_paint_in_bound); - } - } - skip: - pixman_region32_fini(®_bound); - pixman_region32_fini(®_paint_in_bound); - } - pixman_region32_fini(®_paint); - pixman_region32_fini(®_shadow_clip); - - if (ps->o.monitor_repaint) { - const struct color DEBUG_COLOR = {0.5, 0, 0, 0.5}; - auto reg_damage_debug = get_damage(ps, false); - ps->backend_data->ops->fill(ps->backend_data, DEBUG_COLOR, ®_damage_debug); - pixman_region32_fini(®_damage_debug); - } - - // Move the head of the damage ring - ps->damage = ps->damage - 1; - if (ps->damage < ps->damage_ring) { - ps->damage = ps->damage_ring + ps->ndamage - 1; - } - pixman_region32_clear(ps->damage); - - if (ps->backend_data->ops->present) { - // Present the rendered scene - // Vsync is done here - ps->backend_data->ops->present(ps->backend_data, ®_damage); - } - - pixman_region32_fini(®_damage); - -#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 -} - -// vim: set noet sw=8 ts=8 : diff --git a/src/backend/backend.h b/src/backend/backend.h deleted file mode 100644 index ae107d3..0000000 --- a/src/backend/backend.h +++ /dev/null @@ -1,290 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) 2018, Yuxuan Shui <[email protected]> - -#pragma once - -#include <stdbool.h> - -#include "compiler.h" -#include "config.h" -#include "driver.h" -#include "kernel.h" -#include "region.h" -#include "types.h" -#include "x.h" - -typedef struct session session_t; -struct managed_win; - -struct ev_loop; -struct backend_operations; - -typedef struct backend_base { - struct backend_operations *ops; - xcb_connection_t *c; - xcb_window_t root; - struct ev_loop *loop; - - /// Whether the backend can accept new render request at the moment - bool busy; - // ... -} backend_t; - -typedef void (*backend_ready_callback_t)(void *); - -// When image properties are actually applied to the image, they are applied in a -// particular order: -// -// Color inversion -> Dimming -> Opacity multiply -> Limit maximum brightness -enum image_properties { - // Whether the color of the image is inverted - // 1 boolean, default: false - IMAGE_PROPERTY_INVERTED, - // How much the image is dimmed - // 1 double, default: 0 - IMAGE_PROPERTY_DIM_LEVEL, - // Image opacity, i.e. an alpha value multiplied to the alpha channel - // 1 double, default: 1 - IMAGE_PROPERTY_OPACITY, - // The effective size of the image, the image will be tiled to fit. - // 2 int, default: the actual size of the image - IMAGE_PROPERTY_EFFECTIVE_SIZE, - // Limit how bright image can be. The image brightness is estimated by averaging - // the pixels in the image, and dimming will be applied to scale the average - // brightness down to the max brightness value. - // 1 double, default: 1 - IMAGE_PROPERTY_MAX_BRIGHTNESS, -}; - -enum image_operations { - // Multiply the alpha channel by the argument - IMAGE_OP_APPLY_ALPHA, -}; - -struct gaussian_blur_args { - int size; - double deviation; -}; - -struct box_blur_args { - int size; -}; - -struct kernel_blur_args { - struct conv **kernels; - int kernel_count; -}; - -struct dual_kawase_blur_args { - int size; - int strength; -}; - -struct backend_operations { - // =========== Initialization =========== - - /// Initialize the backend, prepare for rendering to the target window. - /// Here is how you should choose target window: - /// 1) if ps->overlay is not XCB_NONE, use that - /// 2) use ps->root otherwise - // TODO(yshui) make the target window a parameter - backend_t *(*init)(session_t *)attr_nonnull(1); - void (*deinit)(backend_t *backend_data) attr_nonnull(1); - - /// Called when rendering will be stopped for an unknown amount of - /// time (e.g. when screen is unredirected). Free some resources. - /// - /// Optional, not yet used - void (*pause)(backend_t *backend_data, session_t *ps); - - /// Called before rendering is resumed - /// - /// Optional, not yet used - void (*resume)(backend_t *backend_data, session_t *ps); - - /// Called when root property changed, returns the new - /// backend_data. Even if the backend_data changed, all - /// the existing image data returned by this backend should - /// remain valid. - /// - /// Optional - void *(*root_change)(backend_t *backend_data, session_t *ps); - - // =========== Rendering ============ - - // NOTE: general idea about reg_paint/reg_op vs reg_visible is that reg_visible is - // merely a hint. Ignoring reg_visible entirely don't affect the correctness of - // the operation performed. OTOH reg_paint/reg_op is part of the parameters of the - // operation, and must be honored in order to complete the operation correctly. - - // NOTE: due to complications introduced by use-damage and blur, the rendering API - // is a bit weird. The idea is, `compose` and `blur` have to update a temporary - // buffer, because `blur` requires data from an area slightly larger than the area - // that will be visible. So the area outside the visible area has to be rendered, - // but we have to discard the result (because the result of blurring that area - // will be wrong). That's why we cannot render into the back buffer directly. - // After rendering is done, `present` is called to update a portion of the actual - // back buffer, then present it to the target (or update the target directly, - // if not back buffered). - - /// Called before when a new frame starts. - /// - /// Optional - void (*prepare)(backend_t *backend_data, const region_t *reg_damage); - - /** - * Paint the content of an image onto the rendering buffer - * - * @param backend_data the backend data - * @param image_data the image to paint - * @param dst_x1, dst_y1 the top left corner of the image in the target - * @param dst_x2, dst_y2 the top right corner of the image in the target - * @param reg_paint the clip region, in target coordinates - * @param reg_visible the visible region, in target coordinates - */ - void (*compose)(backend_t *backend_data, void *image_data, - int dst_x1, int dst_y1, int dst_x2, int dst_y2, - const region_t *reg_paint, const region_t *reg_visible); - - /// Fill rectangle of the rendering buffer, mostly for debug purposes, optional. - void (*fill)(backend_t *backend_data, struct color, const region_t *clip); - - /// Blur a given region of the rendering buffer. - bool (*blur)(backend_t *backend_data, double opacity, void *blur_ctx, - const region_t *reg_blur, const region_t *reg_visible) - attr_nonnull(1, 3, 4, 5); - - /// Update part of the back buffer with the rendering buffer, then present the - /// back buffer onto the target window (if not back buffered, update part of the - /// target window directly). - /// - /// Optional, if NULL, indicates the backend doesn't have render output - /// - /// @param region part of the target that should be updated - void (*present)(backend_t *backend_data, const region_t *region) attr_nonnull(1, 2); - - /** - * Bind a X pixmap to the backend's internal image data structure. - * - * @param backend_data backend data - * @param pixmap X pixmap to bind - * @param fmt information of the pixmap's visual - * @param owned whether the ownership of the pixmap is transfered to the backend - * @return backend internal data structure bound with this pixmap - */ - void *(*bind_pixmap)(backend_t *backend_data, xcb_pixmap_t pixmap, - struct xvisual_info fmt, bool owned); - - /// Create a shadow image based on the parameters - /// Default implementation: default_backend_render_shadow - void *(*render_shadow)(backend_t *backend_data, int width, int height, - const conv *kernel, double r, double g, double b, double a); - - // ============ Resource management =========== - - /// Free resources associated with an image data structure - void (*release_image)(backend_t *backend_data, void *img_data) attr_nonnull(1, 2); - - // =========== Query =========== - - /// Return if image is not completely opaque. - /// - /// This function is needed because some backend might change the content of the - /// window (e.g. when using a custom shader with the glx backend), so only the - /// backend knows if an image is transparent. - bool (*is_image_transparent)(backend_t *backend_data, void *image_data) - attr_nonnull(1, 2); - - /// Get the age of the buffer content we are currently rendering ontop - /// of. The buffer that has just been `present`ed has a buffer age of 1. - /// Everytime `present` is called, buffers get older. Return -1 if the - /// buffer is empty. - /// - /// Optional - int (*buffer_age)(backend_t *backend_data); - - /// The maximum number buffer_age might return. - int max_buffer_age; - - // =========== Post-processing ============ - - /* TODO(yshui) Consider preserving the order of image ops. - * Currently in both backends, the image ops are applied lazily when needed. - * However neither backends preserve the order of image ops, they just applied all - * pending lazy ops in a pre-determined fixed order, regardless in which order - * they were originally applied. This might lead to inconsistencies.*/ - - /** - * Change image properties - * - * @param backend_data backend data - * @param prop the property to change - * @param image_data an image data structure returned by the backend - * @param args property value - * @return whether the operation is successful - */ - bool (*set_image_property)(backend_t *backend_data, enum image_properties prop, - void *image_data, void *args); - - /** - * Manipulate an image. Image properties are untouched. - * - * @param backend_data backend data - * @param op the operation to perform - * @param image_data an image data structure returned by the backend - * @param reg_op the clip region, define the part of the image to be - * operated on. - * @param reg_visible define the part of the image that will eventually - * be visible on target. this is a hint to the backend - * for optimization purposes. - * @param args extra arguments, operation specific - * @return whether the operation is successful - */ - bool (*image_op)(backend_t *backend_data, enum image_operations op, void *image_data, - const region_t *reg_op, const region_t *reg_visible, void *args); - - /** - * Read the color of the pixel at given position of the given image. Image - * properties have no effect. - * - * @param backend_data backend_data - * @param image_data an image data structure previously returned by the - * backend. the image to read pixel from. - * @param x, y coordinate of the pixel to read - * @param[out] color the color of the pixel - * @return whether the operation is successful - */ - bool (*read_pixel)(backend_t *backend_data, void *image_data, int x, int y, - struct color *output); - - /// Create another instance of the `image_data`. All `image_op` and - /// `set_image_property` calls on the returned image should not affect the - /// original image - void *(*clone_image)(backend_t *base, const void *image_data, - const region_t *reg_visible); - - /// Create a blur context that can be used to call `blur` - void *(*create_blur_context)(backend_t *base, enum blur_method, void *args); - /// Destroy a blur context - void (*destroy_blur_context)(backend_t *base, void *ctx); - /// Get how many pixels outside of the blur area is needed for blur - void (*get_blur_size)(void *blur_context, int *width, int *height); - - // =========== Hooks ============ - /// Let the backend hook into the event handling queue - /// Not implemented yet - void (*set_ready_callback)(backend_t *, backend_ready_callback_t cb); - /// Called right after the core has handled its events. - /// Not implemented yet - void (*handle_events)(backend_t *); - // =========== Misc ============ - /// Return the driver that is been used by the backend - enum driver (*detect_driver)(backend_t *backend_data); - - void (*diagnostics)(backend_t *backend_data); -}; - -extern struct backend_operations *backend_list[]; - -void paint_all_new(session_t *ps, struct managed_win *const t, bool ignore_damage) - attr_nonnull(1); diff --git a/src/backend/backend_common.c b/src/backend/backend_common.c deleted file mode 100644 index c0377d3..0000000 --- a/src/backend/backend_common.c +++ /dev/null @@ -1,480 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Yuxuan Shui <[email protected]> -#include <math.h> -#include <string.h> -#include <xcb/render.h> -#include <xcb/xcb_image.h> -#include <xcb/xcb_renderutil.h> - -#include "backend/backend.h" -#include "backend/backend_common.h" -#include "common.h" -#include "config.h" -#include "kernel.h" -#include "log.h" -#include "utils.h" -#include "win.h" -#include "x.h" - -/** - * Generate a 1x1 <code>Picture</code> of a particular color. - */ -xcb_render_picture_t solid_picture(xcb_connection_t *c, xcb_drawable_t d, 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(c, argb ? 32 : 8, d, 1, 1); - if (!pixmap) - return XCB_NONE; - - pa.repeat = 1; - picture = x_create_picture_with_standard_and_pixmap( - c, argb ? XCB_PICT_STANDARD_ARGB_32 : XCB_PICT_STANDARD_A_8, pixmap, - XCB_RENDER_CP_REPEAT, &pa); - - if (!picture) { - xcb_free_pixmap(c, pixmap); - return XCB_NONE; - } - - col.alpha = (uint16_t)(a * 0xffff); - col.red = (uint16_t)(r * 0xffff); - col.green = (uint16_t)(g * 0xffff); - col.blue = (uint16_t)(b * 0xffff); - - rect.x = 0; - rect.y = 0; - rect.width = 1; - rect.height = 1; - - xcb_render_fill_rectangles(c, XCB_RENDER_PICT_OP_SRC, picture, col, 1, &rect); - xcb_free_pixmap(c, pixmap); - - return picture; -} - -xcb_image_t * -make_shadow(xcb_connection_t *c, const conv *kernel, double opacity, int width, int height) { - /* - * We classify shadows into 4 kinds of regions - * r = shadow radius - * (0, 0) is the top left of the window itself - * -r r width-r width+r - * -r +-----+---------+-----+ - * | 1 | 2 | 1 | - * r +-----+---------+-----+ - * | 2 | 3 | 2 | - * height-r +-----+---------+-----+ - * | 1 | 2 | 1 | - * height+r +-----+---------+-----+ - */ - xcb_image_t *ximage; - const double *shadow_sum = kernel->rsum; - assert(shadow_sum); - // We only support square kernels for shadow - assert(kernel->w == kernel->h); - int d = kernel->w; - int r = d / 2; - int swidth = width + r * 2, sheight = height + r * 2; - - assert(d % 2 == 1); - assert(d > 0); - - ximage = xcb_image_create_native(c, to_u16_checked(swidth), to_u16_checked(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; - long sstride = ximage->stride; - - // If the window body is smaller than the kernel, we do convolution directly - if (width < r * 2 && height < r * 2) { - for (int y = 0; y < sheight; y++) { - for (int x = 0; x < swidth; x++) { - double sum = sum_kernel_normalized( - kernel, d - x - 1, d - y - 1, width, height); - data[y * sstride + x] = (uint8_t)(sum * 255.0); - } - } - return ximage; - } - - if (height < r * 2) { - // Implies width >= r * 2 - // If the window height is smaller than the kernel, we divide - // the window like this: - // -r r width-r width+r - // +------+-------------+------+ - // | | | | - // +------+-------------+------+ - for (int y = 0; y < sheight; y++) { - for (int x = 0; x < r * 2; x++) { - double sum = sum_kernel_normalized(kernel, d - x - 1, - d - y - 1, d, height) * - 255.0; - data[y * sstride + x] = (uint8_t)sum; - data[y * sstride + swidth - x - 1] = (uint8_t)sum; - } - } - for (int y = 0; y < sheight; y++) { - double sum = - sum_kernel_normalized(kernel, 0, d - y - 1, d, height) * 255.0; - memset(&data[y * sstride + r * 2], (uint8_t)sum, - (size_t)(width - 2 * r)); - } - return ximage; - } - if (width < r * 2) { - // Similarly, for width smaller than kernel - for (int y = 0; y < r * 2; y++) { - for (int x = 0; x < swidth; x++) { - double sum = sum_kernel_normalized(kernel, d - x - 1, - d - y - 1, width, d) * - 255.0; - data[y * sstride + x] = (uint8_t)sum; - data[(sheight - y - 1) * sstride + x] = (uint8_t)sum; - } - } - for (int x = 0; x < swidth; x++) { - double sum = - sum_kernel_normalized(kernel, d - x - 1, 0, width, d) * 255.0; - for (int y = r * 2; y < height; y++) { - data[y * sstride + x] = (uint8_t)sum; - } - } - return ximage; - } - - // Implies: width >= r * 2 && height >= r * 2 - - // Fill part 3 - for (int y = r; y < height + r; y++) { - memset(data + sstride * y + r, (uint8_t)(255 * opacity), (size_t)width); - } - - // Part 1 - for (int y = 0; y < r * 2; y++) { - for (int x = 0; x < r * 2; x++) { - double tmpsum = shadow_sum[y * d + x] * opacity * 255.0; - data[y * sstride + x] = (uint8_t)tmpsum; - data[(sheight - y - 1) * sstride + x] = (uint8_t)tmpsum; - data[(sheight - y - 1) * sstride + (swidth - x - 1)] = (uint8_t)tmpsum; - data[y * sstride + (swidth - x - 1)] = (uint8_t)tmpsum; - } - } - - // Part 2, top/bottom - for (int y = 0; y < r * 2; y++) { - double tmpsum = shadow_sum[d * y + d - 1] * opacity * 255.0; - memset(&data[y * sstride + r * 2], (uint8_t)tmpsum, (size_t)(width - r * 2)); - memset(&data[(sheight - y - 1) * sstride + r * 2], (uint8_t)tmpsum, - (size_t)(width - r * 2)); - } - - // Part 2, left/right - for (int x = 0; x < r * 2; x++) { - double tmpsum = shadow_sum[d * (d - 1) + x] * opacity * 255.0; - for (int y = r * 2; y < height; y++) { - data[y * sstride + x] = (uint8_t)tmpsum; - data[y * sstride + (swidth - x - 1)] = (uint8_t)tmpsum; - } - } - - return ximage; -} - -/** - * Generate shadow <code>Picture</code> for a window. - */ -bool build_shadow(xcb_connection_t *c, xcb_drawable_t d, double opacity, const int width, - const int height, const conv *kernel, xcb_render_picture_t shadow_pixel, - xcb_pixmap_t *pixmap, xcb_render_picture_t *pict) { - xcb_image_t *shadow_image = NULL; - xcb_pixmap_t shadow_pixmap = 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(c, kernel, opacity, width, height); - if (!shadow_image) { - log_error("Failed to make shadow"); - return false; - } - - shadow_pixmap = x_create_pixmap(c, 8, d, shadow_image->width, shadow_image->height); - shadow_pixmap_argb = - x_create_pixmap(c, 32, d, 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( - c, XCB_PICT_STANDARD_A_8, shadow_pixmap, 0, NULL); - shadow_picture_argb = x_create_picture_with_standard_and_pixmap( - c, XCB_PICT_STANDARD_ARGB_32, shadow_pixmap_argb, 0, NULL); - if (!shadow_picture || !shadow_picture_argb) { - goto shadow_picture_err; - } - - gc = x_new_id(c); - xcb_create_gc(c, gc, shadow_pixmap, 0, NULL); - - // We need to make room for protocol metadata in the request. The metadata should - // be 24 bytes plus padding, let's be generous and give it 1kb - auto maximum_image_size = xcb_get_maximum_request_length(c) * 4 - 1024; - auto maximum_row = - to_u16_checked(clamp(maximum_image_size / shadow_image->stride, 0, UINT16_MAX)); - if (maximum_row <= 0) { - // TODO(yshui) Upload image with XShm - log_error("X server request size limit is too restrictive, or the shadow " - "image is too wide for us to send a single row of the shadow " - "image. Shadow size: %dx%d", - width, height); - goto shadow_picture_err; - } - - for (uint32_t row = 0; row < shadow_image->height; row += maximum_row) { - auto batch_height = maximum_row; - if (batch_height > shadow_image->height - row) { - batch_height = to_u16_checked(shadow_image->height - row); - } - - uint32_t offset = row * shadow_image->stride / sizeof(*shadow_image->data); - xcb_put_image(c, (uint8_t)shadow_image->format, shadow_pixmap, gc, - shadow_image->width, batch_height, 0, to_i16_checked(row), - 0, shadow_image->depth, shadow_image->stride * batch_height, - shadow_image->data + offset); - } - - xcb_render_composite(c, XCB_RENDER_PICT_OP_SRC, shadow_pixel, shadow_picture, - shadow_picture_argb, 0, 0, 0, 0, 0, 0, shadow_image->width, - shadow_image->height); - - *pixmap = shadow_pixmap_argb; - *pict = shadow_picture_argb; - - xcb_free_gc(c, gc); - xcb_image_destroy(shadow_image); - xcb_free_pixmap(c, shadow_pixmap); - xcb_render_free_picture(c, shadow_picture); - - return true; - -shadow_picture_err: - if (shadow_image) { - xcb_image_destroy(shadow_image); - } - if (shadow_pixmap) { - xcb_free_pixmap(c, shadow_pixmap); - } - if (shadow_pixmap_argb) { - xcb_free_pixmap(c, shadow_pixmap_argb); - } - if (shadow_picture) { - xcb_render_free_picture(c, shadow_picture); - } - if (shadow_picture_argb) { - xcb_render_free_picture(c, shadow_picture_argb); - } - if (gc) { - xcb_free_gc(c, gc); - } - - return false; -} - -void * -default_backend_render_shadow(backend_t *backend_data, int width, int height, - const conv *kernel, double r, double g, double b, double a) { - xcb_pixmap_t shadow_pixel = solid_picture(backend_data->c, backend_data->root, - true, 1, r, g, b), - shadow = XCB_NONE; - xcb_render_picture_t pict = XCB_NONE; - - if (!build_shadow(backend_data->c, backend_data->root, a, width, height, kernel, - shadow_pixel, &shadow, &pict)) { - return NULL; - } - - auto visual = x_get_visual_for_standard(backend_data->c, XCB_PICT_STANDARD_ARGB_32); - void *ret = backend_data->ops->bind_pixmap( - backend_data, shadow, x_get_visual_info(backend_data->c, visual), true); - xcb_render_free_picture(backend_data->c, pict); - return ret; -} - -static struct conv **generate_box_blur_kernel(struct box_blur_args *args, int *kernel_count) { - int r = args->size * 2 + 1; - assert(r > 0); - auto ret = ccalloc(2, struct conv *); - ret[0] = cvalloc(sizeof(struct conv) + sizeof(double) * (size_t)r); - ret[1] = cvalloc(sizeof(struct conv) + sizeof(double) * (size_t)r); - ret[0]->w = r; - ret[0]->h = 1; - ret[1]->w = 1; - ret[1]->h = r; - for (int i = 0; i < r; i++) { - ret[0]->data[i] = 1; - ret[1]->data[i] = 1; - } - *kernel_count = 2; - return ret; -} - -static struct conv ** -generate_gaussian_blur_kernel(struct gaussian_blur_args *args, int *kernel_count) { - int r = args->size * 2 + 1; - assert(r > 0); - auto ret = ccalloc(2, struct conv *); - ret[0] = cvalloc(sizeof(struct conv) + sizeof(double) * (size_t)r); - ret[1] = cvalloc(sizeof(struct conv) + sizeof(double) * (size_t)r); - ret[0]->w = r; - ret[0]->h = 1; - ret[1]->w = 1; - ret[1]->h = r; - for (int i = 0; i <= args->size; i++) { - ret[0]->data[i] = ret[0]->data[r - i - 1] = - 1.0 / (sqrt(2.0 * M_PI) * args->deviation) * - exp(-(args->size - i) * (args->size - i) / - (2 * args->deviation * args->deviation)); - ret[1]->data[i] = ret[1]->data[r - i - 1] = ret[0]->data[i]; - } - *kernel_count = 2; - return ret; -} - -/// Generate blur kernels for gaussian and box blur methods. Generated kernel is not -/// normalized, and the center element will always be 1. -struct conv **generate_blur_kernel(enum blur_method method, void *args, int *kernel_count) { - switch (method) { - case BLUR_METHOD_BOX: return generate_box_blur_kernel(args, kernel_count); - case BLUR_METHOD_GAUSSIAN: - return generate_gaussian_blur_kernel(args, kernel_count); - default: break; - } - return NULL; -} - -/// Generate kernel parameters for dual-kawase blur method. Falls back on approximating -/// standard gauss radius if strength is zero or below. -struct dual_kawase_params *generate_dual_kawase_params(void *args) { - struct dual_kawase_blur_args *blur_args = args; - static const struct { - int iterations; /// Number of down- and upsample iterations - float offset; /// Sample offset in half-pixels - int min_radius; /// Approximate gauss-blur with at least this - /// radius and std-deviation - } strength_levels[20] = { - {.iterations = 1, .offset = 1.25f, .min_radius = 1}, // LVL 1 - {.iterations = 1, .offset = 2.25f, .min_radius = 6}, // LVL 2 - {.iterations = 2, .offset = 2.00f, .min_radius = 11}, // LVL 3 - {.iterations = 2, .offset = 3.00f, .min_radius = 17}, // LVL 4 - {.iterations = 2, .offset = 4.25f, .min_radius = 24}, // LVL 5 - {.iterations = 3, .offset = 2.50f, .min_radius = 32}, // LVL 6 - {.iterations = 3, .offset = 3.25f, .min_radius = 40}, // LVL 7 - {.iterations = 3, .offset = 4.25f, .min_radius = 51}, // LVL 8 - {.iterations = 3, .offset = 5.50f, .min_radius = 67}, // LVL 9 - {.iterations = 4, .offset = 3.25f, .min_radius = 83}, // LVL 10 - {.iterations = 4, .offset = 4.00f, .min_radius = 101}, // LVL 11 - {.iterations = 4, .offset = 5.00f, .min_radius = 123}, // LVL 12 - {.iterations = 4, .offset = 6.00f, .min_radius = 148}, // LVL 13 - {.iterations = 4, .offset = 7.25f, .min_radius = 178}, // LVL 14 - {.iterations = 4, .offset = 8.25f, .min_radius = 208}, // LVL 15 - {.iterations = 5, .offset = 4.50f, .min_radius = 236}, // LVL 16 - {.iterations = 5, .offset = 5.25f, .min_radius = 269}, // LVL 17 - {.iterations = 5, .offset = 6.25f, .min_radius = 309}, // LVL 18 - {.iterations = 5, .offset = 7.25f, .min_radius = 357}, // LVL 19 - {.iterations = 5, .offset = 8.50f, .min_radius = 417}, // LVL 20 - }; - - auto params = ccalloc(1, struct dual_kawase_params); - params->iterations = 0; - params->offset = 1.0f; - - if (blur_args->strength <= 0 && blur_args->size) { - // find highest level that approximates blur-strength with the selected - // gaussian blur-radius - int lvl = 1; - while (strength_levels[lvl - 1].min_radius < blur_args->size && lvl < 20) { - ++lvl; - } - blur_args->strength = lvl; - } - if (blur_args->strength <= 0) { - // default value - blur_args->strength = 5; - } - - assert(blur_args->strength > 0 && blur_args->strength <= 20); - params->iterations = strength_levels[blur_args->strength - 1].iterations; - params->offset = strength_levels[blur_args->strength - 1].offset; - - // Expand sample area to cover the smallest texture / highest selected iteration: - // - Smallest texture dimensions are halved `iterations`-times - // - Upsample needs pixels two-times `offset` away from the border - // - Plus one for interpolation differences - params->expand = (1 << params->iterations) * 2 * (int)ceil(params->offset) + 1; - - return params; -} - -void *default_clone_image(backend_t *base attr_unused, const void *image_data, - const region_t *reg_visible attr_unused) { - auto new_img = ccalloc(1, struct backend_image); - *new_img = *(struct backend_image *)image_data; - new_img->inner->refcount++; - return new_img; -} - -bool default_set_image_property(backend_t *base attr_unused, enum image_properties op, - void *image_data, void *arg) { - struct backend_image *tex = image_data; - int *iargs = arg; - bool *bargs = arg; - double *dargs = arg; - switch (op) { - case IMAGE_PROPERTY_INVERTED: tex->color_inverted = bargs[0]; break; - case IMAGE_PROPERTY_DIM_LEVEL: tex->dim = dargs[0]; break; - case IMAGE_PROPERTY_OPACITY: tex->opacity = dargs[0]; break; - case IMAGE_PROPERTY_EFFECTIVE_SIZE: - // texture is already set to repeat, so nothing else we need to do - tex->ewidth = iargs[0]; - tex->eheight = iargs[1]; - break; - case IMAGE_PROPERTY_MAX_BRIGHTNESS: tex->max_brightness = dargs[0]; break; - } - - return true; -} - -bool default_is_image_transparent(backend_t *base attr_unused, void *image_data) { - struct backend_image *img = image_data; - return img->opacity < 1 || img->inner->has_alpha; -} - -struct backend_image *default_new_backend_image(int w, int h) { - auto ret = ccalloc(1, struct backend_image); - ret->opacity = 1; - ret->dim = 0; - ret->max_brightness = 1; - ret->eheight = h; - ret->ewidth = w; - ret->color_inverted = false; - return ret; -} - -void init_backend_base(struct backend_base *base, session_t *ps) { - base->c = ps->c; - base->loop = ps->loop; - base->root = ps->root; - base->busy = false; - base->ops = NULL; -} diff --git a/src/backend/backend_common.h b/src/backend/backend_common.h deleted file mode 100644 index 5c9c806..0000000 --- a/src/backend/backend_common.h +++ /dev/null @@ -1,78 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Yuxuan Shui <[email protected]> -#pragma once - -#include <xcb/render.h> -#include <xcb/xcb_image.h> - -#include <stdbool.h> - -#include "backend.h" -#include "config.h" -#include "region.h" - -typedef struct session session_t; -typedef struct win win; -typedef struct conv conv; -typedef struct backend_base backend_t; -struct backend_operations; - -struct dual_kawase_params { - /// Number of downsample passes - int iterations; - /// Pixel offset for down- and upsample - float offset; - /// Save area around blur target (@ref resize_width, @ref resize_height) - int expand; -}; - -struct backend_image_inner_base { - int refcount; - bool has_alpha; -}; - -struct backend_image { - // Backend dependent inner image data - struct backend_image_inner_base *inner; - double opacity; - double dim; - double max_brightness; - // Effective size of the image - int ewidth, eheight; - bool color_inverted; -}; - -bool build_shadow(xcb_connection_t *, xcb_drawable_t, double opacity, int width, - int height, const conv *kernel, xcb_render_picture_t shadow_pixel, - xcb_pixmap_t *pixmap, xcb_render_picture_t *pict); - -xcb_render_picture_t solid_picture(xcb_connection_t *, xcb_drawable_t, bool argb, - double a, double r, double g, double b); - -xcb_image_t * -make_shadow(xcb_connection_t *c, const conv *kernel, double opacity, int width, int height); - -/// The default implementation of `is_win_transparent`, it simply looks at win::mode. So -/// this is not suitable for backends that alter the content of windows -bool default_is_win_transparent(void *, win *, void *); - -/// The default implementation of `is_frame_transparent`, it uses win::frame_opacity. Same -/// caveat as `default_is_win_transparent` applies. -bool default_is_frame_transparent(void *, win *, void *); - -void * -default_backend_render_shadow(backend_t *backend_data, int width, int height, - const conv *kernel, double r, double g, double b, double a); - -void init_backend_base(struct backend_base *base, session_t *ps); - -struct conv **generate_blur_kernel(enum blur_method method, void *args, int *kernel_count); -struct dual_kawase_params *generate_dual_kawase_params(void *args); - -void *default_clone_image(backend_t *base, const void *image_data, const region_t *reg); -void *default_resize_image(backend_t *base, const void *image_data, uint16_t desired_width, - uint16_t desired_height, const region_t *reg); -bool default_is_image_transparent(backend_t *base attr_unused, void *image_data); -bool default_set_image_property(backend_t *base attr_unused, enum image_properties op, - void *image_data, void *arg); -struct backend_image *default_new_backend_image(int w, int h); diff --git a/src/backend/driver.c b/src/backend/driver.c deleted file mode 100644 index a41d2fd..0000000 --- a/src/backend/driver.c +++ /dev/null @@ -1,82 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Yuxuan Shui <[email protected]> -#include <stdlib.h> -#include <string.h> - -#include <xcb/randr.h> -#include <xcb/xcb.h> - -#include "backend/backend.h" -#include "backend/driver.h" -#include "common.h" -#include "compiler.h" -#include "log.h" - -/// Apply driver specified global workarounds. It's safe to call this multiple times. -void apply_driver_workarounds(struct session *ps, enum driver driver) { - if (driver & DRIVER_NVIDIA) { - // setenv("__GL_YIELD", "usleep", true); - setenv("__GL_MaxFramesAllowed", "1", true); - ps->o.xrender_sync_fence = true; - } -} - -enum driver detect_driver(xcb_connection_t *c, backend_t *backend_data, xcb_window_t window) { - enum driver ret = 0; - // First we try doing backend agnostic detection using RANDR - // There's no way to query the X server about what driver is loaded, so RANDR is - // our best shot. - auto randr_version = xcb_randr_query_version_reply( - c, xcb_randr_query_version(c, XCB_RANDR_MAJOR_VERSION, XCB_RANDR_MINOR_VERSION), - NULL); - if (randr_version && - (randr_version->major_version > 1 || randr_version->minor_version >= 4)) { - auto r = xcb_randr_get_providers_reply( - c, xcb_randr_get_providers(c, window), NULL); - if (r == NULL) { - log_warn("Failed to get RANDR providers"); - free(randr_version); - return 0; - } - - auto providers = xcb_randr_get_providers_providers(r); - for (auto i = 0; i < xcb_randr_get_providers_providers_length(r); i++) { - auto r2 = xcb_randr_get_provider_info_reply( - c, xcb_randr_get_provider_info(c, providers[i], r->timestamp), NULL); - if (r2 == NULL) { - continue; - } - if (r2->num_outputs == 0) { - free(r2); - continue; - } - - auto name_len = xcb_randr_get_provider_info_name_length(r2); - assert(name_len >= 0); - auto name = - strndup(xcb_randr_get_provider_info_name(r2), (size_t)name_len); - if (strcasestr(name, "modesetting") != NULL) { - ret |= DRIVER_MODESETTING; - } else if (strcasestr(name, "Radeon") != NULL) { - // Be conservative, add both radeon drivers - ret |= DRIVER_AMDGPU | DRIVER_RADEON; - } else if (strcasestr(name, "NVIDIA") != NULL) { - ret |= DRIVER_NVIDIA; - } else if (strcasestr(name, "nouveau") != NULL) { - ret |= DRIVER_NOUVEAU; - } else if (strcasestr(name, "Intel") != NULL) { - ret |= DRIVER_INTEL; - } - free(name); - free(r2); - } - free(r); - } - free(randr_version); - - // If the backend supports driver detection, use that as well - if (backend_data && backend_data->ops->detect_driver) { - ret |= backend_data->ops->detect_driver(backend_data); - } - return ret; -} diff --git a/src/backend/driver.h b/src/backend/driver.h deleted file mode 100644 index a37cda3..0000000 --- a/src/backend/driver.h +++ /dev/null @@ -1,62 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Yuxuan Shui <[email protected]> - -#pragma once - -#include <stddef.h> -#include <stdio.h> -#include <xcb/xcb.h> - -#include "utils.h" - -struct session; -struct backend_base; - -// A list of known driver quirks: -// * NVIDIA driver doesn't like seeing the same pixmap under different -// ids, so avoid naming the pixmap again when it didn't actually change. - -/// A list of possible drivers. -/// The driver situation is a bit complicated. There are two drivers we care about: the -/// DDX, and the OpenGL driver. They are usually paired, but not always, since there is -/// also the generic modesetting driver. -/// This enum represents _both_ drivers. -enum driver { - DRIVER_AMDGPU = 1, // AMDGPU for DDX, radeonsi for OpenGL - DRIVER_RADEON = 2, // ATI for DDX, mesa r600 for OpenGL - DRIVER_FGLRX = 4, - DRIVER_NVIDIA = 8, - DRIVER_NOUVEAU = 16, - DRIVER_INTEL = 32, - DRIVER_MODESETTING = 64, -}; - -static const char *driver_names[] = { - "AMDGPU", "Radeon", "fglrx", "NVIDIA", "nouveau", "Intel", "modesetting", -}; - -/// Return a list of all drivers currently in use by the X server. -/// Note, this is a best-effort test, so no guarantee all drivers will be detected. -enum driver detect_driver(xcb_connection_t *, struct backend_base *, xcb_window_t); - -/// Apply driver specified global workarounds. It's safe to call this multiple times. -void apply_driver_workarounds(struct session *ps, enum driver); - -// Print driver names to stdout, for diagnostics -static inline void print_drivers(enum driver drivers) { - const char *seen_drivers[ARR_SIZE(driver_names)]; - int driver_count = 0; - for (size_t i = 0; i < ARR_SIZE(driver_names); i++) { - if (drivers & (1ul << i)) { - seen_drivers[driver_count++] = driver_names[i]; - } - } - - if (driver_count > 0) { - printf("%s", seen_drivers[0]); - for (int i = 1; i < driver_count; i++) { - printf(", %s", seen_drivers[i]); - } - } - printf("\n"); -} diff --git a/src/backend/dummy/dummy.c b/src/backend/dummy/dummy.c deleted file mode 100644 index a057b97..0000000 --- a/src/backend/dummy/dummy.c +++ /dev/null @@ -1,174 +0,0 @@ -#include <uthash.h> -#include <xcb/xcb.h> - -#include "backend/backend.h" -#include "backend/backend_common.h" -#include "common.h" -#include "compiler.h" -#include "config.h" -#include "log.h" -#include "region.h" -#include "types.h" -#include "uthash_extra.h" -#include "utils.h" -#include "x.h" - -struct dummy_image { - xcb_pixmap_t pixmap; - bool transparent; - int *refcount; - UT_hash_handle hh; -}; - -struct dummy_data { - struct backend_base base; - struct dummy_image *images; -}; - -struct backend_base *dummy_init(struct session *ps attr_unused) { - auto ret = (struct backend_base *)ccalloc(1, struct dummy_data); - ret->c = ps->c; - ret->loop = ps->loop; - ret->root = ps->root; - ret->busy = false; - return ret; -} - -void dummy_deinit(struct backend_base *data) { - auto dummy = (struct dummy_data *)data; - HASH_ITER2(dummy->images, img) { - log_warn("Backend image for pixmap %#010x is not freed", img->pixmap); - HASH_DEL(dummy->images, img); - free(img->refcount); - free(img); - } - free(dummy); -} - -static void dummy_check_image(struct backend_base *base, const struct dummy_image *img) { - auto dummy = (struct dummy_data *)base; - struct dummy_image *tmp = NULL; - HASH_FIND_INT(dummy->images, &img->pixmap, tmp); - if (!tmp) { - log_warn("Using an invalid (possibly freed) image"); - assert(false); - } - assert(*tmp->refcount > 0); -} - -void dummy_compose(struct backend_base *base, void *image, int dst_x1 attr_unused, - int dst_y1 attr_unused, int dst_x2 attr_unused, int dst_y2 attr_unused, - const region_t *reg_paint attr_unused, const region_t *reg_visible attr_unused) { - dummy_check_image(base, image); -} - -void dummy_fill(struct backend_base *backend_data attr_unused, struct color c attr_unused, - const region_t *clip attr_unused) { -} - -bool dummy_blur(struct backend_base *backend_data attr_unused, double opacity attr_unused, - void *blur_ctx attr_unused, const region_t *reg_blur attr_unused, - const region_t *reg_visible attr_unused) { - return true; -} - -void *dummy_bind_pixmap(struct backend_base *base, xcb_pixmap_t pixmap, - struct xvisual_info fmt, bool owned attr_unused) { - auto dummy = (struct dummy_data *)base; - struct dummy_image *img = NULL; - HASH_FIND_INT(dummy->images, &pixmap, img); - if (img) { - (*img->refcount)++; - return img; - } - - img = ccalloc(1, struct dummy_image); - img->pixmap = pixmap; - img->transparent = fmt.alpha_size != 0; - img->refcount = ccalloc(1, int); - *img->refcount = 1; - - HASH_ADD_INT(dummy->images, pixmap, img); - return (void *)img; -} - -void dummy_release_image(backend_t *base, void *image) { - auto dummy = (struct dummy_data *)base; - auto img = (struct dummy_image *)image; - assert(*img->refcount > 0); - (*img->refcount)--; - if (*img->refcount == 0) { - HASH_DEL(dummy->images, img); - free(img->refcount); - free(img); - } -} - -bool dummy_is_image_transparent(struct backend_base *base, void *image) { - auto img = (struct dummy_image *)image; - dummy_check_image(base, img); - return img->transparent; -} - -int dummy_buffer_age(struct backend_base *base attr_unused) { - return 2; -} - -bool dummy_image_op(struct backend_base *base, enum image_operations op attr_unused, - void *image, const region_t *reg_op attr_unused, - const region_t *reg_visible attr_unused, void *args attr_unused) { - dummy_check_image(base, image); - return true; -} - -bool dummy_set_image_property(struct backend_base *base, enum image_properties prop attr_unused, - void *image, void *arg attr_unused) { - dummy_check_image(base, image); - return true; -} - -void *dummy_clone_image(struct backend_base *base, const void *image, - const region_t *reg_visible attr_unused) { - auto img = (const struct dummy_image *)image; - dummy_check_image(base, img); - (*img->refcount)++; - return (void *)img; -} - -void *dummy_create_blur_context(struct backend_base *base attr_unused, - enum blur_method method attr_unused, void *args attr_unused) { - static int dummy_context; - return &dummy_context; -} - -void dummy_destroy_blur_context(struct backend_base *base attr_unused, void *ctx attr_unused) { -} - -void dummy_get_blur_size(void *ctx attr_unused, int *width, int *height) { - // These numbers are arbitrary, to make sure the reisze_region code path is - // covered. - *width = 5; - *height = 5; -} - -struct backend_operations dummy_ops = { - .init = dummy_init, - .deinit = dummy_deinit, - .compose = dummy_compose, - .fill = dummy_fill, - .blur = dummy_blur, - .bind_pixmap = dummy_bind_pixmap, - .render_shadow = default_backend_render_shadow, - .release_image = dummy_release_image, - .is_image_transparent = dummy_is_image_transparent, - .buffer_age = dummy_buffer_age, - .max_buffer_age = 5, - - .image_op = dummy_image_op, - .clone_image = dummy_clone_image, - .set_image_property = dummy_set_image_property, - .create_blur_context = dummy_create_blur_context, - .destroy_blur_context = dummy_destroy_blur_context, - .get_blur_size = dummy_get_blur_size, - -}; diff --git a/src/backend/gl/gl_common.c b/src/backend/gl/gl_common.c deleted file mode 100644 index 8cc5a05..0000000 --- a/src/backend/gl/gl_common.c +++ /dev/null @@ -1,1922 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Yuxuan Shui <[email protected]> -#include <GL/gl.h> -#include <GL/glext.h> -#include <locale.h> -#include <stdbool.h> -#include <stdio.h> -#include <string.h> -#include <xcb/render.h> // for xcb_render_fixed_t, XXX - -#include "backend/backend.h" -#include "common.h" -#include "compiler.h" -#include "config.h" -#include "kernel.h" -#include "log.h" -#include "region.h" -#include "string_utils.h" -#include "types.h" -#include "utils.h" - -#include "backend/backend_common.h" -#include "backend/gl/gl_common.h" - -#define GLSL(version, ...) "#version " #version "\n" #__VA_ARGS__ -#define QUOTE(...) #__VA_ARGS__ - -static const GLuint vert_coord_loc = 0; -static const GLuint vert_in_texcoord_loc = 1; - -struct gl_blur_context { - enum blur_method method; - gl_blur_shader_t *blur_shader; - - /// Temporary textures used for blurring - GLuint *blur_textures; - int blur_texture_count; - /// Temporary fbos used for blurring - GLuint *blur_fbos; - int blur_fbo_count; - - /// Cached dimensions of each blur_texture. They are the same size as the target, - /// so they are always big enough without resizing. - /// Turns out calling glTexImage to resize is expensive, so we avoid that. - struct texture_size { - int width; - int height; - } * texture_sizes; - - /// Cached dimensions of the offscreen framebuffer. It's the same size as the - /// target but is expanded in either direction by resize_width / resize_height. - int fb_width, fb_height; - - /// How much do we need to resize the damaged region for blurring. - int resize_width, resize_height; - - int npasses; -}; - -static GLint glGetUniformLocationChecked(GLuint p, const char *name) { - auto ret = glGetUniformLocation(p, name); - if (ret < 0) { - log_info("Failed to get location of uniform '%s'. This is normal when " - "using custom shaders.", - name); - } - return ret; -} - -GLuint gl_create_shader(GLenum shader_type, const char *shader_str) { - log_trace("===\n%s\n===", shader_str); - - bool success = false; - GLuint shader = glCreateShader(shader_type); - if (!shader) { - log_error("Failed to create shader with type %#x.", shader_type); - goto end; - } - glShaderSource(shader, 1, &shader_str, NULL); - glCompileShader(shader); - - // Get shader status - { - GLint status = GL_FALSE; - glGetShaderiv(shader, GL_COMPILE_STATUS, &status); - if (GL_FALSE == status) { - GLint log_len = 0; - glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_len); - if (log_len) { - char log[log_len + 1]; - glGetShaderInfoLog(shader, log_len, NULL, log); - log_error("Failed to compile shader with type %d: %s", - shader_type, log); - } - goto end; - } - } - - success = true; - -end: - if (shader && !success) { - glDeleteShader(shader); - shader = 0; - } - - return shader; -} - -GLuint gl_create_program(const GLuint *const shaders, int nshaders) { - bool success = false; - GLuint program = glCreateProgram(); - if (!program) { - log_error("Failed to create program."); - goto end; - } - - for (int i = 0; i < nshaders; ++i) - glAttachShader(program, shaders[i]); - glLinkProgram(program); - - // Get program status - { - GLint status = GL_FALSE; - glGetProgramiv(program, GL_LINK_STATUS, &status); - if (GL_FALSE == status) { - GLint log_len = 0; - glGetProgramiv(program, GL_INFO_LOG_LENGTH, &log_len); - if (log_len) { - char log[log_len + 1]; - glGetProgramInfoLog(program, log_len, NULL, log); - log_error("Failed to link program: %s", log); - } - goto end; - } - } - success = true; - -end: - if (program) { - for (int i = 0; i < nshaders; ++i) - glDetachShader(program, shaders[i]); - } - if (program && !success) { - glDeleteProgram(program); - program = 0; - } - - return program; -} - -/** - * @brief Create a program from vertex and fragment shader strings. - */ -GLuint gl_create_program_from_str(const char *vert_shader_str, const char *frag_shader_str) { - GLuint vert_shader = 0; - GLuint frag_shader = 0; - GLuint prog = 0; - - if (vert_shader_str) - vert_shader = gl_create_shader(GL_VERTEX_SHADER, vert_shader_str); - if (frag_shader_str) - frag_shader = gl_create_shader(GL_FRAGMENT_SHADER, frag_shader_str); - - { - GLuint shaders[2]; - int count = 0; - if (vert_shader) { - shaders[count++] = vert_shader; - } - if (frag_shader) { - shaders[count++] = frag_shader; - } - if (count) { - prog = gl_create_program(shaders, count); - } - } - - if (vert_shader) - glDeleteShader(vert_shader); - if (frag_shader) - glDeleteShader(frag_shader); - - return prog; -} - -static void gl_free_prog_main(gl_win_shader_t *pprogram) { - if (!pprogram) - return; - if (pprogram->prog) { - glDeleteProgram(pprogram->prog); - pprogram->prog = 0; - } -} - -/* - * @brief Implements recursive part of gl_average_texture_color. - * - * @note In order to reduce number of textures which needs to be - * allocated and deleted during this recursive render - * we reuse the same two textures for render source and - * destination simply by alterating between them. - * Unfortunately on first iteration source_texture might - * be read-only. In this case we will select auxiliary_texture as - * destination_texture in order not to touch that read-only source - * texture in following render iteration. - * Otherwise we simply will switch source and destination textures - * between each other on each render iteration. - */ -static GLuint -_gl_average_texture_color(backend_t *base, GLuint source_texture, GLuint destination_texture, - GLuint auxiliary_texture, GLuint fbo, int width, int height) { - const int max_width = 1; - const int max_height = 1; - const int from_width = next_power_of_two(width); - const int from_height = next_power_of_two(height); - const int to_width = from_width > max_width ? from_width / 2 : from_width; - const int to_height = from_height > max_height ? from_height / 2 : from_height; - - // Prepare coordinates - GLint coord[] = { - // top left - 0, 0, // vertex coord - 0, 0, // texture coord - - // top right - to_width, 0, // vertex coord - width, 0, // texture coord - - // bottom right - to_width, to_height, // vertex coord - width, height, // texture coord - - // bottom left - 0, to_height, // vertex coord - 0, height, // texture coord - }; - glBufferSubData(GL_ARRAY_BUFFER, 0, (long)sizeof(*coord) * 16, coord); - - // Prepare framebuffer for new render iteration - glBindTexture(GL_TEXTURE_2D, destination_texture); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, - destination_texture, 0); - gl_check_fb_complete(GL_FRAMEBUFFER); - - // Bind source texture as downscaling shader uniform input - glBindTexture(GL_TEXTURE_2D, source_texture); - - // Render into framebuffer - glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, NULL); - - // Have we downscaled enough? - GLuint result; - if (to_width > max_width || to_height > max_height) { - GLuint new_source_texture = destination_texture; - GLuint new_destination_texture = - auxiliary_texture != 0 ? auxiliary_texture : source_texture; - result = _gl_average_texture_color(base, new_source_texture, - new_destination_texture, 0, fbo, - to_width, to_height); - } else { - result = destination_texture; - } - - return result; -} - -/* - * @brief Builds a 1x1 texture which has color corresponding to the average of all - * pixels of img by recursively rendering into texture of quorter the size (half - * width and half height). - * Returned texture must not be deleted, since it's owned by the gl_image. It will be - * deleted when the gl_image is released. - */ -static GLuint gl_average_texture_color(backend_t *base, struct backend_image *img) { - auto gd = (struct gl_data *)base; - auto inner = (struct gl_texture *)img->inner; - - // Prepare textures which will be used for destination and source of rendering - // during downscaling. - const int texture_count = ARR_SIZE(inner->auxiliary_texture); - if (!inner->auxiliary_texture[0]) { - assert(!inner->auxiliary_texture[1]); - glGenTextures(texture_count, inner->auxiliary_texture); - glActiveTexture(GL_TEXTURE0); - for (int i = 0; i < texture_count; i++) { - glBindTexture(GL_TEXTURE_2D, inner->auxiliary_texture[i]); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); - glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, - (GLint[]){0, 0, 0, 0}); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, inner->width, - inner->height, 0, GL_BGR, GL_UNSIGNED_BYTE, NULL); - } - } - - // Prepare framebuffer used for rendering and bind it - GLuint fbo; - glGenFramebuffers(1, &fbo); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo); - glDrawBuffer(GL_COLOR_ATTACHMENT0); - - // Enable shaders - glUseProgram(gd->brightness_shader.prog); - glUniform2f(glGetUniformLocationChecked(gd->brightness_shader.prog, "texsize"), - (GLfloat)inner->width, (GLfloat)inner->height); - - // Prepare vertex attributes - GLuint vao; - glGenVertexArrays(1, &vao); - glBindVertexArray(vao); - GLuint bo[2]; - glGenBuffers(2, bo); - glBindBuffer(GL_ARRAY_BUFFER, bo[0]); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]); - glEnableVertexAttribArray(vert_coord_loc); - glEnableVertexAttribArray(vert_in_texcoord_loc); - glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 4, NULL); - glVertexAttribPointer(vert_in_texcoord_loc, 2, GL_INT, GL_FALSE, - sizeof(GLint) * 4, (void *)(sizeof(GLint) * 2)); - - // Allocate buffers for render input - GLint coord[16] = {0}; - GLuint indices[] = {0, 1, 2, 2, 3, 0}; - glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord) * 16, coord, GL_DYNAMIC_DRAW); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(*indices) * 6, indices, - GL_STATIC_DRAW); - - // Do actual recursive render to 1x1 texture - GLuint result_texture = _gl_average_texture_color( - base, inner->texture, inner->auxiliary_texture[0], - inner->auxiliary_texture[1], fbo, inner->width, inner->height); - - // Cleanup vertex attributes - glDisableVertexAttribArray(vert_coord_loc); - glDisableVertexAttribArray(vert_in_texcoord_loc); - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - glDeleteBuffers(2, bo); - glBindVertexArray(0); - glDeleteVertexArrays(1, &vao); - - // Cleanup shaders - glUseProgram(0); - - // Cleanup framebuffers - glDeleteFramebuffers(1, &fbo); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - glDrawBuffer(GL_BACK); - - // Cleanup render textures - glBindTexture(GL_TEXTURE_2D, 0); - - gl_check_err(); - - return result_texture; -} - -/** - * Render a region with texture data. - * - * @param ptex the texture - * @param target the framebuffer to render into - * @param dst_x,dst_y the top left corner of region where this texture - * should go. In OpenGL coordinate system (important!). - * @param reg_tgt the clip region, in Xorg coordinate system - * @param reg_visible ignored - */ -static void _gl_compose(backend_t *base, struct backend_image *img, GLuint target, - GLint *coord, GLuint *indices, int nrects) { - auto gd = (struct gl_data *)base; - auto inner = (struct gl_texture *)img->inner; - if (!img || !inner->texture) { - log_error("Missing texture."); - return; - } - - GLuint brightness = 0; - if (img->max_brightness < 1.0) { - brightness = gl_average_texture_color(base, img); - } - - assert(gd->win_shader.prog); - glUseProgram(gd->win_shader.prog); - if (gd->win_shader.unifm_opacity >= 0) { - glUniform1f(gd->win_shader.unifm_opacity, (float)img->opacity); - } - if (gd->win_shader.unifm_invert_color >= 0) { - glUniform1i(gd->win_shader.unifm_invert_color, img->color_inverted); - } - if (gd->win_shader.unifm_tex >= 0) { - glUniform1i(gd->win_shader.unifm_tex, 0); - } - if (gd->win_shader.unifm_dim >= 0) { - glUniform1f(gd->win_shader.unifm_dim, (float)img->dim); - } - if (gd->win_shader.unifm_brightness >= 0) { - glUniform1i(gd->win_shader.unifm_brightness, 1); - } - if (gd->win_shader.unifm_max_brightness >= 0) { - glUniform1f(gd->win_shader.unifm_max_brightness, (float)img->max_brightness); - } - - // log_trace("Draw: %d, %d, %d, %d -> %d, %d (%d, %d) z %d\n", - // x, y, width, height, dx, dy, ptex->width, ptex->height, z); - - // Bind texture - glActiveTexture(GL_TEXTURE1); - glBindTexture(GL_TEXTURE_2D, brightness); - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, inner->texture); - - GLuint vao; - glGenVertexArrays(1, &vao); - glBindVertexArray(vao); - - GLuint bo[2]; - glGenBuffers(2, bo); - glBindBuffer(GL_ARRAY_BUFFER, bo[0]); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]); - glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord) * nrects * 16, coord, GL_STATIC_DRAW); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(*indices) * nrects * 6, - indices, GL_STATIC_DRAW); - - glEnableVertexAttribArray(vert_coord_loc); - glEnableVertexAttribArray(vert_in_texcoord_loc); - glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 4, NULL); - glVertexAttribPointer(vert_in_texcoord_loc, 2, GL_INT, GL_FALSE, - sizeof(GLint) * 4, (void *)(sizeof(GLint) * 2)); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, target); - glDrawElements(GL_TRIANGLES, nrects * 6, GL_UNSIGNED_INT, NULL); - - glDisableVertexAttribArray(vert_coord_loc); - glDisableVertexAttribArray(vert_in_texcoord_loc); - glBindVertexArray(0); - glDeleteVertexArrays(1, &vao); - - // Cleanup - glBindTexture(GL_TEXTURE_2D, 0); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - glDrawBuffer(GL_BACK); - - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - glDeleteBuffers(2, bo); - - glUseProgram(0); - - gl_check_err(); - - return; -} - -/// Convert rectangles in X coordinates to OpenGL vertex and texture coordinates -/// @param[in] nrects, rects rectangles -/// @param[in] dst_x, dst_y origin of the OpenGL texture, affect the calculated texture -/// coordinates -/// @param[in] texture_height height of the OpenGL texture -/// @param[in] root_height height of the back buffer -/// @param[in] y_inverted whether the texture is y inverted -/// @param[out] coord, indices output -static void -x_rect_to_coords(int nrects, const rect_t *rects, int dst_x, int dst_y, int texture_height, - int root_height, bool y_inverted, GLint *coord, GLuint *indices) { - dst_y = root_height - dst_y; - if (y_inverted) { - dst_y -= texture_height; - } - - for (int i = 0; i < nrects; i++) { - // Y-flip. Note after this, crect.y1 > crect.y2 - rect_t crect = rects[i]; - crect.y1 = root_height - crect.y1; - crect.y2 = root_height - crect.y2; - - // Calculate texture coordinates - // (texture_x1, texture_y1), texture coord for the _bottom left_ corner - GLint texture_x1 = crect.x1 - dst_x, texture_y1 = crect.y2 - dst_y, - texture_x2 = texture_x1 + (crect.x2 - crect.x1), - texture_y2 = texture_y1 + (crect.y1 - crect.y2); - - // X pixmaps might be Y inverted, invert the texture coordinates - if (y_inverted) { - texture_y1 = texture_height - texture_y1; - texture_y2 = texture_height - texture_y2; - } - - // Vertex coordinates - auto vx1 = crect.x1; - auto vy1 = crect.y2; - auto vx2 = crect.x2; - auto vy2 = crect.y1; - - // log_trace("Rect %d: %f, %f, %f, %f -> %d, %d, %d, %d", - // ri, rx, ry, rxe, rye, rdx, rdy, rdxe, rdye); - - memcpy(&coord[i * 16], - ((GLint[][2]){ - {vx1, vy1}, - {texture_x1, texture_y1}, - {vx2, vy1}, - {texture_x2, texture_y1}, - {vx2, vy2}, - {texture_x2, texture_y2}, - {vx1, vy2}, - {texture_x1, texture_y2}, - }), - sizeof(GLint[2]) * 8); - - GLuint u = (GLuint)(i * 4); - memcpy(&indices[i * 6], - ((GLuint[]){u + 0, u + 1, u + 2, u + 2, u + 3, u + 0}), - sizeof(GLuint) * 6); - } -} - -// TODO(yshui) make use of reg_visible -void gl_compose(backend_t *base, void *image_data, - int dst_x1, int dst_y1, int dst_x2, int dst_y2, - const region_t *reg_tgt, const region_t *reg_visible attr_unused) { - auto gd = (struct gl_data *)base; - struct backend_image *img = image_data; - auto inner = (struct gl_texture *)img->inner; - - // Painting - int nrects; - const rect_t *rects; - rects = pixman_region32_rectangles((region_t *)reg_tgt, &nrects); - if (!nrects) { - // Nothing to paint - return; - } - - // Until we start to use glClipControl, reg_tgt, dst_x and dst_y and - // in a different coordinate system than the one OpenGL uses. - // OpenGL window coordinate (or NDC) has the origin at the lower left of the - // screen, with y axis pointing up; Xorg has the origin at the upper left of the - // screen, with y axis pointing down. We have to do some coordinate conversion in - // this function - - auto coord = ccalloc(nrects * 16, GLint); - auto indices = ccalloc(nrects * 6, GLuint); - x_rect_to_coords(nrects, rects, dst_x1, dst_y1, inner->height, gd->height, - inner->y_inverted, coord, indices); - - // Interpolate the texture coordinates into the specified range - for (unsigned int i = 2; i < 16; i+=4) { - coord[i+0] = lerp_range(0, dst_x2 - dst_x1, 0, inner->width, coord[i+0]); - coord[i+1] = lerp_range(0, dst_y2 - dst_y1, 0, inner->height, coord[i+1]); - } - - _gl_compose(base, img, gd->back_fbo, coord, indices, nrects); - - free(indices); - free(coord); -} - -/** - * Blur contents in a particular region. - */ -bool gl_kernel_blur(backend_t *base, double opacity, void *ctx, const rect_t *extent, - const GLuint vao[2], const int vao_nelems[2]) { - auto bctx = (struct gl_blur_context *)ctx; - auto gd = (struct gl_data *)base; - - int dst_y_fb_coord = bctx->fb_height - extent->y2; - - int curr = 0; - for (int i = 0; i < bctx->npasses; ++i) { - const gl_blur_shader_t *p = &bctx->blur_shader[i]; - assert(p->prog); - - assert(bctx->blur_textures[curr]); - - // The origin to use when sampling from the source texture - GLint texorig_x = extent->x1, texorig_y = dst_y_fb_coord; - GLint tex_width, tex_height; - GLuint src_texture; - - if (i == 0) { - src_texture = gd->back_texture; - tex_width = gd->width; - tex_height = gd->height; - } else { - src_texture = bctx->blur_textures[curr]; - auto src_size = bctx->texture_sizes[curr]; - tex_width = src_size.width; - tex_height = src_size.height; - } - - glBindTexture(GL_TEXTURE_2D, src_texture); - glUseProgram(p->prog); - glUniform2f(p->unifm_pixel_norm, 1.0f / (GLfloat)tex_width, - 1.0f / (GLfloat)tex_height); - - // The number of indices in the selected vertex array - GLsizei nelems; - - if (i < bctx->npasses - 1) { - assert(bctx->blur_fbos[0]); - assert(bctx->blur_textures[!curr]); - - // not last pass, draw into framebuffer, with resized regions - glBindVertexArray(vao[1]); - nelems = vao_nelems[1]; - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, bctx->blur_fbos[0]); - - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, - GL_TEXTURE_2D, bctx->blur_textures[!curr], 0); - glDrawBuffer(GL_COLOR_ATTACHMENT0); - if (!gl_check_fb_complete(GL_FRAMEBUFFER)) { - return false; - } - - glUniform1f(p->unifm_opacity, 1.0); - } else { - // last pass, draw directly into the back buffer, with origin - // regions - glBindVertexArray(vao[0]); - nelems = vao_nelems[0]; - glBindFramebuffer(GL_FRAMEBUFFER, gd->back_fbo); - - glUniform1f(p->unifm_opacity, (float)opacity); - } - - glUniform2f(p->texorig_loc, (GLfloat)texorig_x, (GLfloat)texorig_y); - glDrawElements(GL_TRIANGLES, nelems, GL_UNSIGNED_INT, NULL); - - // XXX use multiple draw calls is probably going to be slow than - // just simply blur the whole area. - - curr = !curr; - } - - return true; -} - -bool gl_dual_kawase_blur(backend_t *base, double opacity, void *ctx, const rect_t *extent, - const GLuint vao[2], const int vao_nelems[2]) { - auto bctx = (struct gl_blur_context *)ctx; - auto gd = (struct gl_data *)base; - - int dst_y_fb_coord = bctx->fb_height - extent->y2; - - int iterations = bctx->blur_texture_count; - int scale_factor = 1; - - // Kawase downsample pass - const gl_blur_shader_t *down_pass = &bctx->blur_shader[0]; - assert(down_pass->prog); - glUseProgram(down_pass->prog); - - glUniform2f(down_pass->texorig_loc, (GLfloat)extent->x1, (GLfloat)dst_y_fb_coord); - - for (int i = 0; i < iterations; ++i) { - // Scale output width / height by half in each iteration - scale_factor <<= 1; - - GLuint src_texture; - int tex_width, tex_height; - - if (i == 0) { - // first pass: copy from back buffer - src_texture = gd->back_texture; - tex_width = gd->width; - tex_height = gd->height; - } else { - // copy from previous pass - src_texture = bctx->blur_textures[i - 1]; - auto src_size = bctx->texture_sizes[i - 1]; - tex_width = src_size.width; - tex_height = src_size.height; - } - - assert(src_texture); - assert(bctx->blur_fbos[i]); - - glBindTexture(GL_TEXTURE_2D, src_texture); - glBindVertexArray(vao[1]); - auto nelems = vao_nelems[1]; - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, bctx->blur_fbos[i]); - glDrawBuffer(GL_COLOR_ATTACHMENT0); - - glUniform1f(down_pass->scale_loc, (GLfloat)scale_factor); - - glUniform2f(down_pass->unifm_pixel_norm, 1.0f / (GLfloat)tex_width, - 1.0f / (GLfloat)tex_height); - - glDrawElements(GL_TRIANGLES, nelems, GL_UNSIGNED_INT, NULL); - } - - // Kawase upsample pass - const gl_blur_shader_t *up_pass = &bctx->blur_shader[1]; - assert(up_pass->prog); - glUseProgram(up_pass->prog); - - glUniform2f(up_pass->texorig_loc, (GLfloat)extent->x1, (GLfloat)dst_y_fb_coord); - - for (int i = iterations - 1; i >= 0; --i) { - // Scale output width / height back by two in each iteration - scale_factor >>= 1; - - const GLuint src_texture = bctx->blur_textures[i]; - assert(src_texture); - - // Calculate normalized half-width/-height of a src pixel - auto src_size = bctx->texture_sizes[i]; - int tex_width = src_size.width; - int tex_height = src_size.height; - - // The number of indices in the selected vertex array - GLsizei nelems; - - glBindTexture(GL_TEXTURE_2D, src_texture); - if (i > 0) { - assert(bctx->blur_fbos[i - 1]); - - // not last pass, draw into next framebuffer - glBindVertexArray(vao[1]); - nelems = vao_nelems[1]; - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, bctx->blur_fbos[i - 1]); - glDrawBuffer(GL_COLOR_ATTACHMENT0); - - glUniform1f(up_pass->unifm_opacity, (GLfloat)1); - } else { - // last pass, draw directly into the back buffer - glBindVertexArray(vao[0]); - nelems = vao_nelems[0]; - glBindFramebuffer(GL_FRAMEBUFFER, gd->back_fbo); - - glUniform1f(up_pass->unifm_opacity, (GLfloat)opacity); - } - - glUniform1f(up_pass->scale_loc, (GLfloat)scale_factor); - glUniform2f(up_pass->unifm_pixel_norm, 1.0f / (GLfloat)tex_width, - 1.0f / (GLfloat)tex_height); - - glDrawElements(GL_TRIANGLES, nelems, GL_UNSIGNED_INT, NULL); - } - - return true; -} - -bool gl_blur(backend_t *base, double opacity, void *ctx, const region_t *reg_blur, - const region_t *reg_visible attr_unused) { - auto bctx = (struct gl_blur_context *)ctx; - auto gd = (struct gl_data *)base; - - bool ret = false; - - if (gd->width != bctx->fb_width || gd->height != bctx->fb_height) { - // Resize the temporary textures used for blur in case the root - // size changed - bctx->fb_width = gd->width; - bctx->fb_height = gd->height; - - for (int i = 0; i < bctx->blur_texture_count; ++i) { - auto tex_size = bctx->texture_sizes + i; - if (bctx->method == BLUR_METHOD_DUAL_KAWASE) { - // Use smaller textures for each iteration (quarter of the - // previous texture) - tex_size->width = 1 + ((bctx->fb_width - 1) >> (i + 1)); - tex_size->height = 1 + ((bctx->fb_height - 1) >> (i + 1)); - } else { - tex_size->width = bctx->fb_width; - tex_size->height = bctx->fb_height; - } - - glBindTexture(GL_TEXTURE_2D, bctx->blur_textures[i]); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, tex_size->width, - tex_size->height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL); - - if (bctx->method == BLUR_METHOD_DUAL_KAWASE) { - // Attach texture to FBO target - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, bctx->blur_fbos[i]); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, - GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, - bctx->blur_textures[i], 0); - if (!gl_check_fb_complete(GL_FRAMEBUFFER)) { - glBindFramebuffer(GL_FRAMEBUFFER, 0); - return false; - } - } - } - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - } - - // Remainder: regions are in Xorg coordinates - auto reg_blur_resized = - resize_region(reg_blur, bctx->resize_width, bctx->resize_height); - const rect_t *extent = pixman_region32_extents((region_t *)reg_blur), - *extent_resized = pixman_region32_extents(®_blur_resized); - int width = extent->x2 - extent->x1, height = extent->y2 - extent->y1; - if (width == 0 || height == 0) { - return true; - } - - int nrects, nrects_resized; - const rect_t *rects = pixman_region32_rectangles((region_t *)reg_blur, &nrects), - *rects_resized = - pixman_region32_rectangles(®_blur_resized, &nrects_resized); - if (!nrects || !nrects_resized) { - return true; - } - - auto coord = ccalloc(nrects * 16, GLint); - auto indices = ccalloc(nrects * 6, GLuint); - x_rect_to_coords(nrects, rects, extent_resized->x1, extent_resized->y2, - bctx->fb_height, gd->height, false, coord, indices); - - auto coord_resized = ccalloc(nrects_resized * 16, GLint); - auto indices_resized = ccalloc(nrects_resized * 6, GLuint); - x_rect_to_coords(nrects_resized, rects_resized, extent_resized->x1, - extent_resized->y2, bctx->fb_height, bctx->fb_height, false, - coord_resized, indices_resized); - pixman_region32_fini(®_blur_resized); - - GLuint vao[2]; - glGenVertexArrays(2, vao); - GLuint bo[4]; - glGenBuffers(4, bo); - - glBindVertexArray(vao[0]); - glBindBuffer(GL_ARRAY_BUFFER, bo[0]); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]); - glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord) * nrects * 16, coord, GL_STATIC_DRAW); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(*indices) * nrects * 6, - indices, GL_STATIC_DRAW); - glEnableVertexAttribArray(vert_coord_loc); - glEnableVertexAttribArray(vert_in_texcoord_loc); - glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 4, NULL); - glVertexAttribPointer(vert_in_texcoord_loc, 2, GL_INT, GL_FALSE, - sizeof(GLint) * 4, (void *)(sizeof(GLint) * 2)); - - glBindVertexArray(vao[1]); - glBindBuffer(GL_ARRAY_BUFFER, bo[2]); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[3]); - glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord_resized) * nrects_resized * 16, - coord_resized, GL_STATIC_DRAW); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, - (long)sizeof(*indices_resized) * nrects_resized * 6, indices_resized, - GL_STATIC_DRAW); - glEnableVertexAttribArray(vert_coord_loc); - glEnableVertexAttribArray(vert_in_texcoord_loc); - glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 4, NULL); - glVertexAttribPointer(vert_in_texcoord_loc, 2, GL_INT, GL_FALSE, - sizeof(GLint) * 4, (void *)(sizeof(GLint) * 2)); - - int vao_nelems[2] = {nrects * 6, nrects_resized * 6}; - - if (bctx->method == BLUR_METHOD_DUAL_KAWASE) { - ret = gl_dual_kawase_blur(base, opacity, ctx, extent_resized, vao, vao_nelems); - } else { - ret = gl_kernel_blur(base, opacity, ctx, extent_resized, vao, vao_nelems); - } - - glBindFramebuffer(GL_FRAMEBUFFER, 0); - glBindTexture(GL_TEXTURE_2D, 0); - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - glDeleteBuffers(4, bo); - glBindVertexArray(0); - glDeleteVertexArrays(2, vao); - glUseProgram(0); - - free(indices); - free(coord); - free(indices_resized); - free(coord_resized); - - gl_check_err(); - return ret; -} - -// clang-format off -const char *vertex_shader = GLSL(330, - uniform mat4 projection; - uniform float scale = 1.0; - uniform vec2 texorig; - layout(location = 0) in vec2 coord; - layout(location = 1) in vec2 in_texcoord; - out vec2 texcoord; - void main() { - gl_Position = projection * vec4(coord, 0, scale); - texcoord = in_texcoord + texorig; - } -); -// clang-format on - -/** - * Load a GLSL main program from shader strings. - */ -static int gl_win_shader_from_string(const char *vshader_str, const char *fshader_str, - gl_win_shader_t *ret) { - // Build program - ret->prog = gl_create_program_from_str(vshader_str, fshader_str); - if (!ret->prog) { - log_error("Failed to create GLSL program."); - return -1; - } - - // Get uniform addresses - ret->unifm_opacity = glGetUniformLocationChecked(ret->prog, "opacity"); - ret->unifm_invert_color = glGetUniformLocationChecked(ret->prog, "invert_color"); - ret->unifm_tex = glGetUniformLocationChecked(ret->prog, "tex"); - ret->unifm_dim = glGetUniformLocationChecked(ret->prog, "dim"); - ret->unifm_brightness = glGetUniformLocationChecked(ret->prog, "brightness"); - ret->unifm_max_brightness = - glGetUniformLocationChecked(ret->prog, "max_brightness"); - - gl_check_err(); - - return true; -} - -/** - * Callback to run on root window size change. - */ -void gl_resize(struct gl_data *gd, int width, int height) { - GLint viewport_dimensions[2]; - glGetIntegerv(GL_MAX_VIEWPORT_DIMS, viewport_dimensions); - - gd->height = height; - gd->width = width; - - assert(viewport_dimensions[0] >= gd->width); - assert(viewport_dimensions[1] >= gd->height); - - glBindTexture(GL_TEXTURE_2D, gd->back_texture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, width, height, 0, GL_BGR, - GL_UNSIGNED_BYTE, NULL); - - gl_check_err(); -} - -// clang-format off -static const char dummy_frag[] = GLSL(330, - uniform sampler2D tex; - in vec2 texcoord; - void main() { - gl_FragColor = texelFetch(tex, ivec2(texcoord.xy), 0); - } -); - -static const char fill_frag[] = GLSL(330, - uniform vec4 color; - void main() { - gl_FragColor = color; - } -); - -static const char fill_vert[] = GLSL(330, - layout(location = 0) in vec2 in_coord; - uniform mat4 projection; - void main() { - gl_Position = projection * vec4(in_coord, 0, 1); - } -); - -static const char interpolating_frag[] = GLSL(330, - uniform sampler2D tex; - in vec2 texcoord; - void main() { - gl_FragColor = vec4(texture2D(tex, vec2(texcoord.xy), 0).rgb, 1); - } -); - -static const char interpolating_vert[] = GLSL(330, - uniform mat4 projection; - uniform vec2 texsize; - layout(location = 0) in vec2 in_coord; - layout(location = 1) in vec2 in_texcoord; - out vec2 texcoord; - void main() { - gl_Position = projection * vec4(in_coord, 0, 1); - texcoord = in_texcoord / texsize; - } -); -// clang-format on - -/// Fill a given region in bound framebuffer. -/// @param[in] y_inverted whether the y coordinates in `clip` should be inverted -static void _gl_fill(backend_t *base, struct color c, const region_t *clip, GLuint target, - int height, bool y_inverted) { - static const GLuint fill_vert_in_coord_loc = 0; - int nrects; - const rect_t *rect = pixman_region32_rectangles((region_t *)clip, &nrects); - auto gd = (struct gl_data *)base; - - GLuint vao; - glGenVertexArrays(1, &vao); - glBindVertexArray(vao); - - GLuint bo[2]; - glGenBuffers(2, bo); - glUseProgram(gd->fill_shader.prog); - glUniform4f(gd->fill_shader.color_loc, (GLfloat)c.red, (GLfloat)c.green, - (GLfloat)c.blue, (GLfloat)c.alpha); - glEnableVertexAttribArray(fill_vert_in_coord_loc); - glBindBuffer(GL_ARRAY_BUFFER, bo[0]); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]); - - auto coord = ccalloc(nrects * 8, GLint); - auto indices = ccalloc(nrects * 6, GLuint); - for (int i = 0; i < nrects; i++) { - GLint y1 = y_inverted ? height - rect[i].y2 : rect[i].y1, - y2 = y_inverted ? height - rect[i].y1 : rect[i].y2; - // clang-format off - memcpy(&coord[i * 8], - ((GLint[][2]){ - {rect[i].x1, y1}, {rect[i].x2, y1}, - {rect[i].x2, y2}, {rect[i].x1, y2}}), - sizeof(GLint[2]) * 4); - // clang-format on - indices[i * 6 + 0] = (GLuint)i * 4 + 0; - indices[i * 6 + 1] = (GLuint)i * 4 + 1; - indices[i * 6 + 2] = (GLuint)i * 4 + 2; - indices[i * 6 + 3] = (GLuint)i * 4 + 2; - indices[i * 6 + 4] = (GLuint)i * 4 + 3; - indices[i * 6 + 5] = (GLuint)i * 4 + 0; - } - glBufferData(GL_ARRAY_BUFFER, nrects * 8 * (long)sizeof(*coord), coord, GL_STREAM_DRAW); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, nrects * 6 * (long)sizeof(*indices), - indices, GL_STREAM_DRAW); - - glVertexAttribPointer(fill_vert_in_coord_loc, 2, GL_INT, GL_FALSE, - sizeof(*coord) * 2, (void *)0); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, target); - glDrawElements(GL_TRIANGLES, nrects * 6, GL_UNSIGNED_INT, NULL); - - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - glDisableVertexAttribArray(fill_vert_in_coord_loc); - glBindVertexArray(0); - glDeleteVertexArrays(1, &vao); - - glDeleteBuffers(2, bo); - free(indices); - free(coord); - - gl_check_err(); -} - -void gl_fill(backend_t *base, struct color c, const region_t *clip) { - auto gd = (struct gl_data *)base; - return _gl_fill(base, c, clip, gd->back_fbo, gd->height, true); -} - -static void gl_release_image_inner(backend_t *base, struct gl_texture *inner) { - auto gd = (struct gl_data *)base; - gd->release_user_data(base, inner); - assert(inner->user_data == NULL); - - glDeleteTextures(1, &inner->texture); - glDeleteTextures(2, inner->auxiliary_texture); - free(inner); - gl_check_err(); -} - -void gl_release_image(backend_t *base, void *image_data) { - struct backend_image *wd = image_data; - auto inner = (struct gl_texture *)wd->inner; - inner->refcount--; - assert(inner->refcount >= 0); - if (inner->refcount == 0) { - gl_release_image_inner(base, inner); - } - free(wd); -} - -static inline void gl_free_blur_shader(gl_blur_shader_t *shader) { - if (shader->prog) { - glDeleteProgram(shader->prog); - } - - shader->prog = 0; -} - -void gl_destroy_blur_context(backend_t *base attr_unused, void *ctx) { - auto bctx = (struct gl_blur_context *)ctx; - // Free GLSL shaders/programs - for (int i = 0; i < bctx->npasses; ++i) { - gl_free_blur_shader(&bctx->blur_shader[i]); - } - free(bctx->blur_shader); - - if (bctx->blur_texture_count && bctx->blur_textures) { - glDeleteTextures(bctx->blur_texture_count, bctx->blur_textures); - free(bctx->blur_textures); - } - if (bctx->blur_texture_count && bctx->texture_sizes) { - free(bctx->texture_sizes); - } - if (bctx->blur_fbo_count && bctx->blur_fbos) { - glDeleteFramebuffers(bctx->blur_fbo_count, bctx->blur_fbos); - free(bctx->blur_fbos); - } - - bctx->blur_texture_count = 0; - bctx->blur_fbo_count = 0; - - free(bctx); - - gl_check_err(); -} - -/** - * Initialize GL blur filters. - */ -bool gl_create_kernel_blur_context(void *blur_context, GLfloat *projection, - enum blur_method method, void *args) { - bool success = false; - auto ctx = (struct gl_blur_context *)blur_context; - - struct conv **kernels; - - int nkernels; - ctx->method = BLUR_METHOD_KERNEL; - if (method == BLUR_METHOD_KERNEL) { - nkernels = ((struct kernel_blur_args *)args)->kernel_count; - kernels = ((struct kernel_blur_args *)args)->kernels; - } else { - kernels = generate_blur_kernel(method, args, &nkernels); - } - - if (!nkernels) { - ctx->method = BLUR_METHOD_NONE; - return true; - } - - // Specify required textures and FBOs - ctx->blur_texture_count = 2; - ctx->blur_fbo_count = 1; - - ctx->blur_shader = ccalloc(max2(2, nkernels), gl_blur_shader_t); - - char *lc_numeric_old = strdup(setlocale(LC_NUMERIC, NULL)); - // Enforce LC_NUMERIC locale "C" here to make sure decimal point is sane - // Thanks to hiciu for reporting. - setlocale(LC_NUMERIC, "C"); - - // clang-format off - static const char *FRAG_SHADER_BLUR = GLSL(330, - %s\n // other extension pragmas - uniform sampler2D tex_src; - uniform vec2 pixel_norm; - uniform float opacity; - in vec2 texcoord; - out vec4 out_color; - void main() { - vec2 uv = texcoord * pixel_norm; - vec4 sum = vec4(0.0, 0.0, 0.0, 0.0); - %s //body of the convolution - out_color = sum / float(%.7g) * opacity; - } - ); - static const char *FRAG_SHADER_BLUR_ADD = QUOTE( - sum += float(%.7g) * texture2D(tex_src, uv + pixel_norm * vec2(%.7g, %.7g)); - ); - // clang-format on - - const char *shader_add = FRAG_SHADER_BLUR_ADD; - char *extension = strdup(""); - - for (int i = 0; i < nkernels; i++) { - auto kern = kernels[i]; - // Build shader - int width = kern->w, height = kern->h; - int nele = width * height; - // '%.7g' is at most 14 characters, inserted 3 times - size_t body_len = (strlen(shader_add) + 42) * (uint)nele; - char *shader_body = ccalloc(body_len, char); - char *pc = shader_body; - - // Make use of the linear interpolation hardware by sampling 2 pixels with - // one texture access by sampling between both pixels based on their - // relative weight. Easiest done in a single dimension as 2D bilinear - // filtering would raise additional constraints on the kernels. Therefore - // only use interpolation along the larger dimension. - double sum = 0.0; - if (width > height) { - // use interpolation in x dimension (width) - for (int j = 0; j < height; ++j) { - for (int k = 0; k < width; k += 2) { - double val1, val2; - val1 = kern->data[j * width + k]; - val2 = (k + 1 < width) - ? kern->data[j * width + k + 1] - : 0; - - double combined_weight = val1 + val2; - if (combined_weight == 0) { - continue; - } - sum += combined_weight; - - double offset_x = - k + (val2 / combined_weight) - (width / 2); - double offset_y = j - (height / 2); - pc += snprintf( - pc, body_len - (ulong)(pc - shader_body), - shader_add, combined_weight, offset_x, offset_y); - assert(pc < shader_body + body_len); - } - } - } else { - // use interpolation in y dimension (height) - for (int j = 0; j < height; j += 2) { - for (int k = 0; k < width; ++k) { - double val1, val2; - val1 = kern->data[j * width + k]; - val2 = (j + 1 < height) - ? kern->data[(j + 1) * width + k] - : 0; - - double combined_weight = val1 + val2; - if (combined_weight == 0) { - continue; - } - sum += combined_weight; - - double offset_x = k - (width / 2); - double offset_y = - j + (val2 / combined_weight) - (height / 2); - pc += snprintf( - pc, body_len - (ulong)(pc - shader_body), - shader_add, combined_weight, offset_x, offset_y); - assert(pc < shader_body + body_len); - } - } - } - - auto pass = ctx->blur_shader + i; - size_t shader_len = strlen(FRAG_SHADER_BLUR) + strlen(extension) + - strlen(shader_body) + 10 /* sum */ + - 1 /* null terminator */; - char *shader_str = ccalloc(shader_len, char); - auto real_shader_len = snprintf(shader_str, shader_len, FRAG_SHADER_BLUR, - extension, shader_body, sum); - CHECK(real_shader_len >= 0); - CHECK((size_t)real_shader_len < shader_len); - free(shader_body); - - // Build program - pass->prog = gl_create_program_from_str(vertex_shader, shader_str); - free(shader_str); - if (!pass->prog) { - log_error("Failed to create GLSL program."); - success = false; - goto out; - } - glBindFragDataLocation(pass->prog, 0, "out_color"); - - // Get uniform addresses - pass->unifm_pixel_norm = - glGetUniformLocationChecked(pass->prog, "pixel_norm"); - pass->unifm_opacity = glGetUniformLocationChecked(pass->prog, "opacity"); - pass->texorig_loc = glGetUniformLocationChecked(pass->prog, "texorig"); - - // Setup projection matrix - glUseProgram(pass->prog); - int pml = glGetUniformLocationChecked(pass->prog, "projection"); - glUniformMatrix4fv(pml, 1, false, projection); - glUseProgram(0); - - ctx->resize_width += kern->w / 2; - ctx->resize_height += kern->h / 2; - } - - if (nkernels == 1) { - // Generate an extra null pass so we don't need special code path for - // the single pass case - auto pass = &ctx->blur_shader[1]; - pass->prog = gl_create_program_from_str(vertex_shader, dummy_frag); - pass->unifm_pixel_norm = -1; - pass->unifm_opacity = -1; - pass->texorig_loc = glGetUniformLocationChecked(pass->prog, "texorig"); - - // Setup projection matrix - glUseProgram(pass->prog); - int pml = glGetUniformLocationChecked(pass->prog, "projection"); - glUniformMatrix4fv(pml, 1, false, projection); - glUseProgram(0); - - ctx->npasses = 2; - } else { - ctx->npasses = nkernels; - } - - success = true; -out: - if (method != BLUR_METHOD_KERNEL) { - // We generated the blur kernels, so we need to free them - for (int i = 0; i < nkernels; i++) { - free(kernels[i]); - } - free(kernels); - } - - free(extension); - // Restore LC_NUMERIC - setlocale(LC_NUMERIC, lc_numeric_old); - free(lc_numeric_old); - - return success; -} - -bool gl_create_dual_kawase_blur_context(void *blur_context, GLfloat *projection, - enum blur_method method, void *args) { - bool success = false; - auto ctx = (struct gl_blur_context *)blur_context; - - ctx->method = method; - - auto blur_params = generate_dual_kawase_params(args); - - // Specify required textures and FBOs - ctx->blur_texture_count = blur_params->iterations; - ctx->blur_fbo_count = blur_params->iterations; - - ctx->resize_width += blur_params->expand; - ctx->resize_height += blur_params->expand; - - ctx->npasses = 2; - ctx->blur_shader = ccalloc(ctx->npasses, gl_blur_shader_t); - - char *lc_numeric_old = strdup(setlocale(LC_NUMERIC, NULL)); - // Enforce LC_NUMERIC locale "C" here to make sure decimal point is sane - // Thanks to hiciu for reporting. - setlocale(LC_NUMERIC, "C"); - - // Dual-kawase downsample shader / program - auto down_pass = ctx->blur_shader; - { - // clang-format off - static const char *FRAG_SHADER_DOWN = GLSL(330, - uniform sampler2D tex_src; - uniform float scale = 1.0; - uniform vec2 pixel_norm; - in vec2 texcoord; - out vec4 out_color; - void main() { - vec2 offset = %.7g * pixel_norm; - vec2 uv = texcoord * pixel_norm * (2.0 / scale); - vec4 sum = texture2D(tex_src, uv) * 4.0; - sum += texture2D(tex_src, uv - vec2(0.5, 0.5) * offset); - sum += texture2D(tex_src, uv + vec2(0.5, 0.5) * offset); - sum += texture2D(tex_src, uv + vec2(0.5, -0.5) * offset); - sum += texture2D(tex_src, uv - vec2(0.5, -0.5) * offset); - out_color = sum / 8.0; - } - ); - // clang-format on - - // Build shader - size_t shader_len = - strlen(FRAG_SHADER_DOWN) + 10 /* offset */ + 1 /* null terminator */; - char *shader_str = ccalloc(shader_len, char); - auto real_shader_len = - snprintf(shader_str, shader_len, FRAG_SHADER_DOWN, blur_params->offset); - CHECK(real_shader_len >= 0); - CHECK((size_t)real_shader_len < shader_len); - - // Build program - down_pass->prog = gl_create_program_from_str(vertex_shader, shader_str); - free(shader_str); - if (!down_pass->prog) { - log_error("Failed to create GLSL program."); - success = false; - goto out; - } - glBindFragDataLocation(down_pass->prog, 0, "out_color"); - - // Get uniform addresses - down_pass->unifm_pixel_norm = - glGetUniformLocationChecked(down_pass->prog, "pixel_norm"); - down_pass->texorig_loc = - glGetUniformLocationChecked(down_pass->prog, "texorig"); - down_pass->scale_loc = - glGetUniformLocationChecked(down_pass->prog, "scale"); - - // Setup projection matrix - glUseProgram(down_pass->prog); - int pml = glGetUniformLocationChecked(down_pass->prog, "projection"); - glUniformMatrix4fv(pml, 1, false, projection); - glUseProgram(0); - } - - // Dual-kawase upsample shader / program - auto up_pass = ctx->blur_shader + 1; - { - // clang-format off - static const char *FRAG_SHADER_UP = GLSL(330, - uniform sampler2D tex_src; - uniform float scale = 1.0; - uniform vec2 pixel_norm; - uniform float opacity; - in vec2 texcoord; - out vec4 out_color; - void main() { - vec2 offset = %.7g * pixel_norm; - vec2 uv = texcoord * pixel_norm / (2 * scale); - vec4 sum = texture2D(tex_src, uv + vec2(-1.0, 0.0) * offset); - sum += texture2D(tex_src, uv + vec2(-0.5, 0.5) * offset) * 2.0; - sum += texture2D(tex_src, uv + vec2(0.0, 1.0) * offset); - sum += texture2D(tex_src, uv + vec2(0.5, 0.5) * offset) * 2.0; - sum += texture2D(tex_src, uv + vec2(1.0, 0.0) * offset); - sum += texture2D(tex_src, uv + vec2(0.5, -0.5) * offset) * 2.0; - sum += texture2D(tex_src, uv + vec2(0.0, -1.0) * offset); - sum += texture2D(tex_src, uv + vec2(-0.5, -0.5) * offset) * 2.0; - out_color = sum / 12.0 * opacity; - } - ); - // clang-format on - - // Build shader - size_t shader_len = - strlen(FRAG_SHADER_UP) + 10 /* offset */ + 1 /* null terminator */; - char *shader_str = ccalloc(shader_len, char); - auto real_shader_len = - snprintf(shader_str, shader_len, FRAG_SHADER_UP, blur_params->offset); - CHECK(real_shader_len >= 0); - CHECK((size_t)real_shader_len < shader_len); - - // Build program - up_pass->prog = gl_create_program_from_str(vertex_shader, shader_str); - free(shader_str); - if (!up_pass->prog) { - log_error("Failed to create GLSL program."); - success = false; - goto out; - } - glBindFragDataLocation(up_pass->prog, 0, "out_color"); - - // Get uniform addresses - up_pass->unifm_pixel_norm = - glGetUniformLocationChecked(up_pass->prog, "pixel_norm"); - up_pass->unifm_opacity = - glGetUniformLocationChecked(up_pass->prog, "opacity"); - up_pass->texorig_loc = - glGetUniformLocationChecked(up_pass->prog, "texorig"); - up_pass->scale_loc = glGetUniformLocationChecked(up_pass->prog, "scale"); - - // Setup projection matrix - glUseProgram(up_pass->prog); - int pml = glGetUniformLocationChecked(up_pass->prog, "projection"); - glUniformMatrix4fv(pml, 1, false, projection); - glUseProgram(0); - } - - success = true; -out: - free(blur_params); - - if (!success) { - ctx = NULL; - } - - // Restore LC_NUMERIC - setlocale(LC_NUMERIC, lc_numeric_old); - free(lc_numeric_old); - - return success; -} - -void *gl_create_blur_context(backend_t *base, enum blur_method method, void *args) { - bool success; - auto gd = (struct gl_data *)base; - - auto ctx = ccalloc(1, struct gl_blur_context); - - if (!method || method >= BLUR_METHOD_INVALID) { - ctx->method = BLUR_METHOD_NONE; - return ctx; - } - - // Set projection matrix to gl viewport dimensions so we can use screen - // coordinates for all vertices - // Note: OpenGL matrices are column major - GLint viewport_dimensions[2]; - glGetIntegerv(GL_MAX_VIEWPORT_DIMS, viewport_dimensions); - GLfloat projection_matrix[4][4] = {{2.0f / (GLfloat)viewport_dimensions[0], 0, 0, 0}, - {0, 2.0f / (GLfloat)viewport_dimensions[1], 0, 0}, - {0, 0, 0, 0}, - {-1, -1, 0, 1}}; - - if (method == BLUR_METHOD_DUAL_KAWASE) { - success = gl_create_dual_kawase_blur_context(ctx, projection_matrix[0], - method, args); - } else { - success = - gl_create_kernel_blur_context(ctx, projection_matrix[0], method, args); - } - if (!success || ctx->method == BLUR_METHOD_NONE) { - goto out; - } - - // Texture size will be defined by gl_blur - ctx->blur_textures = ccalloc(ctx->blur_texture_count, GLuint); - ctx->texture_sizes = ccalloc(ctx->blur_texture_count, struct texture_size); - glGenTextures(ctx->blur_texture_count, ctx->blur_textures); - - for (int i = 0; i < ctx->blur_texture_count; ++i) { - glBindTexture(GL_TEXTURE_2D, ctx->blur_textures[i]); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - } - - // Generate FBO and textures when needed - ctx->blur_fbos = ccalloc(ctx->blur_fbo_count, GLuint); - glGenFramebuffers(ctx->blur_fbo_count, ctx->blur_fbos); - - for (int i = 0; i < ctx->blur_fbo_count; ++i) { - if (!ctx->blur_fbos[i]) { - log_error("Failed to generate framebuffer objects for blur"); - success = false; - goto out; - } - } - -out: - if (!success) { - gl_destroy_blur_context(&gd->base, ctx); - ctx = NULL; - } - - gl_check_err(); - return ctx; -} - -void gl_get_blur_size(void *blur_context, int *width, int *height) { - auto ctx = (struct gl_blur_context *)blur_context; - *width = ctx->resize_width; - *height = ctx->resize_height; -} - -// clang-format off -const char *win_shader_glsl = GLSL(330, - uniform float opacity; - uniform float dim; - uniform bool invert_color; - in vec2 texcoord; - uniform sampler2D tex; - uniform sampler2D brightness; - uniform float max_brightness; - - void main() { - vec4 c = texelFetch(tex, ivec2(texcoord), 0); - if (invert_color) { - c = vec4(c.aaa - c.rgb, c.a); - } - c = vec4(c.rgb * (1.0 - dim), c.a) * opacity; - - vec3 rgb_brightness = texelFetch(brightness, ivec2(0, 0), 0).rgb; - // Ref: https://en.wikipedia.org/wiki/Relative_luminance - float brightness = rgb_brightness.r * 0.21 + - rgb_brightness.g * 0.72 + - rgb_brightness.b * 0.07; - if (brightness > max_brightness) - c.rgb = c.rgb * (max_brightness / brightness); - - gl_FragColor = c; - } -); - -const char *present_vertex_shader = GLSL(330, - uniform mat4 projection; - layout(location = 0) in vec2 coord; - out vec2 texcoord; - void main() { - gl_Position = projection * vec4(coord, 0, 1); - texcoord = coord; - } -); -// clang-format on - -bool gl_init(struct gl_data *gd, session_t *ps) { - // Initialize GLX data structure - glDisable(GL_DEPTH_TEST); - glDepthMask(GL_FALSE); - - glEnable(GL_BLEND); - // X pixmap is in premultiplied alpha, so we might just as well use it too. - // Thanks to derhass for help. - glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); - - // Initialize stencil buffer - glDisable(GL_STENCIL_TEST); - glStencilMask(0x1); - glStencilFunc(GL_EQUAL, 0x1, 0x1); - - // Set gl viewport to the maximum supported size so we won't have to worry about - // it later on when the screen is resized. The corresponding projection matrix can - // be set now and won't have to be updated. Since fragments outside the target - // buffer are skipped anyways, this should have no impact on performance. - GLint viewport_dimensions[2]; - glGetIntegerv(GL_MAX_VIEWPORT_DIMS, viewport_dimensions); - glViewport(0, 0, viewport_dimensions[0], viewport_dimensions[1]); - - // Clear screen - glClearColor(0.0f, 0.0f, 0.0f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); - - glGenFramebuffers(1, &gd->back_fbo); - glGenTextures(1, &gd->back_texture); - if (!gd->back_fbo || !gd->back_texture) { - log_error("Failed to generate a framebuffer object"); - return false; - } - - glBindTexture(GL_TEXTURE_2D, gd->back_texture); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glBindTexture(GL_TEXTURE_2D, 0); - - // Set projection matrix to gl viewport dimensions so we can use screen - // coordinates for all vertices - // Note: OpenGL matrices are column major - GLfloat projection_matrix[4][4] = {{2.0f / (GLfloat)viewport_dimensions[0], 0, 0, 0}, - {0, 2.0f / (GLfloat)viewport_dimensions[1], 0, 0}, - {0, 0, 0, 0}, - {-1, -1, 0, 1}}; - - // Initialize shaders - gl_win_shader_from_string(vertex_shader, win_shader_glsl, &gd->win_shader); - int pml = glGetUniformLocationChecked(gd->win_shader.prog, "projection"); - glUseProgram(gd->win_shader.prog); - glUniformMatrix4fv(pml, 1, false, projection_matrix[0]); - glUseProgram(0); - - gd->fill_shader.prog = gl_create_program_from_str(fill_vert, fill_frag); - gd->fill_shader.color_loc = glGetUniformLocation(gd->fill_shader.prog, "color"); - pml = glGetUniformLocationChecked(gd->fill_shader.prog, "projection"); - glUseProgram(gd->fill_shader.prog); - glUniformMatrix4fv(pml, 1, false, projection_matrix[0]); - glUseProgram(0); - - gd->present_prog = gl_create_program_from_str(present_vertex_shader, dummy_frag); - if (!gd->present_prog) { - log_error("Failed to create the present shader"); - return false; - } - pml = glGetUniformLocationChecked(gd->present_prog, "projection"); - glUseProgram(gd->present_prog); - glUniform1i(glGetUniformLocationChecked(gd->present_prog, "tex"), 0); - glUniformMatrix4fv(pml, 1, false, projection_matrix[0]); - glUseProgram(0); - - gd->brightness_shader.prog = - gl_create_program_from_str(interpolating_vert, interpolating_frag); - if (!gd->brightness_shader.prog) { - log_error("Failed to create the brightness shader"); - return false; - } - pml = glGetUniformLocationChecked(gd->brightness_shader.prog, "projection"); - glUseProgram(gd->brightness_shader.prog); - glUniform1i(glGetUniformLocationChecked(gd->brightness_shader.prog, "tex"), 0); - glUniformMatrix4fv(pml, 1, false, projection_matrix[0]); - glUseProgram(0); - - // Set up the size of the back texture - gl_resize(gd, ps->root_width, ps->root_height); - - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, gd->back_fbo); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, - gd->back_texture, 0); - glDrawBuffer(GL_COLOR_ATTACHMENT0); - if (!gl_check_fb_complete(GL_FRAMEBUFFER)) { - return false; - } - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - - gd->logger = gl_string_marker_logger_new(); - if (gd->logger) { - log_add_target_tls(gd->logger); - } - - const char *vendor = (const char *)glGetString(GL_VENDOR); - log_debug("GL_VENDOR = %s", vendor); - if (strcmp(vendor, "NVIDIA Corporation") == 0) { - log_info("GL vendor is NVIDIA, don't use glFinish"); - gd->is_nvidia = true; - } else { - gd->is_nvidia = false; - } - - return true; -} - -void gl_deinit(struct gl_data *gd) { - gl_free_prog_main(&gd->win_shader); - - if (gd->logger) { - log_remove_target_tls(gd->logger); - gd->logger = NULL; - } - - gl_check_err(); -} - -GLuint gl_new_texture(GLenum target) { - GLuint texture; - glGenTextures(1, &texture); - if (!texture) { - log_error("Failed to generate texture"); - return 0; - } - - glBindTexture(target, texture); - glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_REPEAT); - glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_REPEAT); - glBindTexture(target, 0); - - return texture; -} - -/// Actually duplicate a texture into a new one, if this texture is shared -static inline void gl_image_decouple(backend_t *base, struct backend_image *img) { - if (img->inner->refcount == 1) { - return; - } - auto gd = (struct gl_data *)base; - auto inner = (struct gl_texture *)img->inner; - auto new_tex = ccalloc(1, struct gl_texture); - - new_tex->texture = gl_new_texture(GL_TEXTURE_2D); - new_tex->y_inverted = true; - new_tex->height = inner->height; - new_tex->width = inner->width; - new_tex->refcount = 1; - new_tex->user_data = gd->decouple_texture_user_data(base, inner->user_data); - - glBindTexture(GL_TEXTURE_2D, new_tex->texture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, new_tex->width, new_tex->height, 0, - GL_BGRA, GL_UNSIGNED_BYTE, NULL); - glBindTexture(GL_TEXTURE_2D, 0); - - assert(gd->present_prog); - glUseProgram(gd->present_prog); - glBindTexture(GL_TEXTURE_2D, inner->texture); - - GLuint fbo; - glGenFramebuffers(1, &fbo); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, - new_tex->texture, 0); - glDrawBuffer(GL_COLOR_ATTACHMENT0); - gl_check_fb_complete(GL_DRAW_FRAMEBUFFER); - - glClearColor(0, 0, 0, 0); - glClear(GL_COLOR_BUFFER_BIT); - - // clang-format off - GLint coord[] = { - // top left - 0, 0, // vertex coord - 0, 0, // texture coord - - // top right - new_tex->width, 0, // vertex coord - new_tex->width, 0, // texture coord - - // bottom right - new_tex->width, new_tex->height, - new_tex->width, new_tex->height, - - // bottom left - 0, new_tex->height, - 0, new_tex->height, - }; - // clang-format on - GLuint indices[] = {0, 1, 2, 2, 3, 0}; - - GLuint vao; - glGenVertexArrays(1, &vao); - glBindVertexArray(vao); - - GLuint bo[2]; - glGenBuffers(2, bo); - glBindBuffer(GL_ARRAY_BUFFER, bo[0]); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]); - glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord) * 16, coord, GL_STATIC_DRAW); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(*indices) * 6, indices, - GL_STATIC_DRAW); - - glEnableVertexAttribArray(vert_coord_loc); - glEnableVertexAttribArray(vert_in_texcoord_loc); - glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 4, NULL); - glVertexAttribPointer(vert_in_texcoord_loc, 2, GL_INT, GL_FALSE, - sizeof(GLint) * 4, (void *)(sizeof(GLint) * 2)); - - glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, NULL); - - glDisableVertexAttribArray(vert_coord_loc); - glDisableVertexAttribArray(vert_in_texcoord_loc); - glBindVertexArray(0); - glDeleteVertexArrays(1, &vao); - - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - glDeleteBuffers(2, bo); - - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - glDeleteFramebuffers(1, &fbo); - - glBindTexture(GL_TEXTURE_2D, 0); - glUseProgram(0); - - gl_check_err(); - - img->inner = (struct backend_image_inner_base *)new_tex; - inner->refcount--; -} - -static void gl_image_apply_alpha(backend_t *base, struct backend_image *img, - const region_t *reg_op, double alpha) { - // Result color = 0 (GL_ZERO) + alpha (GL_CONSTANT_ALPHA) * original color - auto inner = (struct gl_texture *)img->inner; - glBlendFunc(GL_ZERO, GL_CONSTANT_ALPHA); - glBlendColor(0, 0, 0, (GLclampf)alpha); - GLuint fbo; - glGenFramebuffers(1, &fbo); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, - inner->texture, 0); - glDrawBuffer(GL_COLOR_ATTACHMENT0); - _gl_fill(base, (struct color){0, 0, 0, 0}, reg_op, fbo, 0, false); - glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - glDeleteFramebuffers(1, &fbo); -} - -void gl_present(backend_t *base, const region_t *region) { - auto gd = (struct gl_data *)base; - - int nrects; - const rect_t *rect = pixman_region32_rectangles((region_t *)region, &nrects); - auto coord = ccalloc(nrects * 8, GLint); - auto indices = ccalloc(nrects * 6, GLuint); - for (int i = 0; i < nrects; i++) { - // clang-format off - memcpy(&coord[i * 8], - ((GLint[]){rect[i].x1, gd->height - rect[i].y2, - rect[i].x2, gd->height - rect[i].y2, - rect[i].x2, gd->height - rect[i].y1, - rect[i].x1, gd->height - rect[i].y1}), - sizeof(GLint) * 8); - // clang-format on - - GLuint u = (GLuint)(i * 4); - memcpy(&indices[i * 6], - ((GLuint[]){u + 0, u + 1, u + 2, u + 2, u + 3, u + 0}), - sizeof(GLuint) * 6); - } - - glUseProgram(gd->present_prog); - glBindTexture(GL_TEXTURE_2D, gd->back_texture); - - GLuint vao; - glGenVertexArrays(1, &vao); - glBindVertexArray(vao); - - GLuint bo[2]; - glGenBuffers(2, bo); - glEnableVertexAttribArray(vert_coord_loc); - glBindBuffer(GL_ARRAY_BUFFER, bo[0]); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]); - glBufferData(GL_ARRAY_BUFFER, (long)sizeof(GLint) * nrects * 8, coord, GL_STREAM_DRAW); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(GLuint) * nrects * 6, indices, - GL_STREAM_DRAW); - - glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 2, NULL); - glDrawElements(GL_TRIANGLES, nrects * 6, GL_UNSIGNED_INT, NULL); - - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - glBindVertexArray(0); - glDeleteBuffers(2, bo); - glDeleteVertexArrays(1, &vao); - - free(coord); - free(indices); -} - -bool gl_image_op(backend_t *base, enum image_operations op, void *image_data, - const region_t *reg_op, const region_t *reg_visible attr_unused, void *arg) { - struct backend_image *tex = image_data; - switch (op) { - case IMAGE_OP_APPLY_ALPHA: - gl_image_decouple(base, tex); - assert(tex->inner->refcount == 1); - gl_image_apply_alpha(base, tex, reg_op, *(double *)arg); - break; - } - - return true; -} - -bool gl_read_pixel(backend_t *base attr_unused, void *image_data, int x, int y, - struct color *output) { - struct backend_image *tex = image_data; - auto inner = (struct gl_texture *)tex->inner; - GLfloat color[4]; - glReadPixels(x, inner->y_inverted ? inner->height - y : y, 1, 1, GL_RGBA, - GL_FLOAT, color); - output->alpha = color[3]; - output->red = color[0]; - output->green = color[1]; - output->blue = color[2]; - - bool ret = glGetError() == GL_NO_ERROR; - gl_clear_err(); - return ret; -} diff --git a/src/backend/gl/gl_common.h b/src/backend/gl/gl_common.h deleted file mode 100644 index b1d93b0..0000000 --- a/src/backend/gl/gl_common.h +++ /dev/null @@ -1,220 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Yuxuan Shui <[email protected]> -#pragma once -#include <GL/gl.h> -#include <GL/glext.h> -#include <stdbool.h> -#include <string.h> - -#include "backend/backend.h" -#include "log.h" -#include "region.h" - -#define CASESTRRET(s) \ - case s: return #s - -// Program and uniforms for window shader -typedef struct { - GLuint prog; - GLint unifm_opacity; - GLint unifm_invert_color; - GLint unifm_tex; - GLint unifm_dim; - GLint unifm_brightness; - GLint unifm_max_brightness; -} gl_win_shader_t; - -// Program and uniforms for brightness shader -typedef struct { - GLuint prog; -} gl_brightness_shader_t; - -// Program and uniforms for blur shader -typedef struct { - GLuint prog; - GLint unifm_pixel_norm; - GLint unifm_opacity; - GLint texorig_loc; - GLint scale_loc; -} gl_blur_shader_t; - -typedef struct { - GLuint prog; - GLint color_loc; -} gl_fill_shader_t; - -/// @brief Wrapper of a binded GLX texture. -struct gl_texture { - int refcount; - bool has_alpha; - GLuint texture; - int width, height; - bool y_inverted; - - // Textures for auxiliary uses. - GLuint auxiliary_texture[2]; - void *user_data; -}; - -struct gl_data { - backend_t base; - // If we are using proprietary NVIDIA driver - bool is_nvidia; - // Height and width of the root window - int height, width; - gl_win_shader_t win_shader; - gl_brightness_shader_t brightness_shader; - gl_fill_shader_t fill_shader; - GLuint back_texture, back_fbo; - GLuint present_prog; - - /// Called when an gl_texture is decoupled from the texture it refers. Returns - /// the decoupled user_data - void *(*decouple_texture_user_data)(backend_t *base, void *user_data); - - /// Release the user data attached to a gl_texture - void (*release_user_data)(backend_t *base, struct gl_texture *); - - struct log_target *logger; -}; - -typedef struct session session_t; - -#define GL_PROG_MAIN_INIT \ - { .prog = 0, .unifm_opacity = -1, .unifm_invert_color = -1, .unifm_tex = -1, } - -GLuint gl_create_shader(GLenum shader_type, const char *shader_str); -GLuint gl_create_program(const GLuint *const shaders, int nshaders); -GLuint gl_create_program_from_str(const char *vert_shader_str, const char *frag_shader_str); - -/** - * @brief Render a region with texture data. - */ -void gl_compose(backend_t *, void *ptex, - int dst_x1, int dst_y1, int dst_x2, int dst_y2, - const region_t *reg_tgt, const region_t *reg_visible); - -void gl_resize(struct gl_data *, int width, int height); - -bool gl_init(struct gl_data *gd, session_t *); -void gl_deinit(struct gl_data *gd); - -GLuint gl_new_texture(GLenum target); - -bool gl_image_op(backend_t *base, enum image_operations op, void *image_data, - const region_t *reg_op, const region_t *reg_visible, void *arg); - -void gl_release_image(backend_t *base, void *image_data); - -void *gl_clone(backend_t *base, const void *image_data, const region_t *reg_visible); - -bool gl_blur(backend_t *base, double opacity, void *, const region_t *reg_blur, - const region_t *reg_visible); -void *gl_create_blur_context(backend_t *base, enum blur_method, void *args); -void gl_destroy_blur_context(backend_t *base, void *ctx); -void gl_get_blur_size(void *blur_context, int *width, int *height); - -void gl_fill(backend_t *base, struct color, const region_t *clip); - -void gl_present(backend_t *base, const region_t *); -bool gl_read_pixel(backend_t *base, void *image_data, int x, int y, struct color *output); - -static inline void gl_delete_texture(GLuint texture) { - glDeleteTextures(1, &texture); -} - -/** - * Get a textual representation of an OpenGL error. - */ -static inline const char *gl_get_err_str(GLenum err) { - switch (err) { - CASESTRRET(GL_NO_ERROR); - CASESTRRET(GL_INVALID_ENUM); - CASESTRRET(GL_INVALID_VALUE); - CASESTRRET(GL_INVALID_OPERATION); - CASESTRRET(GL_INVALID_FRAMEBUFFER_OPERATION); - CASESTRRET(GL_OUT_OF_MEMORY); - CASESTRRET(GL_STACK_UNDERFLOW); - CASESTRRET(GL_STACK_OVERFLOW); - CASESTRRET(GL_FRAMEBUFFER_UNDEFINED); - CASESTRRET(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT); - CASESTRRET(GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT); - CASESTRRET(GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER); - CASESTRRET(GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER); - CASESTRRET(GL_FRAMEBUFFER_UNSUPPORTED); - CASESTRRET(GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE); - CASESTRRET(GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS); - } - return NULL; -} - -/** - * Check for GLX error. - * - * http://blog.nobel-joergensen.com/2013/01/29/debugging-opengl-using-glgeterror/ - */ -static inline void gl_check_err_(const char *func, int line) { - GLenum err = GL_NO_ERROR; - - while (GL_NO_ERROR != (err = glGetError())) { - const char *errtext = gl_get_err_str(err); - if (errtext) { - log_printf(tls_logger, LOG_LEVEL_ERROR, func, - "GLX error at line %d: %s", line, errtext); - } else { - log_printf(tls_logger, LOG_LEVEL_ERROR, func, - "GLX error at line %d: %d", line, err); - } - } -} - -static inline void gl_clear_err(void) { - while (glGetError() != GL_NO_ERROR) - ; -} - -#define gl_check_err() gl_check_err_(__func__, __LINE__) - -/** - * Check for GL framebuffer completeness. - */ -static inline bool gl_check_fb_complete_(const char *func, int line, GLenum fb) { - GLenum status = glCheckFramebufferStatus(fb); - - if (status == GL_FRAMEBUFFER_COMPLETE) { - return true; - } - - const char *stattext = gl_get_err_str(status); - if (stattext) { - log_printf(tls_logger, LOG_LEVEL_ERROR, func, - "Framebuffer attachment failed at line %d: %s", line, stattext); - } else { - log_printf(tls_logger, LOG_LEVEL_ERROR, func, - "Framebuffer attachment failed at line %d: %d", line, status); - } - - return false; -} - -#define gl_check_fb_complete(fb) gl_check_fb_complete_(__func__, __LINE__, (fb)) - -/** - * Check if a GLX extension exists. - */ -static inline bool gl_has_extension(const char *ext) { - int nexts = 0; - glGetIntegerv(GL_NUM_EXTENSIONS, &nexts); - for (int i = 0; i < nexts || !nexts; i++) { - const char *exti = (const char *)glGetStringi(GL_EXTENSIONS, (GLuint)i); - if (exti == NULL) { - break; - } - if (strcmp(ext, exti) == 0) { - return true; - } - } - gl_clear_err(); - log_info("Missing GL extension %s.", ext); - return false; -} diff --git a/src/backend/gl/glx.c b/src/backend/gl/glx.c deleted file mode 100644 index 1397d19..0000000 --- a/src/backend/gl/glx.c +++ /dev/null @@ -1,648 +0,0 @@ -// SPDX-License-Identifier: MIT -/* - * Compton - a compositor for X11 - * - * Based on `xcompmgr` - Copyright (c) 2003, Keith Packard - * - * Copyright (c) 2011-2013, Christopher Jeffrey - * Copyright (c) 2019 Yuxuan Shui <[email protected]> - * See LICENSE-mit for more information. - * - */ - -#include <X11/Xlib-xcb.h> -#include <assert.h> -#include <limits.h> -#include <pixman.h> -#include <stdbool.h> -#include <stdlib.h> -#include <string.h> -#include <xcb/composite.h> -#include <xcb/xcb.h> - -#include "backend/backend.h" -#include "backend/backend_common.h" -#include "backend/gl/gl_common.h" -#include "backend/gl/glx.h" -#include "common.h" -#include "compiler.h" -#include "config.h" -#include "log.h" -#include "picom.h" -#include "region.h" -#include "utils.h" -#include "win.h" -#include "x.h" - -struct _glx_pixmap { - GLXPixmap glpixmap; - xcb_pixmap_t pixmap; - bool owned; -}; - -struct _glx_data { - struct gl_data gl; - Display *display; - int screen; - xcb_window_t target_win; - GLXContext ctx; -}; - -#define glXGetFBConfigAttribChecked(a, b, attr, c) \ - do { \ - if (glXGetFBConfigAttrib(a, b, attr, c)) { \ - log_info("Cannot get FBConfig attribute " #attr); \ - continue; \ - } \ - } while (0) - -struct glx_fbconfig_info *glx_find_fbconfig(Display *dpy, int screen, struct xvisual_info m) { - log_debug("Looking for FBConfig for RGBA%d%d%d%d, depth %d", m.red_size, - m.blue_size, m.green_size, m.alpha_size, m.visual_depth); - - int ncfg; - // clang-format off - GLXFBConfig *cfg = - glXChooseFBConfig(dpy, screen, (int[]){ - GLX_RENDER_TYPE, GLX_RGBA_BIT, - GLX_DRAWABLE_TYPE, GLX_PIXMAP_BIT, - GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR, - GLX_X_RENDERABLE, true, - GLX_FRAMEBUFFER_SRGB_CAPABLE_EXT, (GLint)GLX_DONT_CARE, - GLX_BUFFER_SIZE, m.red_size + m.green_size + - m.blue_size + m.alpha_size, - GLX_RED_SIZE, m.red_size, - GLX_BLUE_SIZE, m.blue_size, - GLX_GREEN_SIZE, m.green_size, - GLX_ALPHA_SIZE, m.alpha_size, - GLX_STENCIL_SIZE, 0, - GLX_DEPTH_SIZE, 0, - 0 - }, &ncfg); - // clang-format on - - int texture_tgts, y_inverted, texture_fmt; - bool found = false; - int min_cost = INT_MAX; - GLXFBConfig ret; - for (int i = 0; i < ncfg; i++) { - int depthbuf, stencil, doublebuf, bufsize; - glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_BUFFER_SIZE, &bufsize); - glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_DEPTH_SIZE, &depthbuf); - glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_STENCIL_SIZE, &stencil); - glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_DOUBLEBUFFER, &doublebuf); - if (depthbuf + stencil + bufsize * (doublebuf + 1) >= min_cost) { - continue; - } - int red, green, blue; - glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_RED_SIZE, &red); - glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_BLUE_SIZE, &blue); - glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_GREEN_SIZE, &green); - if (red != m.red_size || green != m.green_size || blue != m.blue_size) { - // Color size doesn't match, this cannot work - continue; - } - - int rgb, rgba; - glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_BIND_TO_TEXTURE_RGB_EXT, &rgb); - glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_BIND_TO_TEXTURE_RGBA_EXT, &rgba); - if (!rgb && !rgba) { - log_info("FBConfig is neither RGBA nor RGB, we cannot " - "handle this setup."); - continue; - } - - int visual; - glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_VISUAL_ID, &visual); - if (m.visual_depth != -1 && - x_get_visual_depth(XGetXCBConnection(dpy), (xcb_visualid_t)visual) != - m.visual_depth) { - // FBConfig and the correspondent X Visual might not have the same - // depth. (e.g. 32 bit FBConfig with a 24 bit Visual). This is - // quite common, seen in both open source and proprietary drivers. - // - // If the FBConfig has a matching depth but its visual doesn't, we - // still cannot use it. - continue; - } - - // All check passed, we are using this one. - found = true; - ret = cfg[i]; - glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_BIND_TO_TEXTURE_TARGETS_EXT, - &texture_tgts); - glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_Y_INVERTED_EXT, &y_inverted); - - // Prefer the texture format with matching alpha, with the other one as - // fallback - if (m.alpha_size) { - texture_fmt = rgba ? GLX_TEXTURE_FORMAT_RGBA_EXT - : GLX_TEXTURE_FORMAT_RGB_EXT; - } else { - texture_fmt = - rgb ? GLX_TEXTURE_FORMAT_RGB_EXT : GLX_TEXTURE_FORMAT_RGBA_EXT; - } - min_cost = depthbuf + stencil + bufsize * (doublebuf + 1); - } - free(cfg); - if (!found) { - return NULL; - } - - auto info = cmalloc(struct glx_fbconfig_info); - info->cfg = ret; - info->texture_tgts = texture_tgts; - info->texture_fmt = texture_fmt; - info->y_inverted = y_inverted; - return info; -} - -/** - * Free a glx_texture_t. - */ -static void glx_release_image(backend_t *base, struct gl_texture *tex) { - struct _glx_data *gd = (void *)base; - - struct _glx_pixmap *p = tex->user_data; - // Release binding - if (p->glpixmap && tex->texture) { - glBindTexture(GL_TEXTURE_2D, tex->texture); - glXReleaseTexImageEXT(gd->display, p->glpixmap, GLX_FRONT_LEFT_EXT); - glBindTexture(GL_TEXTURE_2D, 0); - } - - // Free GLX Pixmap - if (p->glpixmap) { - glXDestroyPixmap(gd->display, p->glpixmap); - p->glpixmap = 0; - } - - if (p->owned) { - xcb_free_pixmap(base->c, p->pixmap); - p->pixmap = XCB_NONE; - } - - free(p); - tex->user_data = NULL; -} - -/** - * Destroy GLX related resources. - */ -void glx_deinit(backend_t *base) { - struct _glx_data *gd = (void *)base; - - gl_deinit(&gd->gl); - - // Destroy GLX context - if (gd->ctx) { - glXMakeCurrent(gd->display, None, NULL); - glXDestroyContext(gd->display, gd->ctx); - gd->ctx = 0; - } - - free(gd); -} - -static void *glx_decouple_user_data(backend_t *base attr_unused, void *ud attr_unused) { - auto ret = cmalloc(struct _glx_pixmap); - ret->owned = false; - ret->glpixmap = 0; - ret->pixmap = 0; - return ret; -} - -static bool glx_set_swap_interval(int interval, Display *dpy, GLXDrawable drawable) { - bool vsync_enabled = false; - if (glxext.has_GLX_MESA_swap_control) { - vsync_enabled = (glXSwapIntervalMESA((uint)interval) == 0); - } - if (!vsync_enabled && glxext.has_GLX_SGI_swap_control) { - vsync_enabled = (glXSwapIntervalSGI(interval) == 0); - } - if (!vsync_enabled && glxext.has_GLX_EXT_swap_control) { - // glXSwapIntervalEXT doesn't return if it's successful - glXSwapIntervalEXT(dpy, drawable, interval); - vsync_enabled = true; - } - return vsync_enabled; -} - -/** - * Initialize OpenGL. - */ -static backend_t *glx_init(session_t *ps) { - bool success = false; - glxext_init(ps->dpy, ps->scr); - auto gd = ccalloc(1, struct _glx_data); - init_backend_base(&gd->gl.base, ps); - - gd->display = ps->dpy; - gd->screen = ps->scr; - gd->target_win = session_get_target_window(ps); - - XVisualInfo *pvis = NULL; - - // Check for GLX extension - if (!ps->glx_exists) { - log_error("No GLX extension."); - goto end; - } - - // Get XVisualInfo - int nitems = 0; - XVisualInfo vreq = {.visualid = ps->vis}; - pvis = XGetVisualInfo(ps->dpy, VisualIDMask, &vreq, &nitems); - if (!pvis) { - log_error("Failed to acquire XVisualInfo for current visual."); - goto end; - } - - // Ensure the visual is double-buffered - int value = 0; - if (glXGetConfig(ps->dpy, pvis, GLX_USE_GL, &value) || !value) { - log_error("Root visual is not a GL visual."); - goto end; - } - - if (glXGetConfig(ps->dpy, pvis, GLX_STENCIL_SIZE, &value) || !value) { - log_error("Root visual lacks stencil buffer."); - goto end; - } - - if (glXGetConfig(ps->dpy, pvis, GLX_DOUBLEBUFFER, &value) || !value) { - log_error("Root visual is not a double buffered GL visual."); - goto end; - } - - if (glXGetConfig(ps->dpy, pvis, GLX_RGBA, &value) || !value) { - log_error("Root visual is a color index visual, not supported"); - goto end; - } - - if (!glxext.has_GLX_EXT_texture_from_pixmap) { - log_error("GLX_EXT_texture_from_pixmap is not supported by your driver"); - goto end; - } - - if (!glxext.has_GLX_ARB_create_context) { - log_error("GLX_ARB_create_context is not supported by your driver"); - goto end; - } - - // Find a fbconfig with visualid matching the one from the target win, so we can - // be sure that the fbconfig is compatible with our target window. - int ncfgs; - GLXFBConfig *cfg = glXGetFBConfigs(gd->display, gd->screen, &ncfgs); - bool found = false; - for (int i = 0; i < ncfgs; i++) { - int visualid; - glXGetFBConfigAttribChecked(gd->display, cfg[i], GLX_VISUAL_ID, &visualid); - if ((VisualID)visualid != pvis->visualid) { - continue; - } - - gd->ctx = glXCreateContextAttribsARB(ps->dpy, cfg[i], 0, true, - (int[]){ - GLX_CONTEXT_MAJOR_VERSION_ARB, - 3, - GLX_CONTEXT_MINOR_VERSION_ARB, - 3, - GLX_CONTEXT_PROFILE_MASK_ARB, - GLX_CONTEXT_CORE_PROFILE_BIT_ARB, - 0, - }); - free(cfg); - - if (!gd->ctx) { - log_error("Failed to get GLX context."); - goto end; - } - found = true; - break; - } - - if (!found) { - log_error("Couldn't find a suitable fbconfig for the target window"); - goto end; - } - - // Attach GLX context - GLXDrawable tgt = gd->target_win; - if (!glXMakeCurrent(ps->dpy, tgt, gd->ctx)) { - log_error("Failed to attach GLX context."); - goto end; - } - - if (!gl_init(&gd->gl, ps)) { - log_error("Failed to setup OpenGL"); - goto end; - } - - gd->gl.decouple_texture_user_data = glx_decouple_user_data; - gd->gl.release_user_data = glx_release_image; - - if (ps->o.vsync) { - if (!glx_set_swap_interval(1, ps->dpy, tgt)) { - log_error("Failed to enable vsync."); - } - } else { - glx_set_swap_interval(0, ps->dpy, tgt); - } - - success = true; - -end: - if (pvis) { - XFree(pvis); - } - - if (!success) { - glx_deinit(&gd->gl.base); - return NULL; - } - - return &gd->gl.base; -} - -static void * -glx_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, bool owned) { - struct _glx_data *gd = (void *)base; - struct _glx_pixmap *glxpixmap = NULL; - // Retrieve pixmap parameters, if they aren't provided - if (fmt.visual_depth > OPENGL_MAX_DEPTH) { - log_error("Requested depth %d higher than max possible depth %d.", - fmt.visual_depth, OPENGL_MAX_DEPTH); - return false; - } - - if (fmt.visual_depth < 0) { - log_error("Pixmap %#010x with invalid depth %d", pixmap, fmt.visual_depth); - return false; - } - - auto r = xcb_get_geometry_reply(base->c, xcb_get_geometry(base->c, pixmap), NULL); - if (!r) { - log_error("Invalid pixmap %#010x", pixmap); - return NULL; - } - - log_trace("Binding pixmap %#010x", pixmap); - auto wd = ccalloc(1, struct backend_image); - wd->max_brightness = 1; - auto inner = ccalloc(1, struct gl_texture); - inner->width = wd->ewidth = r->width; - inner->height = wd->eheight = r->height; - wd->inner = (struct backend_image_inner_base *)inner; - free(r); - - auto fbcfg = glx_find_fbconfig(gd->display, gd->screen, fmt); - if (!fbcfg) { - log_error("Couldn't find FBConfig with requested visual %x", fmt.visual); - goto err; - } - - // Choose a suitable texture target for our pixmap. - // Refer to GLX_EXT_texture_om_pixmap spec to see what are the mean - // of the bits in texture_tgts - if (!(fbcfg->texture_tgts & GLX_TEXTURE_2D_BIT_EXT)) { - log_error("Cannot bind pixmap to GL_TEXTURE_2D, giving up"); - goto err; - } - - log_debug("depth %d, rgba %d", fmt.visual_depth, - (fbcfg->texture_fmt == GLX_TEXTURE_FORMAT_RGBA_EXT)); - - GLint attrs[] = { - GLX_TEXTURE_FORMAT_EXT, - fbcfg->texture_fmt, - GLX_TEXTURE_TARGET_EXT, - GLX_TEXTURE_2D_EXT, - 0, - }; - - inner->y_inverted = fbcfg->y_inverted; - - glxpixmap = cmalloc(struct _glx_pixmap); - glxpixmap->pixmap = pixmap; - glxpixmap->glpixmap = glXCreatePixmap(gd->display, fbcfg->cfg, pixmap, attrs); - glxpixmap->owned = owned; - free(fbcfg); - - if (!glxpixmap->glpixmap) { - log_error("Failed to create glpixmap for pixmap %#010x", pixmap); - goto err; - } - - log_trace("GLXPixmap %#010lx", glxpixmap->glpixmap); - - // Create texture - inner->user_data = glxpixmap; - inner->texture = gl_new_texture(GL_TEXTURE_2D); - inner->has_alpha = fmt.alpha_size != 0; - wd->opacity = 1; - wd->color_inverted = false; - wd->dim = 0; - wd->inner->refcount = 1; - glBindTexture(GL_TEXTURE_2D, inner->texture); - glXBindTexImageEXT(gd->display, glxpixmap->glpixmap, GLX_FRONT_LEFT_EXT, NULL); - glBindTexture(GL_TEXTURE_2D, 0); - - gl_check_err(); - return wd; -err: - if (glxpixmap && glxpixmap->glpixmap) { - glXDestroyPixmap(gd->display, glxpixmap->glpixmap); - } - free(glxpixmap); - - if (owned) { - xcb_free_pixmap(base->c, pixmap); - } - free(wd); - return NULL; -} - -static void glx_present(backend_t *base, const region_t *region attr_unused) { - struct _glx_data *gd = (void *)base; - gl_present(base, region); - glXSwapBuffers(gd->display, gd->target_win); - if (!gd->gl.is_nvidia) { - glFinish(); - } -} - -static int glx_buffer_age(backend_t *base) { - if (!glxext.has_GLX_EXT_buffer_age) { - return -1; - } - - struct _glx_data *gd = (void *)base; - unsigned int val; - glXQueryDrawable(gd->display, gd->target_win, GLX_BACK_BUFFER_AGE_EXT, &val); - return (int)val ?: -1; -} - -static void glx_diagnostics(backend_t *base) { - struct _glx_data *gd = (void *)base; - bool warn_software_rendering = false; - const char *software_renderer_names[] = {"llvmpipe", "SWR", "softpipe"}; - auto glx_vendor = glXGetClientString(gd->display, GLX_VENDOR); - printf("* Driver vendors:\n"); - printf(" * GLX: %s\n", glx_vendor); - printf(" * GL: %s\n", glGetString(GL_VENDOR)); - - auto gl_renderer = (const char *)glGetString(GL_RENDERER); - printf("* GL renderer: %s\n", gl_renderer); - if (strcmp(glx_vendor, "Mesa Project and SGI")) { - for (size_t i = 0; i < ARR_SIZE(software_renderer_names); i++) { - if (strstr(gl_renderer, software_renderer_names[i]) != NULL) { - warn_software_rendering = true; - break; - } - } - } - -#ifdef GLX_MESA_query_renderer - if (glxext.has_GLX_MESA_query_renderer) { - unsigned int accelerated = 0; - glXQueryCurrentRendererIntegerMESA(GLX_RENDERER_ACCELERATED_MESA, &accelerated); - printf("* Accelerated: %d\n", accelerated); - - // Trust GLX_MESA_query_renderer when it's available - warn_software_rendering = (accelerated == 0); - } -#endif - - if (warn_software_rendering) { - printf("\n(You are using a software renderer. Unless you are doing this\n" - "intentionally, this means you don't have a graphics driver\n" - "properly installed. Performance will suffer. Please fix this\n" - "before reporting your issue.)\n"); - } -} - -struct backend_operations glx_ops = { - .init = glx_init, - .deinit = glx_deinit, - .bind_pixmap = glx_bind_pixmap, - .release_image = gl_release_image, - .compose = gl_compose, - .image_op = gl_image_op, - .set_image_property = default_set_image_property, - .read_pixel = gl_read_pixel, - .clone_image = default_clone_image, - .blur = gl_blur, - .is_image_transparent = default_is_image_transparent, - .present = glx_present, - .buffer_age = glx_buffer_age, - .render_shadow = default_backend_render_shadow, - .fill = gl_fill, - .create_blur_context = gl_create_blur_context, - .destroy_blur_context = gl_destroy_blur_context, - .get_blur_size = gl_get_blur_size, - .diagnostics = glx_diagnostics, - .max_buffer_age = 5, // Why? -}; - -/** - * Check if a GLX extension exists. - */ -static inline bool glx_has_extension(Display *dpy, int screen, const char *ext) { - const char *glx_exts = glXQueryExtensionsString(dpy, screen); - if (!glx_exts) { - log_error("Failed get GLX extension list."); - return false; - } - - auto inlen = strlen(ext); - const char *curr = glx_exts; - bool match = false; - while (curr && !match) { - const char *end = strchr(curr, ' '); - if (!end) { - // Last extension string - match = strcmp(ext, curr) == 0; - } else if (curr + inlen == end) { - // Length match, do match string - match = strncmp(ext, curr, (unsigned long)(end - curr)) == 0; - } - curr = end ? end + 1 : NULL; - } - - if (!match) { - log_info("Missing GLX extension %s.", ext); - } else { - log_info("Found GLX extension %s.", ext); - } - - return match; -} - -struct glxext_info glxext = {0}; -PFNGLXGETVIDEOSYNCSGIPROC glXGetVideoSyncSGI; -PFNGLXWAITVIDEOSYNCSGIPROC glXWaitVideoSyncSGI; -PFNGLXGETSYNCVALUESOMLPROC glXGetSyncValuesOML; -PFNGLXWAITFORMSCOMLPROC glXWaitForMscOML; -PFNGLXSWAPINTERVALEXTPROC glXSwapIntervalEXT; -PFNGLXSWAPINTERVALSGIPROC glXSwapIntervalSGI; -PFNGLXSWAPINTERVALMESAPROC glXSwapIntervalMESA; -PFNGLXBINDTEXIMAGEEXTPROC glXBindTexImageEXT; -PFNGLXRELEASETEXIMAGEEXTPROC glXReleaseTexImageEXT; -PFNGLXCREATECONTEXTATTRIBSARBPROC glXCreateContextAttribsARB; - -#ifdef GLX_MESA_query_renderer -PFNGLXQUERYCURRENTRENDERERINTEGERMESAPROC glXQueryCurrentRendererIntegerMESA; -#endif - -void glxext_init(Display *dpy, int screen) { - if (glxext.initialized) { - return; - } - glxext.initialized = true; -#define check_ext(name) glxext.has_##name = glx_has_extension(dpy, screen, #name) - check_ext(GLX_SGI_video_sync); - check_ext(GLX_SGI_swap_control); - check_ext(GLX_OML_sync_control); - check_ext(GLX_MESA_swap_control); - check_ext(GLX_EXT_swap_control); - check_ext(GLX_EXT_texture_from_pixmap); - check_ext(GLX_ARB_create_context); - check_ext(GLX_EXT_buffer_age); -#ifdef GLX_MESA_query_renderer - check_ext(GLX_MESA_query_renderer); -#endif -#undef check_ext - -#define lookup(name) (name = (__typeof__(name))glXGetProcAddress((GLubyte *)#name)) - // Checking if the returned function pointer is NULL is not really necessary, - // or maybe not even useful, since glXGetProcAddress might always return - // something. We are doing it just for completeness' sake. - if (!lookup(glXGetVideoSyncSGI) || !lookup(glXWaitVideoSyncSGI)) { - glxext.has_GLX_SGI_video_sync = false; - } - if (!lookup(glXSwapIntervalEXT)) { - glxext.has_GLX_EXT_swap_control = false; - } - if (!lookup(glXSwapIntervalMESA)) { - glxext.has_GLX_MESA_swap_control = false; - } - if (!lookup(glXSwapIntervalSGI)) { - glxext.has_GLX_SGI_swap_control = false; - } - if (!lookup(glXWaitForMscOML) || !lookup(glXGetSyncValuesOML)) { - glxext.has_GLX_OML_sync_control = false; - } - if (!lookup(glXBindTexImageEXT) || !lookup(glXReleaseTexImageEXT)) { - glxext.has_GLX_EXT_texture_from_pixmap = false; - } - if (!lookup(glXCreateContextAttribsARB)) { - glxext.has_GLX_ARB_create_context = false; - } -#ifdef GLX_MESA_query_renderer - if (!lookup(glXQueryCurrentRendererIntegerMESA)) { - glxext.has_GLX_MESA_query_renderer = false; - } -#endif -#undef lookup -} diff --git a/src/backend/gl/glx.h b/src/backend/gl/glx.h deleted file mode 100644 index 1061f0b..0000000 --- a/src/backend/gl/glx.h +++ /dev/null @@ -1,77 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Yuxuan Shui <[email protected]> -#pragma once -#include <stdbool.h> -// Older version of glx.h defines function prototypes for these extensions... -// Rename them to avoid conflicts -#define glXSwapIntervalMESA glXSwapIntervalMESA_ -#define glXBindTexImageEXT glXBindTexImageEXT_ -#define glXReleaseTexImageEXT glXReleaseTexImageEXT -#include <GL/glx.h> -#undef glXSwapIntervalMESA -#undef glXBindTexImageEXT -#undef glXReleaseTexImageEXT -#include <X11/Xlib.h> -#include <xcb/xcb.h> -#include <xcb/render.h> - -#include "log.h" -#include "compiler.h" -#include "utils.h" -#include "x.h" - -struct glx_fbconfig_info { - GLXFBConfig cfg; - int texture_tgts; - int texture_fmt; - int y_inverted; -}; - -/// The search criteria for glx_find_fbconfig -struct glx_fbconfig_criteria { - /// Bit width of the red component - int red_size; - /// Bit width of the green component - int green_size; - /// Bit width of the blue component - int blue_size; - /// Bit width of the alpha component - int alpha_size; - /// The depth of X visual - int visual_depth; -}; - -struct glx_fbconfig_info *glx_find_fbconfig(Display *, int screen, struct xvisual_info); - - -struct glxext_info { - bool initialized; - bool has_GLX_SGI_video_sync; - bool has_GLX_SGI_swap_control; - bool has_GLX_OML_sync_control; - bool has_GLX_MESA_swap_control; - bool has_GLX_EXT_swap_control; - bool has_GLX_EXT_texture_from_pixmap; - bool has_GLX_ARB_create_context; - bool has_GLX_EXT_buffer_age; - bool has_GLX_MESA_query_renderer; -}; - -extern struct glxext_info glxext; - -extern PFNGLXGETVIDEOSYNCSGIPROC glXGetVideoSyncSGI; -extern PFNGLXWAITVIDEOSYNCSGIPROC glXWaitVideoSyncSGI; -extern PFNGLXGETSYNCVALUESOMLPROC glXGetSyncValuesOML; -extern PFNGLXWAITFORMSCOMLPROC glXWaitForMscOML; -extern PFNGLXSWAPINTERVALEXTPROC glXSwapIntervalEXT; -extern PFNGLXSWAPINTERVALSGIPROC glXSwapIntervalSGI; -extern PFNGLXSWAPINTERVALMESAPROC glXSwapIntervalMESA; -extern PFNGLXBINDTEXIMAGEEXTPROC glXBindTexImageEXT; -extern PFNGLXRELEASETEXIMAGEEXTPROC glXReleaseTexImageEXT; -extern PFNGLXCREATECONTEXTATTRIBSARBPROC glXCreateContextAttribsARB; - -#ifdef GLX_MESA_query_renderer -extern PFNGLXQUERYCURRENTRENDERERINTEGERMESAPROC glXQueryCurrentRendererIntegerMESA; -#endif - -void glxext_init(Display *, int screen); diff --git a/src/backend/meson.build b/src/backend/meson.build deleted file mode 100644 index b8f0ad9..0000000 --- a/src/backend/meson.build +++ /dev/null @@ -1,7 +0,0 @@ -# enable xrender -srcs += [ files('backend_common.c', 'xrender/xrender.c', 'dummy/dummy.c', 'backend.c', 'driver.c') ] - -# enable opengl -if get_option('opengl') - srcs += [ files('gl/gl_common.c', 'gl/glx.c') ] -endif diff --git a/src/backend/xrender/xrender.c b/src/backend/xrender/xrender.c deleted file mode 100644 index ccf358b..0000000 --- a/src/backend/xrender/xrender.c +++ /dev/null @@ -1,779 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Yuxuan Shui <[email protected]> -#include <assert.h> -#include <math.h> -#include <stdlib.h> -#include <string.h> - -#include <xcb/composite.h> -#include <xcb/present.h> -#include <xcb/render.h> -#include <xcb/sync.h> -#include <xcb/xcb.h> - -#include "backend/backend.h" -#include "backend/backend_common.h" -#include "common.h" -#include "config.h" -#include "kernel.h" -#include "log.h" -#include "picom.h" -#include "region.h" -#include "types.h" -#include "utils.h" -#include "win.h" -#include "x.h" - -typedef struct _xrender_data { - backend_t base; - /// If vsync is enabled and supported by the current system - bool vsync; - xcb_visualid_t default_visual; - /// Target window - xcb_window_t target_win; - /// Painting target, it is either the root or the overlay - xcb_render_picture_t target; - /// Back buffers. Double buffer, with 1 for temporary render use - xcb_render_picture_t back[3]; - /// The back buffer that is for temporary use - /// Age of each back buffer. - int buffer_age[3]; - /// The back buffer we should be painting into - int curr_back; - /// The corresponding pixmap to the back buffer - xcb_pixmap_t back_pixmap[3]; - /// Pictures of pixel of different alpha value, used as a mask to - /// paint transparent images - xcb_render_picture_t alpha_pict[256]; - - // XXX don't know if these are really needed - - /// 1x1 white picture - xcb_render_picture_t white_pixel; - /// 1x1 black picture - xcb_render_picture_t black_pixel; - - /// Width and height of the target pixmap - int target_width, target_height; - - xcb_special_event_t *present_event; -} xrender_data; - -struct _xrender_blur_context { - enum blur_method method; - /// Blur kernels converted to X format - struct x_convolution_kernel **x_blur_kernel; - - int resize_width, resize_height; - - /// Number of blur kernels - int x_blur_kernel_count; -}; - -struct _xrender_image_data_inner { - // struct backend_image_inner_base - int refcount; - bool has_alpha; - - // Pixmap that the client window draws to, - // it will contain the content of client window. - xcb_pixmap_t pixmap; - // A Picture links to the Pixmap - xcb_render_picture_t pict; - int width, height; - xcb_visualid_t visual; - uint8_t depth; - // Whether we own this image, e.g. we allocated it; - // or not, e.g. this is a named pixmap of a X window. - bool owned; -}; - -static void compose_impl(struct _xrender_data *xd, const struct backend_image *img, - int dst_x1, int dst_y1, int dst_x2, int dst_y2, - const region_t *reg_paint, const region_t *reg_visible, - xcb_render_picture_t result) { - auto alpha_pict = xd->alpha_pict[(int)(img->opacity * MAX_ALPHA)]; - auto inner = (struct _xrender_image_data_inner *)img->inner; - region_t reg; - - bool has_alpha = inner->has_alpha || img->opacity != 1; - const auto tmpw = to_u16_checked(dst_x2 - dst_x1); - const auto tmph = to_u16_checked(dst_y2 - dst_y1); - const auto tmpew = to_u16_checked(dst_x2 - dst_x1); - const auto tmpeh = to_u16_checked(dst_y2 - dst_y1); - const xcb_render_color_t dim_color = { - .red = 0, .green = 0, .blue = 0, .alpha = (uint16_t)(0xffff * img->dim)}; - - // Clip region of rendered_pict might be set during rendering, clear it to - // make sure we get everything into the buffer - x_clear_picture_clip_region(xd->base.c, inner->pict); - - pixman_region32_init(®); - pixman_region32_intersect(®, (region_t *)reg_paint, (region_t *)reg_visible); - x_set_picture_clip_region(xd->base.c, result, 0, 0, ®); - -#define DOUBLE_TO_XFIXED(value) ((xcb_render_fixed_t)(((double)(value)) * 65536)) - { - const xcb_render_transform_t transform = { - DOUBLE_TO_XFIXED((double)img->ewidth / (double)tmpew), DOUBLE_TO_XFIXED(0.0), DOUBLE_TO_XFIXED(0.0), - DOUBLE_TO_XFIXED(0.0), DOUBLE_TO_XFIXED((double)img->eheight / (double)tmpeh), DOUBLE_TO_XFIXED(0.0), - DOUBLE_TO_XFIXED(0.0), DOUBLE_TO_XFIXED(0.0), DOUBLE_TO_XFIXED(1.0), - }; - xcb_render_set_picture_transform(xd->base.c, inner->pict, transform); - xcb_render_set_picture_filter(xd->base.c, inner->pict, 7, "nearest", 0, NULL); - } -#undef DOUBLE_TO_XFIXED - - if ((img->color_inverted || img->dim != 0) && has_alpha) { - // Apply image properties using a temporary image, because the source - // image is transparent. Otherwise the properties can be applied directly - // on the target image. - auto tmp_pict = - x_create_picture_with_visual(xd->base.c, xd->base.root, inner->width, - inner->height, inner->visual, 0, NULL); - - // Set clip region translated to source coordinate - x_set_picture_clip_region(xd->base.c, tmp_pict, to_i16_checked(-dst_x1), - to_i16_checked(-dst_y1), ®); - // Copy source -> tmp - xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_OVER, inner->pict, - XCB_NONE, tmp_pict, 0, 0, 0, 0, 0, 0, tmpw, tmph); - if (img->color_inverted) { - if (inner->has_alpha) { - auto tmp_pict2 = x_create_picture_with_visual( - xd->base.c, xd->base.root, tmpw, tmph, inner->visual, - 0, NULL); - xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_OVER, - tmp_pict, XCB_NONE, tmp_pict2, 0, 0, - 0, 0, 0, 0, tmpw, tmph); - - xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_DIFFERENCE, - xd->white_pixel, XCB_NONE, tmp_pict, - 0, 0, 0, 0, 0, 0, tmpw, tmph); - xcb_render_composite( - xd->base.c, XCB_RENDER_PICT_OP_IN_REVERSE, tmp_pict2, - XCB_NONE, tmp_pict, 0, 0, 0, 0, 0, 0, tmpw, tmph); - xcb_render_free_picture(xd->base.c, tmp_pict2); - } else { - xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_DIFFERENCE, - xd->white_pixel, XCB_NONE, tmp_pict, - 0, 0, 0, 0, 0, 0, tmpw, tmph); - } - } - if (img->dim != 0) { - // Dim the actually content of window - xcb_rectangle_t rect = { - .x = 0, - .y = 0, - .width = tmpw, - .height = tmph, - }; - - xcb_render_fill_rectangles(xd->base.c, XCB_RENDER_PICT_OP_OVER, - tmp_pict, dim_color, 1, &rect); - } - - xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_OVER, tmp_pict, - alpha_pict, result, 0, 0, 0, 0, to_i16_checked(dst_x1), - to_i16_checked(dst_y1), tmpew, tmpeh); - xcb_render_free_picture(xd->base.c, tmp_pict); - } else { - uint8_t op = (has_alpha ? XCB_RENDER_PICT_OP_OVER : XCB_RENDER_PICT_OP_SRC); - - xcb_render_composite(xd->base.c, op, inner->pict, alpha_pict, result, 0, - 0, 0, 0, to_i16_checked(dst_x1), - to_i16_checked(dst_y1), tmpew, tmpeh); - if (img->dim != 0 || img->color_inverted) { - // Apply properties, if we reach here, then has_alpha == false - assert(!has_alpha); - if (img->color_inverted) { - xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_DIFFERENCE, - xd->white_pixel, XCB_NONE, result, 0, - 0, 0, 0, to_i16_checked(dst_x1), - to_i16_checked(dst_y1), tmpew, tmpeh); - } - - if (img->dim != 0) { - // Dim the actually content of window - xcb_rectangle_t rect = { - .x = to_i16_checked(dst_x1), - .y = to_i16_checked(dst_y1), - .width = tmpew, - .height = tmpeh, - }; - - xcb_render_fill_rectangles(xd->base.c, XCB_RENDER_PICT_OP_OVER, - result, dim_color, 1, &rect); - } - } - } - pixman_region32_fini(®); -} - -static void compose(backend_t *base, void *img_data, - int dst_x1, int dst_y1, int dst_x2, int dst_y2, - const region_t *reg_paint, const region_t *reg_visible) { - // TODO(dccsillag): use dst_{x,y}2 - struct _xrender_data *xd = (void *)base; - return compose_impl(xd, img_data, dst_x1, dst_y1, dst_x2, dst_y2, reg_paint, reg_visible, xd->back[2]); -} - -static void fill(backend_t *base, struct color c, const region_t *clip) { - struct _xrender_data *xd = (void *)base; - const rect_t *extent = pixman_region32_extents((region_t *)clip); - x_set_picture_clip_region(base->c, xd->back[2], 0, 0, clip); - // color is in X fixed point representation - xcb_render_fill_rectangles( - base->c, XCB_RENDER_PICT_OP_OVER, xd->back[2], - (xcb_render_color_t){.red = (uint16_t)(c.red * 0xffff), - .green = (uint16_t)(c.green * 0xffff), - .blue = (uint16_t)(c.blue * 0xffff), - .alpha = (uint16_t)(c.alpha * 0xffff)}, - 1, - (xcb_rectangle_t[]){{.x = to_i16_checked(extent->x1), - .y = to_i16_checked(extent->y1), - .width = to_u16_checked(extent->x2 - extent->x1), - .height = to_u16_checked(extent->y2 - extent->y1)}}); -} - -static bool blur(backend_t *backend_data, double opacity, void *ctx_, - const region_t *reg_blur, const region_t *reg_visible) { - struct _xrender_blur_context *bctx = ctx_; - if (bctx->method == BLUR_METHOD_NONE) { - return true; - } - - struct _xrender_data *xd = (void *)backend_data; - xcb_connection_t *c = xd->base.c; - region_t reg_op; - pixman_region32_init(®_op); - pixman_region32_intersect(®_op, (region_t *)reg_blur, (region_t *)reg_visible); - if (!pixman_region32_not_empty(®_op)) { - pixman_region32_fini(®_op); - return true; - } - - region_t reg_op_resized = - resize_region(®_op, bctx->resize_width, bctx->resize_height); - - const pixman_box32_t *extent_resized = pixman_region32_extents(®_op_resized); - const auto height_resized = to_u16_checked(extent_resized->y2 - extent_resized->y1); - const auto width_resized = to_u16_checked(extent_resized->x2 - extent_resized->x1); - static const char *filter0 = "Nearest"; // The "null" filter - static const char *filter = "convolution"; - - // Create a buffer for storing blurred picture, make it just big enough - // for the blur region - const uint32_t pic_attrs_mask = XCB_RENDER_CP_REPEAT; - const xcb_render_create_picture_value_list_t pic_attrs = {.repeat = XCB_RENDER_REPEAT_PAD}; - xcb_render_picture_t tmp_picture[2] = { - x_create_picture_with_visual(xd->base.c, xd->base.root, width_resized, height_resized, - xd->default_visual, pic_attrs_mask, &pic_attrs), - x_create_picture_with_visual(xd->base.c, xd->base.root, width_resized, height_resized, - xd->default_visual, pic_attrs_mask, &pic_attrs)}; - - if (!tmp_picture[0] || !tmp_picture[1]) { - log_error("Failed to build intermediate Picture."); - pixman_region32_fini(®_op); - pixman_region32_fini(®_op_resized); - return false; - } - - region_t clip; - pixman_region32_init(&clip); - pixman_region32_copy(&clip, ®_op_resized); - pixman_region32_translate(&clip, -extent_resized->x1, -extent_resized->y1); - x_set_picture_clip_region(c, tmp_picture[0], 0, 0, &clip); - x_set_picture_clip_region(c, tmp_picture[1], 0, 0, &clip); - pixman_region32_fini(&clip); - - xcb_render_picture_t src_pict = xd->back[2], dst_pict = tmp_picture[0]; - auto alpha_pict = xd->alpha_pict[(int)(opacity * MAX_ALPHA)]; - int current = 0; - x_set_picture_clip_region(c, src_pict, 0, 0, ®_op_resized); - - // For more than 1 pass, we do: - // back -(pass 1)-> tmp0 -(pass 2)-> tmp1 ... - // -(pass n-1)-> tmp0 or tmp1 -(pass n)-> back - // For 1 pass, we do - // back -(pass 1)-> tmp0 -(copy)-> target_buffer - int i; - for (i = 0; i < bctx->x_blur_kernel_count; i++) { - // 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(c, src_pict, to_u16_checked(strlen(filter)), - filter, - to_u32_checked(bctx->x_blur_kernel[i]->size), - bctx->x_blur_kernel[i]->kernel); - - if (i == 0) { - // First pass, back buffer -> tmp picture - // (we do this even if this is also the last pass, because we - // cannot do back buffer -> back buffer) - xcb_render_composite(c, XCB_RENDER_PICT_OP_SRC, src_pict, XCB_NONE, - dst_pict, to_i16_checked(extent_resized->x1), - to_i16_checked(extent_resized->y1), 0, 0, 0, - 0, width_resized, height_resized); - } else if (i < bctx->x_blur_kernel_count - 1) { - // This is not the last pass or the first pass, - // tmp picture 1 -> tmp picture 2 - xcb_render_composite(c, XCB_RENDER_PICT_OP_SRC, src_pict, - XCB_NONE, dst_pict, 0, 0, 0, 0, 0, 0, - width_resized, height_resized); - } else { - x_set_picture_clip_region(c, xd->back[2], 0, 0, ®_op); - // This is the last pass, and we are doing more than 1 pass - xcb_render_composite(c, XCB_RENDER_PICT_OP_OVER, src_pict, - alpha_pict, xd->back[2], 0, 0, 0, 0, - to_i16_checked(extent_resized->x1), - to_i16_checked(extent_resized->y1), - width_resized, height_resized); - } - - // reset filter - xcb_render_set_picture_filter( - c, src_pict, to_u16_checked(strlen(filter0)), filter0, 0, NULL); - - src_pict = tmp_picture[current]; - dst_pict = tmp_picture[!current]; - current = !current; - } - - // There is only 1 pass - if (i == 1) { - x_set_picture_clip_region(c, xd->back[2], 0, 0, ®_op); - xcb_render_composite( - c, XCB_RENDER_PICT_OP_OVER, src_pict, alpha_pict, xd->back[2], 0, 0, - 0, 0, to_i16_checked(extent_resized->x1), - to_i16_checked(extent_resized->y1), width_resized, height_resized); - } - - xcb_render_free_picture(c, tmp_picture[0]); - xcb_render_free_picture(c, tmp_picture[1]); - pixman_region32_fini(®_op); - pixman_region32_fini(®_op_resized); - return true; -} - -static void * -bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, bool owned) { - xcb_generic_error_t *e; - auto r = xcb_get_geometry_reply(base->c, xcb_get_geometry(base->c, pixmap), &e); - if (!r) { - log_error("Invalid pixmap: %#010x", pixmap); - x_print_error(e->full_sequence, e->major_code, e->minor_code, e->error_code); - return NULL; - } - - auto img = ccalloc(1, struct backend_image); - auto inner = ccalloc(1, struct _xrender_image_data_inner); - inner->depth = (uint8_t)fmt.visual_depth; - inner->width = img->ewidth = r->width; - inner->height = img->eheight = r->height; - inner->pixmap = pixmap; - inner->has_alpha = fmt.alpha_size != 0; - inner->pict = - x_create_picture_with_visual_and_pixmap(base->c, fmt.visual, pixmap, 0, NULL); - inner->owned = owned; - inner->visual = fmt.visual; - inner->refcount = 1; - - img->inner = (struct backend_image_inner_base *)inner; - img->opacity = 1; - free(r); - - if (inner->pict == XCB_NONE) { - free(inner); - free(img); - return NULL; - } - return img; -} -static void release_image_inner(backend_t *base, struct _xrender_image_data_inner *inner) { - xcb_render_free_picture(base->c, inner->pict); - if (inner->owned) { - xcb_free_pixmap(base->c, inner->pixmap); - } - free(inner); -} -static void release_image(backend_t *base, void *image) { - struct backend_image *img = image; - img->inner->refcount--; - if (img->inner->refcount == 0) { - release_image_inner(base, (void *)img->inner); - } - free(img); -} - -static void deinit(backend_t *backend_data) { - struct _xrender_data *xd = (void *)backend_data; - for (int i = 0; i < 256; i++) { - xcb_render_free_picture(xd->base.c, xd->alpha_pict[i]); - } - xcb_render_free_picture(xd->base.c, xd->target); - for (int i = 0; i < 2; i++) { - xcb_render_free_picture(xd->base.c, xd->back[i]); - xcb_free_pixmap(xd->base.c, xd->back_pixmap[i]); - } - if (xd->present_event) { - xcb_unregister_for_special_event(xd->base.c, xd->present_event); - } - xcb_render_free_picture(xd->base.c, xd->white_pixel); - xcb_render_free_picture(xd->base.c, xd->black_pixel); - free(xd); -} - -static void present(backend_t *base, const region_t *region) { - struct _xrender_data *xd = (void *)base; - const rect_t *extent = pixman_region32_extents((region_t *)region); - int16_t orig_x = to_i16_checked(extent->x1), orig_y = to_i16_checked(extent->y1); - uint16_t region_width = to_u16_checked(extent->x2 - extent->x1), - region_height = to_u16_checked(extent->y2 - extent->y1); - - // compose() sets clip region on the back buffer, so clear it first - x_clear_picture_clip_region(base->c, xd->back[xd->curr_back]); - - // limit the region of update - x_set_picture_clip_region(base->c, xd->back[2], 0, 0, region); - - if (xd->vsync) { - // Update the back buffer first, then present - xcb_render_composite(base->c, XCB_RENDER_PICT_OP_SRC, xd->back[2], - XCB_NONE, xd->back[xd->curr_back], orig_x, orig_y, 0, - 0, orig_x, orig_y, region_width, region_height); - - // Make sure we got reply from PresentPixmap before waiting for events, - // to avoid deadlock - auto e = xcb_request_check( - base->c, xcb_present_pixmap_checked( - xd->base.c, xd->target_win, - xd->back_pixmap[xd->curr_back], 0, XCB_NONE, XCB_NONE, 0, - 0, XCB_NONE, XCB_NONE, XCB_NONE, 0, 0, 0, 0, 0, NULL)); - if (e) { - log_error("Failed to present pixmap"); - free(e); - return; - } - // TODO(yshui) don't block wait for present completion - xcb_present_generic_event_t *pev = - (void *)xcb_wait_for_special_event(base->c, xd->present_event); - if (!pev) { - // We don't know what happened, maybe X died - // But reset buffer age, so in case we do recover, we will - // render correctly. - xd->buffer_age[0] = xd->buffer_age[1] = -1; - return; - } - assert(pev->evtype == XCB_PRESENT_COMPLETE_NOTIFY); - xcb_present_complete_notify_event_t *pcev = (void *)pev; - // log_trace("Present complete: %d %ld", pcev->mode, pcev->msc); - xd->buffer_age[xd->curr_back] = 1; - - // buffer_age < 0 means that back buffer is empty - if (xd->buffer_age[1 - xd->curr_back] > 0) { - xd->buffer_age[1 - xd->curr_back]++; - } - if (pcev->mode == XCB_PRESENT_COMPLETE_MODE_FLIP) { - // We cannot use the pixmap we used anymore - xd->curr_back = 1 - xd->curr_back; - } - free(pev); - } else { - // No vsync needed, draw into the target picture directly - xcb_render_composite(base->c, XCB_RENDER_PICT_OP_SRC, xd->back[2], - XCB_NONE, xd->target, orig_x, orig_y, 0, 0, orig_x, - orig_y, region_width, region_height); - } -} - -static int buffer_age(backend_t *backend_data) { - struct _xrender_data *xd = (void *)backend_data; - if (!xd->vsync) { - // Only the target picture really holds the screen content, and its - // content is always up to date. So buffer age is always 1. - return 1; - } - return xd->buffer_age[xd->curr_back]; -} - -static struct _xrender_image_data_inner * -new_inner(backend_t *base, int w, int h, xcb_visualid_t visual, uint8_t depth) { - auto new_inner = ccalloc(1, struct _xrender_image_data_inner); - new_inner->pixmap = x_create_pixmap(base->c, depth, base->root, w, h); - if (new_inner->pixmap == XCB_NONE) { - log_error("Failed to create pixmap for copy"); - free(new_inner); - return NULL; - } - new_inner->pict = x_create_picture_with_visual_and_pixmap( - base->c, visual, new_inner->pixmap, 0, NULL); - if (new_inner->pict == XCB_NONE) { - log_error("Failed to create picture for copy"); - xcb_free_pixmap(base->c, new_inner->pixmap); - free(new_inner); - return NULL; - } - new_inner->width = w; - new_inner->height = h; - new_inner->visual = visual; - new_inner->depth = depth; - new_inner->refcount = 1; - new_inner->owned = true; - return new_inner; -} - -static bool decouple_image(backend_t *base, struct backend_image *img, const region_t *reg) { - if (img->inner->refcount == 1) { - return true; - } - auto inner = (struct _xrender_image_data_inner *)img->inner; - // Force new pixmap to a 32-bit ARGB visual to allow for transparent frames around - // non-transparent windows - auto visual = (inner->depth == 32) - ? inner->visual - : x_get_visual_for_standard(base->c, XCB_PICT_STANDARD_ARGB_32); - auto inner2 = new_inner(base, inner->width, inner->height, visual, 32); - if (!inner2) { - return false; - } - - x_set_picture_clip_region(base->c, inner->pict, 0, 0, reg); - xcb_render_composite(base->c, XCB_RENDER_PICT_OP_SRC, inner->pict, XCB_NONE, - inner2->pict, 0, 0, 0, 0, 0, 0, to_u16_checked(inner->width), - to_u16_checked(inner->height)); - - img->inner = (struct backend_image_inner_base *)inner2; - inner->refcount--; - return true; -} - -static bool image_op(backend_t *base, enum image_operations op, void *image, - const region_t *reg_op, const region_t *reg_visible, void *arg) { - struct _xrender_data *xd = (void *)base; - struct backend_image *img = image; - region_t reg; - double *dargs = arg; - - pixman_region32_init(®); - pixman_region32_intersect(®, (region_t *)reg_op, (region_t *)reg_visible); - - switch (op) { - case IMAGE_OP_APPLY_ALPHA: - assert(reg_op); - - if (!pixman_region32_not_empty(®)) { - break; - } - - if (dargs[0] == 1) { - break; - } - - if (!decouple_image(base, img, reg_visible)) { - pixman_region32_fini(®); - return false; - } - - auto inner = (struct _xrender_image_data_inner *)img->inner; - auto alpha_pict = xd->alpha_pict[(int)((1 - dargs[0]) * MAX_ALPHA)]; - x_set_picture_clip_region(base->c, inner->pict, 0, 0, ®); - xcb_render_composite(base->c, XCB_RENDER_PICT_OP_OUT_REVERSE, alpha_pict, - XCB_NONE, inner->pict, 0, 0, 0, 0, 0, 0, - to_u16_checked(inner->width), - to_u16_checked(inner->height)); - inner->has_alpha = true; - break; - } - pixman_region32_fini(®); - return true; -} - -static void * -create_blur_context(backend_t *base attr_unused, enum blur_method method, void *args) { - auto ret = ccalloc(1, struct _xrender_blur_context); - if (!method || method >= BLUR_METHOD_INVALID) { - ret->method = BLUR_METHOD_NONE; - return ret; - } - if (method == BLUR_METHOD_DUAL_KAWASE) { - log_warn("Blur method 'dual_kawase' is not compatible with the 'xrender' " - "backend."); - ret->method = BLUR_METHOD_NONE; - return ret; - } - - ret->method = BLUR_METHOD_KERNEL; - struct conv **kernels; - int kernel_count; - if (method == BLUR_METHOD_KERNEL) { - kernels = ((struct kernel_blur_args *)args)->kernels; - kernel_count = ((struct kernel_blur_args *)args)->kernel_count; - } else { - kernels = generate_blur_kernel(method, args, &kernel_count); - } - - ret->x_blur_kernel = ccalloc(kernel_count, struct x_convolution_kernel *); - for (int i = 0; i < kernel_count; i++) { - int center = kernels[i]->h * kernels[i]->w / 2; - x_create_convolution_kernel(kernels[i], kernels[i]->data[center], - &ret->x_blur_kernel[i]); - ret->resize_width += kernels[i]->w / 2; - ret->resize_height += kernels[i]->h / 2; - } - ret->x_blur_kernel_count = kernel_count; - - if (method != BLUR_METHOD_KERNEL) { - // Kernels generated by generate_blur_kernel, so we need to free them. - for (int i = 0; i < kernel_count; i++) { - free(kernels[i]); - } - free(kernels); - } - return ret; -} - -static void destroy_blur_context(backend_t *base attr_unused, void *ctx_) { - struct _xrender_blur_context *ctx = ctx_; - for (int i = 0; i < ctx->x_blur_kernel_count; i++) { - free(ctx->x_blur_kernel[i]); - } - free(ctx->x_blur_kernel); - free(ctx); -} - -static void get_blur_size(void *blur_context, int *width, int *height) { - struct _xrender_blur_context *ctx = blur_context; - *width = ctx->resize_width; - *height = ctx->resize_height; -} - -static bool -read_pixel(backend_t *backend_data, void *image_data, int x, int y, struct color *output) { - auto xd = (struct _xrender_data *)backend_data; - auto img = (struct backend_image *)image_data; - auto inner = (struct _xrender_image_data_inner *)img->inner; - - auto r = XCB_AWAIT(xcb_get_image, xd->base.c, XCB_IMAGE_FORMAT_XY_PIXMAP, inner->pixmap, - to_i16_checked(x), to_i16_checked(y), 1, 1, (uint32_t)-1L); - - if (!r) { - return false; - } - - // Color format seems to be BGRA8888, see glamor_format_for_pixmap from the - // Xserver codebase. - uint8_t *pixels = xcb_get_image_data(r); - output->blue = pixels[0] / 255.0; - output->green = pixels[1] / 255.0; - output->red = pixels[2] / 255.0; - output->alpha = pixels[3] / 255.0; - - return true; -} - -static backend_t *backend_xrender_init(session_t *ps) { - auto xd = ccalloc(1, struct _xrender_data); - init_backend_base(&xd->base, ps); - - for (int i = 0; i <= MAX_ALPHA; ++i) { - double o = (double)i / (double)MAX_ALPHA; - xd->alpha_pict[i] = solid_picture(ps->c, ps->root, false, o, 0, 0, 0); - assert(xd->alpha_pict[i] != XCB_NONE); - } - - xd->target_width = ps->root_width; - xd->target_height = ps->root_height; - xd->default_visual = ps->vis; - xd->black_pixel = solid_picture(ps->c, ps->root, true, 1, 0, 0, 0); - xd->white_pixel = solid_picture(ps->c, ps->root, true, 1, 1, 1, 1); - - xd->target_win = session_get_target_window(ps); - xcb_render_create_picture_value_list_t pa = { - .subwindowmode = XCB_SUBWINDOW_MODE_INCLUDE_INFERIORS, - }; - xd->target = x_create_picture_with_visual_and_pixmap( - ps->c, ps->vis, xd->target_win, XCB_RENDER_CP_SUBWINDOW_MODE, &pa); - - auto pictfmt = x_get_pictform_for_visual(ps->c, ps->vis); - if (!pictfmt) { - log_fatal("Default visual is invalid"); - abort(); - } - - xd->vsync = ps->o.vsync; - if (ps->present_exists) { - auto eid = x_new_id(ps->c); - auto e = - xcb_request_check(ps->c, xcb_present_select_input_checked( - ps->c, eid, xd->target_win, - XCB_PRESENT_EVENT_MASK_COMPLETE_NOTIFY)); - if (e) { - log_error("Cannot select present input, vsync will be disabled"); - xd->vsync = false; - free(e); - } - - xd->present_event = - xcb_register_for_special_xge(ps->c, &xcb_present_id, eid, NULL); - if (!xd->present_event) { - log_error("Cannot register for special XGE, vsync will be " - "disabled"); - xd->vsync = false; - } - } else { - xd->vsync = false; - } - - // We might need to do double buffering for vsync, and buffer 0 and 1 are for - // double buffering. - int first_buffer_index = xd->vsync ? 0 : 2; - for (int i = first_buffer_index; i < 3; i++) { - xd->back_pixmap[i] = x_create_pixmap(ps->c, pictfmt->depth, ps->root, - to_u16_checked(ps->root_width), - to_u16_checked(ps->root_height)); - const uint32_t pic_attrs_mask = XCB_RENDER_CP_REPEAT; - const xcb_render_create_picture_value_list_t pic_attrs = { - .repeat = XCB_RENDER_REPEAT_PAD}; - xd->back[i] = x_create_picture_with_pictfmt_and_pixmap( - ps->c, pictfmt, xd->back_pixmap[i], pic_attrs_mask, &pic_attrs); - xd->buffer_age[i] = -1; - if (xd->back_pixmap[i] == XCB_NONE || xd->back[i] == XCB_NONE) { - log_error("Cannot create pixmap for rendering"); - goto err; - } - } - xd->curr_back = 0; - - return &xd->base; -err: - deinit(&xd->base); - return NULL; -} - -struct backend_operations xrender_ops = { - .init = backend_xrender_init, - .deinit = deinit, - .blur = blur, - .present = present, - .compose = compose, - .fill = fill, - .bind_pixmap = bind_pixmap, - .release_image = release_image, - .render_shadow = default_backend_render_shadow, - //.prepare_win = prepare_win, - //.release_win = release_win, - .is_image_transparent = default_is_image_transparent, - .buffer_age = buffer_age, - .max_buffer_age = 2, - - .image_op = image_op, - .read_pixel = read_pixel, - .clone_image = default_clone_image, - .set_image_property = default_set_image_property, - .create_blur_context = create_blur_context, - .destroy_blur_context = destroy_blur_context, - .get_blur_size = get_blur_size, -}; - -// vim: set noet sw=8 ts=8: |