diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000000..8400564e70 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,22 @@ +{ + "configurations": [ + { + "name": "Linux", + "includePath": [ + "/usr/include", + "/usr/include/pixman-1", + "${workspaceFolder}/**" + ], + "defines": [ + "CONFIG_OPENGL", + "_POSIX_C_SOURCE 199309L" + ], + "compilerPath": "/bin/gcc", + "cStandard": "c11", + "cppStandard": "c++17", + "intelliSenseMode": "clang-x64", + "compileCommands": "${workspaceFolder}/build/compile_commands.json" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000000..96f15498c9 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,26 @@ +{ + "configurations": [ + { + "targetArchitecture": "x64", + "name": "Debug with Meson", + "type": "cppdbg", + "request": "launch", + "cwd": "${workspaceRoot}", + "program": "${workspaceRoot}/build/src/picom", + "preLaunchTask": "build debug meson", + "stopAtEntry": false, + "launchCompleteCommand": "exec-run", + "linux": { + "MIMode": "gdb", + "miDebuggerPath": "/usr/bin/gdb" + }, + "osx": { + "MIMode": "lldb" + }, + "windows": { + "MIMode": "gdb", + "miDebuggerPath": "C:\\MinGw\\bin\\gdb.exe" + } + } + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000000..4f32f69c11 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,25 @@ +{ + "tasks": [ + { + "label": "build debug meson", + "type": "shell", + "command": "meson build --buildtype=debug && ninja -C build", + "problemMatcher": [], + "group": { + "kind": "build", + "isDefault": true + } + }, + { + "label": "build release meson", + "type": "shell", + "command": "meson build --buildtype=release && ninja -C build", + "problemMatcher": [], + "group": { + "kind": "build", + "isDefault": true + } + } + ], + "version": "2.0.0" +} \ No newline at end of file diff --git a/README.md b/README.md index 6d525efdf4..bb60ed1bec 100644 --- a/README.md +++ b/README.md @@ -1,146 +1,68 @@ -picom -======= +[**Click here for the official picom README**](https://github.com/yshui/picom) -**This is a development branch, bugs to be expected** +[**Click here for the most recent branch of this fork**](https://github.com/ibhagwan/picom/tree/next-rebase) -This is forked from the original Compton because it seems to have become unmaintained. +![picom.png](https://github.com/ibhagwan/picom/raw/next/picom.png) -The current battle plan of this fork is to refactor it to make the code _possible_ to maintain, so potential contributors won't be scared away when they take a look at the code. +## Why another picom fork? -We also try to fix bugs. +**TL;DR:** rounded corners and dual_kawase blur on all backends. -The original README can be found [here](README_orig.md) +### This fork contains: -## Call for testers +- Dual kawase blur method from [tryone144](https://github.com/tryone144/compton) as well as his new [feature/dual_kawase branch](https://github.com/tryone144/compton/tree/feature/dual_kawase) which implements the dual kawase blur method on the experimental glx backend. -### `--experimental-backends` +- Rounded corners code from [sdhand](https://github.com/sdhand/picom) which is also ported to the experimetnal XRender backend. -This flag enables the refactored/partially rewritten backends. +- New code for rounded corners (+borders) on the glx backend using GLSL frangment shader for both legacy and experimental backends -Currently, new backends feature better vsync with the xrender backend and improved input lag with the glx backend (for non-NVIDIA users). The performance should be on par with the old backends. +For more information read [my reddit post](https://www.reddit.com/r/unixporn/comments/fs8trg/oc_comptonpicom_fork_with_both_tryone144s_dual/) -New backend features will only be implemented on the new backends from now on, and the old backends will eventually be phased out after the new backends stabilize. +## How to install -To test the new backends, add the `--experimental-backends` flag to the command you use to run picom. This flag is not available from the configuration file. +### Arch Linux -To report issues with the new backends, please state explicitly you are using the new backends in your report. - -## Rename - -### Rationale - -Since the inception of this fork, the existence of two compton repositories has caused some number of confusions. Mainly, people will report issues of this fork to the original compton, or report issues of the original compton here. Later, when distros started packaging this fork of compton, some wanted to differentiate the newer compton from the older version. They found themselves having no choice but to invent a name for this fork. This is less than ideal since this has the potential to cause more confusions among users. - -Therefore, we decided to move this fork to a new name. Personally, I consider this more than justified since this version of compton has gone through significant changes since it was forked. - -### The name - -The criteria for a good name were - -0. Being short, so it's easy to remember. -1. Pronounceability, again, helps memorability -2. Searchability, so when people search the name, it's easy for them to find this repository. - -Of course, choosing a name is never easy, and there is no apparent way to objectively evaluate the names. Yet, we have to solve the aforementioned problems as soon as possible. - -In the end, we picked `picom` (a portmanteau of `pico` and `composite`) as our new name. This name might not be perfect, but is what we will move forward with unless there's a compelling reason not to. - -### Migration - -Following the [deprecation process](https://github.com/yshui/picom/issues/114), migration to the new name will be broken into 3 steps: - -1. All mentions of `compton` will be updated to `picom` in the code base. `compton` will still be installed, but only as a symlink to `picom`. When `picom` is launched via the symlink, a warning message is printed, alerting the user to migrate. Similarly, the old configuration file names and dbus interface names will still be accepted but warned. -2. 3 major releases after step 1, the warning messages will be prompted to error messages and `picom` will not start when launched via the symlink. -3. 3 major releases after step 2, the symlink will be removed. - -The dbus interface and service names are unchanged, so no migration needed for that. - -## Change Log - -See [Releases](https://github.com/yshui/picom/releases) - -## Build - -### Dependencies - -Assuming you already have all the usual building tools installed (e.g. gcc, python, meson, ninja, etc.), you still need: - -* libx11 -* libx11-xcb -* libXext -* xproto -* xcb -* xcb-damage -* xcb-xfixes -* xcb-shape -* xcb-renderutil -* xcb-render -* xcb-randr -* xcb-composite -* xcb-image -* xcb-present -* xcb-xinerama -* pixman -* libdbus (optional, disable with the `-Ddbus=false` meson configure flag) -* libconfig (optional, disable with the `-Dconfig_file=false` meson configure flag) -* libGL (optional, disable with the `-Dopengl=false` meson configure flag) -* libpcre (optional, disable with the `-Dregex=false` meson configure flag) -* libev -* uthash - -On Debian based distributions (e.g. Ubuntu), the list of needed packages are - -``` -libxext-dev libxcb1-dev libxcb-damage0-dev libxcb-xfixes0-dev libxcb-shape0-dev libxcb-render-util0-dev libxcb-render0-dev libxcb-randr0-dev libxcb-composite0-dev libxcb-image0-dev libxcb-present-dev libxcb-xinerama0-dev libpixman-1-dev libdbus-1-dev libconfig-dev libgl1-mesa-dev libpcre2-dev libevdev-dev uthash-dev libev-dev libx11-xcb-dev +Install [picom-ibhagwan-git](https://aur.archlinux.org/packages/picom-ibhagwan-git/) from the AUR using your favorite AUR helper such as `yay` +```sh +❯ yay -S picom-ibhagwan-git ``` -To build the documents, you need `asciidoc` +### Void Linux -### To build +Follow the instructions found in [picom-ibhagwan-template](https://github.com/ibhagwan/picom-ibhagwan-template) -```bash -$ git submodule update --init --recursive -$ meson --buildtype=release . build -$ ninja -C build -``` +### Build from source -Built binary can be found in `build/src` +Clone this repo and follow the [build instructions of the official picom README](https://github.com/yshui/picom/blob/next/README.md#build) -If you have libraries and/or headers installed at non-default location (e.g. under `/usr/local/`), you might need to tell meson about them, since meson doesn't look for dependencies there by default. -You can do that by setting the `CPPFLAGS` and `LDFLAGS` environment variables when running `meson`. Like this: +## 2021-02-05 Update -```bash -$ LDFLAGS="-L/path/to/libraries" CPPFLAGS="-I/path/to/headers" meson --buildtype=release . build +It's been a while since this fork had some work and the good people at [the main picom branch](https://github.com/yshui/picom) merged some of this code into the main branch. -``` +However, not all code / features have been merged, ATM the status is as per the below: -As an example, on FreeBSD, you might have to run meson with: -```bash -$ LDFLAGS="-L/usr/local/include" CPPFLAGS="-I/usr/local/include" meson --buildtype=release . build -$ ninja -C build -``` -### To install +### Included in main branch -``` bash -$ ninja -C build install -``` +- Rounded corners on legacy backends (both "glx" and "xrender") +- Dual-kawase blur on experimental "glx" backend only -Default install prefix is `/usr/local`, you can change it with `meson configure -Dprefix= build` +### Not-included in main branch -## How to Contribute +- Rounded corners with "--experimental-backends" +- Rounded borders on the legacy "glx" backend +- Rounded border rules on the legacy "glx" backend +- Dual-kawase blur on the legacy "glx" backend -### Code +### Updated fork -You can look at the [Projects](https://github.com/yshui/picom/projects) page, and see if there is anything that interests you. Or you can take a look at the [Issues](https://github.com/yshui/picom/issues). +Since this fork was released a few issues were opened for bugs that were perhaps fixed in the main branch but not on this one, while I always recommend using the main branch as a better strategy than using older forked code, since not all features were yet implemented in the main branch I thought it might still be useful to rebase this fork on the most current work of the main branch. -### Non-code +However, this fork has also been forked quite a few times (over 15), not being certain what work was done based on this fork or what new bugs the rebase will introduce I created a new branch ([**next-rebase**](https://github.com/ibhagwan/picom/tree/next-rebase)) for the rebased code. -Even if you don't want to contribute code, you can still contribute by compiling and running this branch, and report any issue you can find. +To pull the latest code residing in the `next-rebase` branch: -Contributions to the documents and wiki will also be appreciated. - -## Contributors - -See [CONTRIBUTORS](CONTRIBUTORS) +```sh +❯ git clone --single-branch --branch next-rebase --depth=1 https://github.com/ibhagwan/picom +``` diff --git a/man/picom.1.asciidoc b/man/picom.1.asciidoc index 7f370091dc..1f825e0674 100644 --- a/man/picom.1.asciidoc +++ b/man/picom.1.asciidoc @@ -106,6 +106,18 @@ OPTIONS *--inactive-dim* 'VALUE':: Dim inactive windows. (0.0 - 1.0, defaults to 0.0) +*--corner-radius* 'VALUE':: + Round the corners of windows. (defaults to 0). + +*--rounded-corners-exclude* 'CONDITION':: + Exclude conditions for rounded corners. + +*--round-borders* 'VALUE':: + When rounding corners, Round the borders of windows. (defaults to 1). + +*--round-borders-exclude* 'CONDITION':: + Exclude conditions for rounding borders. + *--mark-wmwin-focused*:: Try to detect WM windows (a non-override-redirect window with no child that has 'WM_STATE') and mark them as active. @@ -166,7 +178,7 @@ OPTIONS *--detect-client-leader*:: Use 'WM_CLIENT_LEADER' to group windows, and consider windows in the same group focused at the same time. 'WM_TRANSIENT_FOR' has higher priority if *--detect-transient* is enabled, too. -*--blur-method*, *--blur-size*, *--blur-deviation*:: +*--blur-method*, *--blur-size*, *--blur-deviation, *--blur-strength*:: Parameters for background blurring, see the *BLUR* section for more information. *--blur-background*:: @@ -407,6 +419,9 @@ Available options of the 'blur' section are: :: *deviation*::: A floating point number. The standard deviation for the 'gaussian' blur method. Corresponds to the *--blur-deviation* command line option (default: 0.84089642). + *strength*::: + An integer in the range 1-20. The strength of the 'dual_kawase' blur method. Corresponds to the *--blur-strength* command line option. If not specified, the value requested by *--blur-size* is approximated. + *kernel*::: A string. The kernel to use for the 'kernel' blur method, specified in the same format as the *--blur-kerns* option. Corresponds to the *--blur-kerns* command line option. diff --git a/picom.png b/picom.png new file mode 100644 index 0000000000..292fbc3572 Binary files /dev/null and b/picom.png differ diff --git a/picom.sample.conf b/picom.sample.conf index f2b45121e1..479f20db2e 100644 --- a/picom.sample.conf +++ b/picom.sample.conf @@ -1,3 +1,39 @@ +################################# +# Corners # +################################# +# requires: https://github.com/sdhand/compton +corner-radius = 25.0; +rounded-corners-exclude = [ + #"window_type = 'normal'", + "class_g = 'awesome'", + "class_g = 'URxvt'", + "class_g = 'XTerm'", + "class_g = 'kitty'", + "class_g = 'Alacritty'", + "class_g = 'Polybar'", + "class_g = 'code-oss'", + "class_g = 'firefox'", + "class_g = 'Thunderbird'" +]; +round-borders = 1; +round-borders-exclude = [ + #"class_g = 'TelegramDesktop'", +]; + +# Specify a list of border width rules, in the format `PIXELS:PATTERN`, +# Note we don't make any guarantee about possible conflicts with the +# border_width set by the window manager. +# +# example: +# round-borders-rule = [ "2:class_g = 'URxvt'" ]; +# +round-borders-rule = [ + "3:class_g = 'XTerm'", + "3:class_g = 'URxvt'", + "10:class_g = 'Alacritty'", + "15:class_g = 'Signal'" +]; + ################################# # Shadows # ################################# @@ -8,7 +44,7 @@ # unless explicitly requested using the wintypes option. # # shadow = false -shadow = true; +shadow = false; # The blur radius for shadows, in pixels. (defaults to 12) # shadow-radius = 12 @@ -66,6 +102,8 @@ shadow-exclude = [ "class_g = 'Conky'", "class_g ?= 'Notify-osd'", "class_g = 'Cairo-clock'", + "class_g = 'slop'", + "class_g = 'Polybar'", "_GTK_FRAME_EXTENTS@:c" ]; @@ -88,7 +126,7 @@ shadow-exclude = [ # Fade windows in/out when opening/closing and when opacity changes, # unless no-fading-openclose is used. # fading = false -fading = true +fading = true; # Opacity change between steps while fading in. (0.01 - 1.0, defaults to 0.028) # fade-in-step = 0.028 @@ -102,7 +140,10 @@ fade-out-step = 0.03; # fade-delta = 10 # Specify a list of conditions of windows that should not be faded. -# fade-exclude = [] +# don't need this, we disable fading for all normal windows with wintypes: {} +fade-exclude = [ + "class_g = 'slop'" # maim +] # Do not fade on window open/close. # no-fading-openclose = false @@ -132,14 +173,18 @@ frame-opacity = 0.7; inactive-opacity-override = false; # Default opacity for active windows. (0.0 - 1.0, defaults to 1.0) -# active-opacity = 1.0 +active-opacity = 1.0; # Dim inactive windows. (0.0 - 1.0, defaults to 0.0) # inactive-dim = 0.0 # Specify a list of conditions of windows that should always be considered focused. # focus-exclude = [] -focus-exclude = [ "class_g = 'Cairo-clock'" ]; +focus-exclude = [ + "class_g = 'Cairo-clock'", + "class_g = 'Bar'", # lemonbar + "class_g = 'slop'" # maim +]; # Use fixed inactive dim value, instead of adjusting according to window opacity. # inactive-dim-fixed = 1.0 @@ -152,6 +197,21 @@ focus-exclude = [ "class_g = 'Cairo-clock'" ]; # opacity-rule = [ "80:class_g = 'URxvt'" ]; # # opacity-rule = [] +opacity-rule = [ + "80:class_g = 'Bar'", # lemonbar + "100:class_g = 'slop'", # maim + "100:class_g = 'XTerm'", + "100:class_g = 'URxvt'", + "100:class_g = 'kitty'", + "100:class_g = 'Alacritty'", + "80:class_g = 'Polybar'", + "100:class_g = 'code-oss'", + "100:class_g = 'Meld'", + "70:class_g = 'TelegramDesktop'", + "90:class_g = 'Joplin'", + "100:class_g = 'firefox'", + "100:class_g = 'Thunderbird'" +]; ################################# @@ -169,18 +229,18 @@ focus-exclude = [ "class_g = 'Cairo-clock'" ]; # Bad in performance, with driver-dependent behavior. # The name of the switch may change without prior notifications. # -# blur-background = false +# blur-background = true; # Blur background of windows when the window frame is not opaque. # Implies: # blur-background # Bad in performance, with driver-dependent behavior. The name may change. # -# blur-background-frame = false +# blur-background-frame = false; # Use fixed blur strength rather than adjusting according to window opacity. -# blur-background-fixed = false +# blur-background-fixed = false; # Specify the blur convolution kernel, with the following format: @@ -188,17 +248,35 @@ focus-exclude = [ "class_g = 'Cairo-clock'" ]; # blur-kern = "5,5,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1"; # # blur-kern = '' -blur-kern = "3x3box"; - +# blur-kern = "3x3box"; + +blur: { + # requires: https://github.com/ibhagwan/picom + method = "kawase"; + #method = "kernel"; + strength = 7; + # deviation = 1.0; + # kernel = "11x11gaussian"; + background = false; + background-frame = false; + background-fixed = false; + kern = "3x3box"; +} # Exclude conditions for background blur. -# blur-background-exclude = [] blur-background-exclude = [ - "window_type = 'dock'", - "window_type = 'desktop'", + #"window_type = 'dock'", + #"window_type = 'desktop'", + #"class_g = 'URxvt'", + # + # prevents picom from blurring the background + # when taking selection screenshot with `main` + # https://github.com/naelstrof/maim/issues/130 + "class_g = 'slop'", "_GTK_FRAME_EXTENTS@:c" ]; + ################################# # General Settings # ################################# @@ -209,8 +287,10 @@ blur-background-exclude = [ # Specify the backend to use: `xrender`, `glx`, or `xr_glx_hybrid`. # `xrender` is the default one. # -# backend = 'glx' -backend = "xrender"; +experimental-backends = true; +backend = "glx"; +#backend = "xrender"; + # Enable/disable VSync. # vsync = false @@ -367,7 +447,7 @@ use-damage = true # using *--log-file*, since it can generate a huge stream of logs. # # log-level = "debug" -log-level = "warn"; +log-level = "info"; # Set the log file. # If *--log-file* is never specified, logs will be written to stderr. @@ -415,6 +495,7 @@ log-level = "warn"; # wintypes: { + normal = { fade = false; shadow = false; } tooltip = { fade = true; shadow = true; opacity = 0.75; focus = true; full-shadow = false; }; dock = { shadow = false; } dnd = { shadow = false; } diff --git a/src/backend/backend.c b/src/backend/backend.c index f03e1ffcd4..52524d879f 100644 --- a/src/backend/backend.c +++ b/src/backend/backend.c @@ -151,7 +151,7 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { } if (ps->root_image) { - ps->backend_data->ops->compose(ps->backend_data, ps->root_image, 0, 0, + ps->backend_data->ops->compose(ps->backend_data, t, ps->root_image, 0, 0, ®_paint, ®_visible); } @@ -168,7 +168,7 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { // 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); + auto reg_bound = win_get_bounding_shape_global_by_val(w, true); // The clip region for the current window, in global/target coordinates // reg_paint_in_bound \in reg_paint @@ -187,6 +187,17 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { ®_paint_in_bound, ®_visible); } + // Store the window background for rounded corners + // If rounded corners backup the region first + if (w->corner_radius > 0) { + const int16_t x = w->g.x; + const int16_t y = w->g.y; + const auto wid = to_u16_checked(w->widthb); + const auto hei = to_u16_checked(w->heightb); + ps->backend_data->ops->store_back_texture(ps->backend_data, w, + ps->backend_round_context, ®_bound, x, y, wid, hei); + } + // Blur window background // TODO since the background might change the content of the window (e.g. // with shaders), we should consult the background whether the window @@ -211,6 +222,9 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { // fading out. blur_opacity = w->opacity / win_calc_opacity_target(ps, w, true); + } else if (!ps->o.blur_background_fixed) { + // Apply blur intensity depending on the window opacity. + blur_opacity = w->opacity; } if (real_win_mode == WMODE_TRANS || ps->o.force_win_blend) { @@ -227,7 +241,7 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { assert(ps->o.blur_background_frame); assert(real_win_mode == WMODE_FRAME_TRANS); - auto reg_blur = win_get_region_frame_local_by_val(w); + auto reg_blur = win_get_region_frame_local_by_val(w, true); pixman_region32_translate(®_blur, w->g.x, w->g.y); // make sure reg_blur \in reg_paint pixman_region32_intersect(®_blur, ®_blur, ®_paint); @@ -283,7 +297,7 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { assert(w->shadow_image); if (w->opacity == 1) { ps->backend_data->ops->compose( - ps->backend_data, w->shadow_image, w->g.x + w->shadow_dx, + ps->backend_data, w, w->shadow_image, w->g.x + w->shadow_dx, w->g.y + w->shadow_dy, ®_shadow, ®_visible); } else { auto new_img = ps->backend_data->ops->copy( @@ -292,7 +306,7 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { ps->backend_data, IMAGE_OP_APPLY_ALPHA_ALL, new_img, NULL, ®_visible, (double[]){w->opacity}); ps->backend_data->ops->compose( - ps->backend_data, new_img, w->g.x + w->shadow_dx, + ps->backend_data, w, new_img, w->g.x + w->shadow_dx, w->g.y + w->shadow_dy, ®_shadow, ®_visible); ps->backend_data->ops->release_image(ps->backend_data, new_img); } @@ -308,7 +322,7 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { // Draw window on target if (!w->invert_color && !w->dim && w->frame_opacity == 1 && w->opacity == 1) { - ps->backend_data->ops->compose(ps->backend_data, w->win_image, + ps->backend_data->ops->compose(ps->backend_data, w, w->win_image, w->g.x, w->g.y, ®_paint_in_bound, ®_visible); } else if (w->opacity * MAX_ALPHA >= 1) { @@ -357,7 +371,7 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { ®_visible_local, (double[]){dim_opacity}); } if (w->frame_opacity != 1) { - auto reg_frame = win_get_region_frame_local_by_val(w); + auto reg_frame = win_get_region_frame_local_by_val(w, true); ps->backend_data->ops->image_op( ps->backend_data, IMAGE_OP_APPLY_ALPHA, new_img, ®_frame, ®_visible_local, (double[]){w->frame_opacity}); @@ -368,13 +382,21 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { ps->backend_data, IMAGE_OP_APPLY_ALPHA_ALL, new_img, NULL, ®_visible_local, (double[]){w->opacity}); } - ps->backend_data->ops->compose(ps->backend_data, new_img, w->g.x, + ps->backend_data->ops->compose(ps->backend_data, w, new_img, w->g.x, w->g.y, ®_paint_in_bound, ®_visible); ps->backend_data->ops->release_image(ps->backend_data, new_img); pixman_region32_fini(®_visible_local); pixman_region32_fini(®_bound_local); } + + // Round the corners as last step after blur/shadow/dim/etc + if (w->corner_radius > 0.0) { + ps->backend_data->ops->round(ps->backend_data, w, + ps->backend_round_context, w->win_image, + ®_bound, ®_visible); + } + pixman_region32_fini(®_bound); pixman_region32_fini(®_paint_in_bound); } diff --git a/src/backend/backend.h b/src/backend/backend.h index 5a18e0ac01..b313d2d03d 100644 --- a/src/backend/backend.h +++ b/src/backend/backend.h @@ -28,6 +28,9 @@ typedef struct backend_base { /// Whether the backend can accept new render request at the moment bool busy; // ... + + // Session data + session_t *ps; } backend_t; typedef void (*backend_ready_callback_t)(void *); @@ -56,6 +59,11 @@ struct gaussian_blur_args { double deviation; }; +struct dual_kawase_blur_args { + int size; + blur_strength_t strength; +}; + struct box_blur_args { int size; }; @@ -65,6 +73,11 @@ struct kernel_blur_args { int kernel_count; }; +struct round_corners_args { + int corner_radius; + bool round_borders; +}; + struct backend_operations { // =========== Initialization =========== @@ -126,7 +139,7 @@ struct backend_operations { * @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_x, int dst_y, + void (*compose)(backend_t *backend_data, struct managed_win *const w, void *image_data, int dst_x, int dst_y, const region_t *reg_paint, const region_t *reg_visible); /// Fill rectangle of the rendering buffer, mostly for debug purposes, optional. @@ -137,6 +150,11 @@ struct backend_operations { const region_t *reg_blur, const region_t *reg_visible) attr_nonnull(1, 3, 4, 5); + /// Round a given region of the rendering buffer. + bool (*round)(backend_t *backend_data, struct managed_win *w, void *round_ctx, + void *image_data, const region_t *reg_round, const region_t *reg_visible) + attr_nonnull(1, 2, 3, 5, 6); + /// 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). @@ -218,6 +236,15 @@ struct backend_operations { /// Get how many pixels outside of the blur area is needed for blur void (*get_blur_size)(void *blur_context, int *width, int *height); + /// Backup our current window background so we can use it for "erasing" corners + bool (*store_back_texture)(backend_t *base, struct managed_win *w, void *ctx_, + const region_t *reg_tgt, int x, int y, int width, int height); + + /// Create a rounded corners context + void *(*create_round_context)(backend_t *base, void *args); + /// Destroy a rounded corners context + void (*destroy_round_context)(backend_t *base, void *ctx); + // =========== Hooks ============ /// Let the backend hook into the event handling queue /// Not implemented yet diff --git a/src/backend/backend_common.c b/src/backend/backend_common.c index 471a6779f5..416b2d38d6 100644 --- a/src/backend/backend_common.c +++ b/src/backend/backend_common.c @@ -362,10 +362,106 @@ struct conv **generate_blur_kernel(enum blur_method method, void *args, int *ker return NULL; } +/// Generate kernel parameters for dual-kawase blur method. Falls back on approximating +/// standard gauss radius if strength is not supplied +struct dual_kawase_params *generate_dual_kawase_params(void *args) { + struct dual_kawase_blur_args *blur_args = args; + static const struct { + int iterations; + float offset; + } strength_levels[20] = { + {.iterations = 1, .offset = 1.25f}, // LVL 1 => radius 4 + {.iterations = 1, .offset = 2.25f}, // LVL 2 => radius 7 + {.iterations = 2, .offset = 2.00f}, // LVL 3 => radius 14 + {.iterations = 2, .offset = 3.00f}, // LVL 4 => radius 20 + {.iterations = 2, .offset = 4.25f}, // LVL 5 => radius 28 + {.iterations = 3, .offset = 2.50f}, // LVL 6 => radius 35 + {.iterations = 3, .offset = 3.25f}, // LVL 7 => radius 45 + {.iterations = 3, .offset = 4.25f}, // LVL 8 => radius 57 + {.iterations = 3, .offset = 5.50f}, // LVL 9 => radius 74 + {.iterations = 4, .offset = 3.25f}, // LVL 10 => radius 91 + {.iterations = 4, .offset = 4.00f}, // LVL 11 => radius 110 + {.iterations = 4, .offset = 5.00f}, // LVL 12 => radius 135 + {.iterations = 4, .offset = 6.00f}, // LVL 13 => radius 161 + {.iterations = 4, .offset = 7.25f}, // LVL 14 => radius 195 + {.iterations = 4, .offset = 8.25f}, // LVL 15 => radius 221 + {.iterations = 5, .offset = 4.50f}, // LVL 16 => radius 250 + {.iterations = 5, .offset = 5.25f}, // LVL 17 => radius 287 + {.iterations = 5, .offset = 6.25f}, // LVL 18 => radius 330 + {.iterations = 5, .offset = 7.25f}, // LVL 19 => radius 383 + {.iterations = 5, .offset = 8.50f}, // LVL 20 => radius >450 + }; + + auto params = ccalloc(1, struct dual_kawase_params); + params->iterations = 0; + params->offset = 1.0f; + + if (blur_args->strength.strength <= 0 && blur_args->size) { + // approximate blur_strength with gaussian blur_radius + if (blur_args->size < 6) { + blur_args->strength.strength = 1; + } else if (blur_args->size < 11) { + blur_args->strength.strength = 2; + } else if (blur_args->size < 17) { + blur_args->strength.strength = 3; + } else if (blur_args->size < 24) { + blur_args->strength.strength = 4; + } else if (blur_args->size < 32) { + blur_args->strength.strength = 5; + } else if (blur_args->size < 40) { + blur_args->strength.strength = 6; + } else if (blur_args->size < 51) { + blur_args->strength.strength = 7; + } else if (blur_args->size < 67) { + blur_args->strength.strength = 8; + } else if (blur_args->size < 83) { + blur_args->strength.strength = 9; + } else if (blur_args->size < 101) { + blur_args->strength.strength = 10; + } else if (blur_args->size < 123) { + blur_args->strength.strength = 11; + } else if (blur_args->size < 148) { + blur_args->strength.strength = 12; + } else if (blur_args->size < 177) { + blur_args->strength.strength = 13; + } else if (blur_args->size < 208) { + blur_args->strength.strength = 14; + } else if (blur_args->size < 236) { + blur_args->strength.strength = 15; + } else if (blur_args->size < 269) { + blur_args->strength.strength = 16; + } else if (blur_args->size < 309) { + blur_args->strength.strength = 17; + } else if (blur_args->size < 357) { + blur_args->strength.strength = 18; + } else if (blur_args->size < 417) { + blur_args->strength.strength = 19; + } else { + blur_args->strength.strength = 20; + } + } + + if (blur_args->strength.strength > 0) { + assert(blur_args->strength.strength <= 20); + params->iterations = strength_levels[blur_args->strength.strength - 1].iterations; + params->offset = strength_levels[blur_args->strength.strength - 1].offset; + } + + params->expand = 2 * (int)exp2f((float)params->iterations) * + (256 - (int)(256.0f - params->offset)) + + 1; + + log_info("blur-strength: %d [.iter = %d, .offset = %f]", + blur_args->strength.strength, params->iterations, params->offset); + + return params; +} + 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; + base->ps = ps; } diff --git a/src/backend/backend_common.h b/src/backend/backend_common.h index 28048b1cdb..59b93ec3c1 100644 --- a/src/backend/backend_common.h +++ b/src/backend/backend_common.h @@ -16,6 +16,15 @@ typedef struct conv conv; typedef struct backend_base backend_t; struct backend_operations; +typedef 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; +} dual_kawase_params_t; + 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); @@ -41,3 +50,5 @@ default_backend_render_shadow(backend_t *backend_data, int width, int height, 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); diff --git a/src/backend/dummy/dummy.c b/src/backend/dummy/dummy.c index 4b24f9fd58..73f2b643bf 100644 --- a/src/backend/dummy/dummy.c +++ b/src/backend/dummy/dummy.c @@ -56,7 +56,7 @@ static void dummy_check_image(struct backend_base *base, const struct dummy_imag assert(*tmp->refcount > 0); } -void dummy_compose(struct backend_base *base, void *image, int dst_x attr_unused, +void dummy_compose(struct backend_base *base, struct managed_win *w attr_unused, void *image, int dst_x attr_unused, int dst_y attr_unused, const region_t *reg_paint attr_unused, const region_t *reg_visible attr_unused) { dummy_check_image(base, image); @@ -72,6 +72,12 @@ bool dummy_blur(struct backend_base *backend_data attr_unused, double opacity at return true; } +bool dummy_round(struct backend_base *backend_data attr_unused, struct managed_win *w attr_unused, + void *ctx_ attr_unused, void *image_data attr_unused, const region_t *reg_round 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; @@ -138,6 +144,14 @@ void *dummy_create_blur_context(struct backend_base *base attr_unused, void dummy_destroy_blur_context(struct backend_base *base attr_unused, void *ctx attr_unused) { } +void *dummy_create_round_context(struct backend_base *base attr_unused, void *args attr_unused) { + static int dummy_context; + return &dummy_context; +} + +void dummy_destroy_round_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. @@ -145,12 +159,18 @@ void dummy_get_blur_size(void *ctx attr_unused, int *width, int *height) { *height = 5; } +bool dummy_store_back_texture(backend_t *backend_data attr_unused, struct managed_win *w attr_unused, void *ctx_ attr_unused, + const region_t *reg_tgt attr_unused, int x attr_unused, int y attr_unused, int width attr_unused, int height attr_unused) { + return true; +} + struct backend_operations dummy_ops = { .init = dummy_init, .deinit = dummy_deinit, .compose = dummy_compose, .fill = dummy_fill, .blur = dummy_blur, + .round = dummy_round, .bind_pixmap = dummy_bind_pixmap, .render_shadow = default_backend_render_shadow, .release_image = dummy_release_image, @@ -162,6 +182,9 @@ struct backend_operations dummy_ops = { .copy = dummy_image_copy, .create_blur_context = dummy_create_blur_context, .destroy_blur_context = dummy_destroy_blur_context, + .create_round_context = dummy_create_round_context, + .destroy_round_context = dummy_destroy_round_context, .get_blur_size = dummy_get_blur_size, + .store_back_texture = dummy_store_back_texture }; diff --git a/src/backend/gl/gl_common.c b/src/backend/gl/gl_common.c index b89c49e8ce..c089bc8cf1 100644 --- a/src/backend/gl/gl_common.c +++ b/src/backend/gl/gl_common.c @@ -18,6 +18,7 @@ #include "string_utils.h" #include "types.h" #include "utils.h" +#include "win.h" #include "backend/backend_common.h" #include "backend/gl/gl_common.h" @@ -35,11 +36,19 @@ struct gl_blur_context { /// Temporary textures used for blurring. They are always 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. - GLuint blur_texture[2]; - /// Temporary fbo used for blurring - GLuint blur_fbo; + GLuint *blur_textures; + /// Temporary fbos used for blurring + GLuint *blur_fbos; + /// Cached size of each blur_texture + struct texture_size { + int width; + int height; + } * texture_sizes; - int texture_width, texture_height; + int blur_texture_count; + int blur_fbo_count; + + int fb_width, fb_height; /// How much do we need to resize the damaged region for blurring. int resize_width, resize_height; @@ -47,6 +56,20 @@ struct gl_blur_context { int npasses; }; +struct gl_round_context { + gl_round_shader_t *round_shader; + GLuint *bg_fbo; + GLuint *bg_tex; + /// Cached size of each blur_texture + struct tex_size { + int width; + int height; + } * tex_sizes; + int tex_count; + int fbo_count; + bool round_borders; +}; + static GLint glGetUniformLocationChecked(GLuint p, const char *name) { auto ret = glGetUniformLocation(p, name); if (ret < 0) { @@ -504,7 +527,7 @@ x_rect_to_coords(int nrects, const rect_t *rects, int dst_x, int dst_y, int text } // TODO: make use of reg_visible -void gl_compose(backend_t *base, void *image_data, int dst_x, int dst_y, +void gl_compose(backend_t *base, struct managed_win *w attr_unused, void *image_data, int dst_x, int dst_y, const region_t *reg_tgt, const region_t *reg_visible attr_unused) { auto gd = (struct gl_data *)base; struct gl_image *img = image_data; @@ -538,51 +561,314 @@ void gl_compose(backend_t *base, void *image_data, int dst_x, int dst_y, /** * Blur contents in a particular region. */ +bool gl_kernel_blur(backend_t *base, double opacity, void *ctx, const rect_t *extent, + const int width attr_unused, const int height attr_unused, + const int nrects, const GLuint vao[2]) { + struct gl_blur_context *bctx = ctx; + struct gl_data *gd = (void *)base; + + int dst_y_screen_coord = gd->height - extent->y2, + 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, texorig_y; + GLuint src_texture; + + if (i == 0) { + texorig_x = extent->x1; + texorig_y = dst_y_screen_coord; + src_texture = gd->back_texture; + } else { + texorig_x = extent->x1 + bctx->resize_width; + texorig_y = dst_y_fb_coord - bctx->resize_height; + src_texture = bctx->blur_textures[curr]; + } + + glBindTexture(GL_TEXTURE_2D, src_texture); + glUseProgram(p->prog); + if (i < bctx->npasses - 1) { + // not last pass, draw into framebuffer, with resized regions + glBindVertexArray(vao[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 (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + log_error("Framebuffer attachment failed."); + return false; + } + + glUniform1f(p->unifm_opacity, 1.0); + // For other than last pass, we are drawing to a texture, we + // translate the render origin so we don't need a big texture + glUniform2f(p->orig_loc, (GLfloat)bctx->resize_width, + -(GLfloat)bctx->resize_height); + glViewport(0, 0, bctx->fb_width, bctx->fb_height); + } else { + // last pass, draw directly into the back buffer, with origin + // regions + glBindVertexArray(vao[0]); + glBindFramebuffer(GL_FRAMEBUFFER, gd->back_fbo); + + glUniform1f(p->unifm_opacity, (float)opacity); + + glUniform2f(p->orig_loc, 0, 0); + glViewport(0, 0, gd->width, gd->height); + } + + glUniform2f(p->texorig_loc, (GLfloat)texorig_x, (GLfloat)texorig_y); + glDrawElements(GL_TRIANGLES, nrects * 6, 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 int width, const int height, const int nrects, + const GLuint vao[2]) { + struct gl_blur_context *bctx = ctx; + struct gl_data *gd = (void *)base; + + int dst_y_screen_coord = gd->height - extent->y2, + dst_y_fb_coord = bctx->fb_height - extent->y2; + + // Reduce number of iterations until the last one renders at least 1px in both + // dimensions + int iterations = bctx->blur_texture_count; + while (((width / (1 << iterations)) < 1 || (height / (1 << iterations)) < 1) && + iterations > 0) { + --iterations; + } + + // Note: OpenGL matrices are column major + GLfloat projection_matrix[4][4] = {{2.0f / (GLfloat)bctx->fb_width, 0, 0, 0}, + {0, 2.0f / (GLfloat)bctx->fb_height, 0, 0}, + {0, 0, 0, 0}, + {-1, -1, 0, 1}}; + + // Kawase downsample pass + const gl_blur_shader_t *down_pass = &bctx->blur_shader[0]; + assert(down_pass->prog); + glUseProgram(down_pass->prog); + + // Update projection matrices in the blur shaders + glUniformMatrix4fv(down_pass->projection_loc, 1, false, projection_matrix[0]); + glUniform2f(down_pass->orig_loc, (GLfloat)bctx->resize_width, + -(GLfloat)bctx->resize_height); + + for (int i = 0; i < iterations; ++i) { + GLuint src_texture; + int tex_width, tex_height; + int texorig_x, texorig_y; + + float halfpixel_x, halfpixel_y; + + if (i == 0) { + // first pass: copy from back buffer + src_texture = gd->back_texture; + tex_width = gd->width; + tex_height = gd->height; + + texorig_x = extent->x1; + texorig_y = dst_y_screen_coord; + + halfpixel_x = 0.5f / (float)gd->width; + halfpixel_y = 0.5f / (float)gd->height; + } else { + // copy from previous pass + src_texture = bctx->blur_textures[i - 1]; + tex_width = bctx->fb_width; + tex_height = bctx->fb_height; + + texorig_x = extent->x1 + bctx->resize_width; + texorig_y = dst_y_fb_coord - bctx->resize_height; + + auto src_size = bctx->texture_sizes[i - 1]; + halfpixel_x = 0.5f / (float)src_size.width; + halfpixel_y = 0.5f / (float)src_size.height; + } + + glBindTexture(GL_TEXTURE_2D, src_texture); + glBindVertexArray(vao[1]); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, bctx->blur_fbos[i]); + glDrawBuffer(GL_COLOR_ATTACHMENT0); + + glUniform2f(down_pass->texorig_loc, (GLfloat)texorig_x, (GLfloat)texorig_y); + glUniform2f(down_pass->unifm_texture_size, (GLfloat)tex_width, + (GLfloat)tex_height); + glUniform2f(down_pass->unifm_halfpixel, halfpixel_x, halfpixel_y); + + auto tgt_size = bctx->texture_sizes[i]; + glViewport(0, 0, tgt_size.width, tgt_size.height); + glDrawElements(GL_TRIANGLES, nrects * 6, 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); + + // Update projection matrices in the blur shaders + glUniformMatrix4fv(up_pass->projection_loc, 1, false, projection_matrix[0]); + + glUniform2f(up_pass->texorig_loc, (GLfloat)(extent->x1 + bctx->resize_width), + (GLfloat)(dst_y_fb_coord - bctx->resize_height)); + glUniform2f(up_pass->unifm_texture_size, (GLfloat)bctx->fb_width, + (GLfloat)bctx->fb_height); + + for (int i = iterations - 1; i >= 0; --i) { + const GLuint src_texture = bctx->blur_textures[i]; + int orig_x, orig_y; + + auto src_size = bctx->texture_sizes[i]; + float halfpixel_x = 0.5f / (float)src_size.width, + halfpixel_y = 0.5f / (float)src_size.height; + + int vp_width, vp_height; + + glBindTexture(GL_TEXTURE_2D, src_texture); + if (i > 0) { + // not last pass, draw into next framebuffer + glBindVertexArray(vao[1]); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, bctx->blur_fbos[i - 1]); + glDrawBuffer(GL_COLOR_ATTACHMENT0); + glClearBufferiv(GL_COLOR, 0, (GLint[4]){255, 0, 0, 255}); + + orig_x = bctx->resize_width; + orig_y = -bctx->resize_height; + + auto tgt_size = bctx->texture_sizes[i - 1]; + vp_width = tgt_size.width; + vp_height = tgt_size.height; + + glUniform1f(up_pass->unifm_opacity, (GLfloat)1); + + // For other than last pass, we are drawing to a texture, we + // translate the render origin so we don't need a big texture + //glUniform2f(up_pass->orig_loc, (GLfloat)bctx->resize_width, + // -(GLfloat)bctx->resize_height); + //glViewport(0, 0, vp_width, vp_height); + + } else { + // last pass, draw directly into the back buffer + glBindVertexArray(vao[0]); + glBindFramebuffer(GL_FRAMEBUFFER, gd->back_fbo); + + // Update projection matrix + projection_matrix[0][0] = 2.0f / (GLfloat)gd->width; + projection_matrix[1][1] = 2.0f / (GLfloat)gd->height; + glUniformMatrix4fv(up_pass->projection_loc, 1, false, + projection_matrix[0]); + + orig_x = 0; + orig_y = 0; + + vp_width = gd->width; + vp_height = gd->height; + + glUniform1f(up_pass->unifm_opacity, (GLfloat)opacity); + + //glUniform2f(up_pass->orig_loc, 0, 0); + //glViewport(0, 0, vp_width, vp_height); + } + + glUniform2f(up_pass->orig_loc, (GLfloat)orig_x, (GLfloat)orig_y); + glUniform2f(up_pass->unifm_halfpixel, halfpixel_x, halfpixel_y); + + glViewport(0, 0, vp_width, vp_height); + glDrawElements(GL_TRIANGLES, nrects * 6, 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) { struct gl_blur_context *bctx = ctx; auto gd = (struct gl_data *)base; - if (gd->width + bctx->resize_width * 2 != bctx->texture_width || - gd->height + bctx->resize_height * 2 != bctx->texture_height) { + bool ret = false; + + if (gd->width + bctx->resize_width * 2 != bctx->fb_width || + gd->height + bctx->resize_height * 2 != bctx->fb_height) { // Resize the temporary textures used for blur in case the root // size changed - bctx->texture_width = gd->width + bctx->resize_width * 2; - bctx->texture_height = gd->height + bctx->resize_height * 2; - - glBindTexture(GL_TEXTURE_2D, bctx->blur_texture[0]); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, bctx->texture_width, - bctx->texture_height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL); - glBindTexture(GL_TEXTURE_2D, bctx->blur_texture[1]); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, bctx->texture_width, - bctx->texture_height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL); - - // XXX: do we need projection matrix for blur at all? - // Note: OpenGL matrices are column major - GLfloat projection_matrix[4][4] = { - {2.0f / (GLfloat)bctx->texture_width, 0, 0, 0}, - {0, 2.0f / (GLfloat)bctx->texture_height, 0, 0}, - {0, 0, 0, 0}, - {-1, -1, 0, 1}}; - - // Update projection matrices in the blur shaders - for (int i = 0; i < bctx->npasses - 1; i++) { - assert(bctx->blur_shader[i].prog); - glUseProgram(bctx->blur_shader[i].prog); - int pml = glGetUniformLocationChecked(bctx->blur_shader[i].prog, - "projection"); - glUniformMatrix4fv(pml, 1, false, projection_matrix[0]); - } + bctx->fb_width = gd->width + bctx->resize_width * 2; + bctx->fb_height = gd->height + bctx->resize_height * 2; + + if (bctx->method == BLUR_METHOD_DUAL_KAWASE || + bctx->method == BLUR_METHOD_ALT_KAWASE) { + // Use smaller textures for each iteration + for (int i = 0; i < bctx->blur_texture_count; ++i) { + auto tex_size = bctx->texture_sizes + i; + tex_size->width = 1 + (bctx->fb_width - 1) / (1 << (i + 1)); + tex_size->height = 1 + (bctx->fb_height - 1) / (1 << (i + 1)); + + 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); + + // 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 (glCheckFramebufferStatus(GL_FRAMEBUFFER) != + GL_FRAMEBUFFER_COMPLETE) { + log_error("Framebuffer attachment failed."); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + return false; + } + } + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + } else { + glBindTexture(GL_TEXTURE_2D, bctx->blur_textures[0]); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, bctx->fb_width, + bctx->fb_height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL); + glBindTexture(GL_TEXTURE_2D, bctx->blur_textures[1]); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, bctx->fb_width, + bctx->fb_height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL); + + // XXX: do we need projection matrix for blur at all? + // Note: OpenGL matrices are column major + GLfloat projection_matrix[4][4] = { + {2.0f / (GLfloat)bctx->fb_width, 0, 0, 0}, + {0, 2.0f / (GLfloat)bctx->fb_height, 0, 0}, + {0, 0, 0, 0}, + {-1, -1, 0, 1}}; + + // Update projection matrices in the blur shaders + for (int i = 0; i < bctx->npasses - 1; i++) { + assert(bctx->blur_shader[i].prog); + glUseProgram(bctx->blur_shader[i].prog); + glUniformMatrix4fv(bctx->blur_shader[i].projection_loc, 1, + false, projection_matrix[0]); + } - GLfloat projection_matrix2[4][4] = {{2.0f / (GLfloat)gd->width, 0, 0, 0}, - {0, 2.0f / (GLfloat)gd->height, 0, 0}, - {0, 0, 0, 0}, - {-1, -1, 0, 1}}; - assert(bctx->blur_shader[bctx->npasses - 1].prog); - glUseProgram(bctx->blur_shader[bctx->npasses - 1].prog); - int pml = glGetUniformLocationChecked( - bctx->blur_shader[bctx->npasses - 1].prog, "projection"); - glUniformMatrix4fv(pml, 1, false, projection_matrix2[0]); + GLfloat projection_matrix2[4][4] = { + {2.0f / (GLfloat)gd->width, 0, 0, 0}, + {0, 2.0f / (GLfloat)gd->height, 0, 0}, + {0, 0, 0, 0}, + {-1, -1, 0, 1}}; + assert(bctx->blur_shader[bctx->npasses - 1].prog); + glUseProgram(bctx->blur_shader[bctx->npasses - 1].prog); + glUniformMatrix4fv(bctx->blur_shader[bctx->npasses - 1].projection_loc, + 1, false, projection_matrix2[0]); + } } // Remainder: regions are in Xorg coordinates @@ -591,13 +877,10 @@ bool gl_blur(backend_t *base, double opacity, void *ctx, const region_t *reg_blu const rect_t *extent = pixman_region32_extents((region_t *)reg_blur), *extent_resized = pixman_region32_extents(®_blur_resized); int width = extent->x2 - extent->x1, height = extent->y2 - extent->y1; - int dst_y_resized_screen_coord = gd->height - extent_resized->y2, - dst_y_resized_fb_coord = bctx->texture_height - extent_resized->y2; if (width == 0 || height == 0) { return true; } - bool ret = false; int nrects, nrects_resized; const rect_t *rects = pixman_region32_rectangles((region_t *)reg_blur, &nrects), *rects_resized = @@ -609,13 +892,13 @@ bool gl_blur(backend_t *base, double opacity, void *ctx, const region_t *reg_blu 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->texture_height, gd->height, false, coord, indices); + 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->texture_height, bctx->texture_height, - false, coord_resized, indices_resized); + extent_resized->y2, bctx->fb_height, bctx->fb_height, false, + coord_resized, indices_resized); pixman_region32_fini(®_blur_resized); GLuint vao[2]; @@ -649,87 +932,209 @@ bool gl_blur(backend_t *base, double opacity, void *ctx, const region_t *reg_blu glVertexAttribPointer(vert_in_texcoord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 4, (void *)(sizeof(GLint) * 2)); - int curr = 0; - for (int i = 0; i < bctx->npasses; ++i) { - const gl_blur_shader_t *p = &bctx->blur_shader[i]; - assert(p->prog); + if (bctx->method == BLUR_METHOD_DUAL_KAWASE || + bctx->method == BLUR_METHOD_ALT_KAWASE) { + ret = gl_dual_kawase_blur(base, opacity, ctx, extent_resized, width, + height, nrects, vao); + } else { + ret = gl_kernel_blur(base, opacity, ctx, extent_resized, width, height, + nrects, vao); + } - assert(bctx->blur_texture[curr]); + 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); - // The origin to use when sampling from the source texture - GLint texorig_x, texorig_y; - GLuint src_texture; + free(indices); + free(coord); + free(indices_resized); + free(coord_resized); - if (i == 0) { - texorig_x = extent_resized->x1; - texorig_y = dst_y_resized_screen_coord; - src_texture = gd->back_texture; - } else { - texorig_x = 0; - texorig_y = 0; - src_texture = bctx->blur_texture[curr]; - } + gl_check_err(); + return ret; +} - glBindTexture(GL_TEXTURE_2D, src_texture); - glUseProgram(p->prog); - if (i < bctx->npasses - 1) { - // not last pass, draw into framebuffer, with resized regions - glBindVertexArray(vao[1]); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, bctx->blur_fbo); +bool gl_round(backend_t *backend_data attr_unused, struct managed_win *w, void *ctx_, void *image_data, + const region_t *reg_round attr_unused, const region_t *reg_visible attr_unused) { - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, - GL_TEXTURE_2D, bctx->blur_texture[!curr], 0); - glDrawBuffer(GL_COLOR_ATTACHMENT0); - if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { - log_error("Framebuffer attachment failed."); - goto end; - } - glUniform1f(p->unifm_opacity, 1.0); - // For other than last pass, we are drawing to a texture, we - // translate the render origin so we don't need a big texture - glUniform2f(p->orig_loc, -(GLfloat)extent_resized->x1, - -(GLfloat)dst_y_resized_fb_coord); - glViewport(0, 0, bctx->texture_width, bctx->texture_height); - } else { - // last pass, draw directly into the back buffer, with origin - // regions - glBindVertexArray(vao[0]); - glBindFramebuffer(GL_FRAMEBUFFER, gd->back_fbo); - glUniform1f(p->unifm_opacity, (float)opacity); - glUniform2f(p->orig_loc, 0, 0); - glViewport(0, 0, gd->width, gd->height); - } + struct gl_round_context *cctx = ctx_; + auto gd = (struct gl_data *)backend_data; + auto img = (struct gl_image*)image_data; - glUniform2f(p->texorig_loc, (GLfloat)texorig_x, (GLfloat)texorig_y); - glDrawElements(GL_TRIANGLES, nrects * 6, GL_UNSIGNED_INT, NULL); - - // XXX use multiple draw calls is probably going to be slow than - // just simply blur the whole area. + //log_warn("r(%d) b(%d) bo(%d), wxy(%d %d) wwh(%d %d) img(%d %d)", + // w->corner_radius, w->g.border_width, w->border_width, w->g.x, w->g.y, + // w->widthb, w->heightb, img->inner->width, img->inner->height); - curr = !curr; + int nrects; + const rect_t *rects; + rects = pixman_region32_rectangles((region_t *)reg_round, &nrects); + if (!nrects) { + // Nothing to paint + return false; } - ret = true; + GLuint target = gd->back_fbo; + int dst_x = w->g.x; + int dst_y = w->g.y; -end: - glBindFramebuffer(GL_FRAMEBUFFER, 0); + auto coord = ccalloc(nrects * 16, GLint); + auto indices = ccalloc(nrects * 6, GLuint); + x_rect_to_coords(nrects, rects, dst_x, dst_y, + img ? img->inner->height : w->heightb, gd->height, + img ? img->inner->y_inverted : true, coord, indices); + + 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)); + + // XXX: do we need projection matrix at all? + // Note: OpenGL matrices are column major + GLfloat projection_matrix[4][4] = {{2.0f / (GLfloat)gd->width, 0, 0, 0}, + {0, 2.0f / (GLfloat)gd->height, 0, 0}, + {0, 0, 0, 0}, + {-1, -1, 0, 1}}; + + //glDisable(GL_BLEND); + glEnable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + + // Bind texture + glViewport(0, 0, gd->width, gd->height); + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, cctx->bg_tex[0]); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, img ? img->inner->texture : gd->back_texture); + + const gl_round_shader_t *ppass = &cctx->round_shader[0]; + glUseProgram(ppass->prog); + + if (ppass->projection_loc >= 0) + glUniformMatrix4fv(ppass->projection_loc, 1, false, projection_matrix[0]); + if (ppass->unifm_tex_bg >= 0) + glUniform1i(ppass->unifm_tex_bg, (GLint)1); + if (ppass->unifm_radius) + glUniform1f(ppass->unifm_radius, (float)w->corner_radius); + if (ppass->unifm_texcoord) + glUniform2f(ppass->unifm_texcoord, (float)w->g.x, (float)w->g.y); + if (ppass->unifm_texsize) + glUniform2f(ppass->unifm_texsize, (float)w->widthb, (float)w->heightb); + if (ppass->unifm_borderw) + glUniform1f(ppass->unifm_borderw, (w->round_borders) ? (w->border_width > 0 ? w->border_width : w->g.border_width) : 0); + if (ppass->unifm_resolution) + glUniform2f(ppass->unifm_resolution, (float)gd->width, (float)gd->height); + + // Draw + 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); + glEnable(GL_BLEND); + glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - glDeleteBuffers(4, bo); - glBindVertexArray(0); - glDeleteVertexArrays(2, vao); + glDeleteBuffers(2, bo); + + glUseProgram(0); + gl_check_err(); free(indices); free(coord); - free(indices_resized); - free(coord_resized); + + return true; +} + +// Assumes the two textures are the same dimensions +static bool copyFrameBufferTexture(int width, int height, GLuint fboIn, GLuint textureIn, GLuint fboOut, GLuint textureOut) +{ + bool ret = false; + + // Bind input FBO + texture to a color attachment + glBindFramebuffer(GL_READ_FRAMEBUFFER, fboIn); + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureIn, 0); + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != + GL_FRAMEBUFFER_COMPLETE) { + log_error("Source framebuffer attachment failed."); + goto out; + } + glReadBuffer(GL_COLOR_ATTACHMENT0); + + // Bind destination FBO + texture to another color attachment + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fboOut); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, textureOut, 0); + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != + GL_FRAMEBUFFER_COMPLETE) { + log_error("Destination framebuffer attachment failed."); + goto out; + } + glDrawBuffer(GL_COLOR_ATTACHMENT1); + + // specify source, destination drawing (sub)rectangles. + glBlitFramebuffer(0, 0, width, height, + 0, 0, width, height, + GL_COLOR_BUFFER_BIT, GL_NEAREST); + + ret = true; + +out: + // unbind the color attachments + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, 0, 0); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + glBindFramebuffer(GL_FRAMEBUFFER, 0); gl_check_err(); return ret; } +bool gl_store_back_texture(backend_t *backend_data attr_unused, + struct managed_win *w attr_unused, void *ctx_ attr_unused, + const region_t *reg_tgt attr_unused, int x attr_unused, int y attr_unused, + int width attr_unused, int height attr_unused) { + + struct gl_round_context *cctx = ctx_; + auto gd = (struct gl_data *)backend_data; + + //log_info("Copying xy(%d %d) wh(%d %d)", x, y, width, height); + + { + // Prepare our backup texture + glBindTexture(GL_TEXTURE_2D, cctx->bg_tex[0]); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, gd->width, + gd->height, 0, GL_BGR, GL_UNSIGNED_BYTE, NULL); + glBindTexture(GL_TEXTURE_2D, 0); + + copyFrameBufferTexture(gd->width, gd->height, gd->back_fbo, gd->back_texture, cctx->bg_fbo[0], cctx->bg_tex[0]); + } + + return true; +} + // clang-format off const char *vertex_shader = GLSL(330, uniform mat4 projection; @@ -966,29 +1371,64 @@ void gl_destroy_blur_context(backend_t *base attr_unused, void *ctx) { } free(bctx->blur_shader); - glDeleteTextures(bctx->npasses > 1 ? 2 : 1, bctx->blur_texture); - if (bctx->npasses > 1) { - glDeleteFramebuffers(1, &bctx->blur_fbo); + if (bctx->blur_textures) { + glDeleteTextures(bctx->blur_texture_count, bctx->blur_textures); + free(bctx->blur_textures); } + if (bctx->blur_fbos) { + glDeleteFramebuffers(bctx->blur_fbo_count, bctx->blur_fbos); + free(bctx->blur_fbos); + } + if (bctx->texture_sizes) { + free(bctx->texture_sizes); + } + + bctx->blur_texture_count = 0; + bctx->blur_fbo_count = 0; + free(bctx); gl_check_err(); } +void gl_destroy_round_context(struct backend_base *base attr_unused, void *ctx attr_unused) { + + struct gl_round_context *cctx = ctx; + + if (cctx->round_shader && cctx->round_shader->prog) { + glDeleteProgram(cctx->round_shader->prog); + cctx->round_shader->prog = 0; + free(cctx->round_shader); + } + + if (cctx->bg_tex) { + glDeleteTextures(cctx->tex_count, cctx->bg_tex); + free(cctx->bg_tex); + } + if (cctx->bg_fbo) { + glDeleteFramebuffers(cctx->fbo_count, cctx->bg_fbo); + free(cctx->bg_fbo); + } + if (cctx->tex_sizes) { + free(cctx->tex_sizes); + } + + cctx->tex_count = 0; + cctx->fbo_count = 0; + + free(cctx); + + gl_check_err(); +} + /** * Initialize GL blur filters. */ -void *gl_create_blur_context(backend_t *base, enum blur_method method, void *args) { - bool success = true; - auto gd = (struct gl_data *)base; +bool gl_create_kernel_blur_context(void *blur_context, enum blur_method method, void *args) { + bool success; + struct gl_blur_context *ctx = blur_context; struct conv **kernels; - auto ctx = ccalloc(1, struct gl_blur_context); - - if (!method || method >= BLUR_METHOD_INVALID) { - ctx->method = BLUR_METHOD_NONE; - return ctx; - } int nkernels; ctx->method = BLUR_METHOD_KERNEL; @@ -1001,7 +1441,7 @@ void *gl_create_blur_context(backend_t *base, enum blur_method method, void *arg if (!nkernels) { ctx->method = BLUR_METHOD_NONE; - return ctx; + return true; } ctx->blur_shader = ccalloc(max2(2, nkernels), gl_blur_shader_t); @@ -1083,6 +1523,8 @@ void *gl_create_blur_context(backend_t *base, enum blur_method method, void *arg pass->unifm_opacity = glGetUniformLocationChecked(pass->prog, "opacity"); pass->orig_loc = glGetUniformLocationChecked(pass->prog, "orig"); pass->texorig_loc = glGetUniformLocationChecked(pass->prog, "texorig"); + pass->projection_loc = + glGetUniformLocationChecked(pass->prog, "projection"); ctx->resize_width += kern->w / 2; ctx->resize_height += kern->h / 2; } @@ -1095,27 +1537,18 @@ void *gl_create_blur_context(backend_t *base, enum blur_method method, void *arg pass->unifm_opacity = -1; pass->orig_loc = glGetUniformLocationChecked(pass->prog, "orig"); pass->texorig_loc = glGetUniformLocationChecked(pass->prog, "texorig"); + pass->projection_loc = + glGetUniformLocationChecked(pass->prog, "projection"); ctx->npasses = 2; } else { ctx->npasses = nkernels; } - // Texture size will be defined by gl_blur - glGenTextures(2, ctx->blur_texture); - glBindTexture(GL_TEXTURE_2D, ctx->blur_texture[0]); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glBindTexture(GL_TEXTURE_2D, ctx->blur_texture[1]); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - // Generate FBO and textures when needed - glGenFramebuffers(1, &ctx->blur_fbo); - if (!ctx->blur_fbo) { - log_error("Failed to generate framebuffer object for blur"); - success = false; - goto out; - } + // Specify required textures and FBOs + ctx->blur_texture_count = 2; + ctx->blur_fbo_count = 1; + success = true; out: if (method != BLUR_METHOD_KERNEL) { // We generated the blur kernels, so we need to free them @@ -1125,16 +1558,218 @@ void *gl_create_blur_context(backend_t *base, enum blur_method method, void *arg 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, enum blur_method method, + void *args) { + bool success; + struct gl_blur_context *ctx = 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 vec2 texture_size; + uniform vec2 halfpixel; + in vec2 texcoord; + out vec4 out_color; + void main() { + float offset = %.7g; + vec2 uv = texcoord / texture_size; + vec4 sum = texture2D(tex_src, uv) * 4.0; + sum += texture2D(tex_src, uv - halfpixel.xy * offset); + sum += texture2D(tex_src, uv + halfpixel.xy * offset); + sum += texture2D(tex_src, uv + vec2(halfpixel.x, -halfpixel.y) * offset); + sum += texture2D(tex_src, uv - vec2(halfpixel.x, -halfpixel.y) * 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_texture_size = + glGetUniformLocationChecked(down_pass->prog, "texture_size"); + down_pass->unifm_halfpixel = + glGetUniformLocationChecked(down_pass->prog, "halfpixel"); + down_pass->orig_loc = + glGetUniformLocationChecked(down_pass->prog, "orig"); + down_pass->texorig_loc = + glGetUniformLocationChecked(down_pass->prog, "texorig"); + down_pass->projection_loc = + glGetUniformLocationChecked(down_pass->prog, "projection"); + } + + // 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 vec2 texture_size; + uniform vec2 halfpixel; + uniform float offset; + uniform float opacity; + in vec2 texcoord; + out vec4 out_color; + void main() { + float offset = %.7g; + vec2 uv = texcoord / texture_size; + vec4 sum = texture2D(tex_src, uv + vec2(-halfpixel.x * 2.0, 0.0) * offset); + sum += texture2D(tex_src, uv + vec2(-halfpixel.x, halfpixel.y) * offset) * 2.0; + sum += texture2D(tex_src, uv + vec2(0.0, halfpixel.y * 2.0) * offset); + sum += texture2D(tex_src, uv + vec2(halfpixel.x, halfpixel.y) * offset) * 2.0; + sum += texture2D(tex_src, uv + vec2(halfpixel.x * 2.0, 0.0) * offset); + sum += texture2D(tex_src, uv + vec2(halfpixel.x, -halfpixel.y) * offset) * 2.0; + sum += texture2D(tex_src, uv + vec2(0.0, -halfpixel.y * 2.0) * offset); + sum += texture2D(tex_src, uv + vec2(-halfpixel.x, -halfpixel.y) * 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_opacity = + glGetUniformLocationChecked(up_pass->prog, "opacity"); + up_pass->unifm_texture_size = + glGetUniformLocationChecked(up_pass->prog, "texture_size"); + up_pass->unifm_halfpixel = + glGetUniformLocationChecked(up_pass->prog, "halfpixel"); + up_pass->orig_loc = glGetUniformLocationChecked(up_pass->prog, "orig"); + up_pass->texorig_loc = + glGetUniformLocationChecked(up_pass->prog, "texorig"); + up_pass->projection_loc = + glGetUniformLocationChecked(up_pass->prog, "projection"); + } + + success = true; +out: + free(blur_params); + if (!success) { - gl_destroy_blur_context(&gd->base, ctx); ctx = NULL; } - free(extension); // 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; + } + + if (method == BLUR_METHOD_DUAL_KAWASE || method == BLUR_METHOD_ALT_KAWASE) { + success = gl_create_dual_kawase_blur_context(ctx, method, args); + } else { + success = gl_create_kernel_blur_context(ctx, method, args); + } + if (!success) { + 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 object for blur"); + success = false; + goto out; + } + } + +out: + if (!success) { + gl_destroy_blur_context(&gd->base, ctx); + ctx = NULL; + } + gl_check_err(); return ctx; } @@ -1145,6 +1780,165 @@ void gl_get_blur_size(void *blur_context, int *width, int *height) { *height = ctx->resize_height; } +void *gl_create_round_context(struct backend_base *base attr_unused, void *args attr_unused) { + bool success; + auto gd = (struct gl_data *)base; + auto ctx = ccalloc(1, struct gl_round_context); + + struct round_corners_args *round_params = (struct round_corners_args *)args; + + ctx->round_borders = round_params->round_borders; + ctx->round_shader = ccalloc(1, gl_round_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 pass = ctx->round_shader; + { + // TEST passthrough shader + /*static const char frag_passthrough[] = GLSL(330, + uniform sampler2D tex; + in vec2 texcoord; + void main() { + //gl_FragColor = texture2D(tex, texcoord); + //gl_FragColor = vec4(texture2D(tex, vec2(texcoord.xy), 0).rgb, 1); + gl_FragColor = texelFetch(tex, ivec2(texcoord.xy), 0); + //gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); + } + );*/ + + // dst0 shader from opengl.c + // clang-format off + static const char *FRAG_SHADER_ROUND_CORNERS = GLSL(330, + uniform sampler2D tex; + uniform sampler2D tex_bg; + uniform float u_radius; + uniform float u_borderw; + uniform vec2 u_texcoord; + uniform vec2 u_texsize; + uniform vec2 u_resolution; + in vec2 texcoord; + out vec4 out_color; + // https://www.shadertoy.com/view/ltS3zW + float RectSDF(vec2 p, vec2 b, float r) { + vec2 d = abs(p) - b + vec2(r); + return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - r; + } + void main() { + vec2 coord = vec2(u_texcoord.x, u_resolution.y-u_texsize.y-u_texcoord.y); + vec4 u_v4WndBgColor = texelFetch(tex_bg, ivec2(gl_FragCoord.xy), 0); + vec4 u_v4BorderColor = texelFetch(tex, ivec2(0, 0), 0); + vec4 u_v4FillColor = vec4(0.0, 0.0, 0.0, 0.0); // Inside rect, transparent + vec4 v4FromColor = u_v4BorderColor; // Always the border color. If no border, this still should be set + vec4 v4ToColor = u_v4WndBgColor; // Outside corners color = background texture + float u_fRadiusPx = u_radius; + float u_fHalfBorderThickness = u_borderw / 2.0; + + // misc tests, uncomment for diff rect colors + //u_v4FillColor = texture2D(tex, texcoord/u_texsize); + //u_v4FillColor = texelFetch(tex, ivec2(texcoord.xy), 0); + //u_v4FillColor = vec4(0.0, 1.0, 0.0, 0.0); // Inside rect color + //v4FromColor = u_v4BorderColor = vec4(1.0, 1.0, 0.0, 1.0); + //v4ToColor = vec4(0.0, 0.0, 1.0, 1.0); //Outside color + + vec2 u_v2HalfShapeSizePx = u_texsize/2.0 - vec2(u_fHalfBorderThickness); + vec2 v_v2CenteredPos = (gl_FragCoord.xy - u_texsize.xy / 2.0 - coord); + + float fDist = RectSDF(v_v2CenteredPos, u_v2HalfShapeSizePx, u_fRadiusPx - u_fHalfBorderThickness); + if (u_fHalfBorderThickness > 0.0) { + if (fDist < 0.0) { + v4ToColor = u_v4FillColor; + } + fDist = abs(fDist) - u_fHalfBorderThickness; + } else { + v4FromColor = u_v4FillColor; + } + float fBlendAmount = smoothstep(-1.0, 1.0, fDist); + + // final color + vec4 c = mix(v4FromColor, v4ToColor, fBlendAmount); + // we don't use discard due to alleged worse perf + // instead we can use alpha blending + //if ( c == vec4(0.0,0.0,0.0,0.0) ) discard; else + out_color = c; + } + ); + // clang-format on + + // Build shader + const char* SHADER_STR = FRAG_SHADER_ROUND_CORNERS; + //const char* SHADER_STR = frag_passthrough; + size_t shader_len = strlen(SHADER_STR) + 1 /* null terminator */; + char *shader_str = ccalloc(shader_len, char); + auto real_shader_len = snprintf(shader_str, shader_len, "%s", SHADER_STR); + CHECK(real_shader_len >= 0); + CHECK((size_t)real_shader_len < shader_len); + + // 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->projection_loc = glGetUniformLocationChecked(pass->prog, "projection"); + pass->unifm_tex_bg = glGetUniformLocationChecked(pass->prog, "tex_bg"); + pass->unifm_radius = glGetUniformLocationChecked(pass->prog, "u_radius"); + pass->unifm_texcoord = glGetUniformLocationChecked(pass->prog, "u_texcoord"); + pass->unifm_texsize = glGetUniformLocationChecked(pass->prog, "u_texsize"); + pass->unifm_borderw = glGetUniformLocationChecked(pass->prog, "u_borderw"); + pass->unifm_resolution = glGetUniformLocationChecked(pass->prog, "u_resolution"); + } + + // Texture size will be defined by gl_round + ctx->tex_count = 1; + ctx->bg_tex = ccalloc(ctx->tex_count, GLuint); + ctx->tex_sizes = ccalloc(ctx->tex_count, struct tex_size); + glGenTextures(ctx->tex_count, ctx->bg_tex); + for (int i = 0; i < ctx->tex_count; ++i) { + glBindTexture(GL_TEXTURE_2D, ctx->bg_tex[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->fbo_count = 1; + ctx->bg_fbo = ccalloc(ctx->fbo_count, GLuint); + glGenFramebuffers(ctx->fbo_count, ctx->bg_fbo); + for (int i = 0; i < ctx->fbo_count; ++i) { + if (!ctx->bg_fbo[i]) { + log_error("Failed to generate framebuffer object for blur"); + success = false; + goto out; + } + } + + success = true; + +out: + + // Restore LC_NUMERIC + setlocale(LC_NUMERIC, lc_numeric_old); + free(lc_numeric_old); + + if (!success) { + gl_destroy_round_context(&gd->base, ctx); + ctx = NULL; + } + + gl_check_err(); + return ctx; +} + // clang-format off const char *win_shader_glsl = GLSL(330, uniform float opacity; @@ -1165,8 +1959,8 @@ const char *win_shader_glsl = GLSL(330, 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; + rgb_brightness.g * 0.72 + + rgb_brightness.b * 0.07; if (brightness > max_brightness) c.rgb = c.rgb * (max_brightness / brightness); @@ -1212,8 +2006,10 @@ bool gl_init(struct gl_data *gd, session_t *ps) { } glBindTexture(GL_TEXTURE_2D, gd->back_texture); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + 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); gl_win_shader_from_string(vertex_shader, win_shader_glsl, &gd->win_shader); @@ -1384,9 +2180,9 @@ void gl_present(backend_t *base, const region_t *region) { // 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}, + 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 diff --git a/src/backend/gl/gl_common.h b/src/backend/gl/gl_common.h index 0a4ff49345..3e6ec0eead 100644 --- a/src/backend/gl/gl_common.h +++ b/src/backend/gl/gl_common.h @@ -33,10 +33,25 @@ typedef struct { typedef struct { GLuint prog; GLint unifm_opacity; + GLint unifm_texture_size; + GLint unifm_halfpixel; GLint orig_loc; GLint texorig_loc; + GLint projection_loc; } gl_blur_shader_t; +typedef struct { + GLuint prog; + GLint projection_loc; + GLint unifm_radius; + GLint unifm_texcoord; + GLint unifm_texsize; + GLint unifm_borderw; + GLint unifm_resolution; + GLint unifm_tex_bg; + GLint unifm_tex_wnd; +} gl_round_shader_t; + typedef struct { GLuint prog; GLint color_loc; @@ -98,7 +113,7 @@ GLuint gl_create_program_from_str(const char *vert_shader_str, const char *frag_ /** * @brief Render a region with texture data. */ -void gl_compose(backend_t *, void *ptex, int dst_x, int dst_y, const region_t *reg_tgt, +void gl_compose(backend_t *, struct managed_win *, void *ptex, int dst_x, int dst_y, const region_t *reg_tgt, const region_t *reg_visible); void gl_resize(struct gl_data *, int width, int height); @@ -121,6 +136,14 @@ 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); + +bool gl_round(backend_t *backend_data, struct managed_win *w, void *ctx_, + void *image_data, const region_t *reg_round, const region_t *reg_visible); +void *gl_create_round_context(backend_t *base, void *args); +void gl_destroy_round_context(backend_t *base, void *ctx); +bool gl_store_back_texture(backend_t *backend_data, struct managed_win *w, + void *ctx_, const region_t *reg_tgt, int x, int y, int width, int height); + bool gl_is_image_transparent(backend_t *base, void *image_data); void gl_fill(backend_t *base, struct color, const region_t *clip); diff --git a/src/backend/gl/glx.c b/src/backend/gl/glx.c index 9303a9869b..39f6ee2c20 100644 --- a/src/backend/gl/glx.c +++ b/src/backend/gl/glx.c @@ -494,6 +494,7 @@ struct backend_operations glx_ops = { .image_op = gl_image_op, .copy = gl_copy, .blur = gl_blur, + .round = gl_round, .is_image_transparent = gl_is_image_transparent, .present = glx_present, .buffer_age = glx_buffer_age, @@ -501,7 +502,10 @@ struct backend_operations glx_ops = { .fill = gl_fill, .create_blur_context = gl_create_blur_context, .destroy_blur_context = gl_destroy_blur_context, + .create_round_context = gl_create_round_context, + .destroy_round_context = gl_destroy_round_context, .get_blur_size = gl_get_blur_size, + .store_back_texture = gl_store_back_texture, .max_buffer_age = 5, // Why? }; diff --git a/src/backend/xrender/xrender.c b/src/backend/xrender/xrender.c index a44dbc197d..3e11caaa19 100644 --- a/src/backend/xrender/xrender.c +++ b/src/backend/xrender/xrender.c @@ -90,7 +90,9 @@ struct _xrender_image_data { bool owned; }; -static void compose(backend_t *base, void *img_data, int dst_x, int dst_y, +uint32_t make_rounded_window_shape(xcb_render_trapezoid_t traps[], uint32_t max_ntraps, int cr, int wid, int hei); + +static void compose(backend_t *base, struct managed_win *w, void *img_data, int dst_x, int dst_y, const region_t *reg_paint, const region_t *reg_visible) { struct _xrender_data *xd = (void *)base; struct _xrender_image_data *img = img_data; @@ -104,10 +106,52 @@ static void compose(backend_t *base, void *img_data, int dst_x, int dst_y, // sure we get everything into the buffer x_clear_picture_clip_region(base->c, img->pict); - x_set_picture_clip_region(base->c, xd->back[2], 0, 0, ®); - xcb_render_composite(base->c, op, img->pict, alpha_pict, xd->back[2], 0, 0, 0, 0, - to_i16_checked(dst_x), to_i16_checked(dst_y), - to_u16_checked(img->ewidth), to_u16_checked(img->eheight)); + // Are we rounding corners? + session_t *ps = base->ps; + int cr = (w ? w->corner_radius : 0); + + if (cr == 0) { + x_set_picture_clip_region(base->c, xd->back[2], 0, 0, ®); + xcb_render_composite(base->c, op, img->pict, alpha_pict, xd->back[2], 0, 0, 0, 0, + to_i16_checked(dst_x), to_i16_checked(dst_y), + to_u16_checked(img->ewidth), to_u16_checked(img->eheight)); + } else { + // Rounded corners + const int fullwid = w ? w->widthb : 0; + const int fullhei = w ? w-> heightb : 0; + //const int fullwid = img->width; + //const int fullhei = img->height; + //log_warn("f(%d, %d) imge(%d %d) imgf(%d %d) sdw(%d %d) dst(%d %d) s:%d b:%d", fullwid, fullhei, img->ewidth, img->eheight, img->width, img->height, w->shadow_width, w->shadow_height, dst_x, dst_y, w->shadow, w->g.border_width); + xcb_render_picture_t p_tmp = x_create_picture_with_standard( + ps->c, ps->root, fullwid, fullhei, XCB_PICT_STANDARD_ARGB_32, 0, 0); + xcb_render_color_t trans = { + .red = 0, .blue = 0, .green = 0, .alpha = 0}; + const xcb_rectangle_t rect = {.x = 0, + .y = 0, + .width = to_u16_checked(fullwid), + .height = to_u16_checked(fullhei)}; + xcb_render_fill_rectangles(ps->c, XCB_RENDER_PICT_OP_SRC, + p_tmp, trans, 1, &rect); + + uint32_t max_ntraps = to_u32_checked(cr); + xcb_render_trapezoid_t traps[4 * max_ntraps + 5]; + + uint32_t n = make_rounded_window_shape(traps, max_ntraps, cr, fullwid, fullhei); + + xcb_render_trapezoids( + ps->c, XCB_RENDER_PICT_OP_OVER, alpha_pict, p_tmp, + x_get_pictfmt_for_standard(ps->c, XCB_PICT_STANDARD_A_8), + 0, 0, n, traps); + + x_set_picture_clip_region(base->c, xd->back[2], 0, 0, ®); + xcb_render_composite(base->c, XCB_RENDER_PICT_OP_OVER, img->pict, p_tmp, xd->back[2], + 0, 0, 0, 0, + //0, 0, to_i16_checked(x), to_i16_checked(y), + to_i16_checked(dst_x), to_i16_checked(dst_y), + to_u16_checked(img->ewidth), to_u16_checked(img->eheight)); + + xcb_render_free_picture(ps->c, p_tmp); + } pixman_region32_fini(®); } @@ -245,6 +289,15 @@ static bool blur(backend_t *backend_data, double opacity, void *ctx_, return true; } +static bool x_round(struct backend_base *backend_data attr_unused, struct managed_win *w attr_unused, + void *ctx_ attr_unused, void *image_data attr_unused, const region_t *reg_blur attr_unused, + const region_t *reg_visible attr_unused) { + + // dummy implementation, we already perform the rounding in compose + // TODO: should move the compose code here and call it from here + return true; +} + static void * bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, bool owned) { xcb_generic_error_t *e; @@ -506,6 +559,13 @@ void *create_blur_context(backend_t *base attr_unused, enum blur_method method, return ret; } + if (method == BLUR_METHOD_DUAL_KAWASE || method == BLUR_METHOD_ALT_KAWASE) { + log_warn("Blur method '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; @@ -551,6 +611,19 @@ void get_blur_size(void *blur_context, int *width, int *height) { *height = ctx->resize_height; } +bool store_back_texture(backend_t *backend_data attr_unused, struct managed_win *w attr_unused, void *ctx_ attr_unused, + const region_t *reg_tgt attr_unused, int x attr_unused, int y attr_unused, int width attr_unused, int height attr_unused) { + return true; +} + +void *create_round_context(struct backend_base *base attr_unused, void *args attr_unused) { + static int dummy_context; + return &dummy_context; +} + +void destroy_round_context(struct backend_base *base attr_unused, void *ctx attr_unused) { +} + backend_t *backend_xrender_init(session_t *ps) { auto xd = ccalloc(1, struct _xrender_data); init_backend_base(&xd->base, ps); @@ -638,6 +711,7 @@ struct backend_operations xrender_ops = { .init = backend_xrender_init, .deinit = deinit, .blur = blur, + .round = x_round, .present = present, .compose = compose, .fill = fill, @@ -654,7 +728,11 @@ struct backend_operations xrender_ops = { .copy = copy, .create_blur_context = create_blur_context, .destroy_blur_context = destroy_blur_context, + .create_round_context = create_round_context, + .destroy_round_context = destroy_round_context, .get_blur_size = get_blur_size, + .store_back_texture = store_back_texture + }; // vim: set noet sw=8 ts=8: diff --git a/src/c2.c b/src/c2.c index eba0cffe0e..aa1c4b51cc 100644 --- a/src/c2.c +++ b/src/c2.c @@ -1028,17 +1028,6 @@ static bool c2_l_postprocess(session_t *ps, c2_l_t *pleaf) { } } - // Enable specific tracking options, if needed by the condition - // TODO: Add track_leader - switch (pleaf->predef) { - // case C2_L_PROUNDED: ps->o.detect_rounded_corners = true; break; - case C2_L_PNAME: - case C2_L_PCLASSG: - case C2_L_PCLASSI: - case C2_L_PROLE: ps->o.track_wdata = true; break; - default: break; - } - // Warn about lower case characters in target name if (pleaf->predef == C2_L_PUNDEFINED) { for (const char *pc = pleaf->tgt; *pc; ++pc) { diff --git a/src/common.h b/src/common.h index d229bf4560..f35825862f 100644 --- a/src/common.h +++ b/src/common.h @@ -75,6 +75,9 @@ /// @brief Maximum OpenGL buffer age. #define CGLX_MAX_BUFFER_AGE 5 +/// @brief Maximum passes for blur. +#define MAX_BLUR_PASS 6 + // Window flags // === Types === @@ -157,6 +160,8 @@ typedef struct session { backend_t *backend_data; /// backend blur context void *backend_blur_context; + /// round corners context + void *backend_round_context; /// graphic drivers used enum driver drivers; /// file watch handle diff --git a/src/config.c b/src/config.c index 17a198b3a0..ec45b7a551 100644 --- a/src/config.c +++ b/src/config.c @@ -88,6 +88,11 @@ enum blur_method parse_blur_method(const char *src) { return BLUR_METHOD_BOX; } else if (strcmp(src, "gaussian") == 0) { return BLUR_METHOD_GAUSSIAN; + } else if (strcmp(src, "kawase") == 0 || strcmp(src, "dual_kawase") == 0) { + return BLUR_METHOD_DUAL_KAWASE; + } else if (strcmp(src, "kawase_alt") == 0 || strcmp(src, "alt_kawase") == 0) { + // new code from tryone144's `improved_dbo` branch + return BLUR_METHOD_ALT_KAWASE; } else if (strcmp(src, "none") == 0) { return BLUR_METHOD_NONE; } @@ -431,6 +436,36 @@ bool parse_rule_opacity(c2_lptr_t **res, const char *src) { return c2_parse(res, endptr, (void *)val); } +/** + * Parse a list of border width rules. + */ +bool parse_rule_border(c2_lptr_t **res, const char *src) { + // Find opacity value + char *endptr = NULL; + long val = strtol(src, &endptr, 0); + if (!endptr || endptr == src) { + log_error("No border width specified: %s", src); + return false; + } + if (val > 100 || val < 0) { + log_error("Border width %ld invalid: %s", val, src); + return false; + } + + // Skip over spaces + while (*endptr && isspace(*endptr)) + ++endptr; + if (':' != *endptr) { + log_error("Border width terminator not found: %s", src); + return false; + } + ++endptr; + + // Parse pattern + // I hope 1-100 is acceptable for (void *) + return c2_parse(res, endptr, (void *)val); +} + /** * Add a pattern to a condition linked list. */ @@ -489,6 +524,12 @@ void set_default_winopts(options_t *opt, win_option_mask_t *mask, bool shadow_en // opacity logic is complicated, and needs an "unset" state opt->wintype_option[i].opacity = NAN; } + if (!mask[i].corner_radius) { + opt->wintype_option[i].corner_radius = -1; + } + if (!mask[i].round_borders) { + opt->wintype_option[i].round_borders = -1; + } } } @@ -542,6 +583,7 @@ char *parse_config(options_t *opt, const char *config_file, bool *shadow_enable, .blur_method = BLUR_METHOD_NONE, .blur_radius = 3, .blur_deviation = 0.84089642, + .blur_strength = { .strength = -1, .iterations = 3, .offset = 2.75, .expand = 50 }, .blur_background_frame = false, .blur_background_fixed = false, .blur_background_blacklist = NULL, @@ -559,8 +601,11 @@ char *parse_config(options_t *opt, const char *config_file, bool *shadow_enable, .detect_client_leader = false, .no_ewmh_fullscreen = false, - .track_wdata = false, .track_leader = false, + + .rounded_corners_blacklist = NULL, + .round_borders_blacklist = NULL, + .round_borders_rules = NULL }; char *ret = NULL; diff --git a/src/config.h b/src/config.h index 403ea548bf..2e95ba114e 100644 --- a/src/config.h +++ b/src/config.h @@ -44,6 +44,8 @@ typedef struct win_option_mask { bool full_shadow : 1; bool redir_ignore : 1; bool opacity : 1; + bool corner_radius : 1; + bool round_borders : 1; } win_option_mask_t; typedef struct win_option { @@ -53,6 +55,8 @@ typedef struct win_option { bool full_shadow; bool redir_ignore; double opacity; + int corner_radius; + int round_borders; } win_option_t; enum blur_method { @@ -60,9 +64,18 @@ enum blur_method { BLUR_METHOD_KERNEL, BLUR_METHOD_BOX, BLUR_METHOD_GAUSSIAN, + BLUR_METHOD_DUAL_KAWASE, + BLUR_METHOD_ALT_KAWASE, BLUR_METHOD_INVALID, }; +typedef struct blur_strength { + int expand; + int strength; + int iterations; + float offset; +} blur_strength_t; + typedef struct _c2_lptr c2_lptr_t; /// Structure representing all options. @@ -189,6 +202,8 @@ typedef struct options { int blur_radius; // Standard deviation for the gaussian blur double blur_deviation; + /// Blur strength (for kawase blur) + blur_strength_t blur_strength; /// Whether to blur background when the window frame is not opaque. /// Implies blur_background. bool blur_background_frame; @@ -228,8 +243,6 @@ typedef struct options { bool detect_client_leader; // === Calculated === - /// Whether we need to track window name and class. - bool track_wdata; /// Whether we need to track window leaders. bool track_leader; @@ -239,6 +252,17 @@ typedef struct options { // Make transparent windows clip other windows, instead of blending on top of // them bool transparent_clipping; + + // === Rounded corners related === + int corner_radius; + /// Rounded corners blacklist. A linked list of conditions. + c2_lptr_t *rounded_corners_blacklist; + /// Do we round the borders of rounded windows? + int round_borders; + /// Rounded borders blacklist. A linked list of conditions. + c2_lptr_t *round_borders_blacklist; + /// Rules to change window border width. + c2_lptr_t *round_borders_rules; } options_t; extern const char *const BACKEND_STRS[NUM_BKEND + 1]; @@ -248,6 +272,7 @@ bool must_use parse_int(const char *, int *); struct conv **must_use parse_blur_kern_lst(const char *, bool *hasneg, int *count); bool must_use parse_geometry(session_t *, const char *, region_t *); bool must_use parse_rule_opacity(c2_lptr_t **, const char *); +bool must_use parse_rule_border(c2_lptr_t **, const char *); enum blur_method must_use parse_blur_method(const char *src); /** @@ -315,3 +340,42 @@ static inline bool parse_vsync(const char *str) { } // vim: set noet sw=8 ts=8 : + +/** + * Parse a blur_strength option argument. + */ +static inline attr_pure blur_strength_t +parse_kawase_blur_strength(const int level) { + static const blur_strength_t values[20] = { + { .expand = 10, .strength =1, .iterations = 1, .offset = 1.5 }, // 1 + { .expand = 10, .strength =2, .iterations = 1, .offset = 2.0 }, // 2 + { .expand = 20, .strength =3, .iterations = 2, .offset = 2.5 }, // 3 + { .expand = 20, .strength =4, .iterations = 2, .offset = 3.0 }, // 4 + { .expand = 50, .strength =5, .iterations = 3, .offset = 2.75 }, // 5 + { .expand = 50, .strength =6, .iterations = 3, .offset = 3.5 }, // 6 + { .expand = 50, .strength =7, .iterations = 3, .offset = 4.25 }, // 7 + { .expand = 50, .strength =8, .iterations = 3, .offset = 5.0 }, // 8 + { .expand = 150, .strength =9, .iterations = 4, .offset = 3.71429f }, // 9 + { .expand = 150, .strength =10, .iterations = 4, .offset = 4.42857f }, // 10 + { .expand = 150, .strength =11, .iterations = 4, .offset = 5.14286f }, // 11 + { .expand = 150, .strength =12, .iterations = 4, .offset = 5.85714f }, // 12 + { .expand = 150, .strength =13, .iterations = 4, .offset = 6.57143f }, // 13 + { .expand = 150, .strength =14, .iterations = 4, .offset = 7.28571f }, // 14 + { .expand = 150, .strength =15, .iterations = 4, .offset = 8.0 }, // 15 + { .expand = 400, .strength =16, .iterations = 5, .offset = 6.0 }, // 16 + { .expand = 400, .strength =17, .iterations = 5, .offset = 7.0 }, // 17 + { .expand = 400, .strength =18, .iterations = 5, .offset = 8.0 }, // 18 + { .expand = 400, .strength =19, .iterations = 5, .offset = 9.0 }, // 19 + { .expand = 400, .strength =20, .iterations = 5, .offset = 10.0 }, // 20 + }; + + if (level < 1 || level > 20) { + log_error("(\"%d\"): Invalid blur_strength argument. Needs to be a number between 1 and 20. Will default to 5", level); + return values[5]; + } + + log_info("blur-strength: %d [.iter = %d, .offset = %f, .expand = %d]", + level, values[level - 1].iterations, values[level - 1].offset, values[level - 1].expand); + + return values[level - 1];; +} diff --git a/src/config_libconfig.c b/src/config_libconfig.c index b07155c56f..c4658c67f8 100644 --- a/src/config_libconfig.c +++ b/src/config_libconfig.c @@ -250,6 +250,31 @@ parse_cfg_condlst_opct(options_t *opt, const config_t *pcfg, const char *name) { } } +/** + * Parse an opacity rule list in configuration file. + */ +static inline void +parse_cfg_condlst_border(options_t *opt, const config_t *pcfg, const char *name) { + config_setting_t *setting = config_lookup(pcfg, name); + if (setting) { + // Parse an array of options + if (config_setting_is_array(setting)) { + int i = config_setting_length(setting); + while (i--) + if (!parse_rule_border( + &opt->round_borders_rules, + config_setting_get_string_elem(setting, i))) + exit(1); + } + // Treat it as a single pattern if it's a string + else if (config_setting_type(setting) == CONFIG_TYPE_STRING) { + if (!parse_rule_border(&opt->round_borders_rules, + config_setting_get_string(setting))) + exit(1); + } + } +} + static inline void parse_wintype_config(const config_t *cfg, const char *member_name, win_option_t *o, win_option_mask_t *mask) { char *str = mstrjoin("wintypes.", member_name); @@ -284,6 +309,18 @@ static inline void parse_wintype_config(const config_t *cfg, const char *member_ o->opacity = normalize_d(fval); mask->opacity = true; } + + if (config_setting_lookup_int(setting, "corner-radius", &ival)) { + o->corner_radius = ival; + mask->corner_radius = true; + //log_warn("%s: corner-radius: %d", member_name, ival); + } + if (config_setting_lookup_int(setting, "round-borders", &ival)) { + o->round_borders = ival; + mask->round_borders = true; + //log_warn("%s: round_borders: %d", member_name, ival); + } + } } @@ -367,6 +404,16 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad // --active_opacity if (config_lookup_float(&cfg, "active-opacity", &dval)) opt->active_opacity = normalize_d(dval); + // --corner-radius + config_lookup_int(&cfg, "corner-radius", &opt->corner_radius); + // --rounded-corners-exclude + parse_cfg_condlst(&cfg, &opt->rounded_corners_blacklist, "rounded-corners-exclude"); + // --round-borders + config_lookup_int(&cfg, "round-borders", &opt->round_borders); + // --round-borders-exclude + parse_cfg_condlst(&cfg, &opt->round_borders_blacklist, "round-borders-exclude"); + // --round-borders-rules + parse_cfg_condlst_border(opt, &cfg, "round-borders-rule"); // -e (frame_opacity) config_lookup_float(&cfg, "frame-opacity", &opt->frame_opacity); // -c (shadow_enable) @@ -446,6 +493,7 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad } lcfg_lookup_bool(&cfg, "vsync", &opt->vsync); // --backend + lcfg_lookup_bool(&cfg, "experimental-backends", &opt->experimental_backends); if (config_lookup_string(&cfg, "backend", &sval)) { opt->backend = parse_backend(sval); if (opt->backend >= NUM_BKEND) { @@ -522,6 +570,10 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad config_lookup_int(&cfg, "blur-size", &opt->blur_radius); // --blur-deviation config_lookup_float(&cfg, "blur-deviation", &opt->blur_deviation); + // --blur-strength + if (config_lookup_int(&cfg, "blur-strength", &ival) && ival) { + opt->blur_strength = parse_kawase_blur_strength(ival); + } // --blur-background if (config_lookup_bool(&cfg, "blur-background", &ival) && ival) { if (opt->blur_method == BLUR_METHOD_NONE) { @@ -635,6 +687,10 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad } config_setting_lookup_float(blur_cfg, "deviation", &opt->blur_deviation); + + if (config_setting_lookup_int(blur_cfg, "strength", &ival) && ival) { + opt->blur_strength = parse_kawase_blur_strength(ival); + } } // Wintype settings diff --git a/src/dbus.c b/src/dbus.c index 229504512d..8a7ab160a2 100644 --- a/src/dbus.c +++ b/src/dbus.c @@ -1039,16 +1039,13 @@ static bool cdbus_process_opts_get(session_t *ps, DBusMessage *msg) { cdbus_m_opts_get_do(use_ewmh_active_win, cdbus_reply_bool); cdbus_m_opts_get_do(detect_transient, cdbus_reply_bool); cdbus_m_opts_get_do(detect_client_leader, cdbus_reply_bool); + cdbus_m_opts_get_do(use_damage, cdbus_reply_bool); #ifdef CONFIG_OPENGL cdbus_m_opts_get_do(glx_no_stencil, cdbus_reply_bool); cdbus_m_opts_get_do(glx_no_rebind_pixmap, cdbus_reply_bool); - cdbus_m_opts_get_do(use_damage, cdbus_reply_bool); #endif - cdbus_m_opts_get_stub(track_focus, cdbus_reply_bool, true); - cdbus_m_opts_get_do(track_wdata, cdbus_reply_bool); - cdbus_m_opts_get_do(track_leader, cdbus_reply_bool); #undef cdbus_m_opts_get_do #undef cdbus_m_opts_get_stub diff --git a/src/event.c b/src/event.c index 3a3369d5b9..4a0333848b 100644 --- a/src/event.c +++ b/src/event.c @@ -495,8 +495,7 @@ static inline void ev_property_notify(session_t *ps, xcb_property_notify_event_t } // If name changes - if (ps->o.track_wdata && - (ps->atoms->aWM_NAME == ev->atom || ps->atoms->a_NET_WM_NAME == ev->atom)) { + if (ps->atoms->aWM_NAME == ev->atom || ps->atoms->a_NET_WM_NAME == ev->atom) { auto w = find_toplevel(ps, ev->window); if (w && win_update_name(ps, w) == 1) { win_on_factor_change(ps, w); @@ -504,7 +503,7 @@ static inline void ev_property_notify(session_t *ps, xcb_property_notify_event_t } // If class changes - if (ps->o.track_wdata && ps->atoms->aWM_CLASS == ev->atom) { + if (ps->atoms->aWM_CLASS == ev->atom) { auto w = find_toplevel(ps, ev->window); if (w) { win_get_class(ps, w); @@ -513,7 +512,7 @@ static inline void ev_property_notify(session_t *ps, xcb_property_notify_event_t } // If role changes - if (ps->o.track_wdata && ps->atoms->aWM_WINDOW_ROLE == ev->atom) { + if (ps->atoms->aWM_WINDOW_ROLE == ev->atom) { auto w = find_toplevel(ps, ev->window); if (w && 1 == win_get_role(ps, w)) { win_on_factor_change(ps, w); @@ -618,14 +617,14 @@ static inline void ev_shape_notify(session_t *ps, xcb_shape_notify_event_t *ev) * if we attempt to rebuild border_size */ // Mark the old border_size as damaged - region_t tmp = win_get_bounding_shape_global_by_val(w); + region_t tmp = win_get_bounding_shape_global_by_val(w, true); add_damage(ps, &tmp); pixman_region32_fini(&tmp); win_update_bounding_shape(ps, w); // Mark the new border_size as damaged - tmp = win_get_bounding_shape_global_by_val(w); + tmp = win_get_bounding_shape_global_by_val(w, true); add_damage(ps, &tmp); pixman_region32_fini(&tmp); diff --git a/src/opengl.c b/src/opengl.c index 0a80c6d376..39b6fb98e6 100644 --- a/src/opengl.c +++ b/src/opengl.c @@ -95,9 +95,31 @@ bool glx_init(session_t *ps, bool need_render) { for (int i = 0; i < ps->o.blur_kernel_count; ++i) { glx_blur_pass_t *ppass = &ps->psglx->blur_passes[i]; - ppass->unifm_factor_center = -1; ppass->unifm_offset_x = -1; ppass->unifm_offset_y = -1; + ppass->unifm_opacity = -1; + ppass->unifm_offset = -1; + ppass->unifm_halfpixel = -1; + ppass->unifm_fulltex = -1; + } + + glx_blur_cache_t *pbc = &ps->psglx->blur_cache; + for (int i = 0; i < MAX_BLUR_PASS; ++i) { + pbc->fbos[i] = 0; + pbc->textures[i] = 0; + } + + ps->psglx->round_passes = ccalloc(2, glx_round_pass_t); + for (int i = 0; i < 2; ++i) { + glx_round_pass_t *ppass = &ps->psglx->round_passes[i]; + ppass->unifm_radius = -1; + ppass->unifm_texcoord = -1; + ppass->unifm_texsize = -1; + ppass->unifm_borderw = -1; + ppass->unifm_borderc = -1; + ppass->unifm_resolution = -1; + ppass->unifm_tex_scr = -1; + ppass->unifm_tex_wnd = -1; } } @@ -243,6 +265,18 @@ void glx_destroy(session_t *ps) { } free(ps->psglx->blur_passes); + glx_blur_cache_t *pbc = &ps->psglx->blur_cache; + if (pbc) free_glx_bc(ps, pbc); + + for (int i = 0; i < 2; ++i) { + glx_round_pass_t *ppass = &ps->psglx->round_passes[i]; + if (ppass->frag_shader) + glDeleteShader(ppass->frag_shader); + if (ppass->prog) + glDeleteProgram(ppass->prog); + } + free(ps->psglx->round_passes); + glx_free_prog_main(&ps->glx_prog_win); gl_check_err(); @@ -271,10 +305,13 @@ void glx_on_root_change(session_t *ps) { glLoadIdentity(); } + +static inline GLuint glx_gen_texture(GLenum tex_tgt, int width, int height); + /** * Initialize GLX blur filter. */ -bool glx_init_blur(session_t *ps) { +bool glx_init_conv_blur(session_t *ps) { assert(ps->o.blur_kernel_count > 0); assert(ps->o.blur_kerns); assert(ps->o.blur_kerns[0]); @@ -304,7 +341,7 @@ bool glx_init_blur(session_t *ps) { "%s" "uniform float offset_x;\n" "uniform float offset_y;\n" - "uniform float factor_center;\n" + "uniform float opacity;\n" "uniform %s tex_scr;\n" "\n" "void main() {\n" @@ -313,9 +350,9 @@ bool glx_init_blur(session_t *ps) { " sum += float(%.7g) * %s(tex_scr, vec2(gl_TexCoord[0].x + offset_x " "* float(%d), gl_TexCoord[0].y + offset_y * float(%d)));\n"; static const char *FRAG_SHADER_BLUR_SUFFIX = - " sum += %s(tex_scr, vec2(gl_TexCoord[0].x, gl_TexCoord[0].y)) * " - "factor_center;\n" - " gl_FragColor = sum / (factor_center + float(%.7g));\n" + " sum += %s(tex_scr, vec2(gl_TexCoord[0].x, gl_TexCoord[0].y));\n" + " gl_FragColor = sum / (float(%.7g));\n" + " gl_FragColor.a = opacity;\n" "}\n"; const bool use_texture_rect = !ps->psglx->has_texture_non_power_of_two; @@ -399,7 +436,7 @@ bool glx_init_blur(session_t *ps) { } \ } - P_GET_UNIFM_LOC("factor_center", unifm_factor_center); + P_GET_UNIFM_LOC("opacity", unifm_opacity); P_GET_UNIFM_LOC("offset_x", unifm_offset_x); P_GET_UNIFM_LOC("offset_y", unifm_offset_y); @@ -417,6 +454,678 @@ bool glx_init_blur(session_t *ps) { return true; } +bool glx_init_kawase_blur(session_t *ps) { + assert(ps->o.blur_kernel_count > 0); + + // Allocate PBO if more than one blur kernel is present + if (ps->o.blur_kernel_count > 1) { + // Try to generate a framebuffer + GLuint fbo = 0; + glGenFramebuffers(1, &fbo); + if (!fbo) { + log_error("Failed to generate Framebuffer. Cannot do multi-pass " + "blur with GLX" + " backend."); + return false; + } + glDeleteFramebuffers(1, &fbo); + } + + { + char *lc_numeric_old = strdup(setlocale(LC_NUMERIC, NULL)); + // Enforce LC_NUMERIC locale "C" here to make sure decimal point is sane + // Thanks to hiciu for reporting. + setlocale(LC_NUMERIC, "C"); + + static const char *FRAG_SHADER_PREFIX = + "#version 110\n" + "%s" // extensions + "uniform float offset;\n" + "uniform vec2 halfpixel;\n" + "uniform vec2 fulltex;\n" + "uniform %s tex_scr;\n" // sampler2D | sampler2DRect + "vec4 clamp_tex(vec2 uv)\n" + "{\n" + " return %s(tex_scr, clamp(uv, vec2(0), fulltex));\n" // texture2D | texture2DRect + "}\n" + "\n" + "void main()\n" + "{\n" + " vec2 uv = (gl_TexCoord[0].xy / fulltex);\n" + "\n"; + + // Fragment shader (Dual Kawase Blur) - Downsample + static const char *FRAG_SHADER_KAWASE_DOWN = + " vec4 sum = clamp_tex(uv) * 4.0;\n" + " sum += clamp_tex(uv - halfpixel.xy * offset);\n" + " sum += clamp_tex(uv + halfpixel.xy * offset);\n" + " sum += clamp_tex(uv + vec2(halfpixel.x, -halfpixel.y) * offset);\n" + " sum += clamp_tex(uv - vec2(halfpixel.x, -halfpixel.y) * offset);\n" + "\n" + " gl_FragColor = sum / 8.0;\n" + "}\n"; + + // Fragment shader (Dual Kawase Blur) - Upsample + static const char *FRAG_SHADER_KAWASE_UP = + " vec4 sum = clamp_tex(uv + vec2(-halfpixel.x * 2.0, 0.0) * offset);\n" + " sum += clamp_tex(uv + vec2(-halfpixel.x, halfpixel.y) * offset) * 2.0;\n" + " sum += clamp_tex(uv + vec2(0.0, halfpixel.y * 2.0) * offset);\n" + " sum += clamp_tex(uv + vec2(halfpixel.x, halfpixel.y) * offset) * 2.0;\n" + " sum += clamp_tex(uv + vec2(halfpixel.x * 2.0, 0.0) * offset);\n" + " sum += clamp_tex(uv + vec2(halfpixel.x, -halfpixel.y) * offset) * 2.0;\n" + " sum += clamp_tex(uv + vec2(0.0, -halfpixel.y * 2.0) * offset);\n" + " sum += clamp_tex(uv + vec2(-halfpixel.x, -halfpixel.y) * offset) * 2.0;\n" + "\n" + " gl_FragColor = sum / 12.0;\n" + "}\n"; + + const bool use_texture_rect = !ps->psglx->has_texture_non_power_of_two; + const char *sampler_type = (use_texture_rect ? "sampler2DRect": "sampler2D"); + const char *texture_func = (use_texture_rect ? "texture2DRect": "texture2D"); + char *extension = NULL; + if (use_texture_rect) { + mstrextend(&extension, "#extension GL_ARB_texture_rectangle : " + "require\n"); + } + if (!extension) { + extension = strdup(""); + } + + // Build kawase downsample shader + glx_blur_pass_t *down_pass = &ps->psglx->blur_passes[0]; + { + size_t len = strlen(FRAG_SHADER_PREFIX) + strlen(extension) + strlen(sampler_type) + strlen(texture_func) + strlen(FRAG_SHADER_KAWASE_DOWN) + 1; + char *shader_str = calloc(len, sizeof(char)); + if (!shader_str) { + log_error("Failed to allocate %zd bytes for shader string.", len); + return false; + } + + char *pc = shader_str; + sprintf(pc, FRAG_SHADER_PREFIX, extension, sampler_type, texture_func); + pc += strlen(pc); + assert(strlen(shader_str) < len); + + sprintf(pc, "%s", FRAG_SHADER_KAWASE_DOWN); + assert(strlen(shader_str) < len); +#ifdef DEBUG_GLX + log_debug("Generated kawase downsample shader:\n%s\n", shader_str); +#endif + down_pass->frag_shader = gl_create_shader(GL_FRAGMENT_SHADER, shader_str); + free(shader_str); + + if (!down_pass->frag_shader) { + log_error("Failed to create kawase downsample fragment shader."); + free(extension); + free(lc_numeric_old); + return false; + } + + // Build program + down_pass->prog = gl_create_program(&down_pass->frag_shader, 1); + if (!down_pass->prog) { + log_error("Failed to create GLSL program."); + free(extension); + free(lc_numeric_old); + return false; + } + + // Get uniform addresses +#define P_GET_UNIFM_LOC(name, target) \ + { \ + down_pass->target = glGetUniformLocation(down_pass->prog, name); \ + if (down_pass->target < 0) { \ + log_error("Failed to get location of kawase downsample uniform '" name "'. Might be troublesome."); \ + } \ + } + P_GET_UNIFM_LOC("offset", unifm_offset); + P_GET_UNIFM_LOC("halfpixel", unifm_halfpixel); + P_GET_UNIFM_LOC("fulltex", unifm_fulltex); +#undef P_GET_UNIFM_LOC + } + + // Build kawase downsample shader + glx_blur_pass_t *up_pass = &ps->psglx->blur_passes[1]; + { + size_t len = strlen(FRAG_SHADER_PREFIX) + strlen(extension) + strlen(sampler_type) + strlen(texture_func) + strlen(FRAG_SHADER_KAWASE_UP) + 1; + char *shader_str = calloc(len, sizeof(char)); + if (!shader_str) { + log_error("Failed to allocate %zd bytes for shader string.", len); + return false; + } + + char *pc = shader_str; + sprintf(pc, FRAG_SHADER_PREFIX, extension, sampler_type, texture_func); + pc += strlen(pc); + assert(strlen(shader_str) < len); + + sprintf(pc, "%s", FRAG_SHADER_KAWASE_UP); + assert(strlen(shader_str) < len); +#ifdef DEBUG_GLX + log_debug("Generated kawase upsample shader:\n%s\n", shader_str); +#endif + up_pass->frag_shader = gl_create_shader(GL_FRAGMENT_SHADER, shader_str); + free(shader_str); + + if (!up_pass->frag_shader) { + log_error("Failed to create kawase upsample fragment shader."); + return false; + } + + // Build program + up_pass->prog = gl_create_program(&up_pass->frag_shader, 1); + if (!up_pass->prog) { + log_error("Failed to create GLSL program."); + return false; + } + + // Get uniform addresses +#define P_GET_UNIFM_LOC(name, target) \ + { \ + up_pass->target = glGetUniformLocation(up_pass->prog, name); \ + if (up_pass->target < 0) { \ + log_error("Failed to get location of kawase upsample uniform '" name "'. Might be troublesome."); \ + } \ + } + P_GET_UNIFM_LOC("offset", unifm_offset); + P_GET_UNIFM_LOC("halfpixel", unifm_halfpixel); + P_GET_UNIFM_LOC("fulltex", unifm_fulltex); +#undef P_GET_UNIFM_LOC + } + + free(extension); + + // Restore LC_NUMERIC + setlocale(LC_NUMERIC, lc_numeric_old); + free(lc_numeric_old); + } + + gl_check_err(); + + return true; +} + +/** + * Initialize GLX blur filter for the dual-filter kawase blur. + */ + +bool +glx_init_dualkawase_blur(session_t *ps) { + assert(ps->o.blur_strength.iterations); + int iterations = ps->o.blur_strength.iterations; + assert(iterations < MAX_BLUR_PASS); + + // Allocate PBO if more than one blur kernel is present + /*if (ps->o.blur_kernel_count > 1) { + // Try to generate a framebuffer + GLuint fbo = 0; + glGenFramebuffers(1, &fbo); + if (!fbo) { + log_error("Failed to generate Framebuffer. Cannot do multi-pass " + "blur with GLX" + " backend."); + return false; + } + glDeleteFramebuffers(1, &fbo); + }*/ + + // Allocate required FBOs for dual-filter support + { + glx_blur_cache_t *pbc = &ps->psglx->blur_cache; + glGenFramebuffers(iterations, pbc->fbos); + if (!pbc->fbos[0]) { + log_error("Failed to generate Framebuffer. Cannot do " + "multi-pass blur with GLX backend."); + return false; + } + } + + // Allocate textures if needed and bind to the respective framebuffer + { + GLenum tex_tgt = GL_TEXTURE_RECTANGLE; + if (ps->psglx->has_texture_non_power_of_two) + tex_tgt = GL_TEXTURE_2D; + + // Allocate scaled texture + glx_blur_cache_t *pbc = &ps->psglx->blur_cache; + + int tex_width; + int tex_height; + for (int i = 0; i <= iterations; ++i) { + if (!pbc->textures[i]) { + tex_width = ps->root_width / (1 << (i)); + tex_height = ps->root_height / (1 << (i)); + pbc->textures[i] = glx_gen_texture(tex_tgt, tex_width, tex_height); + pbc->width[i] = tex_width; + pbc->height[i] = tex_height; + } + if (!pbc->textures[i]) { + log_error("Failed to allocate texture."); + return false; + } + + // Bind texture to framebuffer + if ((i > 0) && pbc->fbos[i-1]) { + static const GLenum DRAWBUFS[2] = { GL_COLOR_ATTACHMENT0 }; + glBindFramebuffer(GL_FRAMEBUFFER, pbc->fbos[i-1]); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, pbc->textures[i], 0); + glDrawBuffers(1, DRAWBUFS); + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) + != GL_FRAMEBUFFER_COMPLETE) { + log_error("Framebuffer attachment failed."); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + return false; + } + } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + } + } + + // Compile blur shader + { + char *lc_numeric_old = strdup(setlocale(LC_NUMERIC, NULL)); + // Enforce LC_NUMERIC locale "C" here to make sure decimal point is sane + // Thanks to hiciu for reporting. + setlocale(LC_NUMERIC, "C"); + + static const char *FRAG_SHADER_PREFIX = + "#version 110\n" + "%s" // extensions + "uniform float offset;\n" + "uniform float opacity;\n" + "uniform vec2 halfpixel;\n" + "uniform vec2 fulltex;\n" + "uniform %s tex_scr;\n" // sampler2D | sampler2DRect + "\n" + "vec4 clamp_tex(vec2 uv)\n" + "{\n" + " return %s(tex_scr, clamp(uv, vec2(0), fulltex));\n" // texture2D | texture2DRect + "}\n" + "\n" + "void main()\n" + "{\n" + " vec2 uv = (gl_FragCoord.xy / fulltex);\n" + "\n"; + + // Fragment shader (Dual Kawase Blur) - Downsample + static const char *FRAG_SHADER_KAWASE_DOWN = + " vec4 sum = clamp_tex(uv) * 4.0;\n" + " sum += clamp_tex(uv - halfpixel.xy * offset);\n" + " sum += clamp_tex(uv + halfpixel.xy * offset);\n" + " sum += clamp_tex(uv + vec2(halfpixel.x, -halfpixel.y) * offset);\n" + " sum += clamp_tex(uv - vec2(halfpixel.x, -halfpixel.y) * offset);\n" + "\n" + " gl_FragColor = sum / 8.0;\n" + "}\n"; + + // Fragment shader (Dual Kawase Blur) - Upsample + static const char *FRAG_SHADER_KAWASE_UP = + " vec4 sum = clamp_tex(uv + vec2(-halfpixel.x * 2.0, 0.0) * offset);\n" + " sum += clamp_tex(uv + vec2(-halfpixel.x, halfpixel.y) * offset) * 2.0;\n" + " sum += clamp_tex(uv + vec2(0.0, halfpixel.y * 2.0) * offset);\n" + " sum += clamp_tex(uv + vec2(halfpixel.x, halfpixel.y) * offset) * 2.0;\n" + " sum += clamp_tex(uv + vec2(halfpixel.x * 2.0, 0.0) * offset);\n" + " sum += clamp_tex(uv + vec2(halfpixel.x, -halfpixel.y) * offset) * 2.0;\n" + " sum += clamp_tex(uv + vec2(0.0, -halfpixel.y * 2.0) * offset);\n" + " sum += clamp_tex(uv + vec2(-halfpixel.x, -halfpixel.y) * offset) * 2.0;\n" + "\n" + " gl_FragColor = sum / 12.0;\n" + " gl_FragColor.a = opacity;\n" + "}\n"; + + const bool use_texture_rect = !ps->psglx->has_texture_non_power_of_two; + const char *sampler_type = (use_texture_rect ? "sampler2DRect": "sampler2D"); + const char *texture_func = (use_texture_rect ? "texture2DRect": "texture2D"); + char *extension = NULL; + if (use_texture_rect) { + mstrextend(&extension, "#extension GL_ARB_texture_rectangle : " + "require\n"); + } + if (!extension) { + extension = strdup(""); + } + + // Build kawase downsample shader + glx_blur_pass_t *down_pass = &ps->psglx->blur_passes[0]; + { + size_t len = strlen(FRAG_SHADER_PREFIX) + strlen(extension) + strlen(sampler_type) + strlen(texture_func) + strlen(FRAG_SHADER_KAWASE_DOWN) + 1; + char *shader_str = calloc(len, sizeof(char)); + if (!shader_str) { + log_error("Failed to allocate %zd bytes for shader string.", len); + return false; + } + + char *pc = shader_str; + sprintf(pc, FRAG_SHADER_PREFIX, extension, sampler_type, texture_func); + pc += strlen(pc); + assert(strlen(shader_str) < len); + + sprintf(pc, "%s", FRAG_SHADER_KAWASE_DOWN); + assert(strlen(shader_str) < len); + down_pass->frag_shader = gl_create_shader(GL_FRAGMENT_SHADER, shader_str); + free(shader_str); + + if (!down_pass->frag_shader) { + log_error("Failed to create dual_kawase downsample fragment shader."); + return false; + } + + // Build program + down_pass->prog = gl_create_program(&down_pass->frag_shader, 1); + if (!down_pass->prog) { + log_error("Failed to create GLSL program."); + return false; + } + + // Get uniform addresses +#define P_GET_UNIFM_LOC(name, target) { \ + down_pass->target = glGetUniformLocation(down_pass->prog, name); \ + if (down_pass->target < 0) { \ + log_error("Failed to get location of dual_kawase downsample uniform '" name "'. Might be troublesome."); \ + } \ + } + P_GET_UNIFM_LOC("offset", unifm_offset); + P_GET_UNIFM_LOC("halfpixel", unifm_halfpixel); + P_GET_UNIFM_LOC("fulltex", unifm_fulltex); +#undef P_GET_UNIFM_LOC + } + + // Build kawase upsample shader + glx_blur_pass_t *up_pass = &ps->psglx->blur_passes[1]; + { + size_t len = strlen(FRAG_SHADER_PREFIX) + strlen(extension) + strlen(sampler_type) + strlen(texture_func) + strlen(FRAG_SHADER_KAWASE_UP) + 1; + char *shader_str = calloc(len, sizeof(char)); + if (!shader_str) { + log_error("Failed to allocate %zd bytes for shader string.", len); + return false; + } + + char *pc = shader_str; + sprintf(pc, FRAG_SHADER_PREFIX, extension, sampler_type, texture_func); + pc += strlen(pc); + assert(strlen(shader_str) < len); + + sprintf(pc, "%s", FRAG_SHADER_KAWASE_UP); + assert(strlen(shader_str) < len); + up_pass->frag_shader = gl_create_shader(GL_FRAGMENT_SHADER, shader_str); + free(shader_str); + + if (!up_pass->frag_shader) { + log_error("Failed to create dual_kawase upsample fragment shader."); + return false; + } + + // Build program + up_pass->prog = gl_create_program(&up_pass->frag_shader, 1); + if (!up_pass->prog) { + log_error("Failed to create GLSL program."); + return false; + } + + // Get uniform addresses +#define P_GET_UNIFM_LOC(name, target) { \ + up_pass->target = glGetUniformLocation(up_pass->prog, name); \ + if (up_pass->target < 0) { \ + log_error("Failed to get location of dual_kawase upsample uniform '" name "'. Might be troublesome."); \ + } \ + } + P_GET_UNIFM_LOC("offset", unifm_offset); + P_GET_UNIFM_LOC("opacity", unifm_opacity); + P_GET_UNIFM_LOC("halfpixel", unifm_halfpixel); + P_GET_UNIFM_LOC("fulltex", unifm_fulltex); +#undef P_GET_UNIFM_LOC + } + + free(extension); + + // Restore LC_NUMERIC + setlocale(LC_NUMERIC, lc_numeric_old); + free(lc_numeric_old); + } + + gl_check_err(); + + return true; +} + +/** + * Initialize GLX blur filter. + */ +bool glx_init_blur(session_t *ps) { + + switch (ps->o.blur_method) { + case BLUR_METHOD_DUAL_KAWASE: + return glx_init_kawase_blur(ps); + case BLUR_METHOD_ALT_KAWASE: + return glx_init_dualkawase_blur(ps); + case BLUR_METHOD_KERNEL: + case BLUR_METHOD_BOX: + case BLUR_METHOD_GAUSSIAN: + return glx_init_conv_blur(ps); + default: + return false; + } +} + +static inline bool glx_init_frag_shader_corners(glx_round_pass_t *ppass, + const int shader_idx, const char *PREFIX_STR, const char* SHADER_STR, + const char *extension, const char *sampler_type, const char *texture_func) { + + // Build rounded corners shader + { + auto len = strlen(PREFIX_STR) + strlen(extension) + + strlen(sampler_type)*2 + strlen(texture_func)*2 + + strlen(SHADER_STR) + 1; + char *shader_str = calloc(len, sizeof(char)); + if (!shader_str) { + log_error("Failed to allocate %zd bytes for shader string.", len); + return false; + } + + char *pc = shader_str; + sprintf(pc, PREFIX_STR, extension, sampler_type, sampler_type, texture_func, texture_func); + pc += strlen(pc); + assert(strlen(shader_str) < len); + + sprintf(pc, "%s", SHADER_STR); + assert(strlen(shader_str) < len); +#ifdef DEBUG_GLX + log_debug("Generated rounded corners shader %d:\n%s\n", shader_idx, shader_str); +#endif + + //log_info("Generated rounded corners shader %d:\n%s\n", shader_idx, shader_str); + + ppass->frag_shader = gl_create_shader(GL_FRAGMENT_SHADER, shader_str); + free(shader_str); + + if (!ppass->frag_shader) { + log_error("Failed to create rounded corners fragment shader."); + return false; + } + + // Build program + ppass->prog = gl_create_program(&ppass->frag_shader, 1); + if (!ppass->prog) { + log_error("Failed to create GLSL program."); + return false; + } + + // Get uniform addresses +#define P_GET_UNIFM_LOC(name, target) \ +{ \ + ppass->target = glGetUniformLocation(ppass->prog, name); \ + if (ppass->target < 0) { \ + log_error("Failed to get location of rounded corners uniform '" name \ + "'. Might be troublesome. (shader_idx: %d)" \ + , shader_idx); \ + } \ +} + P_GET_UNIFM_LOC("u_radius", unifm_radius); + P_GET_UNIFM_LOC("u_texcoord", unifm_texcoord); + P_GET_UNIFM_LOC("u_texsize", unifm_texsize); + P_GET_UNIFM_LOC("u_borderw", unifm_borderw); + P_GET_UNIFM_LOC("u_borderc", unifm_borderc); + P_GET_UNIFM_LOC("u_resolution", unifm_resolution); + P_GET_UNIFM_LOC("tex_scr", unifm_tex_scr); + // We don't need this one anymore since we get + // the border color using glReadPixel + // uncomment if you need to use 'tex_wnd' in the shader + //P_GET_UNIFM_LOC("tex_wnd", unifm_tex_wnd); +#undef P_GET_UNIFM_LOC + } + + return true; +} + +/** + * Initialize GLX rounded corners filter. + */ +bool glx_init_rounded_corners(session_t *ps) { + + { + char *lc_numeric_old = strdup(setlocale(LC_NUMERIC, NULL)); + // Enforce LC_NUMERIC locale "C" here to make sure decimal point is sane + // Thanks to hiciu for reporting. + setlocale(LC_NUMERIC, "C"); + + static const char *FRAG_SHADER_PREFIX = + "#version 110\n" + "%s" // extensions + "uniform float u_radius;\n" + "uniform float u_borderw;\n" + "uniform vec4 u_borderc;\n" + "uniform vec2 u_texcoord;\n" + "uniform vec2 u_texsize;\n" + "uniform vec2 u_resolution;\n" + "uniform %s tex_scr;\n" // sampler2D | sampler2DRect + "uniform %s tex_wnd;\n" // sampler2D | sampler2DRect + "\n" + "// https://www.shadertoy.com/view/ltS3zW\n" + "float RectSDF(vec2 p, vec2 b, float r) {\n" + " vec2 d = abs(p) - b + vec2(r);\n" + " return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - r;\n" + "}\n\n" + "void main()\n" + "{\n" + " vec2 coord = vec2(u_texcoord.x, u_resolution.y-u_texsize.y-u_texcoord.y);\n" + " // Get the window_bg color (so we can \"erase\" corners) from the bg texture\n" + " // and the border color from the mid x-axis of the target window (hacky...)\n" + " vec4 u_v4WndBgColor = %s(tex_scr, vec2(gl_TexCoord[0].st));\n" + " //vec4 u_v4BorderColor = %s(tex_wnd, vec2(0, u_texsize.t/2.));\n" + " vec4 u_v4BorderColor = u_borderc;\n" + " vec4 u_v4FillColor = vec4(0.0, 0.0, 0.0, 0.0); // Inside rect, transparent\n" + " vec4 v4FromColor = u_v4BorderColor; //Always the border color. If no border, this still should be set\n" + " vec4 v4ToColor = u_v4WndBgColor; //Outside corners color, we set it to background texture\n" + "\n"; + + // Fragment shader (round corners) + // dst0 shader + static const char *FRAG_SHADER_ROUND_CORNERS_0 = + " float u_fRadiusPx = u_radius;\n" + " float u_fHalfBorderThickness = u_borderw / 2.0;\n" + " //v4FromColor = u_v4BorderColor = vec4(1.0, 0.0, 0.0, 1.0);\n" + " //u_v4FillColor = vec4(0.0, 0.0, 0.0, 0.0); // Inside rect color\n" + "\n" + " vec2 u_v2HalfShapeSizePx = u_texsize/2.0 - vec2(u_fHalfBorderThickness);\n" + " vec2 v_v2CenteredPos = (gl_FragCoord.xy - u_texsize.xy / 2.0 - coord);\n" + "\n" + " float fDist = RectSDF(v_v2CenteredPos, u_v2HalfShapeSizePx, u_fRadiusPx - u_fHalfBorderThickness);\n" + " if (u_fHalfBorderThickness > 0.0) {\n" + " if (fDist < 0.0) {\n" + " v4ToColor = u_v4FillColor;\n" + " }\n" + " fDist = abs(fDist) - u_fHalfBorderThickness;\n" + " } else {\n" + " v4FromColor = u_v4FillColor;\n" + " }\n" + " float fBlendAmount = smoothstep(-1.0, 1.0, fDist);\n" + " vec4 c = mix(v4FromColor, v4ToColor, fBlendAmount);" + "\n" + " // final color\n" + " //if ( c == vec4(0.0,0.0,0.0,0.0) ) discard; else\n" + " gl_FragColor = c;\n" + "}\n"; + + // Fragment shader (round corners) + // dst1 shader + static const char *FRAG_SHADER_ROUND_CORNERS_1 = + " float u_fRadiusPx = u_radius;\n" + " float u_fHalfBorderThickness = u_borderw / 2.0;\n" + " //float u_fHalfBorderThickness = 20.0 /2.0;\n" + " //u_v4FillColor = vec4(0.0, 1.0, 0.0, 1.0);\n" + " //v4FromColor = u_v4BorderColor = vec4(1.0, 0.0, 0.0, 1.0);\n" + " //v4ToColor = vec4(0.0, 0.0, 1.0, 1.0); //Outside color\n" + "\n" + " vec2 u_v2HalfShapeSizePx = u_texsize/2.0 - vec2(u_fHalfBorderThickness);\n" + " vec2 v_v2CenteredPos = (gl_FragCoord.xy - u_texsize.xy / 2.0 - coord);\n" + "\n" + " float fDist = RectSDF(v_v2CenteredPos, u_v2HalfShapeSizePx, u_fRadiusPx - u_fHalfBorderThickness);\n" + " if (u_fHalfBorderThickness > 0.0) {\n" + " if (fDist < 0.0) {\n" + " v4ToColor = u_v4FillColor;\n" + " }\n" + " fDist = abs(fDist) - u_fHalfBorderThickness;\n" + " } else {\n" + " v4FromColor = u_v4FillColor;\n" + " }\n" + " float fBlendAmount = smoothstep(-1.0, 1.0, fDist);\n" + " vec4 c = mix(v4FromColor, v4ToColor, fBlendAmount);" + "\n" + " // final color\n" + " //if ( c == vec4(0.0,0.0,0.0,0.0) ) discard; else\n" + " gl_FragColor = c;\n" + " //gl_FragColor = vec4(vec3(fBlendAmount), 1.0);\n" + " //gl_FragColor = vec4(vec3(abs(dist) / (2.0 * corner)), 1.0);\n" + "}\n"; + + const bool use_texture_rect = !ps->psglx->has_texture_non_power_of_two; + const char *sampler_type = (use_texture_rect ? "sampler2DRect": "sampler2D"); + const char *texture_func = (use_texture_rect ? "texture2DRect": "texture2D"); + char *extension = NULL; + if (use_texture_rect) { + mstrextend(&extension, "#extension GL_ARB_texture_rectangle : " + "require\n"); + } + if (!extension) { + extension = strdup(""); + } + + if (!glx_init_frag_shader_corners(&ps->psglx->round_passes[0], 0, + FRAG_SHADER_PREFIX, FRAG_SHADER_ROUND_CORNERS_0, + extension, sampler_type, texture_func)) { + + log_error("Failed to create rounded corners fragment shader PRE."); + setlocale(LC_NUMERIC, lc_numeric_old); + free(lc_numeric_old); + free(extension); + return false; + } + + if (!glx_init_frag_shader_corners(&ps->psglx->round_passes[1], 1, + FRAG_SHADER_PREFIX, FRAG_SHADER_ROUND_CORNERS_1, + extension, sampler_type, texture_func)) { + + log_error("Failed to create rounded corners fragment shader POST."); + setlocale(LC_NUMERIC, lc_numeric_old); + free(lc_numeric_old); + free(extension); + return false; + } + + free(extension); + + // Restore LC_NUMERIC + setlocale(LC_NUMERIC, lc_numeric_old); + free(lc_numeric_old); + } + + gl_check_err(); + + return true; +} + /** * Load a GLSL main program from shader strings. */ @@ -451,26 +1160,40 @@ bool glx_load_prog_main(const char *vshader_str, const char *fshader_str, return true; } +/** + * @brief Release binding of a texture. + */ +void glx_release_texture(session_t *ps attr_unused, glx_texture_t **pptex) { + glx_texture_t *ptex = *pptex; + // Release binding + if (ptex->texture) { + //log_info("Deleting texture wh(%d %d)", ptex->width, ptex->height); + glBindTexture(ptex->target, 0); + glDeleteTextures(1, &ptex->texture); + } + free(ptex); + *pptex = NULL; + + gl_check_err(); +} + /** * Bind a X pixmap to an OpenGL texture. */ -bool glx_bind_pixmap(session_t *ps, glx_texture_t **pptex, xcb_pixmap_t pixmap, int width, - int height, bool repeat, const struct glx_fbconfig_info *fbcfg) { +bool glx_bind_texture(session_t *ps attr_unused, glx_texture_t **pptex, + int x, int y, int width attr_unused, int height attr_unused, bool repeat attr_unused) { if (ps->o.backend != BKEND_GLX && ps->o.backend != BKEND_XR_GLX_HYBRID) return true; - if (!pixmap) { - log_error("Binding to an empty pixmap %#010x. This can't work.", pixmap); - return false; - } - - assert(fbcfg); glx_texture_t *ptex = *pptex; - bool need_release = true; - // Release pixmap if parameters are inconsistent - if (ptex && ptex->texture && ptex->pixmap != pixmap) { - glx_release_pixmap(ps, ptex); + //log_trace("Copying xy(%d %d) wh(%d %d) ptex(%p)", x, y, width, height, ptex); + + // Release texture if parameters are inconsistent + if (ptex && ptex->texture && + (ptex->width != width || ptex->height != height)) { + //log_info("Windows size changed old_wh(%d %d) new_wh(%d %d)", ptex->width, ptex->height, width, height); + glx_release_texture(ps, &ptex); } // Allocate structure @@ -488,14 +1211,108 @@ bool glx_bind_pixmap(session_t *ps, glx_texture_t **pptex, xcb_pixmap_t pixmap, ptex = cmalloc(glx_texture_t); memcpy(ptex, &GLX_TEX_DEF, sizeof(glx_texture_t)); *pptex = ptex; - } - // Create GLX pixmap - int depth = 0; - if (!ptex->glpixmap) { - need_release = false; + ptex->width = width; + ptex->height = height; + ptex->target = GL_TEXTURE_RECTANGLE; + if (ps->psglx->has_texture_non_power_of_two) + ptex->target = GL_TEXTURE_2D; + } - // Retrieve pixmap parameters, if they aren't provided + // Create texture + if (!ptex->texture) { + //log_info("Generating texture for xy(%d %d) wh(%d %d)", x, y, width, height); + GLuint texture = 0; + glGenTextures(1, &texture); + + if (texture) { + glEnable(ptex->target); + glBindTexture(ptex->target, texture); + + glTexParameteri(ptex->target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(ptex->target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + if (repeat) { + glTexParameteri(ptex->target, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(ptex->target, GL_TEXTURE_WRAP_T, GL_REPEAT); + } else { + glTexParameteri(ptex->target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(ptex->target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + } + + glTexImage2D(ptex->target, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); + + glBindTexture(ptex->target, 0); + //glDisable(ptex->target); + } + + ptex->texture = texture; + } + if (!ptex->texture) { + log_error("Failed to allocate texture."); + return false; + } + + // Read destination pixels into a texture + glEnable(ptex->target); + glBindTexture(ptex->target, ptex->texture); + if (width > 0 && height > 0) + glCopyTexSubImage2D(ptex->target, 0, 0, 0, x, ps->root_height - y - height, width, height); + + // Cleanup + glBindTexture(ptex->target, 0); + glDisable(ptex->target); + + gl_check_err(); + + return true; +} + + +/** + * Bind a X pixmap to an OpenGL texture. + */ +bool glx_bind_pixmap(session_t *ps, glx_texture_t **pptex, xcb_pixmap_t pixmap, int width, + int height, bool repeat, const struct glx_fbconfig_info *fbcfg) { + if (ps->o.backend != BKEND_GLX && ps->o.backend != BKEND_XR_GLX_HYBRID) + return true; + + if (!pixmap) { + log_error("Binding to an empty pixmap %#010x. This can't work.", pixmap); + return false; + } + + assert(fbcfg); + glx_texture_t *ptex = *pptex; + bool need_release = true; + + // Release pixmap if parameters are inconsistent + if (ptex && ptex->texture && ptex->pixmap != pixmap) { + glx_release_pixmap(ps, ptex); + } + + // Allocate structure + if (!ptex) { + static const glx_texture_t GLX_TEX_DEF = { + .texture = 0, + .glpixmap = 0, + .pixmap = 0, + .target = 0, + .width = 0, + .height = 0, + .y_inverted = false, + }; + + ptex = cmalloc(glx_texture_t); + memcpy(ptex, &GLX_TEX_DEF, sizeof(glx_texture_t)); + *pptex = ptex; + } + + // Create GLX pixmap + int depth = 0; + if (!ptex->glpixmap) { + need_release = false; + + // Retrieve pixmap parameters, if they aren't provided if (!width || !height) { auto r = xcb_get_geometry_reply( ps->c, xcb_get_geometry(ps->c, pixmap), NULL); @@ -688,13 +1505,29 @@ static inline void glx_copy_region_to_tex(session_t *ps, GLenum tex_tgt, int bas ps->root_height - dy - height, width, height); } +static inline void +glx_copy_region_to_tex_new(session_t *ps, GLenum tex_tgt, int basex, int basey, int width, int height) { + if (width > 0 && height > 0) { + int dx = (basex < 0) ? 0 : basex; + basey = ps->root_height - (basey + height); + int dy = (basey < 0) ? 0 : basey; + + width += basex; + width = (ps->root_width < width) ? ps->root_width - dx : width - dx; + height += basey; + height = (ps->root_height < height) ? ps->root_height - dy : height - dy; + + glCopyTexSubImage2D(tex_tgt, 0, (basex < 0) ? 0 : dx, dy, dx, dy, width, height); + } +} + /** * Blur contents in a particular region. * * XXX seems to be way to complex for what it does */ -bool glx_blur_dst(session_t *ps, int dx, int dy, int width, int height, float z, - GLfloat factor_center, const region_t *reg_tgt, glx_blur_cache_t *pbc) { +bool glx_conv_blur_dst(session_t *ps, int dx, int dy, int width, int height, float z, + double opacity, const region_t *reg_tgt, glx_blur_cache_t *pbc) { assert(ps->psglx->blur_passes[0].prog); const bool more_passes = ps->o.blur_kernel_count > 1; const bool have_scissors = glIsEnabled(GL_SCISSOR_TEST); @@ -735,7 +1568,7 @@ bool glx_blur_dst(session_t *ps, int dx, int dy, int width, int height, float z, tex_tgt = GL_TEXTURE_2D; // Free textures if size inconsistency discovered - if (mwidth != pbc->width || mheight != pbc->height) + if (mwidth != pbc->width[0] || mheight != pbc->height[0]) free_glx_bc_resize(ps, pbc); // Generate FBO and textures if needed @@ -744,26 +1577,27 @@ bool glx_blur_dst(session_t *ps, int dx, int dy, int width, int height, float z, GLuint tex_scr = pbc->textures[0]; if (more_passes && !pbc->textures[1]) pbc->textures[1] = glx_gen_texture(tex_tgt, mwidth, mheight); - pbc->width = mwidth; - pbc->height = mheight; + pbc->width[0] = mwidth; + pbc->height[0] = mheight; GLuint tex_scr2 = pbc->textures[1]; - if (more_passes && !pbc->fbo) - glGenFramebuffers(1, &pbc->fbo); - const GLuint fbo = pbc->fbo; + if (more_passes && !pbc->fbos[0]) + glGenFramebuffers(1, &pbc->fbos[0]); + const GLuint fbo = pbc->fbos[0]; if (!tex_scr || (more_passes && !tex_scr2)) { log_error("Failed to allocate texture."); - goto glx_blur_dst_end; + goto glx_conv_blur_dst_end; } if (more_passes && !fbo) { log_error("Failed to allocate framebuffer."); - goto glx_blur_dst_end; + goto glx_conv_blur_dst_end; } // Read destination pixels into a texture glEnable(tex_tgt); glBindTexture(tex_tgt, tex_scr); glx_copy_region_to_tex(ps, tex_tgt, mdx, mdy, mdx, mdy, mwidth, mheight); + //glx_copy_region_to_tex_new(ps, tex_tgt, mdx, mdy, mwidth, mheight); /* if (tex_scr2) { glBindTexture(tex_tgt, tex_scr2); @@ -804,7 +1638,7 @@ bool glx_blur_dst(session_t *ps, int dx, int dy, int width, int height, float z, glDrawBuffer(GL_COLOR_ATTACHMENT0); if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { log_error("Framebuffer attachment failed."); - goto glx_blur_dst_end; + goto glx_conv_blur_dst_end; } } else { glBindFramebuffer(GL_FRAMEBUFFER, 0); @@ -813,6 +1647,11 @@ bool glx_blur_dst(session_t *ps, int dx, int dy, int width, int height, float z, glEnable(GL_SCISSOR_TEST); if (have_stencil) glEnable(GL_STENCIL_TEST); + + if (opacity < 1.0) { // Blend blur texture to fade in and out with window opacity + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } } // Color negation for testing... @@ -826,8 +1665,8 @@ bool glx_blur_dst(session_t *ps, int dx, int dy, int width, int height, float z, glUniform1f(ppass->unifm_offset_x, texfac_x); if (ppass->unifm_offset_y >= 0) glUniform1f(ppass->unifm_offset_y, texfac_y); - if (ppass->unifm_factor_center >= 0) - glUniform1f(ppass->unifm_factor_center, factor_center); + if (ppass->unifm_opacity >= 0) + glUniform1f(ppass->unifm_opacity, (float)opacity); P_PAINTREG_START(crect) { auto rx = (GLfloat)(crect.x1 - mdx) * texfac_x; @@ -873,7 +1712,7 @@ bool glx_blur_dst(session_t *ps, int dx, int dy, int width, int height, float z, ret = true; -glx_blur_dst_end: +glx_conv_blur_dst_end: glBindFramebuffer(GL_FRAMEBUFFER, 0); glBindTexture(tex_tgt, 0); glDisable(tex_tgt); @@ -882,6 +1721,8 @@ bool glx_blur_dst(session_t *ps, int dx, int dy, int width, int height, float z, if (have_stencil) glEnable(GL_STENCIL_TEST); + glDisable(GL_BLEND); + if (&ibc == pbc) { free_glx_bc(ps, pbc); } @@ -891,6 +1732,902 @@ bool glx_blur_dst(session_t *ps, int dx, int dy, int width, int height, float z, return ret; } +/** + * Blur contents in a particular region using the dual-filter kawase blur. + */ +bool +glx_dualkawase_blur_dst(session_t *ps, int dx, int dy, int width, int height, float z attr_unused, + double opacity attr_unused, const region_t *reg_tgt attr_unused, glx_blur_cache_t *wpbc attr_unused) { + assert(ps->psglx->blur_passes[0].prog); + assert(ps->psglx->blur_passes[1].prog); + const bool have_scissors = glIsEnabled(GL_SCISSOR_TEST); + const bool have_stencil = glIsEnabled(GL_STENCIL_TEST); + bool ret = false; + + int iterations = ps->o.blur_strength.iterations; + float offset = ps->o.blur_strength.offset; + int expand = ps->o.blur_strength.expand; + + // Calculate copy region size + int mdx = dx - expand, mdy = dy - expand, mwidth = width + 2 * expand, mheight = height + 2 * expand; +#ifdef DEBUG_GLX + log_debug("%d, %d, %d, %d\n", mdx, mdy, mwidth, mheight); +#endif + + glx_blur_cache_t *psbc = &ps->psglx->blur_cache; + //glx_blur_cache_t *psbc = wpbc; + + GLenum tex_tgt = GL_TEXTURE_RECTANGLE; + if (ps->psglx->has_texture_non_power_of_two) + tex_tgt = GL_TEXTURE_2D; + + // Shrink blur_strength.iterations to still have at least 1px left + while ((width / (1 << (iterations-1))) < 1 || (height / (1 << (iterations-1))) < 1) + --iterations; + assert(iterations < MAX_BLUR_PASS); + + + // Allocate textures if needed and bind to the respective framebuffer + if (!psbc->fbos[0]) { + + log_warn("Allocating blur_cache [iter:%d] for dxy(%d:%d) wh(%d:%d)", iterations, dx, dy, width, height); + + glGenFramebuffers(iterations, psbc->fbos); + if (!psbc->fbos[0]) { + log_error("Failed to generate Framebuffer. Cannot do " + "multi-pass blur with GLX backend."); + goto glx_dualkawase_blur_dst_end; + } + + int tex_width; + int tex_height; + for (int i = 0; i <= iterations; ++i) { + if (!psbc->textures[i]) { + tex_width = ps->root_width / (1 << (i)); + tex_height = ps->root_height / (1 << (i)); + psbc->textures[i] = glx_gen_texture(tex_tgt, tex_width, tex_height); + psbc->width[i] = tex_width; + psbc->height[i] = tex_height; + } + if (!psbc->textures[i]) { + log_error("Failed to allocate texture."); + goto glx_dualkawase_blur_dst_end; + } + + // Bind texture to framebuffer + if ((i > 0) && psbc->fbos[i-1]) { + static const GLenum DRAWBUFS[2] = { GL_COLOR_ATTACHMENT0 }; + glBindFramebuffer(GL_FRAMEBUFFER, psbc->fbos[i-1]); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, psbc->textures[i], 0); + glDrawBuffers(1, DRAWBUFS); + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) + != GL_FRAMEBUFFER_COMPLETE) { + log_error("Framebuffer attachment failed."); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + goto glx_dualkawase_blur_dst_end; + } + } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + } + } + + // Check for FBO and textures + GLuint tex_scr = psbc->textures[0]; + if (!tex_scr) { + log_error("Blur cache texture not allocated."); + goto glx_dualkawase_blur_dst_end; + } + + for (int i = 1; i <= iterations; i++) { + if (!psbc->textures[i]) { + log_error("Blur cache texture not allocated."); + goto glx_dualkawase_blur_dst_end; + } + if (!psbc->fbos[i - 1]) { + log_error("Blur cache framebuffer not allocated."); + goto glx_dualkawase_blur_dst_end; + } + } + + // Read destination pixels into a texture + glEnable(tex_tgt); + glBindTexture(tex_tgt, tex_scr); + glx_copy_region_to_tex_new(ps, tex_tgt, mdx, mdy, mwidth, mheight); + + // Paint it back + glDisable(GL_STENCIL_TEST); + glDisable(GL_SCISSOR_TEST); + + // First pass: Kawase Downsample + const glx_blur_pass_t *down_pass = &ps->psglx->blur_passes[0]; + for (int i = 1; i <= iterations; i++) { + const int dest_width = psbc->width[i], dest_height = psbc->height[i]; + GLuint tex_src2 = psbc->textures[i - 1]; + GLuint fbo = psbc->fbos[i - 1]; + + //assert(tex_src2); + //assert(fbo); + glBindTexture(tex_tgt, tex_src2); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glUseProgram(down_pass->prog); + if (down_pass->unifm_offset >= 0) + glUniform1f(down_pass->unifm_offset, offset); + if (down_pass->unifm_halfpixel >= 0) + glUniform2f(down_pass->unifm_halfpixel, (GLfloat)0.5 / (GLfloat)dest_width, (GLfloat)0.5 / (GLfloat)dest_height); + if (down_pass->unifm_fulltex >= 0) + glUniform2f(down_pass->unifm_fulltex, (GLfloat)dest_width, (GLfloat)dest_height); + + // Start actual rendering + P_PAINTREG_START(crect) { + int w = (crect.x2 - crect.x1) + 2 * expand; + int h = (crect.y2 - crect.y1) + 2 * expand; + crect.x1 -= expand; crect.y1 -= expand; + //crect.width += 2 * expand; crect.height += 2 * expand; + + const GLfloat rx = (GLfloat)(crect.x1); + const GLfloat ry = (GLfloat)ps->root_height - (GLfloat)(crect.y1); + const GLfloat rxe = rx + (GLfloat)(w); + const GLfloat rye = ry - (GLfloat)(h); + GLfloat rdx = rx / (GLfloat)(1 << i); + GLfloat rdy = ry / (GLfloat)(1 << i); + GLfloat rdxe = rxe / (GLfloat)(1 << i); + GLfloat rdye = rye / (GLfloat)(1 << i); + + +#ifdef DEBUG_GLX + log_info("Downsample Pass %d xy(%d:%d) wh(%d:%d) dwh(%d:%d):\n\t%.2f, %.2f, %.2f, %.2f -> %.2f, %.2f, %.2f, %.2f\n", + i, dx, dy, width, height, dest_width, dest_height, rx, ry, rxe, rye, rdx, rdy, rdxe, rdye); +#endif + + glTexCoord2f(rx, ry); + glVertex3f(rdx, rdy, z); + + glTexCoord2f(rxe, ry); + glVertex3f(rdxe, rdy, z); + + glTexCoord2f(rxe, rye); + glVertex3f(rdxe, rdye, z); + + glTexCoord2f(rx, rye); + glVertex3f(rdx, rdye, z); + } + P_PAINTREG_END(); + } + + // Second pass: Kawase Upsample + const glx_blur_pass_t *up_pass = &ps->psglx->blur_passes[1]; + for (int i = iterations; i >= 1; i--) { + const int dest_width = psbc->width[i - 1], dest_height = psbc->height[i - 1]; + GLuint tex_src2 = psbc->textures[i]; + //assert(tex_src2); + glBindTexture(tex_tgt, tex_src2); + + if (i != 1) { // is not last pass + GLuint fbo = psbc->fbos[i - 2]; + //assert(fbo); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + } else { // last pass -> render to screen + static const GLenum DRAWBUFS[2] = { GL_BACK }; + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glDrawBuffers(1, DRAWBUFS); + if (have_scissors) + glEnable(GL_SCISSOR_TEST); + if (have_stencil) + glEnable(GL_STENCIL_TEST); + + if (opacity < 1.0) { // Blend blur texture to fade in and out with window opacity + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + } + + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glUseProgram(up_pass->prog); + if (up_pass->unifm_offset >= 0) + glUniform1f(up_pass->unifm_offset, offset); + if (up_pass->unifm_opacity >= 0) + glUniform1f(up_pass->unifm_opacity, (float)opacity); + if (up_pass->unifm_halfpixel >= 0) + glUniform2f(up_pass->unifm_halfpixel, (GLfloat)0.5 / (GLfloat)dest_width, (GLfloat)0.5 / (GLfloat)dest_height); + if (up_pass->unifm_fulltex >= 0) + glUniform2f(up_pass->unifm_fulltex, (GLfloat)dest_width, (GLfloat)dest_height); + + // Start actual rendering + P_PAINTREG_START(crect) { + int w = (crect.x2 - crect.x1); + int h = (crect.y2 - crect.y1); + const GLfloat rx = (GLfloat)(crect.x1 - expand); + const GLfloat ry = (GLfloat)ps->root_height - (GLfloat)(crect.y2) - (GLfloat)expand; + //const GLfloat ry = (GLfloat)ps->root_height - (GLfloat)(crect.y1+h) - (GLfloat)expand; + const GLfloat rxe = rx + (GLfloat)w + (GLfloat)(2 * expand); + const GLfloat rye = ry + (GLfloat)h + (GLfloat)(2 * expand); + GLfloat rdx; + GLfloat rdy; + GLfloat rdxe; + GLfloat rdye; + + if (i != 1) { // is not last pass + rdx = rx / (GLfloat)(1 << (i-1)); + rdy = ry / (GLfloat)(1 << (i-1)); + rdxe = rxe / (GLfloat)(1 << (i-1)); + rdye = rye / (GLfloat)(1 << (i-1)); + } else { // last pass -> render to screen coordinates + rdx = (GLfloat)crect.x1; + rdy = (GLfloat)ps->root_height - ((GLfloat)crect.y2); + //rdy = (GLfloat)ps->root_height - ((GLfloat)crect.y1 + (GLfloat)h); + rdxe = rdx + (GLfloat)w; + rdye = rdy + (GLfloat)h; + } + +#ifdef DEBUG_GLX + log_info("Upsample Pass %d xy(%d:%d) wh(%d:%d):\n\t%.2f, %.2f, %.2f, %.2f -> %.2f, %.2f, %.2f, %.2f\n", + i, dx, dy, width, height, rx, ry, rxe, rye, rdx, rdy, rdxe, rdye); +#endif + + glTexCoord2f(rx, ry); + glVertex3f(rdx, rdy, z); + + glTexCoord2f(rxe, ry); + glVertex3f(rdxe, rdy, z); + + glTexCoord2f(rxe, rye); + glVertex3f(rdxe, rdye, z); + + glTexCoord2f(rx, rye); + glVertex3f(rdx, rdye, z); + } + P_PAINTREG_END(); + } + + glUseProgram(0); + ret = true; + +glx_dualkawase_blur_dst_end: + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glBindTexture(tex_tgt, 0); + glDisable(tex_tgt); + if (have_scissors) + glEnable(GL_SCISSOR_TEST); + if (have_stencil) + glEnable(GL_STENCIL_TEST); + + glDisable(GL_BLEND); + + //if (&ibc == pbc) { free_glx_bc(ps, pbc); } + + gl_check_err(); + + return ret; +} + + +bool glx_kawase_blur_dst(session_t *ps, int dx, int dy, int width, int height, float z attr_unused, + double opacity attr_unused, const region_t *reg_tgt attr_unused, glx_blur_cache_t *pbc) { + assert(ps->psglx->blur_passes[0].prog); + const bool have_scissors = glIsEnabled(GL_SCISSOR_TEST); + const bool have_stencil = glIsEnabled(GL_STENCIL_TEST); + bool ret = false; + + int iterations = ps->o.blur_strength.iterations; + float offset = ps->o.blur_strength.offset; + + // Calculate copy region size + glx_blur_cache_t ibc = { .width = 0, .height = 0 }; + if (!pbc) + pbc = &ibc; + + int mdx = dx, mdy = dy, mwidth = width, mheight = height; +#ifdef DEBUG_GLX + log_debug("%d, %d, %d, %d\n", mdx, mdy, mwidth, mheight); +#endif + + GLenum tex_tgt = GL_TEXTURE_RECTANGLE; + if (ps->psglx->has_texture_non_power_of_two) + tex_tgt = GL_TEXTURE_2D; + + // Free textures if size inconsistency discovered + if (mwidth != pbc->width[0] || mheight != pbc->height[0]) + free_glx_bc_resize(ps, pbc); + + // Generate FBO and textures if needed + if (!pbc->textures[0]) + pbc->textures[0] = glx_gen_texture(tex_tgt, mwidth, mheight); + GLuint tex_scr = pbc->textures[0]; + + // Check if we can scale down blur_strength.iterations + while ((mwidth / (1 << (iterations-1))) < 1 || (mheight / (1 << (iterations-1))) < 1) + --iterations; + + assert(iterations < MAX_BLUR_PASS); + for (int i = 1; i <= iterations; i++) { + if (!pbc->textures[i]) + pbc->textures[i] = glx_gen_texture(tex_tgt, mwidth / (1 << (i-1)), mheight / (1 << (i-1))); + } + + pbc->width[0] = mwidth; + pbc->height[0] = mheight; + + if (!pbc->fbos[0]) + glGenFramebuffers(1, &pbc->fbos[0]); + const GLuint fbo = pbc->fbos[0]; + + if (!tex_scr) { + log_error("Failed to allocate texture."); + goto glx_kawase_blur_dst_end; + } + for (int i = 1; i <= iterations; i++) { + if (!pbc->textures[i]) { + log_error("Failed to allocate additional textures."); + goto glx_kawase_blur_dst_end; + } + } + if (!fbo) { + log_error("Failed to allocate framebuffer."); + goto glx_kawase_blur_dst_end; + } + + // Read destination pixels into a texture + glEnable(tex_tgt); + glBindTexture(tex_tgt, tex_scr); + glx_copy_region_to_tex(ps, tex_tgt, mdx, mdy, mdx, mdy, mwidth, mheight); + //glx_copy_region_to_tex_new(ps, tex_tgt, mdx, mdy, mwidth, mheight); + + // Paint it back + glDisable(GL_STENCIL_TEST); + glDisable(GL_SCISSOR_TEST); + + // First pass(es): Kawase Downsample + for (int i = 1; i <= iterations; ++i) { + const glx_blur_pass_t *down_pass = &ps->psglx->blur_passes[0]; + assert(down_pass->prog); + + int tex_width = mwidth / (1 << (i-1)), tex_height = mheight / (1 << (i-1)); + GLuint tex_src2 = pbc->textures[i - 1]; + GLuint tex_dest = pbc->textures[i]; + + assert(tex_src2); + assert(tex_dest); + glBindTexture(tex_tgt, tex_src2); + + //static const GLenum DRAWBUFS[2] = { GL_COLOR_ATTACHMENT0 }; + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, tex_dest, 0); + glDrawBuffer(GL_COLOR_ATTACHMENT0); + //glDrawBuffers(1, DRAWBUFS); + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + log_error("Framebuffer attachment failed."); + goto glx_kawase_blur_dst_end; + } + + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glUseProgram(down_pass->prog); + if (down_pass->unifm_offset >= 0) + glUniform1f(down_pass->unifm_offset, offset); + if (down_pass->unifm_halfpixel >= 0) + glUniform2f(down_pass->unifm_halfpixel, (float)(0.5 / tex_width), (float)(0.5 / tex_height)); + if (down_pass->unifm_fulltex >= 0) + glUniform2f(down_pass->unifm_fulltex, (float)tex_width, (float)tex_height); + + // Start actual rendering + P_PAINTREG_START(crect) { + auto rx = (GLfloat)(crect.x1 - mdx); + auto ry = (GLfloat)(mheight - (crect.y1 - mdy)); + auto rxe = rx + (GLfloat)(crect.x2 - crect.x1); + auto rye = ry - (GLfloat)(crect.y2 - crect.y1); + + #ifdef DEBUG_GLX + log_debug("Downsample Pass %d: %f, %f, %f, %f -> %f, %f, %f, %f\n", i, rx, ry, rxe, rye, rdx, rdy, rdxe, rdye); + #endif + + glTexCoord2f(rx, ry); + glVertex3f(rx, ry, z); + + glTexCoord2f(rxe, ry); + glVertex3f(rxe, ry, z); + + glTexCoord2f(rxe, rye); + glVertex3f(rxe, rye, z); + + glTexCoord2f(rx, rye); + glVertex3f(rx, rye, z); + } + P_PAINTREG_END(); + + glUseProgram(0); + } + + + // Second pass(es): Kawase Upsample + for (int i = iterations; i >= 1; i--) { + const glx_blur_pass_t *up_pass = &ps->psglx->blur_passes[1]; + bool is_last = (i == 1); + assert(up_pass->prog); + + int tex_width = mwidth / (1 << (i-2)), tex_height = mheight / (1 << (i-2)); + if (is_last) { + tex_width = mwidth, tex_height = mheight; + } + GLuint tex_src2 = pbc->textures[i]; + GLuint tex_dest = pbc->textures[i - 1]; + + assert(tex_src2); + assert(tex_dest); + glBindTexture(tex_tgt, tex_src2); + + if (!is_last) { + //static const GLenum DRAWBUFS[2] = { GL_COLOR_ATTACHMENT0 }; + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, tex_dest, 0); + //glDrawBuffers(1, DRAWBUFS); + glDrawBuffer(GL_COLOR_ATTACHMENT0); + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + log_error("Framebuffer attachment failed."); + goto glx_kawase_blur_dst_end; + } + } else { + //static const GLenum DRAWBUFS[2] = { GL_BACK }; + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glDrawBuffer(GL_BACK); + //glDrawBuffers(1, DRAWBUFS); + if (have_scissors) + glEnable(GL_SCISSOR_TEST); + if (have_stencil) + glEnable(GL_STENCIL_TEST); + } + + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glUseProgram(up_pass->prog); + if (up_pass->unifm_offset >= 0) + glUniform1f(up_pass->unifm_offset, offset); + if (up_pass->unifm_halfpixel >= 0) + glUniform2f(up_pass->unifm_halfpixel, (float)(0.5 / tex_width), (float)(0.5 / tex_height)); + if (up_pass->unifm_fulltex >= 0) + glUniform2f(up_pass->unifm_fulltex, (float)tex_width, (float)tex_height); + + // Start actual rendering + P_PAINTREG_START(crect) { + auto rx = (GLfloat)(crect.x1 - mdx); + auto ry = (GLfloat)(mheight - (crect.y1 - mdy)); + auto rxe = rx + (GLfloat)(crect.x2 - crect.x1); + auto rye = ry - (GLfloat)(crect.y2 - crect.y1); + auto rdx = (GLfloat)(crect.x1 - mdx); + auto rdy = (GLfloat)(mheight - crect.y1 + mdy); + if (is_last) { + rdx = (GLfloat)crect.x1; + rdy = (GLfloat)(ps->root_height - crect.y1); + } + auto rdxe = rdx + (GLfloat)(crect.x2 - crect.x1); + auto rdye = rdy - (GLfloat)(crect.y2 - crect.y1); + + #ifdef DEBUG_GLX + log_debug("Upsample Pass %d: %f, %f, %f, %f -> %f, %f, %f, %f\n", i, rx, ry, rxe, rye, rdx, rdy, rdxe, rdye); + #endif + + glTexCoord2f(rx, ry); + glVertex3f(rdx, rdy, z); + + glTexCoord2f(rxe, ry); + glVertex3f(rdxe, rdy, z); + + glTexCoord2f(rxe, rye); + glVertex3f(rdxe, rdye, z); + + glTexCoord2f(rx, rye); + glVertex3f(rdx, rdye, z); + } + P_PAINTREG_END(); + + glUseProgram(0); + } + + //glUseProgram(0); + + ret = true; + +glx_kawase_blur_dst_end: + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glBindTexture(tex_tgt, 0); + glDisable(tex_tgt); + if (have_scissors) + glEnable(GL_SCISSOR_TEST); + if (have_stencil) + glEnable(GL_STENCIL_TEST); + + if (&ibc == pbc) { + free_glx_bc(ps, pbc); + } + + gl_check_err(); + + return ret; +} + +bool glx_blur_dst(session_t *ps, int dx, int dy, int width, int height, float z, + double opacity, const region_t *reg_tgt, glx_blur_cache_t *pbc) { + assert(ps->psglx->blur_passes[0].prog); + + bool ret; + switch (ps->o.blur_method) { + case BLUR_METHOD_DUAL_KAWASE: + ret = glx_kawase_blur_dst(ps, dx, dy, width, height, z, + opacity, reg_tgt, pbc); + break; + case BLUR_METHOD_ALT_KAWASE: + ret = glx_dualkawase_blur_dst(ps, dx, dy, width, height, z, + opacity, reg_tgt, pbc); + break; + case BLUR_METHOD_KERNEL: + case BLUR_METHOD_BOX: + case BLUR_METHOD_GAUSSIAN: + ret = glx_conv_blur_dst(ps, dx, dy, width, height, z, opacity, reg_tgt, pbc); + break; + default: + ret = false; + break; + } + + gl_check_err(); + + return ret; +} + + +// TODO: this is a mess and needs a more consistent way of getting the border pixel +// I tried looking for a notify event for XCB_CW_BORDER_PIXEL (in xcb_create_window()) +// or a way to get the pixels from xcb_render_picture_t but the documentation for +// the xcb_xrender extension is literaly non existent... +bool glx_read_border_pixel(struct managed_win *w, int root_height, int x, int y, + int width attr_unused, int height, int cr, float *ppixel) +{ + if (!ppixel) return false; + + // First try bottom left corner past the + // circle radius (after the rounded corner ends) + auto openglx = x + cr*2; + auto opengly = root_height-height-y; + + // X is out of bounds + // move to the right side + if (openglx < 0) + openglx = x + width - cr; + + // Y is out of bounds + // move to to top part + if (opengly < 0) { + opengly += height-1; + } + + // bottom left corner is out of bounds + // use top border line instead + if (openglx < 0 || opengly < 0) { + + //log_warn("OUT OF BOUNDS: xy(%d, %d), glxy(%d %d) wh(%d %d), border_col(%.2f, %.2f, %.2f, %.2f)", + // x, y, openglx, opengly, width, height, + // (float)w->border_col[0], (float)w->border_col[1], (float)w->border_col[2], (float)w->border_col[3]); + + // Reset the color so the shader doesn't use it + w->border_col[0] = w->border_col[1] = w->border_col[2] = w->border_col[3] = -1.0; + } + + // Invert Y-axis so we can query border color from texture (0,0) + glReadPixels((openglx < 0) ? 0 : openglx, (opengly < 0) ? 0 : opengly, 1, 1, + GL_RGBA, GL_FLOAT, (void*)&w->border_col[0]); + + //log_warn("xy(%d, %d), glxy(%d %d) wh(%d %d), border_col(%.2f, %.2f, %.2f, %.2f)", + // x, y, openglx, opengly, width, height, + // (float)w->border_col[0], (float)w->border_col[1], (float)w->border_col[2], (float)w->border_col[3]); + + gl_check_err(); + + return true; +} + +bool glx_round_corners_dst0(session_t *ps, struct managed_win *w, const glx_texture_t *ptex attr_unused, int shader_idx, + int dx, int dy, int width, int height, float z, float cr, + const region_t *reg_tgt attr_unused, glx_blur_cache_t *pbc) { + + assert(shader_idx >= 0 && shader_idx <= 1); + assert(ps->psglx->round_passes[0].prog); + assert(ps->psglx->round_passes[1].prog); + const bool have_scissors = glIsEnabled(GL_SCISSOR_TEST); + const bool have_stencil = glIsEnabled(GL_STENCIL_TEST); + bool ret = false; + + //log_warn("dxy(%d, %d) wh(%d %d) rwh(%d %d) bw(%d)", + // dx, dy, width, height, ps->root_width, ps->root_height, w->g.border_width); + + if (w->g.border_width >= 1 || w->border_width > 0) { + glx_read_border_pixel(w, ps->root_height, dx, dy, width, height, w->corner_radius, &w->border_col[0]); + } + + // Calculate copy region size + glx_blur_cache_t ibc = {.width = 0, .height = 0}; + if (!pbc) + pbc = &ibc; + + int mdx = dx, mdy = dy, mwidth = width, mheight = height; + // log_trace("%d, %d, %d, %d", mdx, mdy, mwidth, mheight); + + GLenum tex_tgt = GL_TEXTURE_RECTANGLE; + if (ps->psglx->has_texture_non_power_of_two) + tex_tgt = GL_TEXTURE_2D; + + // Free textures if size inconsistency discovered + if (mwidth != pbc->width[0] || mheight != pbc->height[0]) + free_glx_bc_resize(ps, pbc); + + // Generate FBO and textures if needed + if (!pbc->textures[0]) + pbc->textures[0] = glx_gen_texture(tex_tgt, mwidth, mheight); + GLuint tex_scr = pbc->textures[0]; + + pbc->width[0] = mwidth; + pbc->height[0] = mheight; + + if (!tex_scr) { + log_error("Failed to allocate texture."); + goto glx_round_corners_dst_end; + } + + // Read destination pixels into a texture + glEnable(tex_tgt); + glBindTexture(tex_tgt, tex_scr); + glx_copy_region_to_tex(ps, tex_tgt, mdx, mdy, mdx, mdy, mwidth, mheight); + + // Texture scaling factor + GLfloat texfac_x = 1.0f, texfac_y = 1.0f; + if (tex_tgt == GL_TEXTURE_2D) { + texfac_x /= (GLfloat)mwidth; + texfac_y /= (GLfloat)mheight; + } + + // Paint it back + { + glDisable(GL_STENCIL_TEST); + glDisable(GL_SCISSOR_TEST); + } + + { + const glx_round_pass_t *ppass = &ps->psglx->round_passes[shader_idx]; + assert(ppass->prog); + + assert(tex_scr); + + glActiveTexture(GL_TEXTURE1); + glBindTexture(tex_tgt, tex_scr); + + // If caller specified a texture use it as source + if (ptex) { + glActiveTexture(GL_TEXTURE0); + glBindTexture(ptex->target, ptex->texture); + } else { + glActiveTexture(GL_TEXTURE0); + glBindTexture(tex_tgt, tex_scr); + } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glDrawBuffer(GL_BACK); + if (have_scissors) + glEnable(GL_SCISSOR_TEST); + if (have_stencil) + glEnable(GL_STENCIL_TEST); + + + // Our shader generates a transparent mid section + // with opaque corners copied from the background texture + // We must use blending to get the window pixesl to appear + //glDisable(GL_BLEND); + glEnable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + + glUseProgram(ppass->prog); + + if (ppass->unifm_tex_scr >= 0) + glUniform1i(ppass->unifm_tex_scr, (GLint)0); + if (ppass->unifm_tex_wnd >= 0) + glUniform1i(ppass->unifm_tex_wnd, (GLint)1); + + if (ppass->unifm_radius >= 0) + glUniform1f(ppass->unifm_radius, cr); + if (ppass->unifm_texcoord >= 0) + glUniform2f(ppass->unifm_texcoord, (float)dx, (float)dy); + if (ppass->unifm_texsize >= 0) + glUniform2f(ppass->unifm_texsize, (float)mwidth, (float)mheight); + if (ppass->unifm_borderw >= 0) + glUniform1f(ppass->unifm_borderw, (w->round_borders && w->border_col[0] != -1.) ? (w->border_width > 0 ? w->border_width : w->g.border_width) : 0); + if (ppass->unifm_borderc >= 0) + glUniform4fv(ppass->unifm_borderc, 1, (GLfloat *)&w->border_col[0]); + if (ppass->unifm_resolution >= 0) + glUniform2f(ppass->unifm_resolution, (float)ps->root_width, (float)ps->root_height); + + // Painting + { + P_PAINTREG_START(crect) { + // XXX explain these variables + auto rx = (GLfloat)(crect.x1 - dx); + auto ry = (GLfloat)(crect.y1 - dy); + auto rxe = rx + (GLfloat)(crect.x2 - crect.x1); + auto rye = ry + (GLfloat)(crect.y2 - crect.y1); + // Rectangle textures have [0-w] [0-h] while 2D texture has [0-1] + // [0-1] Thanks to amonakov for pointing out! + if (GL_TEXTURE_2D == tex_tgt) { + rx = rx / (GLfloat)width; + ry = ry / (GLfloat)height; + rxe = rxe / (GLfloat)width; + rye = rye / (GLfloat)height; + } + auto rdx = (GLfloat)crect.x1; + auto rdy = (GLfloat)(ps->root_height - crect.y1); + auto rdxe = (GLfloat)rdx + (GLfloat)(crect.x2 - crect.x1); + auto rdye = (GLfloat)rdy - (GLfloat)(crect.y2 - crect.y1); + + // Invert Y if needed, this may not work as expected, though. I + // don't have such a FBConfig to test with. + //if (ptex && !ptex->y_inverted) { + { + ry = 1.0f - ry; + rye = 1.0f - rye; + } + + //log_trace("Rect %d (i:%d): %f, %f, %f, %f -> %f, %f, %f, %f", + // ri ,ptex ? ptex->y_inverted : -1, rx, ry, rxe, rye, rdx, rdy, rdxe, rdye); + + glTexCoord2f(rx, ry); + glVertex3f(rdx, rdy, z); + + glTexCoord2f(rxe, ry); + glVertex3f(rdxe, rdy, z); + + glTexCoord2f(rxe, rye); + glVertex3f(rdxe, rdye, z); + + glTexCoord2f(rx, rye); + glVertex3f(rdx, rdye, z); + + } + P_PAINTREG_END(); + } + + glUseProgram(0); + glDisable(GL_BLEND); + } + + ret = true; + +glx_round_corners_dst_end: + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glBindTexture(tex_tgt, 0); + glDisable(tex_tgt); + if (have_scissors) + glEnable(GL_SCISSOR_TEST); + if (have_stencil) + glEnable(GL_STENCIL_TEST); + + if (&ibc == pbc) { + free_glx_bc(ps, pbc); + } + + gl_check_err(); + + return ret; +} + +bool glx_round_corners_dst1(session_t *ps, struct managed_win *w, const glx_texture_t *ptex, int shader_idx, + int dx, int dy, int width, int height, float z, float cr, + const region_t *reg_tgt attr_unused, glx_blur_cache_t *pbc attr_unused) { + + assert(shader_idx >= 0 && shader_idx <= 1); + assert(ps->psglx->round_passes[0].prog); + assert(ps->psglx->round_passes[1].prog); + bool ret = false; + + if (w->g.border_width >= 1 || w->border_width > 0) { + glx_read_border_pixel(w, ps->root_height, dx, dy, width, height, w->corner_radius, &w->border_col[0]); + } + + { + const glx_round_pass_t *ppass = &ps->psglx->round_passes[shader_idx]; + assert(ppass->prog); + + // If caller specified a texture use it as source + if (ptex) { + glActiveTexture(GL_TEXTURE0); + glBindTexture(ptex->target, ptex->texture); + } + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glUseProgram(ppass->prog); + + if (ppass->unifm_tex_scr >= 0) + glUniform1i(ppass->unifm_tex_scr, (GLint)0); + // We have no GL_TEXTURE1 here so just pass the default + if (ppass->unifm_tex_wnd >= 0) + glUniform1i(ppass->unifm_tex_wnd, (GLint)0); + + if (ppass->unifm_radius >= 0) + glUniform1f(ppass->unifm_radius, cr); + if (ppass->unifm_texcoord >= 0) + glUniform2f(ppass->unifm_texcoord, (float)dx, (float)dy); + if (ppass->unifm_texsize >= 0) + glUniform2f(ppass->unifm_texsize, (float)width, (float)height); + if (ppass->unifm_borderw >= 0) + glUniform1f(ppass->unifm_borderw, (w->round_borders && w->border_col[0] != -1.) ? (w->border_width > 0 ? w->border_width : w->g.border_width) : 0); + if (ppass->unifm_borderc >= 0) + glUniform4fv(ppass->unifm_borderc, 1, (GLfloat *)&w->border_col[0]); + if (ppass->unifm_resolution >= 0) + glUniform2f(ppass->unifm_resolution, (float)ps->root_width, (float)ps->root_height); + + // Painting + { + P_PAINTREG_START(crect) { + // XXX explain these variables + auto rx = (GLfloat)(crect.x1 - dx); + auto ry = (GLfloat)(crect.y1 - dy); + auto rxe = rx + (GLfloat)(crect.x2 - crect.x1); + auto rye = ry + (GLfloat)(crect.y2 - crect.y1); + // Rectangle textures have [0-w] [0-h] while 2D texture has [0-1] + // [0-1] Thanks to amonakov for pointing out! + if (GL_TEXTURE_2D == ptex->target) { + rx = rx / (GLfloat)width; + ry = ry / (GLfloat)height; + rxe = rxe / (GLfloat)width; + rye = rye / (GLfloat)height; + } + auto rdx = (GLfloat)crect.x1; + auto rdy = (GLfloat)(ps->root_height - crect.y1); + auto rdxe = (GLfloat)rdx + (GLfloat)(crect.x2 - crect.x1); + auto rdye = (GLfloat)rdy - (GLfloat)(crect.y2 - crect.y1); + + // Invert Y if needed, this may not work as expected, though. I + // don't have such a FBConfig to test with. + //if (ptex && !ptex->y_inverted) { + { + ry = 1.0f - ry; + rye = 1.0f - rye; + } + + //log_trace("Rect %d (i:%d): %f, %f, %f, %f -> %f, %f, %f, %f", + // ri ,ptex ? ptex->y_inverted : -1, rx, ry, rxe, rye, rdx, rdy, rdxe, rdye); + + glTexCoord2f(rx, ry); + glVertex3f(rdx, rdy, z); + + glTexCoord2f(rxe, ry); + glVertex3f(rdxe, rdy, z); + + glTexCoord2f(rxe, rye); + glVertex3f(rdxe, rdye, z); + + glTexCoord2f(rx, rye); + glVertex3f(rdx, rdye, z); + + } + P_PAINTREG_END(); + } + + glUseProgram(0); + glDisable(GL_BLEND); + } + + ret = true; + + //glBindFramebuffer(GL_FRAMEBUFFER, 0); + + gl_check_err(); + + return ret; +} + bool glx_dim_dst(session_t *ps, int dx, int dy, int width, int height, int z, GLfloat factor, const region_t *reg_tgt) { // It's possible to dim in glx_render(), but it would be over-complicated @@ -924,9 +2661,9 @@ bool glx_dim_dst(session_t *ps, int dx, int dy, int width, int height, int z, /** * @brief Render a region with texture data. */ -bool glx_render(session_t *ps, const glx_texture_t *ptex, int x, int y, int dx, int dy, - int width, int height, int z, double opacity, bool argb, bool neg, - const region_t *reg_tgt, const glx_prog_main_t *pprogram) { +bool glx_render(session_t *ps, struct managed_win *w attr_unused, const glx_texture_t *ptex, + int x, int y, int dx, int dy, int width, int height, int z, double opacity, bool argb, + bool neg, int cr attr_unused, const region_t *reg_tgt, const glx_prog_main_t *pprogram) { if (!ptex || !ptex->texture) { log_error("Missing texture."); return false; @@ -940,7 +2677,7 @@ bool glx_render(session_t *ps, const glx_texture_t *ptex, int x, int y, int dx, glEnable(ptex->target); // Enable blending if needed - if (opacity < 1.0 || argb) { + if (opacity < 1.0 || argb || cr > 0) { glEnable(GL_BLEND); diff --git a/src/opengl.h b/src/opengl.h index 033cf16ea8..1751e627a4 100644 --- a/src/opengl.h +++ b/src/opengl.h @@ -36,10 +36,40 @@ typedef struct { GLint unifm_offset_x; /// Location of uniform "offset_y" in blur GLSL program. GLint unifm_offset_y; - /// Location of uniform "factor_center" in blur GLSL program. - GLint unifm_factor_center; + /// Location of uniform "opacity" in conv-blur and (dual filter) kawase-blur GLSL program. + GLint unifm_opacity; + /// Location of uniform "offset" in kawase-blur GLSL program. + GLint unifm_offset; + /// Location of uniform "halfpixel" in kawase-blur GLSL program. + GLint unifm_halfpixel; + /// Location of uniform "fulltex" in kawase-blur GLSL program. + GLint unifm_fulltex; } glx_blur_pass_t; +typedef struct { + /// Fragment shader for rounded corners. + GLuint frag_shader; + /// GLSL program for rounded corners. + GLuint prog; + /// Location of uniform "radius" in rounded-corners GLSL program. + GLint unifm_radius; + /// Location of uniform "texcoord" in rounded-corners GLSL program. + GLint unifm_texcoord; + /// Location of uniform "texsize" in rounded-corners GLSL program. + GLint unifm_texsize; + /// Location of uniform "borderw" in rounded-corners GLSL program. + GLint unifm_borderw; + /// Location of uniform "borderc" in rounded-corners GLSL program. + GLint unifm_borderc; + /// Location of uniform "resolution" in rounded-corners GLSL program. + GLint unifm_resolution; + /// Location of uniform "texture_scr" in rounded-corners GLSL program. + GLint unifm_tex_scr; + /// Location of uniform "texture_wnd" in rounded-corners GLSL program. + GLint unifm_tex_wnd; + +} glx_round_pass_t; + /// Structure containing GLX-dependent data for a session. typedef struct glx_session { // === OpenGL related === @@ -49,7 +79,10 @@ typedef struct glx_session { bool has_texture_non_power_of_two; /// Current GLX Z value. int z; + /// Cached blur textures for every pass + glx_blur_cache_t blur_cache; glx_blur_pass_t *blur_passes; + glx_round_pass_t *round_passes; } glx_session_t; /// @brief Wrapper of a binded GLX texture. @@ -69,8 +102,8 @@ typedef struct _glx_texture { bool glx_dim_dst(session_t *ps, int dx, int dy, int width, int height, int z, GLfloat factor, const region_t *reg_tgt); -bool glx_render(session_t *ps, const glx_texture_t *ptex, int x, int y, int dx, int dy, - int width, int height, int z, double opacity, bool argb, bool neg, +bool glx_render(session_t *ps, struct managed_win *w, const glx_texture_t *ptex, int x, int y, int dx, int dy, + int width, int height, int z, double opacity, bool argb, bool neg, int cr, const region_t *reg_tgt, const glx_prog_main_t *pprogram); bool glx_init(session_t *ps, bool need_render); @@ -81,6 +114,8 @@ void glx_on_root_change(session_t *ps); bool glx_init_blur(session_t *ps); +bool glx_init_rounded_corners(session_t *ps); + #ifdef CONFIG_OPENGL bool glx_load_prog_main(const char *vshader_str, const char *fshader_str, glx_prog_main_t *pprogram); @@ -91,6 +126,11 @@ bool glx_bind_pixmap(session_t *ps, glx_texture_t **pptex, xcb_pixmap_t pixmap, void glx_release_pixmap(session_t *ps, glx_texture_t *ptex); +bool glx_bind_texture(session_t *ps, glx_texture_t **pptex, + int x, int y, int width, int height, bool repeat); + +void glx_release_texture(session_t *ps, glx_texture_t **ptex); + void glx_paint_pre(session_t *ps, region_t *preg) attr_nonnull(1, 2); /** @@ -103,7 +143,15 @@ static inline bool glx_tex_binded(const glx_texture_t *ptex, xcb_pixmap_t pixmap void glx_set_clip(session_t *ps, const region_t *reg); bool glx_blur_dst(session_t *ps, int dx, int dy, int width, int height, float z, - GLfloat factor_center, const region_t *reg_tgt, glx_blur_cache_t *pbc); + double opacity, const region_t *reg_tgt, glx_blur_cache_t *pbc); + +bool glx_round_corners_dst0(session_t *ps, struct managed_win *w, const glx_texture_t *ptex, int shader_idx, + int dx, int dy, int width, int height, float z, float cr, + const region_t *reg_tgt, glx_blur_cache_t *pbc); + +bool glx_round_corners_dst1(session_t *ps, struct managed_win *w, const glx_texture_t *ptex, int shader_idx, + int dx, int dy, int width, int height, float z, float cr, + const region_t *reg_tgt, glx_blur_cache_t *pbc); GLuint glx_create_shader(GLenum shader_type, const char *shader_str); @@ -157,17 +205,19 @@ static inline void free_glx_fbo(GLuint *pfbo) { * Free data in glx_blur_cache_t on resize. */ static inline void free_glx_bc_resize(session_t *ps, glx_blur_cache_t *pbc) { - free_texture_r(ps, &pbc->textures[0]); - free_texture_r(ps, &pbc->textures[1]); - pbc->width = 0; - pbc->height = 0; + for (int i = 0; i < MAX_BLUR_PASS; i++) { + free_texture_r(ps, &pbc->textures[i]); + pbc->width[i] = 0; + pbc->height[i] = 0; + } } /** * Free a glx_blur_cache_t */ static inline void free_glx_bc(session_t *ps, glx_blur_cache_t *pbc) { - free_glx_fbo(&pbc->fbo); + for (int i = 0; i < MAX_BLUR_PASS; i++) + free_glx_fbo(&pbc->fbos[i]); free_glx_bc_resize(ps, pbc); } @@ -206,6 +256,8 @@ static inline void free_win_res_glx(session_t *ps, struct managed_win *w) { free_paint_glx(ps, &w->shadow_paint); #ifdef CONFIG_OPENGL free_glx_bc(ps, &w->glx_blur_cache); + free_glx_bc(ps, &w->glx_round_cache); + free_texture(ps, &w->glx_texture_bg); free(w->paint.fbcfg); #endif } diff --git a/src/options.c b/src/options.c index 8f44b4336f..1d0788e17b 100644 --- a/src/options.c +++ b/src/options.c @@ -114,6 +114,18 @@ static void usage(const char *argv0, int ret) { "--active-opacity opacity\n" " Default opacity for active windows. (0.0 - 1.0)\n" "\n" + "--corner-radius value\n" + " Round the corners of windows. (defaults to 0)\n" + "\n" + "--rounded-corners-exclude condition\n" + " Exclude conditions for rounded corners.\n" + "\n" + "--round-borders value\n" + " When rounding corners, round the borders of windows. (defaults to 1)\n" + "\n" + "--round-borders-exclude condition\n" + " Exclude conditions for rounding borders.\n" + "\n" "--mark-wmwin-focused\n" " Try to detect WM windows and mark them as active.\n" "\n" @@ -202,8 +214,8 @@ static void usage(const char *argv0, int ret) { "\n" "--blur-method\n" " The algorithm used for background bluring. Available choices are:\n" - " 'none' to disable, 'gaussian', 'box' or 'kernel' for custom\n" - " convolution blur with --blur-kern.\n" + " 'none' to disable, 'dual_kawase', 'gaussian', 'box' or 'kernel'\n" + " for custom convolution blur with --blur-kern.\n" " Note: 'gaussian' and 'box' require --experimental-backends.\n" "\n" "--blur-size\n" @@ -211,6 +223,10 @@ static void usage(const char *argv0, int ret) { "\n" "--blur-deviation\n" " The standard deviation for the 'gaussian' blur method.\n" + "\n" + "--blur-strength\n" + " Only valid for '--blur-method dual_kawase'!\n" + " The strength of the kawase blur as an integer between 1 and 20. Defaults to 5.\n" "\n" "--blur-background\n" " Blur background of semi-transparent / ARGB windows. Bad in\n" @@ -227,6 +243,7 @@ static void usage(const char *argv0, int ret) { " opacity.\n" "\n" "--blur-kern matrix\n" + " Only valid for '--blur-method convolution'!\n" " Specify the blur convolution kernel, with the following format:\n" " WIDTH,HEIGHT,ELE1,ELE2,ELE3,ELE4,ELE5...\n" " The element in the center must not be included, it will be forever\n" @@ -437,6 +454,12 @@ static const struct option longopts[] = { {"blur-method", required_argument, NULL, 328}, {"blur-size", required_argument, NULL, 329}, {"blur-deviation", required_argument, NULL, 330}, + {"blur-strength", required_argument, NULL, 331}, + {"corner-radius", required_argument, NULL, 332}, + {"rounded-corners-exclude", required_argument, NULL, 333}, + {"round-borders", required_argument, NULL, 334}, + {"round-borders-exclude", required_argument, NULL, 335}, + {"round-borders-rule", required_argument, NULL, 336}, {"experimental-backends", no_argument, NULL, 733}, {"monitor-repaint", no_argument, NULL, 800}, {"diagnostics", no_argument, NULL, 801}, @@ -841,6 +864,19 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, // --blur-deviation opt->blur_deviation = atof(optarg); break; + case 331: + // --blur-strength + opt->blur_strength = parse_kawase_blur_strength(atoi(optarg)); + break; + case 332: opt->corner_radius = atoi(optarg); break; + case 333: condlst_add(&opt->rounded_corners_blacklist, optarg); break; + case 334: opt->round_borders = atoi(optarg); break; + case 335: condlst_add(&opt->round_borders_blacklist, optarg); break; + case 336: + // --round_borders_rule + if (!parse_rule_border(&opt->round_borders_rules, optarg)) + exit(1); + break; P_CASEBOOL(733, experimental_backends); P_CASEBOOL(800, monitor_repaint); @@ -921,6 +957,13 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, opt->track_leader = true; } + // Blur method kawase is not compatible with the xrender backend + if (opt->backend != BKEND_GLX && (opt->blur_method == BLUR_METHOD_DUAL_KAWASE + || opt->blur_method == BLUR_METHOD_ALT_KAWASE)) { + log_warn("Blur method 'kawase' is incompatible with the XRender backend. Fall back to default.\n"); + opt->blur_method = BLUR_METHOD_KERNEL; + } + // Fill default blur kernel if (opt->blur_method == BLUR_METHOD_KERNEL && (!opt->blur_kerns || !opt->blur_kerns[0])) { @@ -930,6 +973,15 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, CHECK(opt->blur_kernel_count); } + // override blur_kernel_count for kawase + if (opt->blur_method == BLUR_METHOD_DUAL_KAWASE || + opt->blur_method == BLUR_METHOD_ALT_KAWASE) { + opt->blur_kernel_count = MAX_BLUR_PASS; + opt->blur_kerns = ccalloc(opt->blur_kernel_count, struct conv *); + CHECK(opt->blur_kerns); + CHECK(opt->blur_kernel_count); + } + if (opt->resize_damage < 0) { log_warn("Negative --resize-damage will not work correctly."); } diff --git a/src/picom.c b/src/picom.c index 643d53e417..28053b3f19 100644 --- a/src/picom.c +++ b/src/picom.c @@ -309,11 +309,9 @@ uint32_t determine_evmask(session_t *ps, xcb_window_t wid, win_evmode_t mode) { } // Check if it's a mapped client window - if (WIN_EVMODE_CLIENT == mode || + if (mode == WIN_EVMODE_CLIENT || ((w = find_toplevel(ps, wid)) && w->a.map_state == XCB_MAP_STATE_VIEWABLE)) { - if (ps->o.frame_opacity > 0 || ps->o.track_wdata || ps->track_atom_lst || - ps->o.detect_client_opacity) - evmask |= XCB_EVENT_MASK_PROPERTY_CHANGE; + evmask |= XCB_EVENT_MASK_PROPERTY_CHANGE; } return evmask; @@ -472,8 +470,20 @@ static struct managed_win *paint_preprocess(session_t *ps, bool *fade_running) { w->frame_opacity = 1.0; } + // The below moved to it's own function: + // `win_determine_rounded_corners` (win.c) + /* + // Don't round full screen windows & excluded windows + if ((w && win_is_fullscreen(ps, w)) || + c2_match(ps, w, ps->o.rounded_corners_blacklist, NULL)) { + w->corner_radius = 0; + } else { + w->corner_radius = ps->o.corner_radius; + } + */ + // Update window mode - w->mode = win_calc_mode(w); + w->mode = win_calc_mode(ps, w); // Destroy all reg_ignore above when frame opaque state changes on // SOLID mode @@ -500,6 +510,11 @@ static struct managed_win *paint_preprocess(session_t *ps, bool *fade_running) { rc_region_unref(&w->reg_ignore); } + // Clear flags if we are not using experimental backends + if (!ps->o.experimental_backends) { + w->flags = 0; + } + // log_trace("%d %d %s", w->a.map_state, w->ever_damaged, w->name); // Give up if it's not damaged or invisible, or it's unmapped and its @@ -554,10 +569,10 @@ static struct managed_win *paint_preprocess(session_t *ps, bool *fade_running) { // w->mode == WMODE_SOLID or WMODE_FRAME_TRANS region_t *tmp = rc_region_new(); if (w->mode == WMODE_SOLID) { - *tmp = win_get_bounding_shape_global_by_val(w); + *tmp = win_get_bounding_shape_global_by_val(w, false); } else { // w->mode == WMODE_FRAME_TRANS - win_get_region_noframe_local(w, tmp); + win_get_region_noframe_local(w, tmp, false); pixman_region32_intersect(tmp, tmp, &w->bounding_shape); pixman_region32_translate(tmp, w->g.x, w->g.y); } @@ -695,6 +710,11 @@ static void destroy_backend(session_t *ps) { ps->backend_data, ps->backend_blur_context); ps->backend_blur_context = NULL; } + if (ps->backend_round_context) { + ps->backend_data->ops->destroy_round_context( + ps->backend_data, ps->backend_round_context); + ps->backend_round_context = NULL; + } ps->backend_data->ops->deinit(ps->backend_data); ps->backend_data = NULL; } @@ -703,6 +723,7 @@ static void destroy_backend(session_t *ps) { static bool initialize_blur(session_t *ps) { struct kernel_blur_args kargs; struct gaussian_blur_args gargs; + struct dual_kawase_blur_args dkargs; struct box_blur_args bargs; void *args = NULL; @@ -721,6 +742,12 @@ static bool initialize_blur(session_t *ps) { gargs.deviation = ps->o.blur_deviation; args = (void *)&gargs; break; + case BLUR_METHOD_ALT_KAWASE: + case BLUR_METHOD_DUAL_KAWASE: + dkargs.size = ps->o.blur_radius; + dkargs.strength = ps->o.blur_strength; + args = (void *)&dkargs; + break; default: return true; } @@ -729,6 +756,15 @@ static bool initialize_blur(session_t *ps) { return ps->backend_blur_context != NULL; } +static bool initialize_round_corners(session_t *ps) { + struct round_corners_args cargs; + cargs.corner_radius = ps->o.corner_radius; + cargs.round_borders = ps->o.round_borders; + ps->backend_round_context = ps->backend_data->ops->create_round_context( + ps->backend_data, &cargs); + return ps->backend_round_context != NULL; +} + /// Init the backend and bind all the window pixmap to backend images static bool initialize_backend(session_t *ps) { if (ps->o.experimental_backends) { @@ -751,6 +787,11 @@ static bool initialize_backend(session_t *ps) { return false; } + if (!initialize_round_corners(ps)) { + log_fatal("Failed to prepare for rounded corners, will ignore..."); + ps->o.corner_radius = 0; + } + // window_stack shouldn't include window that's // not in the hash table at this point. Since // there cannot be any fading windows. @@ -848,6 +889,9 @@ void configure_root(session_t *ps, int width, int height) { ev_break(ps->loop, EVBREAK_ALL); return; } + + // Re-acquire the root pixmap. + root_damaged(ps); } force_repaint(ps); } @@ -1202,7 +1246,13 @@ static bool redirect_start(session_t *ps) { xcb_map_window(ps->c, ps->overlay); } - xcb_composite_redirect_subwindows(ps->c, ps->root, session_redirection_mode(ps)); + bool success = XCB_AWAIT_VOID(xcb_composite_redirect_subwindows, ps->c, ps->root, + session_redirection_mode(ps)); + if (!success) { + log_fatal("Another composite manager is already running " + "(and does not handle _NET_WM_CM_Sn correctly)"); + return false; + } x_sync(ps->c); @@ -1346,9 +1396,7 @@ static void handle_pending_updates(EV_P_ struct session *ps) { log_debug("Delayed handling of events, entering critical section"); auto e = xcb_request_check(ps->c, xcb_grab_server_checked(ps->c)); if (e) { - log_fatal("failed to grab x server"); - x_print_error(e->full_sequence, e->major_code, e->minor_code, - e->error_code); + log_fatal_x_error(e, "failed to grab x server"); return quit(ps); } @@ -1380,9 +1428,7 @@ static void handle_pending_updates(EV_P_ struct session *ps) { e = xcb_request_check(ps->c, xcb_ungrab_server_checked(ps->c)); if (e) { - log_fatal("failed to ungrab x server"); - x_print_error(e->full_sequence, e->major_code, e->minor_code, - e->error_code); + log_fatal_x_error(e, "failed to ungrab x server"); return quit(ps); } @@ -1692,8 +1738,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy, XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE})); if (e) { - log_error("Failed to setup root window event mask"); - free(e); + log_error_x_error(e, "Failed to setup root window event mask"); } xcb_prefetch_extension_data(ps->c, &xcb_render_id); @@ -1827,6 +1872,9 @@ static session_t *session_init(int argc, char **argv, Display *dpy, c2_list_postprocess(ps, ps->o.blur_background_blacklist) && c2_list_postprocess(ps, ps->o.invert_color_list) && c2_list_postprocess(ps, ps->o.opacity_rules) && + c2_list_postprocess(ps, ps->o.rounded_corners_blacklist) && + c2_list_postprocess(ps, ps->o.round_borders_blacklist) && + c2_list_postprocess(ps, ps->o.round_borders_rules) && c2_list_postprocess(ps, ps->o.focus_blacklist))) { log_error("Post-processing of conditionals failed, some of your rules " "might not work"); @@ -1894,8 +1942,8 @@ static session_t *session_init(int argc, char **argv, Display *dpy, e = xcb_request_check( ps->c, xcb_sync_create_fence(ps->c, ps->root, ps->sync_fence, 0)); if (e) { - log_error("Failed to create a XSync fence. xrender-sync-fence " - "will be disabled"); + log_error_x_error(e, "Failed to create a XSync fence. " + "xrender-sync-fence will be disabled"); ps->o.xrender_sync_fence = false; ps->sync_fence = XCB_NONE; free(e); @@ -2092,7 +2140,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy, e = xcb_request_check(ps->c, xcb_grab_server_checked(ps->c)); if (e) { - log_fatal("Failed to grab X server"); + log_fatal_x_error(e, "Failed to grab X server"); goto err; } @@ -2109,7 +2157,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy, e = xcb_request_check(ps->c, xcb_ungrab_server(ps->c)); if (e) { - log_error("Failed to ungrab server"); + log_fatal_x_error(e, "Failed to ungrab server"); free(e); } @@ -2200,6 +2248,9 @@ static void session_destroy(session_t *ps) { free_wincondlst(&ps->o.opacity_rules); free_wincondlst(&ps->o.paint_blacklist); free_wincondlst(&ps->o.unredir_if_possible_blacklist); + free_wincondlst(&ps->o.rounded_corners_blacklist); + free_wincondlst(&ps->o.round_borders_blacklist); + free_wincondlst(&ps->o.round_borders_rules); // Free tracked atom list { diff --git a/src/render.c b/src/render.c index c0e33e339f..31e8e60b40 100644 --- a/src/render.c +++ b/src/render.c @@ -186,28 +186,139 @@ void free_paint(session_t *ps, paint_t *ppaint) { ppaint->pixmap = XCB_NONE; } -void render(session_t *ps, int x, int y, int dx, int dy, int wid, int hei, double opacity, - bool argb, bool neg, xcb_render_picture_t pict, glx_texture_t *ptex, - const region_t *reg_paint, const glx_prog_main_t *pprogram) { +uint32_t +make_circle(int cx, int cy, int radius, uint32_t max_ntraps, xcb_render_trapezoid_t traps[]) { + uint32_t n = 0, k = 0; + int y1, y2; + double w; + while (k < max_ntraps) { + y1 = (int)(-radius * cos(M_PI * k / max_ntraps)); + traps[n].top = (cy + y1) << 16; + traps[n].left.p1.y = (cy + y1) << 16; + traps[n].right.p1.y = (cy + y1) << 16; + w = sqrt(radius * radius - y1 * y1) * 65536; + traps[n].left.p1.x = (int)((cx << 16) - w); + traps[n].right.p1.x = (int)((cx << 16) + w); + + do { + k++; + y2 = (int)(-radius * cos(M_PI * k / max_ntraps)); + } while (y1 == y2); + + traps[n].bottom = (cy + y2) << 16; + traps[n].left.p2.y = (cy + y2) << 16; + traps[n].right.p2.y = (cy + y2) << 16; + w = sqrt(radius * radius - y2 * y2) * 65536; + traps[n].left.p2.x = (int)((cx << 16) - w); + traps[n].right.p2.x = (int)((cx << 16) + w); + n++; + } + return n; +} + +uint32_t make_rectangle(int x, int y, int wid, int hei, xcb_render_trapezoid_t traps[]) { + traps[0].top = y << 16; + traps[0].left.p1.y = y << 16; + traps[0].left.p1.x = x << 16; + traps[0].left.p2.y = (y + hei) << 16; + traps[0].left.p2.x = x << 16; + traps[0].bottom = (y + hei) << 16; + traps[0].right.p1.x = (x + wid) << 16; + traps[0].right.p1.y = y << 16; + traps[0].right.p2.x = (x + wid) << 16; + traps[0].right.p2.y = (y + hei) << 16; + return 1; +} + +uint32_t make_rounded_window_shape(xcb_render_trapezoid_t traps[], uint32_t max_ntraps, int cr, int wid, int hei) +{ + uint32_t n = make_circle(cr, cr, cr, max_ntraps, traps); + n += make_circle(wid - cr, cr, cr, max_ntraps, traps + n); + n += make_circle(wid - cr, hei - cr, cr, max_ntraps, traps + n); + n += make_circle(cr, hei - cr, cr, max_ntraps, traps + n); + n += make_rectangle(0, cr, cr, hei - 2 * cr, traps + n); + n += make_rectangle(cr, 0, wid - 2 * cr, cr, traps + n); + n += make_rectangle(wid - cr, cr, cr, hei - 2 * cr, traps + n); + n += make_rectangle(cr, hei - cr, wid - 2 * cr, cr, traps + n); + n += make_rectangle(cr, cr, wid - 2 * cr, hei - 2 * cr, + traps + n); + return n; +} + +void render(session_t *ps, struct managed_win *w attr_unused, int x, int y, + int dx, int dy, int wid, int hei, int fullwid, int fullhei, double opacity, + bool argb, bool neg, int cr, xcb_render_picture_t pict, glx_texture_t *ptex, + const region_t *reg_paint, const glx_prog_main_t *pprogram, clip_t *clip) { switch (ps->o.backend) { case BKEND_XRENDER: case BKEND_XR_GLX_HYBRID: { auto alpha_step = (int)(opacity * MAX_ALPHA); xcb_render_picture_t alpha_pict = ps->alpha_picts[alpha_step]; if (alpha_step != 0) { - uint8_t op = ((!argb && !alpha_pict) ? XCB_RENDER_PICT_OP_SRC + if (cr) { + //log_warn("f(%d, %d) wh(%d %d) xy(%d %d) dxy(%d %d)", fullwid, fullhei, wid, hei, x, y, dx, dy); + xcb_render_picture_t p_tmp = x_create_picture_with_standard( + ps->c, ps->root, fullwid, fullhei, XCB_PICT_STANDARD_ARGB_32, 0, 0); + xcb_render_color_t trans = { + .red = 0, .blue = 0, .green = 0, .alpha = 0}; + const xcb_rectangle_t rect = {.x = 0, + .y = 0, + .width = to_u16_checked(fullwid), + .height = to_u16_checked(fullhei)}; + xcb_render_fill_rectangles(ps->c, XCB_RENDER_PICT_OP_SRC, + p_tmp, trans, 1, &rect); + + uint32_t max_ntraps = to_u32_checked(cr); + xcb_render_trapezoid_t traps[4 * max_ntraps + 5]; + + uint32_t n = make_rounded_window_shape(traps, max_ntraps, cr, fullwid, fullhei); + + xcb_render_trapezoids( + ps->c, XCB_RENDER_PICT_OP_OVER, alpha_pict, p_tmp, + x_get_pictfmt_for_standard(ps->c, XCB_PICT_STANDARD_A_8), + 0, 0, n, traps); + + xcb_render_composite( + ps->c, XCB_RENDER_PICT_OP_OVER, pict, p_tmp, + ps->tgt_buffer.pict, to_i16_checked(x), to_i16_checked(y), + to_i16_checked(x), to_i16_checked(y), to_i16_checked(dx), to_i16_checked(dy), + to_u16_checked(wid), to_u16_checked(hei)); + + xcb_render_free_picture(ps->c, p_tmp); + + } else { + + xcb_render_picture_t p_tmp = alpha_pict; + if(clip){ + p_tmp = x_create_picture_with_standard(ps->c, ps->root, wid, hei, XCB_PICT_STANDARD_ARGB_32, 0, 0); + + xcb_render_color_t black = {.red = 255, .blue = 255, .green = 255, .alpha = 255}; + const xcb_rectangle_t rect = {.x = 0, .y = 0, .width = to_u16_checked(wid), .height = to_u16_checked(hei)}; + xcb_render_fill_rectangles(ps->c, XCB_RENDER_PICT_OP_SRC, p_tmp, black, 1, &rect); + if(alpha_pict) { + xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_SRC, alpha_pict, XCB_NONE, p_tmp, 0, 0, 0, 0, 0, 0, to_u16_checked(wid), to_u16_checked(hei)); + } + xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_OUT_REVERSE, clip->pict, XCB_NONE, p_tmp, 0, 0, 0, 0, to_i16_checked(clip->x), to_i16_checked(clip->y), to_u16_checked(wid), to_u16_checked(hei)); + } + + uint8_t op = ((!argb && !alpha_pict && !clip) ? XCB_RENDER_PICT_OP_SRC : XCB_RENDER_PICT_OP_OVER); - xcb_render_composite( - ps->c, op, pict, alpha_pict, ps->tgt_buffer.pict, - to_i16_checked(x), to_i16_checked(y), 0, 0, to_i16_checked(dx), - to_i16_checked(dy), to_u16_checked(wid), to_u16_checked(hei)); + xcb_render_composite( + ps->c, op, pict, p_tmp/*alpha_pict*/, ps->tgt_buffer.pict, + to_i16_checked(x), to_i16_checked(y), 0, 0, to_i16_checked(dx), + to_i16_checked(dy), to_u16_checked(wid), to_u16_checked(hei)); + + if(clip){ + xcb_render_free_picture(ps->c, p_tmp); + } + } } break; } #ifdef CONFIG_OPENGL case BKEND_GLX: - glx_render(ps, ptex, x, y, dx, dy, wid, hei, ps->psglx->z, opacity, argb, - neg, reg_paint, pprogram); + glx_render(ps, w, ptex, x, y, dx, dy, wid, hei, ps->psglx->z, opacity, argb, + neg, cr, reg_paint, pprogram); ps->psglx->z += 1; break; #endif @@ -222,21 +333,24 @@ void render(session_t *ps, int x, int y, int dx, int dy, int wid, int hei, doubl } static inline void -paint_region(session_t *ps, const struct managed_win *w, int x, int y, int wid, int hei, +paint_region(session_t *ps, struct managed_win *w, int x, int y, int wid, int hei, double opacity, const region_t *reg_paint, xcb_render_picture_t pict) { const int dx = (w ? w->g.x : 0) + x; const int dy = (w ? w->g.y : 0) + y; + const int fullwid = w ? w->widthb : 0; + const int fullhei = w ? w-> heightb : 0; const bool argb = (w && (win_has_alpha(w) || ps->o.force_win_blend)); const bool neg = (w && w->invert_color); - render(ps, x, y, dx, dy, wid, hei, opacity, argb, neg, pict, - (w ? w->paint.ptex : ps->root_tile_paint.ptex), reg_paint, + render(ps, w, x, y, dx, dy, wid, hei, fullwid, fullhei, opacity, argb, neg, + (w ? w->corner_radius : 0), + pict, (w ? w->paint.ptex : ps->root_tile_paint.ptex), reg_paint, #ifdef CONFIG_OPENGL w ? &ps->glx_prog_win : NULL #else NULL #endif - ); + , XCB_NONE); } /** @@ -259,6 +373,46 @@ static inline bool paint_isvalid(session_t *ps, const paint_t *ppaint) { return true; } +/** + * Rounde the corners of a window. + * Applies a fragment shader to discard corners + * + */ +static inline void +win_round_corners(session_t *ps, struct managed_win *w attr_unused, int shader_idx attr_unused, float cr attr_unused, + xcb_render_picture_t tgt_buffer attr_unused, const region_t *reg_paint) { +#ifdef CONFIG_OPENGL + const int16_t x = w->g.x; + const int16_t y = w->g.y; + const auto wid = to_u16_checked(w->widthb); + const auto hei = to_u16_checked(w->heightb); +#endif + + //log_debug("x:%d y:%d w:%d h:%d", x, y, wid, hei); + + switch (ps->o.backend) { + case BKEND_XRENDER: + case BKEND_XR_GLX_HYBRID: { + // XRender method is implemented inside render() + } break; +#ifdef CONFIG_OPENGL + case BKEND_GLX: + if (shader_idx == 1) { + glx_round_corners_dst1(ps, w, w->glx_texture_bg, shader_idx, x, y, wid, hei, + (float)ps->psglx->z - 0.5f, cr, reg_paint, &w->glx_round_cache); + } else { + glx_round_corners_dst0(ps, w, w->glx_texture_bg, shader_idx, x, y, wid, hei, + (float)ps->psglx->z - 0.5f, cr, reg_paint, &w->glx_round_cache); + } + break; +#endif + default: assert(0); + } +#ifndef CONFIG_OPENGL + (void)reg_paint; +#endif +} + /** * Paint a window itself and dim it if asked. */ @@ -302,7 +456,7 @@ void paint_one(session_t *ps, struct managed_win *w, const region_t *reg_paint) log_error("Window %#010x is missing painting data.", w->base.id); return; } - + const int x = w->g.x; const int y = w->g.y; const uint16_t wid = to_u16_checked(w->widthb); @@ -612,9 +766,29 @@ win_paint_shadow(session_t *ps, struct managed_win *w, region_t *reg_paint) { return; } - render(ps, 0, 0, w->g.x + w->shadow_dx, w->g.y + w->shadow_dy, w->shadow_width, - w->shadow_height, w->shadow_opacity, true, false, w->shadow_paint.pict, - w->shadow_paint.ptex, reg_paint, NULL); + xcb_render_picture_t td = XCB_NONE; + if (w->corner_radius) { + uint32_t max_ntraps = to_u32_checked(w->corner_radius); + xcb_render_trapezoid_t traps[4 * max_ntraps + 5]; + uint32_t n = make_rounded_window_shape(traps, max_ntraps, w->corner_radius, w->widthb, w->heightb); + + td = x_create_picture_with_standard(ps->c, ps->root, w->widthb, w->heightb, XCB_PICT_STANDARD_ARGB_32, 0, 0); + xcb_render_color_t trans = {.red = 0, .blue = 0, .green = 0, .alpha = 0}; + const xcb_rectangle_t rect = {.x = 0, .y = 0, .width = to_u16_checked(w->widthb), .height = to_u16_checked(w->heightb)}; + xcb_render_fill_rectangles(ps->c, XCB_RENDER_PICT_OP_SRC, td, trans, 1, &rect); + + xcb_render_trapezoids(ps->c, XCB_RENDER_PICT_OP_OVER, solid_picture(ps->c, ps->root, false, 1, 0, 0, 0), td, x_get_pictfmt_for_standard(ps->c, XCB_PICT_STANDARD_A_8), 0, 0, n, traps); + } + + clip_t clip = { .pict = td, -(w->shadow_dx), .y = -(w->shadow_dy) }; + + render(ps, w, 0, 0, w->g.x + w->shadow_dx, w->g.y + w->shadow_dy, w->shadow_width, + w->shadow_height, w->widthb, w->heightb, w->shadow_opacity, true, false, 0, w->shadow_paint.pict, + w->shadow_paint.ptex, reg_paint, NULL, w->corner_radius ? &clip : NULL); + + if(td){ + xcb_render_free_picture(ps->c, td); + } } /** @@ -634,7 +808,7 @@ win_paint_shadow(session_t *ps, struct managed_win *w, region_t *reg_paint) { */ static bool xr_blur_dst(session_t *ps, xcb_render_picture_t tgt_buffer, int16_t x, int16_t y, uint16_t wid, uint16_t hei, struct x_convolution_kernel **blur_kerns, - int nkernels, const region_t *reg_clip) { + int nkernels, const region_t *reg_clip, xcb_render_picture_t rounded) { assert(blur_kerns); assert(blur_kerns[0]); @@ -679,7 +853,8 @@ static bool xr_blur_dst(session_t *ps, xcb_render_picture_t tgt_buffer, int16_t } if (src_pict != tgt_buffer) - xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_SRC, src_pict, XCB_NONE, + //xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_SRC, src_pict, rounded, + xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_OVER, src_pict, rounded, tgt_buffer, 0, 0, 0, 0, x, y, wid, hei); free_picture(ps->c, &tmp_picture); @@ -697,6 +872,7 @@ win_blur_background(session_t *ps, struct managed_win *w, xcb_render_picture_t t const int16_t y = w->g.y; const auto wid = to_u16_checked(w->widthb); const auto hei = to_u16_checked(w->heightb); + const int cr = (w ? w->corner_radius : 0); double factor_center = 1.0; // Adjust blur strength according to window opacity, to make it appear @@ -728,13 +904,27 @@ win_blur_background(session_t *ps, struct managed_win *w, xcb_render_picture_t t &ps->blur_kerns_cache[i]); } + xcb_render_picture_t td = XCB_NONE; + if (cr) { + uint32_t max_ntraps = to_u32_checked(cr); + xcb_render_trapezoid_t traps[4 * max_ntraps + 5]; + uint32_t n = make_rounded_window_shape(traps, max_ntraps, cr, wid, hei); + + td = x_create_picture_with_standard(ps->c, ps->root, wid, hei, XCB_PICT_STANDARD_ARGB_32, 0, 0); + xcb_render_color_t trans = {.red = 0, .blue = 0, .green = 0, .alpha = 0}; + const xcb_rectangle_t rect = {.x = 0, .y = 0, .width = to_u16_checked(wid), .height = to_u16_checked(hei)}; + xcb_render_fill_rectangles(ps->c, XCB_RENDER_PICT_OP_SRC, td, trans, 1, &rect); + + xcb_render_trapezoids(ps->c, XCB_RENDER_PICT_OP_OVER, solid_picture(ps->c, ps->root, false, 1, 0, 0, 0), td, x_get_pictfmt_for_standard(ps->c, XCB_PICT_STANDARD_A_8), 0, 0, n, traps); + } + // Minimize the region we try to blur, if the window itself is not // opaque, only the frame is. - region_t reg_blur = win_get_bounding_shape_global_by_val(w); + region_t reg_blur = win_get_bounding_shape_global_by_val(w, true); if (w->mode == WMODE_FRAME_TRANS && !ps->o.force_win_blend) { region_t reg_noframe; pixman_region32_init(®_noframe); - win_get_region_noframe_local(w, ®_noframe); + win_get_region_noframe_local(w, ®_noframe, true); pixman_region32_translate(®_noframe, w->g.x, w->g.y); pixman_region32_subtract(®_blur, ®_blur, ®_noframe); pixman_region32_fini(®_noframe); @@ -742,14 +932,14 @@ win_blur_background(session_t *ps, struct managed_win *w, xcb_render_picture_t t // Translate global coordinates to local ones pixman_region32_translate(®_blur, -x, -y); xr_blur_dst(ps, tgt_buffer, x, y, wid, hei, ps->blur_kerns_cache, - ps->o.blur_kernel_count, ®_blur); + ps->o.blur_kernel_count, ®_blur, td); pixman_region32_clear(®_blur); } break; #ifdef CONFIG_OPENGL case BKEND_GLX: // TODO: Handle frame opacity glx_blur_dst(ps, x, y, wid, hei, (float)ps->psglx->z - 0.5f, - (float)factor_center, reg_paint, &w->glx_blur_cache); + (float)w->opacity, reg_paint, &w->glx_blur_cache); break; #endif default: assert(0); @@ -851,7 +1041,9 @@ void paint_all(session_t *ps, struct managed_win *t, bool ignore_damage) { // // Whether this is beneficial is to be determined XXX for (auto w = t; w; w = w->prev_trans) { - region_t bshape = win_get_bounding_shape_global_by_val(w); + region_t bshape_no_corners = win_get_bounding_shape_global_by_val(w, false); + region_t bshape_corners = win_get_bounding_shape_global_by_val(w, true); + // Painting shadow if (w->shadow) { // Lazy shadow building @@ -880,7 +1072,7 @@ void paint_all(session_t *ps, struct managed_win *t, bool ignore_damage) { // saving GPU power and handling shaped windows (XXX // unconfirmed) if (!ps->o.wintype_option[w->window_type].full_shadow) - pixman_region32_subtract(®_tmp, ®_tmp, &bshape); + pixman_region32_subtract(®_tmp, ®_tmp, &bshape_no_corners); if (ps->o.xinerama_shadow_crop && w->xinerama_scr >= 0 && w->xinerama_scr < ps->xinerama_nscrs) @@ -907,11 +1099,24 @@ void paint_all(session_t *ps, struct managed_win *t, bool ignore_damage) { // Remember, reg_ignore is the union of all windows above the current // window. pixman_region32_subtract(®_tmp, ®ion, w->reg_ignore); - pixman_region32_intersect(®_tmp, ®_tmp, &bshape); - pixman_region32_fini(&bshape); + pixman_region32_intersect(®_tmp, ®_tmp, &bshape_corners); + pixman_region32_fini(&bshape_corners); + pixman_region32_fini(&bshape_no_corners); if (pixman_region32_not_empty(®_tmp)) { set_tgt_clip(ps, ®_tmp); + + // If rounded corners backup the region first +#ifdef CONFIG_OPENGL + if (w->corner_radius > 0) { + const int16_t x = w->g.x; + const int16_t y = w->g.y; + const auto wid = to_u16_checked(w->widthb); + const auto hei = to_u16_checked(w->heightb); + glx_bind_texture(ps, &w->glx_texture_bg, x, y, wid, hei, false); + } +#endif + // Blur window background if (w->blur_background && (w->mode == WMODE_TRANS || @@ -921,6 +1126,12 @@ void paint_all(session_t *ps, struct managed_win *t, bool ignore_damage) { // Painting the window paint_one(ps, w, ®_tmp); + + // Round window corners + if (w->corner_radius > 0) { + win_round_corners(ps, w, 1, (float)w->corner_radius, + ps->tgt_buffer.pict, ®_tmp); + } } } @@ -1009,8 +1220,8 @@ void paint_all(session_t *ps, struct managed_win *t, bool ignore_damage) { else glFlush(); glXWaitX(); - glx_render(ps, ps->tgt_buffer.ptex, 0, 0, 0, 0, ps->root_width, - ps->root_height, 0, 1.0, false, false, ®ion, NULL); + glx_render(ps, t, ps->tgt_buffer.ptex, 0, 0, 0, 0, ps->root_width, + ps->root_height, 0, 1.0, false, false, 0, ®ion, NULL); // falls through case BKEND_GLX: glXSwapBuffers(ps->dpy, get_tgt_window(ps)); break; #endif @@ -1091,7 +1302,7 @@ bool init_render(session_t *ps) { if (ps->o.backend == BKEND_DUMMY) { return false; } - + // Initialize OpenGL as early as possible #ifdef CONFIG_OPENGL glxext_init(ps->dpy, ps->scr); @@ -1129,13 +1340,15 @@ bool init_render(session_t *ps) { } // Blur filter - if (ps->o.blur_method && ps->o.blur_method != BLUR_METHOD_KERNEL) { - log_warn("Old backends only support blur method \"kernel\". Your blur " + if (ps->o.blur_method && ps->o.blur_method != BLUR_METHOD_KERNEL && + ps->o.blur_method != BLUR_METHOD_DUAL_KAWASE && ps->o.blur_method != BLUR_METHOD_ALT_KAWASE) { + log_warn("Old backends only support blur methods \"kernel|kawase\". Your blur " "setting will not be applied"); ps->o.blur_method = BLUR_METHOD_NONE; } - if (ps->o.blur_method == BLUR_METHOD_KERNEL) { + if (ps->o.blur_method == BLUR_METHOD_KERNEL || ps->o.blur_method == BLUR_METHOD_DUAL_KAWASE + || ps->o.blur_method == BLUR_METHOD_ALT_KAWASE) { ps->blur_kerns_cache = ccalloc(ps->o.blur_kernel_count, struct x_convolution_kernel *); @@ -1174,6 +1387,18 @@ bool init_render(session_t *ps) { return false; } } + + // Initialize our rounded corners fragment shader + if (ps->o.corner_radius > 0 && ps->o.backend == BKEND_GLX) { +#ifdef CONFIG_OPENGL + if (!glx_init_rounded_corners(ps)) { + log_error("Failed to init rounded corners shader."); + return false; + } +#else + assert(false); +#endif + } return true; } diff --git a/src/render.h b/src/render.h index 92b71c8e01..e61783d1b7 100644 --- a/src/render.h +++ b/src/render.h @@ -25,9 +25,15 @@ typedef struct paint { #endif } paint_t; -void render(session_t *ps, int x, int y, int dx, int dy, int w, int h, double opacity, - bool argb, bool neg, xcb_render_picture_t pict, glx_texture_t *ptex, - const region_t *reg_paint, const glx_prog_main_t *pprogram); +typedef struct clip { + xcb_render_picture_t pict; + int x; + int y; +} clip_t; + +void render(session_t *ps, struct managed_win *, int x, int y, int dx, int dy, int w, int h, int fullw, int fullh, double opacity, + bool argb, bool neg, int cr, xcb_render_picture_t pict, glx_texture_t *ptex, + const region_t *reg_paint, const glx_prog_main_t *pprogram, clip_t *clip); void paint_one(session_t *ps, struct managed_win *w, const region_t *reg_paint); void paint_all(session_t *ps, struct managed_win *const t, bool ignore_damage); diff --git a/src/win.c b/src/win.c index 8dea68117c..a8b5275f3b 100644 --- a/src/win.c +++ b/src/win.c @@ -56,6 +56,17 @@ static const int WIN_GET_LEADER_MAX_RECURSION = 20; static const int ROUNDED_PIXELS = 1; static const double ROUNDED_PERCENT = 0.05; +/// Generate a "return by value" function, from a function that returns the +/// region via a region_t pointer argument. +/// Function signature has to be (win *, region_t *, bool) +#define gen_by_val_corners(fun) \ + region_t fun##_by_val(const struct managed_win *w, bool include_corners) { \ + region_t ret; \ + pixman_region32_init(&ret); \ + fun(w, &ret, include_corners); \ + return ret; \ + } + /// Generate a "return by value" function, from a function that returns the /// region via a region_t pointer argument. /// Function signature has to be (win *, region_t *) @@ -112,6 +123,19 @@ static void win_update_focused(session_t *ps, struct managed_win *w) { w->focused = true; } } + + // Always recalculate the window target opacity, since some opacity-related + // options depend on the output value of win_is_focused_real() instead of + // w->focused + auto opacity_target_old = w->opacity_target; + w->opacity_target = win_calc_opacity_target(ps, w, false); + if (opacity_target_old != w->opacity_target && w->state == WSTATE_MAPPED) { + // Only MAPPED can transition to FADING + w->state = WSTATE_FADING; + if (!ps->redirected) { + CHECK(!win_skip_fading(ps, w)); + } + } } /** @@ -173,16 +197,18 @@ static inline bool group_is_focused(session_t *ps, xcb_window_t leader) { /** * Get a rectangular region a window occupies, excluding shadow. */ -static void win_get_region_local(const struct managed_win *w, region_t *res) { +static void win_get_region_local(const struct managed_win *w, region_t *res, bool include_corners) { assert(w->widthb >= 0 && w->heightb >= 0); pixman_region32_fini(res); pixman_region32_init_rect(res, 0, 0, (uint)w->widthb, (uint)w->heightb); + + if(!include_corners) win_region_remove_corners(w, res); } /** * Get a rectangular region a window occupies, excluding frame and shadow. */ -void win_get_region_noframe_local(const struct managed_win *w, region_t *res) { +void win_get_region_noframe_local(const struct managed_win *w, region_t *res, bool include_corners) { const margin_t extents = win_calc_frame_extents(w); int x = extents.left; @@ -193,10 +219,11 @@ void win_get_region_noframe_local(const struct managed_win *w, region_t *res) { pixman_region32_fini(res); if (width > 0 && height > 0) { pixman_region32_init_rect(res, x, y, (uint)width, (uint)height); + if(!include_corners) win_region_remove_corners(w, res); } } -void win_get_region_frame_local(const struct managed_win *w, region_t *res) { +void win_get_region_frame_local(const struct managed_win *w, region_t *res, bool include_corners) { const margin_t extents = win_calc_frame_extents(w); auto outer_width = extents.left + extents.right + w->g.width; auto outer_height = extents.top + extents.bottom + w->g.height; @@ -219,10 +246,11 @@ void win_get_region_frame_local(const struct managed_win *w, region_t *res) { region_t reg_win; pixman_region32_init_rects(®_win, (rect_t[]){0, 0, outer_width, outer_height}, 1); pixman_region32_intersect(res, ®_win, res); + if(!include_corners) win_region_remove_corners(w, res); pixman_region32_fini(®_win); } -gen_by_val(win_get_region_frame_local); +gen_by_val_corners(win_get_region_frame_local); /** * Add a window to damaged area. @@ -538,11 +566,15 @@ bool win_client_has_alpha(const struct managed_win *w) { w->client_pictfmt->direct.alpha_mask; } -winmode_t win_calc_mode(const struct managed_win *w) { +winmode_t win_calc_mode(session_t *ps, const struct managed_win *w) { if (w->opacity < 1.0) { return WMODE_TRANS; } + if (ps->o.backend == BKEND_GLX && w->corner_radius > 0) { + return WMODE_TRANS; + } + if (win_has_alpha(w)) { if (w->client_win == XCB_NONE) { // This is a window not managed by the WM, and it has alpha, @@ -694,7 +726,7 @@ static void win_set_shadow(session_t *ps, struct managed_win *w, bool shadow_new w->shadow = shadow_new; assert(!w->shadow_image); assert(!w->win_image); - assert(w->flags & WIN_FLAGS_IMAGES_NONE); + //assert(w->flags & WIN_FLAGS_IMAGES_NONE); return; } @@ -875,6 +907,56 @@ static void win_determine_blur_background(session_t *ps, struct managed_win *w) win_set_blur_background(ps, w, blur_background_new); } +/** + * Determine if a window should have rounded corners. + */ +static void win_determine_rounded_corners(session_t *ps, struct managed_win *w) { + if (w->a.map_state != XCB_MAP_STATE_VIEWABLE /*|| ps->o.corner_radius == 0*/) + return; + + // Don't round full screen windows & excluded windows + if ((w && win_is_fullscreen(ps, w)) || + c2_match(ps, w, ps->o.rounded_corners_blacklist, NULL)) { + w->corner_radius = 0; + //log_warn("xy(%d %d) wh(%d %d) will NOT round corners", w->g.x, w->g.y, w->widthb, w->heightb); + } else { + w->corner_radius = ps->o.corner_radius; + //log_warn("xy(%d %d) wh(%d %d) will round corners", w->g.x, w->g.y, w->widthb, w->heightb); + + // HACK: we reset this so we can query the color once + // we query the color in glx_round_corners_dst0 using glReadPixels + //w->border_col = { -1., -1, -1, -1 }; + w->border_col[0] = w->border_col[1] = w->border_col[2] = w->border_col[3] = -1.0; + + // wintypes config section override + if (!safe_isnan(ps->o.wintype_option[w->window_type].corner_radius) && + ps->o.wintype_option[w->window_type].corner_radius >= 0) { + w->corner_radius = ps->o.wintype_option[w->window_type].corner_radius; + //log_warn("xy(%d %d) wh(%d %d) wintypes:corner_radius: %d", w->g.x, w->g.y, w->widthb, w->heightb, w->corner_radius); + } + + void *val = NULL; + if (c2_match(ps, w, ps->o.round_borders_rules, &val)) { + w->border_width = (uint16_t)((long)val); + //log_warn("xy(%d %d) wh(%d %d) border_width:rule:%d", w->g.x, w->g.y, w->widthb, w->heightb, w->g.border_width); + } else { + w->border_width = 0; + } + + if (w && c2_match(ps, w, ps->o.round_borders_blacklist, NULL)) { + w->round_borders = 0; + } else { + w->round_borders = ps->o.round_borders; + // wintypes config section override + if (!safe_isnan(ps->o.wintype_option[w->window_type].round_borders) && + ps->o.wintype_option[w->window_type].round_borders >= 0) { + w->round_borders = ps->o.wintype_option[w->window_type].round_borders; + //log_warn("wintypes:round_borders: %d", w->round_borders); + } + } + } +} + /** * Update window opacity according to opacity rules. */ @@ -908,6 +990,7 @@ void win_on_factor_change(session_t *ps, struct managed_win *w) { win_determine_shadow(ps, w); win_determine_invert_color(ps, w); win_determine_blur_background(ps, w); + win_determine_rounded_corners(ps, w); win_update_opacity_rule(ps, w); if (w->a.map_state == XCB_MAP_STATE_VIEWABLE) w->paint_excluded = c2_match(ps, w, ps->o.paint_blacklist, NULL); @@ -1008,11 +1091,9 @@ void win_mark_client(session_t *ps, struct managed_win *w, xcb_window_t client) win_update_leader(ps, w); // Get window name and class if we are tracking them - if (ps->o.track_wdata) { - win_update_name(ps, w); - win_get_class(ps, w); - win_get_role(ps, w); - } + win_update_name(ps, w); + win_get_class(ps, w); + win_get_role(ps, w); // Update everything related to conditions win_on_factor_change(ps, w); @@ -1229,6 +1310,8 @@ struct win *fill_win(session_t *ps, struct win *w) { // Initialized during paint .paint = PAINT_INIT, .shadow_paint = PAINT_INIT, + + .corner_radius = 0, }; assert(!w->destroyed); @@ -1518,7 +1601,7 @@ void win_update_bounding_shape(session_t *ps, struct managed_win *w) { pixman_region32_clear(&w->bounding_shape); // Start with the window rectangular region - win_get_region_local(w, &w->bounding_shape); + win_get_region_local(w, &w->bounding_shape, true); // Only request for a bounding region if the window is shaped // (while loop is used to avoid goto, not an actual loop) @@ -2067,7 +2150,7 @@ void map_win_start(session_t *ps, struct managed_win *w) { } // Update window mode here to check for ARGB windows - w->mode = win_calc_mode(w); + w->mode = win_calc_mode(ps, w); // Detect client window here instead of in add_win() as the client // window should have been prepared at this point @@ -2104,6 +2187,7 @@ void map_win_start(session_t *ps, struct managed_win *w) { w->opacity, w->opacity_target); win_determine_blur_background(ps, w); + win_determine_rounded_corners(ps, w); // Cannot set w->ever_damaged = false here, since window mapping could be // delayed, so a damage event might have already arrived before this function diff --git a/src/win.h b/src/win.h index 68037b19ee..f93bbb9893 100644 --- a/src/win.h +++ b/src/win.h @@ -40,13 +40,13 @@ typedef struct _glx_texture glx_texture_t; // it is very unideal for it to be here typedef struct { /// Framebuffer used for blurring. - GLuint fbo; + GLuint fbos[MAX_BLUR_PASS]; /// Textures used for blurring. - GLuint textures[2]; + GLuint textures[MAX_BLUR_PASS]; /// Width of the textures. - int width; + int width[MAX_BLUR_PASS]; /// Height of the textures. - int height; + int height[MAX_BLUR_PASS]; } glx_blur_cache_t; #endif @@ -202,6 +202,12 @@ struct managed_win { /// Last window opacity value set by the rules. double opacity_set; + /// Corner radius + int corner_radius; + bool round_borders; + float border_col[4]; + uint16_t border_width; + // Fading-related members /// Override value of window fade state. Set by D-Bus method calls. switch_t fade_force; @@ -249,6 +255,9 @@ struct managed_win { #ifdef CONFIG_OPENGL /// Textures and FBO background blur use. glx_blur_cache_t glx_blur_cache; + glx_blur_cache_t glx_round_cache; + /// Background texture of the window + glx_texture_t *glx_texture_bg; #endif }; @@ -279,7 +288,7 @@ bool must_use destroy_win_start(session_t *ps, struct win *w); void win_release_images(struct backend_base *base, struct managed_win *w); int win_update_name(session_t *ps, struct managed_win *w); int win_get_role(session_t *ps, struct managed_win *w); -winmode_t attr_pure win_calc_mode(const struct managed_win *w); +winmode_t attr_pure win_calc_mode(session_t *ps, const struct managed_win *w); void win_set_shadow_force(session_t *ps, struct managed_win *w, switch_t val); void win_set_fade_force(struct managed_win *w, switch_t val); void win_set_focused_force(session_t *ps, struct managed_win *w, switch_t val); @@ -357,12 +366,12 @@ void add_damage_from_win(session_t *ps, const struct managed_win *w); * * Return region in global coordinates. */ -void win_get_region_noframe_local(const struct managed_win *w, region_t *); +void win_get_region_noframe_local(const struct managed_win *w, region_t *, bool include_corners); /// Get the region for the frame of the window -void win_get_region_frame_local(const struct managed_win *w, region_t *res); +void win_get_region_frame_local(const struct managed_win *w, region_t *res, bool include_corners); /// Get the region for the frame of the window, by value -region_t win_get_region_frame_local_by_val(const struct managed_win *w); +region_t win_get_region_frame_local_by_val(const struct managed_win *w, bool include_corners); /** * Retrieve frame extents from a window. */ @@ -438,10 +447,25 @@ struct managed_win *attr_pure win_stack_find_next_managed(const session_t *ps, /// Free all resources in a struct win void free_win_res(session_t *ps, struct managed_win *w); -static inline region_t win_get_bounding_shape_global_by_val(struct managed_win *w) { +static inline void win_region_remove_corners(const struct managed_win *w, region_t *res) { + region_t corners; + pixman_region32_init_rects( + &corners, + (rect_t[]){ + {.x1 = 0, .y1 = 0, .x2 = w->corner_radius, .y2 = w->corner_radius}, + {.x1 = 0, .y1 = w->heightb-w->corner_radius, .x2 = w->corner_radius, .y2 = w->heightb}, + {.x1 = w->widthb-w->corner_radius, .y1 = 0, .x2 = w->widthb, .y2 = w->corner_radius}, + {.x1 = w->widthb-w->corner_radius, .y1 = w->heightb-w->corner_radius, .x2 = w->widthb, .y2 = w->heightb}, + }, + 4); + pixman_region32_subtract(res, res, &corners); +} + +static inline region_t win_get_bounding_shape_global_by_val(struct managed_win *w, bool include_corners) { region_t ret; pixman_region32_init(&ret); pixman_region32_copy(&ret, &w->bounding_shape); + if(!include_corners) win_region_remove_corners(w, &ret); pixman_region32_translate(&ret, w->g.x, w->g.y); return ret; } diff --git a/src/x.c b/src/x.c index fe162d269e..f195894fa1 100644 --- a/src/x.c +++ b/src/x.c @@ -167,6 +167,15 @@ xcb_visualid_t x_get_visual_for_standard(xcb_connection_t *c, xcb_pict_standard_ return x_get_visual_for_pictfmt(g_pictfmts, pictfmt->id); } +xcb_render_pictformat_t +x_get_pictfmt_for_standard(xcb_connection_t *c, xcb_pict_standard_t std) { + x_get_server_pictfmts(c); + + auto pictfmt = xcb_render_util_find_standard_format(g_pictfmts, std); + + return pictfmt->id; +} + int x_get_visual_depth(xcb_connection_t *c, xcb_visualid_t visual) { auto setup = xcb_get_setup(c); for (auto screen = xcb_setup_roots_iterator(setup); screen.rem; @@ -205,8 +214,7 @@ x_create_picture_with_pictfmt_and_pixmap(xcb_connection_t *c, c, tmp_picture, pixmap, pictfmt->id, valuemask, buf)); free(buf); if (e) { - x_print_error(e->full_sequence, e->major_code, e->minor_code, e->error_code); - log_error("failed to create picture"); + log_error_x_error(e, "failed to create picture"); return XCB_NONE; } return tmp_picture; @@ -231,6 +239,17 @@ x_create_picture_with_standard_and_pixmap(xcb_connection_t *c, xcb_pict_standard return x_create_picture_with_pictfmt_and_pixmap(c, pictfmt, pixmap, valuemask, attr); } +xcb_render_picture_t +x_create_picture_with_standard(xcb_connection_t *c, xcb_drawable_t d, int w, int h, + xcb_pict_standard_t standard, uint32_t valuemask, + const xcb_render_create_picture_value_list_t *attr) { + x_get_server_pictfmts(c); + + auto pictfmt = xcb_render_util_find_standard_format(g_pictfmts, standard); + assert(pictfmt); + return x_create_picture_with_pictfmt(c, d, w, h, pictfmt, valuemask, attr); +} + /** * Create an picture. */ @@ -265,7 +284,7 @@ bool x_fetch_region(xcb_connection_t *c, xcb_xfixes_region_t r, pixman_region32_ xcb_xfixes_fetch_region_reply_t *xr = xcb_xfixes_fetch_region_reply(c, xcb_xfixes_fetch_region(c, r), &e); if (!xr) { - log_error("Failed to fetch rectangles"); + log_error_x_error(e, "Failed to fetch rectangles"); return false; } @@ -301,9 +320,10 @@ void x_set_picture_clip_region(xcb_connection_t *c, xcb_render_picture_t pict, xcb_generic_error_t *e = xcb_request_check( c, xcb_render_set_picture_clip_rectangles_checked( c, pict, clip_x_origin, clip_y_origin, to_u32_checked(nrects), xrects)); - if (e) - log_error("Failed to set clip region"); - free(e); + if (e) { + log_error_x_error(e, "Failed to set clip region"); + free(e); + } free(xrects); return; } @@ -312,9 +332,10 @@ void x_clear_picture_clip_region(xcb_connection_t *c, xcb_render_picture_t pict) xcb_render_change_picture_value_list_t v = {.clipmask = XCB_NONE}; xcb_generic_error_t *e = xcb_request_check( c, xcb_render_change_picture(c, pict, XCB_RENDER_CP_CLIP_MASK, &v)); - if (e) - log_error("failed to clear clip region"); - free(e); + if (e) { + log_error_x_error(e, "failed to clear clip region"); + free(e); + } return; } @@ -324,25 +345,22 @@ enum { XSyncBadCounter = 0, }; /** - * X11 error handler function. + * Convert a X11 error to string * - * XXX consider making this error to string + * @return a pointer to a string. this pointer shouldn NOT be freed, same buffer is used + * for multiple calls to this function, */ -void x_print_error(unsigned long serial, uint8_t major, uint16_t minor, uint8_t error_code) { +static const char * +_x_strerror(unsigned long serial, uint8_t major, uint16_t minor, uint8_t error_code) { session_t *const ps = ps_g; int o = 0; const char *name = "Unknown"; - if (major == ps->composite_opcode && minor == XCB_COMPOSITE_REDIRECT_SUBWINDOWS) { - log_fatal("Another composite manager is already running " - "(and does not handle _NET_WM_CM_Sn correctly)"); - exit(1); - } - #define CASESTRRET2(s) \ case s: name = #s; break + // TODO separate error code out from session_t o = error_code - ps->xfixes_error; switch (o) { CASESTRRET2(XCB_XFIXES_BAD_REGION); } @@ -404,8 +422,27 @@ void x_print_error(unsigned long serial, uint8_t major, uint16_t minor, uint8_t #undef CASESTRRET2 - log_debug("X error %d %s request %d minor %d serial %lu", error_code, name, major, - minor, serial); + thread_local static char buffer[256]; + snprintf(buffer, sizeof(buffer), "X error %d %s request %d minor %d serial %lu", + error_code, name, major, minor, serial); + return buffer; +} + +/** + * Log a X11 error + */ +void x_print_error(unsigned long serial, uint8_t major, uint16_t minor, uint8_t error_code) { + log_debug("%s", _x_strerror(serial, major, minor, error_code)); +} + +/* + * Convert a xcb_generic_error_t to a string that describes the error + * + * @return a pointer to a string. this pointer shouldn NOT be freed, same buffer is used + * for multiple calls to this function, + */ +const char *x_strerror(xcb_generic_error_t *e) { + return _x_strerror(e->full_sequence, e->major_code, e->minor_code, e->error_code); } /** @@ -420,8 +457,7 @@ xcb_pixmap_t x_create_pixmap(xcb_connection_t *c, uint8_t depth, xcb_drawable_t if (err == NULL) return pix; - log_error("Failed to create pixmap:"); - x_print_error(err->sequence, err->major_code, err->minor_code, err->error_code); + log_error_x_error(err, "Failed to create pixmap"); free(err); return XCB_NONE; } @@ -492,25 +528,26 @@ bool x_fence_sync(xcb_connection_t *c, xcb_sync_fence_t f) { auto e = xcb_request_check(c, xcb_sync_trigger_fence_checked(c, f)); if (e) { - log_error("Failed to trigger the fence."); - free(e); - return false; + log_error_x_error(e, "Failed to trigger the fence"); + goto err; } e = xcb_request_check(c, xcb_sync_await_fence_checked(c, 1, &f)); if (e) { - log_error("Failed to await on a fence."); - free(e); - return false; + log_error_x_error(e, "Failed to await on a fence"); + goto err; } e = xcb_request_check(c, xcb_sync_reset_fence_checked(c, f)); if (e) { - log_error("Failed to reset the fence."); - free(e); - return false; + log_error_x_error(e, "Failed to reset the fence"); + goto err; } return true; + +err: + free(e); + return false; } // xcb-render specific macros diff --git a/src/x.h b/src/x.h index e98b21154d..20709eed8b 100644 --- a/src/x.h +++ b/src/x.h @@ -50,29 +50,34 @@ struct xvisual_info { #define XCB_AWAIT_VOID(func, c, ...) \ ({ \ - bool success = true; \ - __auto_type e = xcb_request_check(c, func##_checked(c, __VA_ARGS__)); \ - if (e) { \ - x_print_error(e->sequence, e->major_code, e->minor_code, \ - e->error_code); \ - free(e); \ - success = false; \ + bool __success = true; \ + __auto_type __e = xcb_request_check(c, func##_checked(c, __VA_ARGS__)); \ + if (__e) { \ + x_print_error(__e->sequence, __e->major_code, __e->minor_code, \ + __e->error_code); \ + free(__e); \ + __success = false; \ } \ - success; \ + __success; \ }) #define XCB_AWAIT(func, c, ...) \ ({ \ - xcb_generic_error_t *e = NULL; \ - __auto_type r = func##_reply(c, func(c, __VA_ARGS__), &e); \ - if (e) { \ - x_print_error(e->sequence, e->major_code, e->minor_code, \ - e->error_code); \ - free(e); \ + xcb_generic_error_t *__e = NULL; \ + __auto_type __r = func##_reply(c, func(c, __VA_ARGS__), &__e); \ + if (__e) { \ + x_print_error(__e->sequence, __e->major_code, __e->minor_code, \ + __e->error_code); \ + free(__e); \ } \ - r; \ + __r; \ }) +#define log_error_x_error(e, fmt, ...) \ + LOG(ERROR, fmt " (%s)", ##__VA_ARGS__, x_strerror(e)) +#define log_fatal_x_error(e, fmt, ...) \ + LOG(FATAL, fmt " (%s)", ##__VA_ARGS__, x_strerror(e)) + /// Wraps x_new_id. abort the program if x_new_id returns error static inline uint32_t x_new_id(xcb_connection_t *c) { auto ret = xcb_generate_id(c); @@ -167,6 +172,12 @@ x_create_picture_with_standard_and_pixmap(xcb_connection_t *, xcb_pict_standard_ const xcb_render_create_picture_value_list_t *attr) attr_nonnull(1); +xcb_render_picture_t +x_create_picture_with_standard(xcb_connection_t *c, xcb_drawable_t d, int w, int h, + xcb_pict_standard_t standard, uint32_t valuemask, + const xcb_render_create_picture_value_list_t *attr) + attr_nonnull(1); + /** * Create an picture. */ @@ -191,12 +202,18 @@ void x_set_picture_clip_region(xcb_connection_t *, xcb_render_picture_t, int16_t void x_clear_picture_clip_region(xcb_connection_t *, xcb_render_picture_t pict); /** - * X11 error handler function. - * - * XXX consider making this error to string + * Log a X11 error */ void x_print_error(unsigned long serial, uint8_t major, uint16_t minor, uint8_t error_code); +/* + * Convert a xcb_generic_error_t to a string that describes the error + * + * @return a pointer to a string. this pointer shouldn NOT be freed, same buffer is used + * for multiple calls to this function, + */ +const char *x_strerror(xcb_generic_error_t *e); + xcb_pixmap_t x_create_pixmap(xcb_connection_t *, uint8_t depth, xcb_drawable_t drawable, int width, int height); @@ -250,6 +267,8 @@ struct xvisual_info x_get_visual_info(xcb_connection_t *c, xcb_visualid_t visual xcb_visualid_t x_get_visual_for_standard(xcb_connection_t *c, xcb_pict_standard_t std); +xcb_render_pictformat_t x_get_pictfmt_for_standard(xcb_connection_t *c, xcb_pict_standard_t std); + xcb_screen_t *x_screen_of_display(xcb_connection_t *c, int screen); uint32_t attr_deprecated xcb_generate_id(xcb_connection_t *c); diff --git a/tests/testcases/common.py b/tests/testcases/common.py new file mode 100644 index 0000000000..cef5ac372f --- /dev/null +++ b/tests/testcases/common.py @@ -0,0 +1,43 @@ +import xcffib.xproto as xproto +import xcffib.randr as randr +import time +import random +import string +def set_window_name(conn, wid, name): + prop_name = "_NET_WM_NAME" + prop_name = conn.core.InternAtom(True, len(prop_name), prop_name).reply().atom + str_type = "STRING" + str_type = conn.core.InternAtom(True, len(str_type), str_type).reply().atom + conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid, prop_name, str_type, 8, len(name), name).check() + +def find_picom_window(conn): + prop_name = "WM_NAME" + prop_name = conn.core.InternAtom(True, len(prop_name), prop_name).reply().atom + setup = conn.get_setup() + root = setup.roots[0].root + windows = conn.core.QueryTree(root).reply() + + ext = xproto.xprotoExtension(conn) + for w in windows.children: + name = ext.GetProperty(False, w, prop_name, xproto.GetPropertyType.Any, 0, (2 ** 32) - 1).reply() + if name.value.buf() == b"picom": + return w + +def trigger_root_configure(conn): + setup = conn.get_setup() + root = setup.roots[0].root + # Xorg sends root ConfigureNotify when we add a new mode to an output + rr = conn(randr.key) + name = ''.join([random.choice(string.ascii_letters + string.digits) for n in range(0, 32)]) + mode_info = randr.ModeInfo.synthetic(id = 0, width = 1000, height = 1000, dot_clock = 0, + hsync_start = 0, hsync_end = 0, htotal = 0, hskew = 0, vsync_start = 0, vsync_end = 0, + vtotal = 0, name_len = len(name), mode_flags = 0) + + reply = rr.CreateMode(root, mode_info, len(name), name).reply() + mode = reply.mode + reply = rr.GetScreenResourcesCurrent(root).reply() + # our xvfb is setup to only have 1 output + output = reply.outputs[0] + rr.AddOutputModeChecked(output, mode).check() + rr.SetCrtcConfig(reply.crtcs[0], reply.timestamp, reply.config_timestamp, 0, 0, mode, randr.Rotation.Rotate_0, 1, [output]).reply() + diff --git a/tests/testcases/issue239.py b/tests/testcases/issue239.py index ac46428027..fe3f636494 100755 --- a/tests/testcases/issue239.py +++ b/tests/testcases/issue239.py @@ -3,6 +3,7 @@ import xcffib.xproto as xproto import xcffib import time +from common import set_window_name conn = xcffib.connect() setup = conn.get_setup() @@ -18,13 +19,7 @@ conn.core.CreateWindowChecked(depth, wid, root, 0, 0, 100, 100, 0, xproto.WindowClass.InputOutput, visual, 0, []).check() # Set Window name so it doesn't get a shadow -name = "_NET_WM_NAME" -name_atom = conn.core.InternAtom(True, len(name), name).reply().atom -str_type = "STRING" -str_type_atom = conn.core.InternAtom(True, len(str_type), str_type).reply().atom - -win_name = "NoShadow" -conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid, name_atom, str_type_atom, 8, len(win_name), win_name).check() +set_window_name(conn, wid, "NoShadow") # Map the window print("mapping") @@ -34,8 +29,7 @@ # Set the Window name so it gets a shadow print("set new name") -win_name = "YesShadow" -conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid, name_atom, str_type_atom, 8, len(win_name), win_name).check() +set_window_name(conn, wid, "YesShadow") # Unmap the window conn.core.UnmapWindowChecked(wid).check() diff --git a/tests/testcases/issue239_3.py b/tests/testcases/issue239_3.py index c3d6e151af..3bc966113a 100755 --- a/tests/testcases/issue239_3.py +++ b/tests/testcases/issue239_3.py @@ -3,6 +3,7 @@ import xcffib.xproto as xproto import xcffib import time +from common import set_window_name conn = xcffib.connect() setup = conn.get_setup() @@ -17,14 +18,8 @@ # Create a window conn.core.CreateWindowChecked(depth, wid, root, 0, 0, 100, 100, 0, xproto.WindowClass.InputOutput, visual, 0, []).check() -# Set Window name so it doesn't get a shadow -name = "_NET_WM_NAME" -name_atom = conn.core.InternAtom(True, len(name), name).reply().atom -str_type = "STRING" -str_type_atom = conn.core.InternAtom(True, len(str_type), str_type).reply().atom - -win_name = "YesShadow" -conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid, name_atom, str_type_atom, 8, len(win_name), win_name).check() +# Set Window name so it gets a shadow +set_window_name(conn, wid, "YesShadow") # Map the window print("mapping") @@ -33,15 +28,13 @@ time.sleep(0.5) print("set new name") -win_name = "NoShadow" -conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid, name_atom, str_type_atom, 8, len(win_name), win_name).check() +set_window_name(conn, wid, "NoShadow") time.sleep(0.5) # Set the Window name so it gets a shadow print("set new name") -win_name = "YesShadow" -conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid, name_atom, str_type_atom, 8, len(win_name), win_name).check() +set_window_name(conn, wid, "YesShadow") time.sleep(0.5) diff --git a/tests/testcases/issue239_3_norefresh.py b/tests/testcases/issue239_3_norefresh.py index 9e9bc2f7c7..edcf1c2300 100755 --- a/tests/testcases/issue239_3_norefresh.py +++ b/tests/testcases/issue239_3_norefresh.py @@ -3,6 +3,7 @@ import xcffib.xproto as xproto import xcffib import time +from common import set_window_name conn = xcffib.connect() setup = conn.get_setup() @@ -17,14 +18,8 @@ # Create a window conn.core.CreateWindowChecked(depth, wid, root, 0, 0, 100, 100, 0, xproto.WindowClass.InputOutput, visual, 0, []).check() -# Set Window name so it doesn't get a shadow -name = "_NET_WM_NAME" -name_atom = conn.core.InternAtom(True, len(name), name).reply().atom -str_type = "STRING" -str_type_atom = conn.core.InternAtom(True, len(str_type), str_type).reply().atom - -win_name = "YesShadow" -conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid, name_atom, str_type_atom, 8, len(win_name), win_name).check() +# Set Window name so it gets a shadow +set_window_name(conn, wid, "YesShadow") # Map the window print("mapping") @@ -33,13 +28,11 @@ time.sleep(0.5) print("set new name") -win_name = "NoShadow" -conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid, name_atom, str_type_atom, 8, len(win_name), win_name).check() +set_window_name(conn, wid, "NoShadow") # Set the Window name so it gets a shadow print("set new name") -win_name = "YesShadow" -conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid, name_atom, str_type_atom, 8, len(win_name), win_name).check() +set_window_name(conn, wid, "YesShadow") time.sleep(0.5)