aboutsummaryrefslogtreecommitdiff
path: root/src/backend
diff options
context:
space:
mode:
authorallusive-dev <[email protected]>2023-09-19 17:47:33 +1000
committerallusive-dev <[email protected]>2023-09-19 17:47:33 +1000
commita93aba600b1c5d019b680b9f4ff3fa85d5d43a60 (patch)
tree77f8152222655657472a70e0bfa413a0495dd555 /src/backend
parentreset (diff)
downloadcompfy-a93aba600b1c5d019b680b9f4ff3fa85d5d43a60.tar.xz
compfy-a93aba600b1c5d019b680b9f4ff3fa85d5d43a60.zip
Fixed broken files/code and other errors
Diffstat (limited to 'src/backend')
-rw-r--r--src/backend/backend.c494
-rw-r--r--src/backend/backend.h290
-rw-r--r--src/backend/backend_common.c480
-rw-r--r--src/backend/backend_common.h78
-rw-r--r--src/backend/driver.c82
-rw-r--r--src/backend/driver.h62
-rw-r--r--src/backend/dummy/dummy.c174
-rw-r--r--src/backend/gl/gl_common.c1922
-rw-r--r--src/backend/gl/gl_common.h220
-rw-r--r--src/backend/gl/glx.c648
-rw-r--r--src/backend/gl/glx.h77
-rw-r--r--src/backend/meson.build7
-rw-r--r--src/backend/xrender/xrender.c779
13 files changed, 5313 insertions, 0 deletions
diff --git a/src/backend/backend.c b/src/backend/backend.c
new file mode 100644
index 0000000..b0e562a
--- /dev/null
+++ b/src/backend/backend.c
@@ -0,0 +1,494 @@
+// 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(&region);
+ if (buffer_age == -1 || buffer_age > ps->ndamage) {
+ pixman_region32_copy(&region, &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(&region, &region, &ps->damage_ring[curr]);
+ }
+ pixman_region32_intersect(&region, &region, &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(&reg_bound_local);
+ pixman_region32_copy(&reg_bound_local, reg_bound);
+ pixman_region32_translate(&reg_bound_local, -w->g.x, -w->g.y);
+
+ pixman_region32_init(&reg_visible_local);
+ pixman_region32_intersect(&reg_visible_local,
+ reg_visible, reg_paint);
+ pixman_region32_translate(&reg_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(
+ &reg_visible_local, &reg_visible_local, &reg_bound_local);
+ pixman_region32_fini(&reg_bound_local);
+ }
+
+ auto new_img = ps->backend_data->ops->clone_image(
+ ps->backend_data, win_image, &reg_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, &reg_frame,
+ &reg_visible_local, (double[]){w->frame_opacity});
+ pixman_region32_fini(&reg_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(&reg_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(&reg_damage);
+ pixman_region32_copy(&reg_damage, &ps->screen_reg);
+ }
+
+ if (!pixman_region32_not_empty(&reg_damage)) {
+ pixman_region32_fini(&reg_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(&reg_damage, blur_width * resize_factor,
+ blur_height * resize_factor);
+ reg_paint = resize_region(&reg_damage, blur_width * resize_factor,
+ blur_height * resize_factor);
+ pixman_region32_intersect(&reg_paint, &reg_paint, &ps->screen_reg);
+ pixman_region32_intersect(&reg_damage, &reg_damage, &ps->screen_reg);
+ } else {
+ pixman_region32_init(&reg_paint);
+ pixman_region32_copy(&reg_paint, &reg_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(&reg_visible);
+ pixman_region32_copy(&reg_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(&reg_visible, &reg_visible, t->reg_ignore);
+ }
+
+ // Region on screen we don't want any shadows on
+ region_t reg_shadow_clip;
+ pixman_region32_init(&reg_shadow_clip);
+
+ if (ps->backend_data->ops->prepare) {
+ ps->backend_data->ops->prepare(ps->backend_data, &reg_paint);
+ }
+
+ if (ps->root_image) {
+ ps->backend_data->ops->compose(ps->backend_data, ps->root_image,
+ 0, 0, ps->root_width, ps->root_height,
+ &reg_paint, &reg_visible);
+ } else {
+ ps->backend_data->ops->fill(ps->backend_data, (struct color){0, 0, 0, 1},
+ &reg_paint);
+ }
+
+ // Windows are sorted from bottom to top
+ // Each window has a reg_ignore, which is the region obscured by all the windows
+ // on top of that window. This is used to reduce the number of pixels painted.
+ //
+ // Whether this is beneficial is to be determined XXX
+ for (auto w = t; w; w = w->prev_trans) {
+ pixman_region32_subtract(&reg_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(&reg_paint_in_bound);
+ pixman_region32_intersect(&reg_paint_in_bound, &reg_bound, &reg_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(&reg_paint_in_bound,
+ &reg_paint_in_bound, &reg_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,
+ &reg_paint_in_bound, &reg_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(&reg_blur, w->g.x, w->g.y);
+ // make sure reg_blur \in reg_paint
+ pixman_region32_intersect(&reg_blur, &reg_blur, &reg_paint);
+ if (ps->o.transparent_clipping) {
+ // ref: <transparent-clipping-note>
+ pixman_region32_intersect(&reg_blur, &reg_blur,
+ &reg_visible);
+ }
+ ps->backend_data->ops->blur(ps->backend_data, blur_opacity,
+ ps->backend_blur_context,
+ &reg_blur, &reg_visible);
+ pixman_region32_fini(&reg_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(&reg_shadow, &reg_shadow, &reg_paint);
+ if (!ps->o.wintype_option[w->window_type].full_shadow) {
+ pixman_region32_subtract(&reg_shadow, &reg_shadow, &reg_bound);
+ }
+
+ // Mask out the region we don't want shadow on
+ if (pixman_region32_not_empty(&ps->shadow_exclude_reg)) {
+ pixman_region32_subtract(&reg_shadow, &reg_shadow,
+ &ps->shadow_exclude_reg);
+ }
+ if (pixman_region32_not_empty(&reg_shadow_clip)) {
+ pixman_region32_subtract(&reg_shadow, &reg_shadow,
+ &reg_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(
+ &reg_shadow, &reg_shadow,
+ &ps->xinerama_scr_regs[w->xinerama_scr]);
+ }
+
+ if (ps->o.transparent_clipping) {
+ // ref: <transparent-clipping-note>
+ pixman_region32_intersect(&reg_shadow, &reg_shadow,
+ &reg_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,
+ &reg_shadow, &reg_visible);
+ pixman_region32_fini(&reg_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(&reg_shadow_clip, &reg_shadow_clip, &reg_bound);
+ } else {
+ // Remove overlapping window bounds from shadow-clip region
+ pixman_region32_subtract(&reg_shadow_clip, &reg_shadow_clip, &reg_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,
+ &reg_paint_in_bound, &reg_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,
+ &reg_bound, &reg_visible,
+ &reg_paint, &reg_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,
+ &reg_bound, &reg_visible,
+ &reg_paint, &reg_paint_in_bound);
+ }
+
+ } else {
+ process_window_for_painting(ps, w, w->win_image,
+ 1,
+ &reg_bound, &reg_visible,
+ &reg_paint, &reg_paint_in_bound);
+ }
+ }
+ skip:
+ pixman_region32_fini(&reg_bound);
+ pixman_region32_fini(&reg_paint_in_bound);
+ }
+ pixman_region32_fini(&reg_paint);
+ pixman_region32_fini(&reg_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, &reg_damage_debug);
+ pixman_region32_fini(&reg_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, &reg_damage);
+ }
+
+ pixman_region32_fini(&reg_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
new file mode 100644
index 0000000..ae107d3
--- /dev/null
+++ b/src/backend/backend.h
@@ -0,0 +1,290 @@
+// 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
new file mode 100644
index 0000000..c0377d3
--- /dev/null
+++ b/src/backend/backend_common.c
@@ -0,0 +1,480 @@
+// 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
new file mode 100644
index 0000000..5c9c806
--- /dev/null
+++ b/src/backend/backend_common.h
@@ -0,0 +1,78 @@
+// 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
new file mode 100644
index 0000000..a41d2fd
--- /dev/null
+++ b/src/backend/driver.c
@@ -0,0 +1,82 @@
+// 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
new file mode 100644
index 0000000..a37cda3
--- /dev/null
+++ b/src/backend/driver.h
@@ -0,0 +1,62 @@
+// 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
new file mode 100644
index 0000000..a057b97
--- /dev/null
+++ b/src/backend/dummy/dummy.c
@@ -0,0 +1,174 @@
+#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
new file mode 100644
index 0000000..8cc5a05
--- /dev/null
+++ b/src/backend/gl/gl_common.c
@@ -0,0 +1,1922 @@
+// 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(&reg_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(&reg_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(&reg_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
new file mode 100644
index 0000000..b1d93b0
--- /dev/null
+++ b/src/backend/gl/gl_common.h
@@ -0,0 +1,220 @@
+// 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
new file mode 100644
index 0000000..1397d19
--- /dev/null
+++ b/src/backend/gl/glx.c
@@ -0,0 +1,648 @@
+// 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
new file mode 100644
index 0000000..1061f0b
--- /dev/null
+++ b/src/backend/gl/glx.h
@@ -0,0 +1,77 @@
+// 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
new file mode 100644
index 0000000..b8f0ad9
--- /dev/null
+++ b/src/backend/meson.build
@@ -0,0 +1,7 @@
+# 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
new file mode 100644
index 0000000..ccf358b
--- /dev/null
+++ b/src/backend/xrender/xrender.c
@@ -0,0 +1,779 @@
+// 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(&reg);
+ pixman_region32_intersect(&reg, (region_t *)reg_paint, (region_t *)reg_visible);
+ x_set_picture_clip_region(xd->base.c, result, 0, 0, &reg);
+
+#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), &reg);
+ // 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(&reg);
+}
+
+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(&reg_op);
+ pixman_region32_intersect(&reg_op, (region_t *)reg_blur, (region_t *)reg_visible);
+ if (!pixman_region32_not_empty(&reg_op)) {
+ pixman_region32_fini(&reg_op);
+ return true;
+ }
+
+ region_t reg_op_resized =
+ resize_region(&reg_op, bctx->resize_width, bctx->resize_height);
+
+ const pixman_box32_t *extent_resized = pixman_region32_extents(&reg_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(&reg_op);
+ pixman_region32_fini(&reg_op_resized);
+ return false;
+ }
+
+ region_t clip;
+ pixman_region32_init(&clip);
+ pixman_region32_copy(&clip, &reg_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, &reg_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, &reg_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, &reg_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(&reg_op);
+ pixman_region32_fini(&reg_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(&reg);
+ pixman_region32_intersect(&reg, (region_t *)reg_op, (region_t *)reg_visible);
+
+ switch (op) {
+ case IMAGE_OP_APPLY_ALPHA:
+ assert(reg_op);
+
+ if (!pixman_region32_not_empty(&reg)) {
+ break;
+ }
+
+ if (dargs[0] == 1) {
+ break;
+ }
+
+ if (!decouple_image(base, img, reg_visible)) {
+ pixman_region32_fini(&reg);
+ 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, &reg);
+ 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(&reg);
+ 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: