From 0375dad5d14b46772d095bac242396c641ae5825 Mon Sep 17 00:00:00 2001 From: jon Date: Tue, 14 Jul 2020 18:04:45 +0200 Subject: [PATCH] Added New Backend Dual Kawase Blur + Rounded Corners - merged with Ibhagwan --- .github/workflows/coding-style-pr.yml | 13 - .github/workflows/coding-style.yml | 14 - .gitignore | 2 - .vscode/c_cpp_properties.json | 22 + .vscode/launch.json | 26 + .vscode/tasks.json | 25 + CONTRIBUTORS | 15 +- README.md | 28 +- dbus-examples/inverter.sh | 49 +- man/meson.build | 3 +- man/picom.1.asciidoc | 19 +- meson.build | 17 +- meson_options.txt | 2 - picom.sample.conf | 100 +- src/backend/backend.c | 68 +- src/backend/backend.h | 29 +- src/backend/backend_common.c | 96 ++ src/backend/backend_common.h | 11 + src/backend/driver.c | 8 - src/backend/driver.h | 3 - src/backend/dummy/dummy.c | 25 +- src/backend/gl/gl_common.c | 1157 +++++++++++++--- src/backend/gl/gl_common.h | 27 +- src/backend/gl/glx.c | 13 +- src/backend/xrender/xrender.c | 88 +- src/common.h | 11 +- src/config.c | 34 +- src/config.h | 65 +- src/config_libconfig.c | 107 +- src/event.c | 328 +++-- src/meson.build | 6 +- src/opengl.c | 1797 ++++++++++++++++++++++++- src/opengl.h | 73 +- src/options.c | 106 +- src/picom.c | 666 +++++---- src/picom.h | 11 +- src/render.c | 302 ++++- src/render.h | 12 +- src/utils.h | 3 + src/win.c | 364 +++-- src/win.h | 80 +- src/win_defs.h | 15 +- src/x.c | 107 +- src/x.h | 8 + tests/configs/issue314.conf | 6 - tests/configs/issue357.conf | 3 - tests/run_one_test.sh | 2 +- tests/run_tests.sh | 7 - tests/testcases/common.py | 45 +- tests/testcases/issue299.py | 112 -- tests/testcases/issue314.py | 44 - tests/testcases/issue314_2.py | 46 - tests/testcases/issue314_3.py | 63 - tests/testcases/issue357.py | 33 - 54 files changed, 4696 insertions(+), 1620 deletions(-) delete mode 100644 .github/workflows/coding-style-pr.yml delete mode 100644 .github/workflows/coding-style.yml create mode 100644 .vscode/c_cpp_properties.json create mode 100644 .vscode/launch.json create mode 100644 .vscode/tasks.json delete mode 100644 tests/configs/issue314.conf delete mode 100644 tests/configs/issue357.conf delete mode 100755 tests/testcases/issue299.py delete mode 100755 tests/testcases/issue314.py delete mode 100755 tests/testcases/issue314_2.py delete mode 100755 tests/testcases/issue314_3.py delete mode 100755 tests/testcases/issue357.py diff --git a/.github/workflows/coding-style-pr.yml b/.github/workflows/coding-style-pr.yml deleted file mode 100644 index 0712b38..0000000 --- a/.github/workflows/coding-style-pr.yml +++ /dev/null @@ -1,13 +0,0 @@ -name: coding-style -on: pull_request - -jobs: - check: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - run: git fetch --depth=1 origin ${{ github.event.pull_request.base.sha }} - - uses: yshui/git-clang-format-lint@v1.10 - with: - base: ${{ github.event.pull_request.base.sha }} diff --git a/.github/workflows/coding-style.yml b/.github/workflows/coding-style.yml deleted file mode 100644 index 7e85f12..0000000 --- a/.github/workflows/coding-style.yml +++ /dev/null @@ -1,14 +0,0 @@ -name: coding-style -on: push - -jobs: - check: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 2 - - uses: yshui/git-clang-format-lint@v1.10 - with: - base: ${{ github.event.ref }}~1 diff --git a/.gitignore b/.gitignore index 63ff3ad..7edc5b7 100644 --- a/.gitignore +++ b/.gitignore @@ -52,5 +52,3 @@ doxygen/ /src/backtrace-symbols.[ch] /compton*.trace *.orig -/tests/log -/tests/testcases/__pycache__/ diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..8400564 --- /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 0000000..96f1549 --- /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 0000000..4f32f69 --- /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/CONTRIBUTORS b/CONTRIBUTORS index db8eb90..03ac9e7 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -4,10 +4,7 @@ Open an issue or pull request if you don't want your name listed here. Adam Jackson Alexander Kapshuna Antonin Décimo -Antonio Vivace Avi-D-coder -Ben Friesen -Bernd Busse Brottweiler Carl Worth Christopher Jeffrey @@ -15,46 +12,36 @@ Corax26 Dan Elkouby Dana Jansens Dave Airlie -David Schlachter dolio Duncan -Dylan Araps Einar Lielmanis Eric Anholt Greg Flynn hasufell James Cloos Jamey Sharp -Jan Beich Jarrad Javeed Shaikh -Jerónimo Navarro Keith Packard Kevin Kelley mæp Mark Tiefenbruck Matthew Allum -Maxim Solovyov Michael Reed -Michele Lambertucci Namkhai Bourquin Nate Hart notfoss -@Paradigm0001 Patrick Collins Peter Mattern Phil Blundell Que Quotion Richard Grenville -Rytis Karpuska Scott Leggett Tasos Sahanidis +The Gitter Badger Tilman Sauerbeck Tim van Dalen -Tomas Janousek Uli Schlachter -Walter Lapchynski Will Dietz XeCycle Yuxuan Shui -ಠ_ಠ diff --git a/README.md b/README.md index d2aa63c..4b0f52e 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,21 @@ picom ======= -This is a forked version from Sandmark's picom branch, including Blackcapcoder's animation code inside. The animations here are further smoothed and time deltas reduced from 1ms to 40us for high refresh rates and buttery smooth transitions. +## Why another picom fork? -You'll need to run it with the experimental backend with: +TL;DR: rounded corners and dual_kawase blur on all backends. -`picom --experimental-backend` +This fork contains: + +- 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. + +- Rounded corners code from [sdhand](https://github.com/sdhand/picom) which is also ported to the experimetnal XRender backend. + +- New code for rounded corners on the glx backend using GLSL frangment shader + +For more information read [my reddit post](https://www.reddit.com/r/unixporn/comments/fs8trg/oc_comptonpicom_fork_with_both_tryone144s_dual/) -Your picom config can also now take advantage of some of the options that were previously implemented in blackcapcoder's compton version: -- [x] * `transition-length` length of animation in milliseconds (default: 300) -- [x] * `transition-pow-x` animation easing on the x-axis (default: 0.1) -- [x] * `transition-pow-y` animation easing on the y-axis (default: 0.1) -- [x] * `transition-pow-w` animation easing on the window width (default: 0.1) -- [x] * `transition-pow-h` animation easing on the window height (default: 0.1) -- [x] * `size-transition` whether to animate window size changes (default: true) -- [ ] * `spawn-center-screen` whether to animate new windows from the center of the screen (default: false) -- [ ] * `spawn-center` whether to animate new windows from their own center (default: true) -- [ ] * `no-scale-down` Whether to animate down scaling (some programs handle this poorly) (default: false) ----- **This is a development branch, bugs to be expected** This is forked from the original Compton because it seems to have become unmaintained. @@ -98,7 +95,6 @@ Assuming you already have all the usual building tools installed (e.g. gcc, pyth * xcb-image * xcb-present * xcb-xinerama -* xcb-glx * pixman * libdbus (optional, disable with the `-Ddbus=false` meson configure flag) * libconfig (optional, disable with the `-Dconfig_file=false` meson configure flag) @@ -110,7 +106,7 @@ Assuming you already have all the usual building tools installed (e.g. gcc, pyth 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 libxcb-glx0-dev libpixman-1-dev libdbus-1-dev libconfig-dev libgl1-mesa-dev libpcre2-dev libevdev-dev uthash-dev libev-dev libx11-xcb-dev +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 ``` To build the documents, you need `asciidoc` diff --git a/dbus-examples/inverter.sh b/dbus-examples/inverter.sh index e13cb2d..5e31508 100755 --- a/dbus-examples/inverter.sh +++ b/dbus-examples/inverter.sh @@ -1,41 +1,38 @@ #!/bin/sh -# == Declare stderr function === +# === Verify `compton --dbus` status === -stderr() { - printf "\033[1;31m%s\n\033[0m" "$@" >&2 -} - -# === Verify `picom --dbus` status === - -if [ -z "$(dbus-send --session --dest=org.freedesktop.DBus --type=method_call --print-reply /org/freedesktop/DBus org.freedesktop.DBus.ListNames | grep compton)" ]; then - stderr "picom DBus interface unavailable" - if [ -n "$(pgrep picom)" ]; then - stderr "picom running without dbus interface" - #killall picom & # Causes all windows to flicker away and come back ugly. - #picom --dbus & # Causes all windows to flicker away and come back beautiful +if [ -z "`dbus-send --session --dest=org.freedesktop.DBus --type=method_call --print-reply /org/freedesktop/DBus org.freedesktop.DBus.ListNames | grep compton`" ]; then + echo "compton DBus interface unavailable" + if [ -n "`pgrep picom`" ]; then + echo "compton running without dbus interface" + #killall compton & # Causes all windows to flicker away and come back ugly. + #compton --dbus & # Causes all windows to flicker away and come back beautiful else - stderr "picom not running" + echo "compton not running" fi - exit 1 + exit 1; fi # === Setup sed === -SED="${SED:-$(command -v gsed || printf 'sed')}" +if [ -z "$SED" ]; then + SED="sed" + command -v gsed > /dev/null && SED="gsed" +fi # === Get connection parameters === -dpy=$(printf "$DISPLAY" | tr -c '[:alnum:]' _) +dpy=$(echo -n "$DISPLAY" | tr -c '[:alnum:]' _) if [ -z "$dpy" ]; then - stderr "Cannot find display." - exit 1 + echo "Cannot find display." + exit 1; fi service="com.github.chjj.compton.${dpy}" interface="com.github.chjj.compton" -picom_dbus="dbus-send --print-reply --dest="${service}" / "${interface}"." +compton_dbus="dbus-send --print-reply --dest="${service}" / "${interface}"." type_win='uint32' type_enum='uint32' @@ -46,7 +43,7 @@ if [ -z "$1" -o "$1" = "selected" ]; then window=$(xwininfo -frame | sed -n 's/^xwininfo: Window id: \(0x[[:xdigit:]][[:xdigit:]]*\).*/\1/p') # Select window by mouse elif [ "$1" = "focused" ]; then # Ensure we are tracking focus - window=$(${picom_dbus}find_win string:focused | $SED -n 's/^[[:space:]]*'${type_win}'[[:space:]]*\([[:digit:]]*\).*/\1/p') # Query picom for the active window + window=$(${compton_dbus}find_win string:focused | $SED -n 's/^[[:space:]]*'${type_win}'[[:space:]]*\([[:digit:]]*\).*/\1/p') # Query compton for the active window elif echo "$1" | grep -Eiq '^([[:digit:]][[:digit:]]*|0x[[:xdigit:]][[:xdigit:]]*)$'; then window="$1" # Accept user-specified window-id if the format is correct else @@ -55,15 +52,15 @@ fi # Color invert the selected or focused window if [ -n "$window" ]; then - invert_status="$(${picom_dbus}win_get "${type_win}:${window}" string:invert_color | $SED -n 's/^[[:space:]]*boolean[[:space:]]*\([[:alpha:]]*\).*/\1/p')" + invert_status="$(${compton_dbus}win_get "${type_win}:${window}" string:invert_color | $SED -n 's/^[[:space:]]*boolean[[:space:]]*\([[:alpha:]]*\).*/\1/p')" if [ "$invert_status" = true ]; then invert=0 # Set the window to have normal color else invert=1 # Set the window to have inverted color fi - ${picom_dbus}win_set "${type_win}:${window}" string:invert_color_force "${type_enum}:${invert}" & + ${compton_dbus}win_set "${type_win}:${window}" string:invert_color_force "${type_enum}:${invert}" & else - stderr "Cannot find $1 window." - exit 1 + echo "Cannot find $1 window." + exit 1; fi -exit 0 +exit 0; diff --git a/man/meson.build b/man/meson.build index 6324830..7d00274 100644 --- a/man/meson.build +++ b/man/meson.build @@ -7,7 +7,6 @@ if get_option('with_docs') 'picom-version='+version, '--format', 'manpage', '@INPUT@', '-D', meson.current_build_dir()], - install: true, - install_dir: join_paths(get_option('mandir'), 'man1')) + install: true, install_dir: 'share/man/man1/') endforeach endif diff --git a/man/picom.1.asciidoc b/man/picom.1.asciidoc index f4a53d0..1f825e0 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. @@ -140,7 +152,7 @@ OPTIONS Use EWMH '_NET_ACTIVE_WINDOW' to determine currently focused window, rather than listening to 'FocusIn'/'FocusOut' event. Might have more accuracy, provided that the WM supports it. *--unredir-if-possible*:: - Unredirect all windows if a full-screen opaque window is detected, to maximize performance for full-screen windows. Known to cause flickering when redirecting/unredirecting windows. + Unredirect all windows if a full-screen opaque window is detected, to maximize performance for full-screen windows. Known to cause flickering when redirecting/unredirecting windows. *--paint-on-overlay* may make the flickering less obvious. *--unredir-if-possible-delay* 'MILLISECONDS':: Delay before unredirecting the window, in milliseconds. Defaults to 0. @@ -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/meson.build b/meson.build index c8bdb24..9e63e13 100644 --- a/meson.build +++ b/meson.build @@ -1,4 +1,4 @@ -project('picom', 'c', version: '8', +project('picom', 'c', version: '7', default_options: ['c_std=c11']) cc = meson.get_compiler('c') @@ -70,14 +70,11 @@ subdir('src') subdir('man') install_data('bin/picom-trans', install_dir: get_option('bindir')) +install_data('compton.desktop', install_dir: 'share/applications') install_data('picom.desktop', install_dir: 'share/applications') +install_data('media/icons/48x48/compton.png', + install_dir: 'share/icons/hicolor/48x48/apps') +install_data('media/compton.svg', + install_dir: 'share/icons/hicolor/scalable/apps') -if get_option('compton') - install_data('compton.desktop', install_dir: 'share/applications') - install_data('media/icons/48x48/compton.png', - install_dir: 'share/icons/hicolor/48x48/apps') - install_data('media/compton.svg', - install_dir: 'share/icons/hicolor/scalable/apps') - - meson.add_install_script('meson/install.sh') -endif +meson.add_install_script('meson/install.sh') diff --git a/meson_options.txt b/meson_options.txt index 9c3b0da..a53e35a 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -9,8 +9,6 @@ option('dbus', type: 'boolean', value: true, description: 'Enable support for D- option('xrescheck', type: 'boolean', value: false, description: 'Enable X resource leak checker (for debug only)') -option('compton', type: 'boolean', value: true, description: 'Install backwards compat with compton') - option('with_docs', type: 'boolean', value: false, description: 'Build documentation and man pages') option('modularize', type: 'boolean', value: false, description: 'Build with clang\'s module system') diff --git a/picom.sample.conf b/picom.sample.conf index eba24d1..754e62e 100644 --- a/picom.sample.conf +++ b/picom.sample.conf @@ -1,3 +1,26 @@ +################################# +# 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 = 'TelegramDesktop'", + "class_g = 'firefox'", + "class_g = 'Thunderbird'" +]; +round-borders = 1; +round-borders-exclude = [ + #"class_g = 'TelegramDesktop'", +]; + ################################# # Shadows # ################################# @@ -8,7 +31,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 +89,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 +113,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 +127,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 +160,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 +184,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 +216,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 +235,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 +274,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 @@ -263,7 +330,7 @@ refresh-rate = 0 # Unredirect all windows if a full-screen opaque window is detected, # to maximize performance for full-screen windows. Known to cause flickering -# when redirecting/unredirecting windows. +# when redirecting/unredirecting windows. paint-on-overlay may make the flickering less obvious. # # unredir-if-possible = false @@ -367,7 +434,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 +482,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 07f2f98..52524d8 100644 --- a/src/backend/backend.c +++ b/src/backend/backend.c @@ -55,7 +55,7 @@ region_t get_damage(session_t *ps, bool all_damage) { /// paint all windows void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { - if (ps->o.xrender_sync_fence) { + if (ps->o.xrender_sync_fence || (ps->drivers & DRIVER_NVIDIA)) { if (ps->xsync_exists && !x_fence_sync(ps->c, ps->sync_fence)) { log_error("x_fence_sync failed, xrender-sync-fence will be " "disabled from now on."); @@ -151,11 +151,8 @@ 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); - } else { - ps->backend_data->ops->fill(ps->backend_data, (struct color){0, 0, 0, 1}, - ®_paint); } // Windows are sorted from bottom to top @@ -171,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 @@ -190,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 @@ -204,36 +212,20 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) { // itself is not opaque, only the frame is. double blur_opacity = 1; - if (w->opacity < (1.0 / MAX_ALPHA)) { - // Hide blur for fully transparent windows. - blur_opacity = 0; - } else if (w->state == WSTATE_MAPPING) { + if (w->state == WSTATE_MAPPING) { // Gradually increase the blur intensity during // fading in. - assert(w->opacity <= w->opacity_target); blur_opacity = w->opacity / w->opacity_target; } else if (w->state == WSTATE_UNMAPPING || w->state == WSTATE_DESTROYING) { // Gradually decrease the blur intensity during // fading out. - assert(w->opacity <= w->opacity_target_old); - blur_opacity = w->opacity / w->opacity_target_old; - } else if (w->state == WSTATE_FADING) { - if (w->opacity < w->opacity_target && - w->opacity_target_old < (1.0 / MAX_ALPHA)) { - // Gradually increase the blur intensity during - // fading in. - assert(w->opacity <= w->opacity_target); - blur_opacity = w->opacity / w->opacity_target; - } else if (w->opacity > w->opacity_target && - w->opacity_target < (1.0 / MAX_ALPHA)) { - // Gradually decrease the blur intensity during - // fading out. - assert(w->opacity <= w->opacity_target_old); - blur_opacity = w->opacity / w->opacity_target_old; - } + 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; } - assert(blur_opacity >= 0 && blur_opacity <= 1); if (real_win_mode == WMODE_TRANS || ps->o.force_win_blend) { // We need to blur the bounding shape of the window @@ -249,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); @@ -305,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( @@ -314,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); } @@ -330,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) { @@ -379,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}); @@ -390,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 5a18e0a..b313d2d 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 471a677..416b2d3 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 28048b1..59b93ec 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/driver.c b/src/backend/driver.c index 34406bf..a91fd53 100644 --- a/src/backend/driver.c +++ b/src/backend/driver.c @@ -12,14 +12,6 @@ #include "compiler.h" #include "log.h" -/// Apply driver specified global workarounds. It's safe to call this multiple times. -void apply_driver_workarounds(struct session *ps, enum driver driver) { - if (driver & DRIVER_NVIDIA) { - setenv("__GL_YIELD", "usleep", true); - ps->o.xrender_sync_fence = true; - } -} - enum driver detect_driver(xcb_connection_t *c, backend_t *backend_data, xcb_window_t window) { enum driver ret = 0; // First we try doing backend agnostic detection using RANDR diff --git a/src/backend/driver.h b/src/backend/driver.h index e236a6c..29447e1 100644 --- a/src/backend/driver.h +++ b/src/backend/driver.h @@ -32,9 +32,6 @@ enum driver { /// Note, this is a best-effort test, so no guarantee all drivers will be detected. enum driver detect_driver(xcb_connection_t *, struct backend_base *, xcb_window_t); -/// Apply driver specified global workarounds. It's safe to call this multiple times. -void apply_driver_workarounds(struct session *ps, enum driver); - // Print driver names to stdout, for diagnostics static inline void print_drivers(enum driver drivers) { if (drivers & DRIVER_AMDGPU) { diff --git a/src/backend/dummy/dummy.c b/src/backend/dummy/dummy.c index 4b24f9f..73f2b64 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 61dc7ca..639639b 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) { @@ -207,6 +230,8 @@ _gl_average_texture_color(backend_t *base, GLuint source_texture, GLuint destina const int to_width = from_width > max_width ? from_width / 2 : from_width; const int to_height = from_height > max_height ? from_height / 2 : from_height; + glViewport(0, 0, width, height); + // Prepare coordinates GLint coord[] = { // top left @@ -393,6 +418,7 @@ static void _gl_compose(backend_t *base, struct gl_image *img, GLuint target, // x, y, width, height, dx, dy, ptex->width, ptex->height, z); // Bind texture + glViewport(0, 0, gd->width, gd->height); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, brightness); glActiveTexture(GL_TEXTURE0); @@ -417,7 +443,6 @@ static void _gl_compose(backend_t *base, struct gl_image *img, GLuint target, sizeof(GLint) * 4, (void *)(sizeof(GLint) * 2)); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, target); glDrawElements(GL_TRIANGLES, nrects * 6, GL_UNSIGNED_INT, NULL); - glDisableVertexAttribArray(vert_coord_loc); glDisableVertexAttribArray(vert_in_texcoord_loc); glBindVertexArray(0); @@ -502,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; @@ -536,24 +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; + bctx->fb_width = gd->width + bctx->resize_width * 2; + bctx->fb_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); + 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); + glUniformMatrix4fv(bctx->blur_shader[bctx->npasses - 1].projection_loc, + 1, false, projection_matrix2[0]); + } } // Remainder: regions are in Xorg coordinates @@ -562,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 = @@ -580,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]; @@ -620,67 +932,15 @@ 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); - - assert(bctx->blur_texture[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_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]; - } - - 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); - - 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); - } 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); - } - - 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; + 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); } - ret = true; - -end: glBindFramebuffer(GL_FRAMEBUFFER, 0); glBindTexture(GL_TEXTURE_2D, 0); glBindBuffer(GL_ARRAY_BUFFER, 0); @@ -688,17 +948,193 @@ end: glDeleteBuffers(4, bo); glBindVertexArray(0); glDeleteVertexArrays(2, vao); + glUseProgram(0); free(indices); free(coord); free(indices_resized); free(coord_resized); + gl_check_err(); + return ret; +} + +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) { + + struct gl_round_context *cctx = ctx_; + auto gd = (struct gl_data *)backend_data; + auto img = (struct gl_image*)image_data; + + //log_warn("r(%d) b(%d), wxy(%d %d) wwh(%d %d) img(%d %d)", + // w->corner_radius, w->g.border_width, w->g.x, w->g.y, + // w->widthb, w->heightb, img->inner->width, img->inner->height); + + int nrects; + const rect_t *rects; + rects = pixman_region32_rectangles((region_t *)reg_round, &nrects); + if (!nrects) { + // Nothing to paint + return false; + } + + GLuint target = gd->back_fbo; + int dst_x = w->g.x; + int dst_y = w->g.y; + + 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->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(2, bo); + + glUseProgram(0); + gl_check_err(); + + free(indices); + free(coord); + + 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; @@ -748,14 +1184,32 @@ static int gl_win_shader_from_string(const char *vshader_str, const char *fshade * Callback to run on root window size change. */ void gl_resize(struct gl_data *gd, int width, int height) { - GLint viewport_dimensions[2]; - glGetIntegerv(GL_MAX_VIEWPORT_DIMS, viewport_dimensions); - gd->height = height; gd->width = width; - assert(viewport_dimensions[0] >= gd->width); - assert(viewport_dimensions[1] >= gd->height); + // XXX: do we need projection matrix at all? + // Note: OpenGL matrices are column major + GLfloat projection_matrix[4][4] = {{2.0f / (GLfloat)width, 0, 0, 0}, + {0, 2.0f / (GLfloat)height, 0, 0}, + {0, 0, 0, 0}, + {-1, -1, 0, 1}}; + + // Update projection matrix in the win shader + glUseProgram(gd->win_shader.prog); + int pml = glGetUniformLocationChecked(gd->win_shader.prog, "projection"); + glUniformMatrix4fv(pml, 1, false, projection_matrix[0]); + + glUseProgram(gd->fill_shader.prog); + pml = glGetUniformLocationChecked(gd->fill_shader.prog, "projection"); + glUniformMatrix4fv(pml, 1, false, projection_matrix[0]); + + glUseProgram(gd->present_prog); + pml = glGetUniformLocationChecked(gd->present_prog, "projection"); + glUniformMatrix4fv(pml, 1, false, projection_matrix[0]); + + glUseProgram(gd->brightness_shader.prog); + pml = glGetUniformLocationChecked(gd->brightness_shader.prog, "projection"); + glUniformMatrix4fv(pml, 1, false, projection_matrix[0]); glBindTexture(GL_TEXTURE_2D, gd->back_texture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, width, height, 0, GL_BGR, @@ -855,7 +1309,6 @@ static void _gl_fill(backend_t *base, struct color c, const region_t *clip, GLui sizeof(*coord) * 2, (void *)0); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, target); glDrawElements(GL_TRIANGLES, nrects * 6, GL_UNSIGNED_INT, NULL); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); @@ -918,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; @@ -953,19 +1441,9 @@ 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; } - // Set projection matrix to gl viewport dimensions so we can use screen - // coordinates for all vertices - // Note: OpenGL matrices are column major - GLint viewport_dimensions[2]; - glGetIntegerv(GL_MAX_VIEWPORT_DIMS, viewport_dimensions); - GLfloat projection_matrix[4][4] = {{2.0f / (GLfloat)viewport_dimensions[0], 0, 0, 0}, - {0, 2.0f / (GLfloat)viewport_dimensions[1], 0, 0}, - {0, 0, 0, 0}, - {-1, -1, 0, 1}}; - ctx->blur_shader = ccalloc(max2(2, nkernels), gl_blur_shader_t); char *lc_numeric_old = strdup(setlocale(LC_NUMERIC, NULL)); @@ -1045,13 +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"); - - // Setup projection matrix - glUseProgram(pass->prog); - int pml = glGetUniformLocationChecked(pass->prog, "projection"); - glUniformMatrix4fv(pml, 1, false, projection_matrix[0]); - glUseProgram(0); - + pass->projection_loc = + glGetUniformLocationChecked(pass->prog, "projection"); ctx->resize_width += kern->w / 2; ctx->resize_height += kern->h / 2; } @@ -1064,34 +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"); - - // Setup projection matrix - glUseProgram(pass->prog); - int pml = glGetUniformLocationChecked(pass->prog, "projection"); - glUniformMatrix4fv(pml, 1, false, projection_matrix[0]); - glUseProgram(0); - + 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 @@ -1101,16 +1558,218 @@ out: free(kernels); } - 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; +} + +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) { + ctx = NULL; + } + + // Restore LC_NUMERIC + setlocale(LC_NUMERIC, lc_numeric_old); + free(lc_numeric_old); + + return success; +} + +void *gl_create_blur_context(backend_t *base, enum blur_method method, void *args) { + bool success; + auto gd = (struct gl_data *)base; + + auto ctx = ccalloc(1, struct gl_blur_context); + + if (!method || method >= BLUR_METHOD_INVALID) { + ctx->method = BLUR_METHOD_NONE; + return ctx; + } + + 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; } @@ -1121,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; @@ -1141,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); @@ -1176,14 +1994,6 @@ bool gl_init(struct gl_data *gd, session_t *ps) { glStencilMask(0x1); glStencilFunc(GL_EQUAL, 0x1, 0x1); - // Set gl viewport to the maximum supported size so we won't have to worry about - // it later on when the screen is resized. The corresponding projection matrix can - // be set now and won't have to be updated. Since fragments outside the target - // buffer are skipped anyways, this should have no impact on performance. - GLint viewport_dimensions[2]; - glGetIntegerv(GL_MAX_VIEWPORT_DIMS, viewport_dimensions); - glViewport(0, 0, viewport_dimensions[0], viewport_dimensions[1]); - // Clear screen glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); @@ -1196,41 +2006,23 @@ 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); - // Set projection matrix to gl viewport dimensions so we can use screen - // coordinates for all vertices - // Note: OpenGL matrices are column major - GLfloat projection_matrix[4][4] = {{2.0f / (GLfloat)viewport_dimensions[0], 0, 0, 0}, - {0, 2.0f / (GLfloat)viewport_dimensions[1], 0, 0}, - {0, 0, 0, 0}, - {-1, -1, 0, 1}}; - - // Initialize shaders gl_win_shader_from_string(vertex_shader, win_shader_glsl, &gd->win_shader); - int pml = glGetUniformLocationChecked(gd->win_shader.prog, "projection"); - glUseProgram(gd->win_shader.prog); - glUniformMatrix4fv(pml, 1, false, projection_matrix[0]); - glUseProgram(0); - gd->fill_shader.prog = gl_create_program_from_str(fill_vert, fill_frag); gd->fill_shader.color_loc = glGetUniformLocation(gd->fill_shader.prog, "color"); - pml = glGetUniformLocationChecked(gd->fill_shader.prog, "projection"); - glUseProgram(gd->fill_shader.prog); - glUniformMatrix4fv(pml, 1, false, projection_matrix[0]); - glUseProgram(0); gd->present_prog = gl_create_program_from_str(present_vertex_shader, dummy_frag); if (!gd->present_prog) { log_error("Failed to create the present shader"); return false; } - pml = glGetUniformLocationChecked(gd->present_prog, "projection"); glUseProgram(gd->present_prog); glUniform1i(glGetUniformLocationChecked(gd->present_prog, "tex"), 0); - glUniformMatrix4fv(pml, 1, false, projection_matrix[0]); glUseProgram(0); gd->brightness_shader.prog = @@ -1239,13 +2031,12 @@ bool gl_init(struct gl_data *gd, session_t *ps) { log_error("Failed to create the brightness shader"); return false; } - pml = glGetUniformLocationChecked(gd->brightness_shader.prog, "projection"); glUseProgram(gd->brightness_shader.prog); glUniform1i(glGetUniformLocationChecked(gd->brightness_shader.prog, "tex"), 0); - glUniformMatrix4fv(pml, 1, false, projection_matrix[0]); glUseProgram(0); - // Set up the size of the back texture + // Set up the size of the viewport. We do this last because it expects the blur + // textures are already set up. gl_resize(gd, ps->root_width, ps->root_height); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, gd->back_fbo); @@ -1325,7 +2116,6 @@ static inline void gl_image_decouple(backend_t *base, struct gl_image *img) { glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, new_tex->texture, 0); glDrawBuffer(GL_COLOR_ATTACHMENT0); - glClearColor(0, 0, 0, 0); glClear(GL_COLOR_BUFFER_BIT); @@ -1390,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 @@ -1401,6 +2191,7 @@ void gl_present(backend_t *base, const region_t *region) { sizeof(GLuint) * 6); } + glViewport(0, 0, gd->width, gd->height); glUseProgram(gd->present_prog); glBindTexture(GL_TEXTURE_2D, gd->back_texture); diff --git a/src/backend/gl/gl_common.h b/src/backend/gl/gl_common.h index c27c530..3e6ec0e 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; @@ -68,7 +83,7 @@ struct gl_data { backend_t base; // If we are using proprietary NVIDIA driver bool is_nvidia; - // Height and width of the root window + // Height and width of the viewport int height, width; gl_win_shader_t win_shader; gl_brightness_shader_t brightness_shader; @@ -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 1f37885..39f6ee2 100644 --- a/src/backend/gl/glx.c +++ b/src/backend/gl/glx.c @@ -45,6 +45,8 @@ struct _glx_data { Display *display; int screen; xcb_window_t target_win; + int glx_event; + int glx_error; GLXContext ctx; }; @@ -244,7 +246,7 @@ static backend_t *glx_init(session_t *ps) { XVisualInfo *pvis = NULL; // Check for GLX extension - if (!ps->glx_exists) { + if (!glXQueryExtension(ps->dpy, &gd->glx_event, &gd->glx_error)) { log_error("No GLX extension."); goto end; } @@ -466,7 +468,10 @@ static void glx_present(backend_t *base, const region_t *region attr_unused) { struct _glx_data *gd = (void *)base; gl_present(base, region); glXSwapBuffers(gd->display, gd->target_win); - glFinish(); + // XXX there should be no need to block, the core should wait for render to finish + if (!gd->gl.is_nvidia) { + glFinish(); + } } static int glx_buffer_age(backend_t *base) { @@ -489,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, @@ -496,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 a44dbc1..3e11caa 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/common.h b/src/common.h index 27d0076..31a8af8 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 @@ -214,7 +219,6 @@ typedef struct session { /// Custom GLX program used for painting window. // XXX should be in struct glx_session glx_prog_main_t glx_prog_win; - struct glx_fbconfig_info *argb_fbconfig; #endif /// Sync fence to sync draw operations xcb_sync_fence_t sync_fence; @@ -340,12 +344,14 @@ typedef struct session { int randr_error; /// Whether X Present extension exists. bool present_exists; +#ifdef CONFIG_OPENGL /// Whether X GLX extension exists. bool glx_exists; /// Event base number for X GLX extension. int glx_event; /// Error base number for X GLX extension. int glx_error; +#endif /// Whether X Xinerama extension exists. bool xinerama_exists; /// Xinerama screen regions. @@ -528,7 +534,8 @@ static inline void wintype_arr_enable(bool arr[]) { } } + /** - * Get current system clock in 40 microseconds. + * Get current system clock in milliseconds. */ int64_t get_time_ms(void); diff --git a/src/config.c b/src/config.c index db69f28..b4c25a5 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; } @@ -489,6 +494,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; + } } } @@ -497,15 +508,6 @@ char *parse_config(options_t *opt, const char *config_file, bool *shadow_enable, *opt = (struct options){ .backend = BKEND_XRENDER, .glx_no_stencil = false, - .transition_length = 300, - .transition_pow_x = 0.1, - .transition_pow_y = 0.1, - .transition_pow_w = 0.1, - .transition_pow_h = 0.1, - .size_transition = true, - .no_scale_down = false, - .spawn_center_screen = false, - .spawn_center = true, .mark_wmwin_focused = false, .mark_ovredir_focused = false, .detect_rounded_corners = false, @@ -519,6 +521,15 @@ char *parse_config(options_t *opt, const char *config_file, bool *shadow_enable, .benchmark = 0, .benchmark_wid = XCB_NONE, .logpath = NULL, + .transition_length = 300, + .transition_pow_x = 0.1, + .transition_pow_y = 0.1, + .transition_pow_w = 0.1, + .transition_pow_h = 0.1, + .size_transition = true, + .no_scale_down = false, + .spawn_center_screen = false, + .spawn_center = true, .refresh_rate = 0, .sw_opti = false, @@ -551,6 +562,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, @@ -569,7 +581,9 @@ char *parse_config(options_t *opt, const char *config_file, bool *shadow_enable, .no_ewmh_fullscreen = false, .track_leader = false, - }; + + .rounded_corners_blacklist = NULL, + .round_borders_blacklist = NULL}; char *ret = NULL; #ifdef CONFIG_LIBCONFIG diff --git a/src/config.h b/src/config.h index 3e54116..2d9ae46 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. @@ -87,7 +100,7 @@ typedef struct options { bool glx_no_stencil; /// Whether to avoid rebinding pixmap on window damage. bool glx_no_rebind_pixmap; - /// Length of window transitions + /// Length of window transitions int transition_length; /// For smoothing on the x-coordinate of window animations float transition_pow_x; @@ -207,6 +220,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; @@ -255,6 +270,15 @@ 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; } options_t; extern const char *const BACKEND_STRS[NUM_BKEND + 1]; @@ -331,3 +355,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 f5b1a41..3e744d2 100644 --- a/src/config_libconfig.c +++ b/src/config_libconfig.c @@ -284,6 +284,17 @@ 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); + } } } @@ -353,29 +364,31 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad // -O (fade_out_step) if (config_lookup_float(&cfg, "fade-out-step", &dval)) opt->fade_out_step = normalize_d(dval); - // --transition-length - if (config_lookup_int(&cfg, "transition-length", &ival)) - opt->transition_length = ival; - // --transition-pow-x - if (config_lookup_float(&cfg, "transition-pow-x", &dval)) - opt->transition_pow_x = dval; - // --transition-pow-y - if (config_lookup_float(&cfg, "transition-pow-y", &dval)) - opt->transition_pow_y = dval; - // --transition-pow-w - if (config_lookup_float(&cfg, "transition-pow-w", &dval)) - opt->transition_pow_w = dval; - // --transition-pow-h - if (config_lookup_float(&cfg, "transition-pow-h", &dval)) - opt->transition_pow_h = dval; - // --size-transition - lcfg_lookup_bool(&cfg, "size-transition", &opt->size_transition); - // --spawn-center-screen - lcfg_lookup_bool(&cfg, "spawn-center-screen", &opt->spawn_center_screen); - // --spawn-center - lcfg_lookup_bool(&cfg, "spawn-center", &opt->spawn_center); - // --no-scale-down - lcfg_lookup_bool(&cfg, "no-scale-down", &opt->no_scale_down); + + // --transition-length + if (config_lookup_int(&cfg, "transition-length", &ival)) + opt->transition_length = ival; + // --transition-pow-x + if (config_lookup_float(&cfg, "transition-pow-x", &dval)) + opt->transition_pow_x = dval; + // --transition-pow-y + if (config_lookup_float(&cfg, "transition-pow-y", &dval)) + opt->transition_pow_y = dval; + // --transition-pow-w + if (config_lookup_float(&cfg, "transition-pow-w", &dval)) + opt->transition_pow_w = dval; + // --transition-pow-h + if (config_lookup_float(&cfg, "transition-pow-h", &dval)) + opt->transition_pow_h = dval; + // --size-transition + lcfg_lookup_bool(&cfg, "size-transition", &opt->size_transition); + // --spawn-center-screen + lcfg_lookup_bool(&cfg, "spawn-center-screen", &opt->spawn_center_screen); + // --spawn-center + lcfg_lookup_bool(&cfg, "spawn-center", &opt->spawn_center); + // --no-scale-down + lcfg_lookup_bool(&cfg, "no-scale-down", &opt->no_scale_down); + // -r (shadow_radius) config_lookup_int(&cfg, "shadow-radius", &opt->shadow_radius); // -o (shadow_opacity) @@ -390,6 +403,14 @@ 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"); // -e (frame_opacity) config_lookup_float(&cfg, "frame-opacity", &opt->frame_opacity); // -c (shadow_enable) @@ -469,6 +490,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) { @@ -545,6 +567,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) { @@ -570,7 +596,6 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad lcfg_lookup_bool(&cfg, "glx-no-stencil", &opt->glx_no_stencil); // --glx-no-rebind-pixmap lcfg_lookup_bool(&cfg, "glx-no-rebind-pixmap", &opt->glx_no_rebind_pixmap); - lcfg_lookup_bool(&cfg, "force-win-blend", &opt->force_win_blend); // --glx-swap-method if (config_lookup_string(&cfg, "glx-swap-method", &sval)) { char *endptr; @@ -608,8 +633,8 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad } // --xrender-sync if (config_lookup_bool(&cfg, "xrender-sync", &ival) && ival) { - log_error("Please use xrender-sync-fence instead of xrender-sync."); - goto err; + log_warn("Please use xrender-sync-fence instead of xrender-sync."); + opt->xrender_sync_fence = true; } // --xrender-sync-fence lcfg_lookup_bool(&cfg, "xrender-sync-fence", &opt->xrender_sync_fence); @@ -617,21 +642,25 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad if (lcfg_lookup_bool(&cfg, "clear-shadow", &bval)) log_warn("\"clear-shadow\" is removed as an option, and is always" " enabled now. Consider removing it from your config file"); - if (lcfg_lookup_bool(&cfg, "paint-on-overlay", &bval)) { - log_error("\"paint-on-overlay\" has been removed as an option, and " - "the feature is enabled whenever possible"); - goto err; - } + if (lcfg_lookup_bool(&cfg, "paint-on-overlay", &bval)) + log_warn("\"paint-on-overlay\" has been removed as an option, and " + "is enabled whenever possible"); - if (config_lookup_float(&cfg, "alpha-step", &dval)) { - log_error("\"alpha-step\" has been removed, compton now tries to make use" - " of all alpha values"); - goto err; - } + if (config_lookup_float(&cfg, "alpha-step", &dval)) + log_warn("\"alpha-step\" has been removed, compton now tries to make use" + " of all alpha values"); - const char *deprecation_message attr_unused = + const char *deprecation_message = "has been removed. If you encounter problems " "without this feature, please feel free to open a bug report"; + if (lcfg_lookup_bool(&cfg, "glx-use-copysubbuffermesa", &bval) && bval) { + log_error("\"glx-use-copysubbuffermesa\" %s", deprecation_message); + return ERR_PTR(-1); + } + if (lcfg_lookup_bool(&cfg, "glx-copy-from-front", &bval) && bval) { + log_error("\"glx-copy-from-front\" %s", deprecation_message); + return ERR_PTR(-1); + } config_setting_t *blur_cfg = config_lookup(&cfg, "blur"); if (blur_cfg) { @@ -655,6 +684,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/event.c b/src/event.c index 05197e2..aee1910 100644 --- a/src/event.c +++ b/src/event.c @@ -100,9 +100,6 @@ static inline xcb_window_t attr_pure ev_window(session_t *ps, xcb_generic_event_ } } -#define CASESTRRET(s) \ - case s: return #s; - static inline const char *ev_name(session_t *ps, xcb_generic_event_t *ev) { static char buf[128]; switch (ev->response_type & 0x7f) { @@ -165,8 +162,6 @@ static inline const char *attr_pure ev_focus_detail_name(xcb_focus_in_event_t *e return "Unknown"; } -#undef CASESTRRET - static inline void ev_focus_in(session_t *ps, xcb_focus_in_event_t *ev) { log_debug("{ mode: %s, detail: %s }\n", ev_focus_mode_name(ev), ev_focus_detail_name(ev)); @@ -180,9 +175,8 @@ static inline void ev_focus_out(session_t *ps, xcb_focus_out_event_t *ev) { } static inline void ev_create_notify(session_t *ps, xcb_create_notify_event_t *ev) { - if (ev->parent == ps->root) { - add_win_top(ps, ev->window); - } + assert(ev->parent == ps->root); + add_win_top(ps, ev->window); } /// Handle configure event of a regular window @@ -202,105 +196,122 @@ static void configure_win(session_t *ps, xcb_configure_notify_event_t *ce) { auto mw = (struct managed_win *)w; - float t = get_time_ms(); - if (mw->oldX == -500 && mw->oldY == -500 && mw->oldW == 0 && mw->oldH == 0) { - if (!mw->isOld) { - /* mw->isOld = true; */ + float t = get_time_ms(); + if (mw->oldX == -10000 && mw->oldY == -10000 && mw->oldW == 0 && mw->oldH == 0) { + if (!mw->isOld) { + /* mw->isOld = true; */ - if (ps->o.spawn_center_screen) { - mw->oldX = ps->root_width/2; - mw->oldY = ps->root_height/2; - mw->oldW = 1; - mw->oldH = 1; - } else if (ps->o.spawn_center) { - mw->oldX = ce->x + ce->width/2; - mw->oldY = ce->y + ce->height/2; - mw->oldW = 1; - mw->oldH = 1; - } else { - mw->oldX = ce->x; - mw->oldY = ce->y; - mw->oldW = ce->width; - mw->oldH = ce->height; - } - } else { - mw->oldX = ce->x; - mw->oldY = ce->y; - mw->oldW = ce->width; - mw->oldH = ce->height; - } + if (ps->o.spawn_center_screen) { + mw->oldX = ps->root_width / 2; + mw->oldY = ps->root_height / 2; + mw->oldW = 1; + mw->oldH = 1; + } else if (ps->o.spawn_center) { + mw->oldX = ce->x + ce->width / 2; + mw->oldY = ce->y + ce->height / 2; + mw->oldW = 1; + mw->oldH = 1; + } else { + mw->oldX = ce->x; + mw->oldY = ce->y; + mw->oldW = ce->width; + mw->oldH = ce->height; + } + } else { + mw->oldX = ce->x; + mw->oldY = ce->y; + mw->oldW = ce->width; + mw->oldH = ce->height; + } - mw->newX = ce->x; - mw->newY = ce->y; - mw->newW = ce->width; - mw->newH = ce->height; - mw->moveTimeX = t; - mw->moveTimeY = t; - mw->moveTimeW = t; - mw->moveTimeH = t; - } else { - if (mw->newX == mw->g.x && mw->newY == mw->g.y) { - mw->oldX = mw->g.x; - mw->oldY = mw->g.y; - mw->oldW = mw->g.width; - mw->oldH = mw->g.height; - mw->moveTimeX = t; - mw->moveTimeY = t; - mw->moveTimeW = t; - mw->moveTimeH = t; - } - if (mw->newX != ce->x || mw->newY != ce->y || mw->newW != ce->width || mw->newH != ce->height) { - float moveDx = ((float) t - mw->moveTimeX) / ps->o.transition_length; - float moveDy = ((float) t - mw->moveTimeY) / ps->o.transition_length; - float moveDw = ((float) t - mw->moveTimeW) / ps->o.transition_length; - float moveDh = ((float) t - mw->moveTimeH) / ps->o.transition_length; + mw->newX = ce->x; + mw->newY = ce->y; + mw->newW = ce->width; + mw->newH = ce->height; + mw->moveTimeX = t; + mw->moveTimeY = t; + mw->moveTimeW = t; + mw->moveTimeH = t; + } else { + if (mw->newX == mw->g.x && mw->newY == mw->g.y) { + mw->oldX = mw->g.x; + mw->oldY = mw->g.y; + mw->oldW = mw->g.width; + mw->oldH = mw->g.height; + mw->moveTimeX = t; + mw->moveTimeY = t; + mw->moveTimeW = t; + mw->moveTimeH = t; + } + if (mw->newX != ce->x || mw->newY != ce->y || mw->newW != ce->width || + mw->newH != ce->height) { + float moveDx = ((float)t - mw->moveTimeX) / ps->o.transition_length; + float moveDy = ((float)t - mw->moveTimeY) / ps->o.transition_length; + float moveDw = ((float)t - mw->moveTimeW) / ps->o.transition_length; + float moveDh = ((float)t - mw->moveTimeH) / ps->o.transition_length; - if (mw->moveTimeX != 0.0 && moveDx < 1.0 && mw->oldX != mw->newX) { - float oldMoveDx = pow((float) (mw->newX - mw->g.x) / (float) (mw->newX - ce->x), 1 / ps->o.transition_pow_x); - float fakeT = (t - oldMoveDx * (float) ps->o.transition_length); - /* printf("X: %f,%f\n", fakeT, t); */ - mw->moveTimeX = isnanf(fakeT)? t : fakeT; - } else { - mw->moveTimeX = t; - } - if (mw->moveTimeY != 0.0 && moveDy < 1.0 && mw->oldY != mw->newY) { - float oldMoveDy = pow((float) (mw->newY - mw->g.y) / (float) (mw->newY - ce->y), 1 / ps->o.transition_pow_y); - float fakeT = (t - oldMoveDy * (float) ps->o.transition_length); - /* printf("Y: %f,%f\n", fakeT, t); */ - mw->moveTimeY = isnanf(fakeT)? t : fakeT; - } else { - mw->moveTimeY = t; - } - if (mw->moveTimeW != 0.0 && moveDw < 1.0 && mw->oldW != mw->newW) { - float oldMoveDw = pow((float) (mw->newW - mw->g.width) / (float) (mw->newW - ce->width), 1 / ps->o.transition_pow_w); - float fakeT = (t - oldMoveDw * (float) ps->o.transition_length); - /* printf("Y: %f,%f\n", fakeT, t); */ - mw->moveTimeW = isnanf(fakeT)? t : fakeT; - } else { - mw->moveTimeW = t; - } - if (mw->moveTimeH != 0.0 && moveDh < 1.0 && mw->oldH != mw->newH) { - float oldMoveDh = pow((float) (mw->newH - mw->g.height) / (float) (mw->newH - ce->height), 1 / ps->o.transition_pow_h); - float fakeT = (t - oldMoveDh * (float) ps->o.transition_length); - /* printf("Y: %f,%f\n", fakeT, t); */ - mw->moveTimeH = isnanf(fakeT)? t : fakeT; - } else { - mw->moveTimeH = t; - } + if (mw->moveTimeX != 0.0 && moveDx < 1.0 && mw->oldX != mw->newX) { + float oldMoveDx = pow( + (float)(mw->newX - mw->g.x) / (float)(mw->newX - ce->x), + 1 / ps->o.transition_pow_x); + float fakeT = + (t - oldMoveDx * (float)ps->o.transition_length); + /* printf("X: %f,%f\n", fakeT, t); */ + mw->moveTimeX = isnanf(fakeT) ? t : fakeT; + } else { + mw->moveTimeX = t; + } + if (mw->moveTimeY != 0.0 && moveDy < 1.0 && mw->oldY != mw->newY) { + float oldMoveDy = pow( + (float)(mw->newY - mw->g.y) / (float)(mw->newY - ce->y), + 1 / ps->o.transition_pow_y); + float fakeT = + (t - oldMoveDy * (float)ps->o.transition_length); + /* printf("Y: %f,%f\n", fakeT, t); */ + mw->moveTimeY = isnanf(fakeT) ? t : fakeT; + } else { + mw->moveTimeY = t; + } + if (mw->moveTimeW != 0.0 && moveDw < 1.0 && mw->oldW != mw->newW) { + float oldMoveDw = pow((float)(mw->newW - mw->g.width) / + (float)(mw->newW - ce->width), + 1 / ps->o.transition_pow_w); + float fakeT = + (t - oldMoveDw * (float)ps->o.transition_length); + /* printf("Y: %f,%f\n", fakeT, t); */ + mw->moveTimeW = isnanf(fakeT) ? t : fakeT; + } else { + mw->moveTimeW = t; + } + if (mw->moveTimeH != 0.0 && moveDh < 1.0 && mw->oldH != mw->newH) { + float oldMoveDh = pow((float)(mw->newH - mw->g.height) / + (float)(mw->newH - ce->height), + 1 / ps->o.transition_pow_h); + float fakeT = + (t - oldMoveDh * (float)ps->o.transition_length); + /* printf("Y: %f,%f\n", fakeT, t); */ + mw->moveTimeH = isnanf(fakeT) ? t : fakeT; + } else { + mw->moveTimeH = t; + } - mw->oldX = mw->newX; - mw->oldY = mw->newY; - mw->oldW = mw->newW; - mw->oldH = mw->newH; - mw->newX = ce->x; - mw->newY = ce->y; - mw->newW = ce->width; - mw->newH = ce->height; + mw->oldX = mw->newX; + mw->oldY = mw->newY; + mw->oldW = mw->newW; + mw->oldH = mw->newH; + mw->newX = ce->x; + mw->newY = ce->y; + mw->newW = ce->width; + mw->newH = ce->height; - if (ps->o.no_scale_down && mw->newW < mw->oldW) { mw->oldW = mw->newW; } - if (ps->o.no_scale_down && mw->newH < mw->oldH) { mw->oldH = mw->newH; } - } - } + if (ps->o.no_scale_down && mw->newW < mw->oldW) { + mw->oldW = mw->newW; + } + if (ps->o.no_scale_down && mw->newH < mw->oldH) { + mw->oldH = mw->newH; + } + } + } if (mw->state == WSTATE_UNMAPPED || mw->state == WSTATE_UNMAPPING || mw->state == WSTATE_DESTROYING) { @@ -353,7 +364,7 @@ static inline void ev_configure_notify(session_t *ps, xcb_configure_notify_event log_debug("{ send_event: %d, id: %#010x, above: %#010x, override_redirect: %d }", ev->event, ev->window, ev->above_sibling, ev->override_redirect); if (ev->window == ps->root) { - set_root_flags(ps, ROOT_FLAGS_CONFIGURED); + configure_root(ps, ev->width, ev->height); } else { configure_win(ps, ev); } @@ -361,23 +372,8 @@ static inline void ev_configure_notify(session_t *ps, xcb_configure_notify_event static inline void ev_destroy_notify(session_t *ps, xcb_destroy_notify_event_t *ev) { auto w = find_win(ps, ev->window); - auto mw = find_toplevel(ps, ev->window); - if (mw && mw->client_win == mw->base.id) { - // We only want _real_ frame window - assert(&mw->base == w); - mw = NULL; - } - assert(w == NULL || mw == NULL); - - if (w != NULL) { + if (w) { auto _ attr_unused = destroy_win_start(ps, w); - } else if (mw != NULL) { - win_unmark_client(ps, mw); - win_set_flags(mw, WIN_FLAGS_CLIENT_STALE); - ps->pending_updates = true; - } else { - log_debug("Received a destroy notify from an unknown window, %#010x", - ev->window); } } @@ -400,7 +396,7 @@ static inline void ev_map_notify(session_t *ps, xcb_map_notify_event_t *ev) { return; } - win_set_flags(w, WIN_FLAGS_MAPPED); + win_queue_update(w, WIN_UPDATE_MAP); // FocusIn/Out may be ignored when the window is unmapped, so we must // recheck focus here @@ -415,14 +411,8 @@ static inline void ev_unmap_notify(session_t *ps, xcb_unmap_notify_event_t *ev) } static inline void ev_reparent_notify(session_t *ps, xcb_reparent_notify_event_t *ev) { - log_debug("Window %#010x has new parent: %#010x, override_redirect: %d", - ev->window, ev->parent, ev->override_redirect); - auto w_top = find_toplevel(ps, ev->window); - if (w_top) { - win_unmark_client(ps, w_top); - win_set_flags(w_top, WIN_FLAGS_CLIENT_STALE); - ps->pending_updates = true; - } + log_debug("{ new_parent: %#010x, override_redirect: %d }", ev->parent, + ev->override_redirect); if (ev->parent == ps->root) { // X will generate reparent notifiy even if the parent didn't actually @@ -442,11 +432,7 @@ static inline void ev_reparent_notify(session_t *ps, xcb_reparent_notify_event_t { auto w = find_win(ps, ev->window); if (w) { - auto ret = destroy_win_start(ps, w); - if (!ret && w->managed) { - auto mw = (struct managed_win *)w; - CHECK(win_skip_fading(ps, mw)); - } + auto _ attr_unused = destroy_win_start(ps, w); } } @@ -455,35 +441,28 @@ static inline void ev_reparent_notify(session_t *ps, xcb_reparent_notify_event_t ps->c, ev->window, XCB_CW_EVENT_MASK, (const uint32_t[]){determine_evmask(ps, ev->window, WIN_EVMODE_UNKNOWN)}); - if (!wid_has_prop(ps, ev->window, ps->atoms->aWM_STATE)) { - log_debug("Window %#010x doesn't have WM_STATE property, it is " - "probably not a client window. But we will listen for " - "property change in case it gains one.", - ev->window); - xcb_change_window_attributes( - ps->c, ev->window, XCB_CW_EVENT_MASK, - (const uint32_t[]){determine_evmask(ps, ev->window, WIN_EVMODE_UNKNOWN) | - XCB_EVENT_MASK_PROPERTY_CHANGE}); - } else { - auto w_real_top = find_managed_window_or_parent(ps, ev->parent); - if (w_real_top && w_real_top->state != WSTATE_UNMAPPED && - w_real_top->state != WSTATE_UNMAPPING) { - log_debug("Mark window %#010x (%s) as having a stale " - "client", - w_real_top->base.id, w_real_top->name); - win_set_flags(w_real_top, WIN_FLAGS_CLIENT_STALE); - ps->pending_updates = true; - } else { - if (!w_real_top) - log_debug("parent %#010x not found", ev->parent); + // Check if the window is an undetected client window + // Firstly, check if it's a known client window + if (!find_toplevel(ps, ev->window)) { + // If not, look for its frame window + auto w_top = find_toplevel2(ps, ev->parent); + // If found, and the client window has not been determined, or its + // frame may not have a correct client, continue + if (w_top && + (!w_top->client_win || w_top->client_win == w_top->base.id)) { + // If it has WM_STATE, mark it the client window + if (wid_has_prop(ps, ev->window, ps->atoms->aWM_STATE)) { + w_top->wmwin = false; + win_unmark_client(ps, w_top); + win_mark_client(ps, w_top, ev->window); + } + // Otherwise, watch for WM_STATE on it else { - // Window is not currently mapped, unmark its - // client to trigger a client recheck when it is - // mapped later. - win_unmark_client(ps, w_real_top); - log_debug("parent %#010x (%s) is in state %d", - w_real_top->base.id, w_real_top->name, - w_real_top->state); + xcb_change_window_attributes( + ps->c, ev->window, XCB_CW_EVENT_MASK, + (const uint32_t[]){ + determine_evmask(ps, ev->window, WIN_EVMODE_UNKNOWN) | + XCB_EVENT_MASK_PROPERTY_CHANGE}); } } } @@ -576,12 +555,14 @@ static inline void ev_property_notify(session_t *ps, xcb_property_notify_event_t (const uint32_t[]){determine_evmask( ps, ev->window, WIN_EVMODE_UNKNOWN)}); - auto w_top = find_managed_window_or_parent(ps, ev->window); - // ev->window might have not been managed yet, in that case w_top - // would be NULL. - if (w_top) { - win_set_flags(w_top, WIN_FLAGS_CLIENT_STALE); - ps->pending_updates = true; + auto w_top = find_toplevel2(ps, ev->window); + // Initialize client_win as early as possible + if (w_top && + (!w_top->client_win || w_top->client_win == w_top->base.id) && + wid_has_prop(ps, ev->window, ps->atoms->aWM_STATE)) { + w_top->wmwin = false; + win_unmark_client(ps, w_top); + win_mark_client(ps, w_top, ev->window); } } } @@ -606,7 +587,14 @@ static inline void ev_property_notify(session_t *ps, xcb_property_notify_event_t win_update_opacity_prop(ps, w); // we cannot receive OPACITY change when window is destroyed assert(w->state != WSTATE_DESTROYING); - win_update_opacity_target(ps, w); + w->opacity_target = win_calc_opacity_target(ps, w, false); + if (w->state == WSTATE_MAPPED) { + // See the winstate_t transition table + w->state = WSTATE_FADING; + } + if (!ps->redirected) { + CHECK(!win_skip_fading(ps, w)); + } } } @@ -743,14 +731,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/meson.build b/src/meson.build index 0a882f9..4f6ac9a 100644 --- a/src/meson.build +++ b/src/meson.build @@ -16,7 +16,7 @@ cflags = [] required_xcb_packages = [ 'xcb-render', 'xcb-damage', 'xcb-randr', 'xcb-sync', 'xcb-composite', - 'xcb-shape', 'xcb-xinerama', 'xcb-xfixes', 'xcb-present', 'xcb-glx', 'xcb' + 'xcb-shape', 'xcb-xinerama', 'xcb-xfixes', 'xcb-present', 'xcb' ] required_packages = [ @@ -81,8 +81,8 @@ endif host_system = host_machine.system() if host_system == 'linux' cflags += ['-DHAS_INOTIFY'] -elif (host_system == 'freebsd' or host_system == 'netbsd' or - host_system == 'dragonfly' or host_system == 'openbsd') +elif host_system == 'freebsd' or host_system == 'netbsd' or + host_system == 'dragonfly' or host_system == 'openbsd' cflags += ['-DHAS_KQUEUE'] endif diff --git a/src/opengl.c b/src/opengl.c index 069eaac..afd6aff 100644 --- a/src/opengl.c +++ b/src/opengl.c @@ -51,8 +51,12 @@ bool glx_init(session_t *ps, bool need_render) { // Check for GLX extension if (!ps->glx_exists) { - log_error("No GLX extension."); - goto glx_init_end; + if (glXQueryExtension(ps->dpy, &ps->glx_event, &ps->glx_error)) + ps->glx_exists = true; + else { + log_error("No GLX extension."); + goto glx_init_end; + } } // Get XVisualInfo @@ -91,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; } } @@ -239,20 +265,30 @@ 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(); // Destroy GLX context if (ps->psglx->context) { - glXMakeCurrent(ps->dpy, None, NULL); glXDestroyContext(ps->dpy, ps->psglx->context); ps->psglx->context = NULL; } free(ps->psglx); ps->psglx = NULL; - ps->argb_fbconfig = NULL; } /** @@ -269,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]); @@ -302,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" @@ -311,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; @@ -397,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); @@ -415,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. */ @@ -449,6 +1160,114 @@ 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_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; + + glx_texture_t *ptex = *pptex; + + //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 + 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; + + 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; + } + + // 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. */ @@ -686,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); @@ -733,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 @@ -742,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); @@ -802,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); @@ -811,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... @@ -824,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; @@ -871,7 +1712,525 @@ 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); + 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; +} + +/** + * 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); @@ -889,6 +2248,386 @@ glx_blur_dst_end: 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_col[0] == -1.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->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_col[0] == -1.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->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 @@ -922,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; @@ -938,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 f50b092..1751e62 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); } @@ -196,7 +246,6 @@ static inline void free_texture(session_t *ps, glx_texture_t **pptex) { */ static inline void free_paint_glx(session_t *ps, paint_t *ppaint) { free_texture(ps, &ppaint->ptex); - ppaint->fbcfg = NULL; } /** @@ -207,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 436b82d..1aa7399 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" @@ -398,8 +415,10 @@ static const struct option longopts[] = { {"opengl", no_argument, NULL, 289}, {"backend", required_argument, NULL, 290}, {"glx-no-stencil", no_argument, NULL, 291}, + {"glx-copy-from-front", no_argument, NULL, 292}, {"benchmark", required_argument, NULL, 293}, {"benchmark-wid", required_argument, NULL, 294}, + {"glx-use-copysubbuffermesa", no_argument, NULL, 295}, {"blur-background-exclude", required_argument, NULL, 296}, {"active-opacity", required_argument, NULL, 297}, {"glx-no-rebind-pixmap", no_argument, NULL, 298}, @@ -435,6 +454,11 @@ 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}, {"experimental-backends", no_argument, NULL, 733}, {"monitor-repaint", no_argument, NULL, 800}, {"diagnostics", no_argument, NULL, 801}, @@ -467,23 +491,21 @@ bool get_early_config(int argc, char *const *argv, char **config_file, bool *all } else if (o == 'b') { *fork = true; } else if (o == 'd') { - log_error("-d is removed, please use the DISPLAY " - "environment variable"); - goto err; + log_warn("-d will be ignored, please use the DISPLAY " + "environment variable"); } else if (o == 314) { *all_xerrors = true; } else if (o == 318) { printf("%s\n", COMPTON_VERSION); return true; } else if (o == 'S') { - log_error("-S is no longer available"); - goto err; + log_warn("-S will be ignored"); } else if (o == 320) { - log_error("--no-name-pixmap is no longer available"); - goto err; + log_warn("--no-name-pixmap will be ignored"); } else if (o == '?' || o == ':') { usage(argv[0], 1); - goto err; + *exit_code = 1; + return true; } } @@ -491,13 +513,11 @@ bool get_early_config(int argc, char *const *argv, char **config_file, bool *all if (optind < argc) { // log is not initialized here yet fprintf(stderr, "picom doesn't accept positional arguments.\n"); - goto err; + *exit_code = 1; + return true; } return false; -err: - *exit_code = 1; - return true; } /** @@ -516,10 +536,9 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, // Parse commandline arguments. Range checking will be done later. - const char *deprecation_message attr_unused = - "has been removed. If you encounter problems " - "without this feature, please feel free to " - "open a bug report."; + const char *deprecation_message = "has been removed. If you encounter problems " + "without this feature, please feel free to " + "open a bug report."; optind = 1; while (-1 != (o = getopt_long(argc, argv, shortopts, longopts, &longopt_idx))) { switch (o) { @@ -645,14 +664,14 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, break; case 271: // --alpha-step - log_error("--alpha-step has been removed, we now tries to " + log_warn("--alpha-step has been removed, we now tries to " "make use of all alpha values"); - return false; - case 272: log_error("use of --dbe is deprecated"); return false; + break; + case 272: log_warn("use of --dbe is deprecated"); break; case 273: - log_error("--paint-on-overlay has been removed, the feature is enabled " - "whenever possible"); - return false; + log_warn("--paint-on-overlay has been removed, and is enabled " + "when possible"); + break; P_CASEBOOL(274, sw_opti); case 275: // --vsync-aggressive @@ -704,11 +723,19 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, exit(1); break; P_CASEBOOL(291, glx_no_stencil); + case 292: + log_error("--glx-copy-from-front %s", deprecation_message); + exit(1); + break; P_CASEINT(293, benchmark); case 294: // --benchmark-wid opt->benchmark_wid = (xcb_window_t)strtol(optarg, NULL, 0); break; + case 295: + log_error("--glx-use-copysubbuffermesa %s", deprecation_message); + exit(1); + break; case 296: // --blur-background-exclude condlst_add(&opt->blur_background_blacklist, optarg); @@ -787,8 +814,9 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable, P_CASEBOOL(311, vsync_use_glfinish); case 312: // --xrender-sync - log_error("Please use --xrender-sync-fence instead of --xrender-sync"); - return false; + log_warn("Please use --xrender-sync-fence instead of --xrender-sync"); + opt->xrender_sync_fence = true; + break; P_CASEBOOL(313, xrender_sync_fence); P_CASEBOOL(315, no_fading_destroyed_argb); P_CASEBOOL(316, force_win_blend); @@ -835,7 +863,15 @@ 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; + P_CASEBOOL(733, experimental_backends); P_CASEBOOL(800, monitor_repaint); case 801: opt->print_diagnostics = true; break; @@ -915,6 +951,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])) { @@ -924,6 +967,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 8a728cb..202a928 100644 --- a/src/picom.c +++ b/src/picom.c @@ -19,7 +19,6 @@ #include #include #include -#include #include #include #include @@ -99,9 +98,7 @@ const char *const BACKEND_STRS[] = {[BKEND_XRENDER] = "xrender", session_t *ps_g = NULL; void set_root_flags(session_t *ps, uint64_t flags) { - log_debug("Setting root flags: %lu", flags); ps->root_flags |= flags; - ps->pending_updates = true; } void quit(session_t *ps) { @@ -124,16 +121,15 @@ static inline void free_xinerama_info(session_t *ps) { } /** - * Get current system clock in 40micro second accuracy. + * Get current system clock in milliseconds. */ int64_t get_time_ms(void) { struct timespec tp; clock_gettime(CLOCK_MONOTONIC, &tp); -/* return (int64_t)tp.tv_sec * 1000 + (int64_t)tp.tv_nsec / 1000000; //ultra high refresh */ - return (int64_t)tp.tv_sec * 100 + (int64_t)tp.tv_nsec / 250000; //ultra high refresh + // return (int64_t)tp.tv_sec * 1000 + (int64_t)tp.tv_nsec / 1000000; + return (int64_t)tp.tv_sec * 100 + (int64_t)tp.tv_nsec / 250000; } - // XXX Move to x.c void cxinerama_upd_scrs(session_t *ps) { // XXX Consider deprecating Xinerama, switch to RandR when necessary @@ -182,7 +178,7 @@ static inline struct managed_win *find_win_all(session_t *ps, const xcb_window_t if (!w) w = find_toplevel(ps, wid); if (!w) - w = find_managed_window_or_parent(ps, wid); + w = find_toplevel2(ps, wid); return w; } @@ -305,9 +301,9 @@ uint32_t determine_evmask(session_t *ps, xcb_window_t wid, win_evmode_t mode) { struct managed_win *w = NULL; // Check if it's a mapped frame window - if (mode == WIN_EVMODE_FRAME || + if (WIN_EVMODE_FRAME == mode || ((w = find_managed_win(ps, wid)) && w->a.map_state == XCB_MAP_STATE_VIEWABLE)) { - evmask |= XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY; + evmask |= XCB_EVENT_MASK_PROPERTY_CHANGE; if (!ps->o.use_ewmh_active_win) { evmask |= XCB_EVENT_MASK_FOCUS_CHANGE; } @@ -377,219 +373,31 @@ static void recheck_focus(session_t *ps) { } /** - * Rebuild cached screen_reg. + * Look for the client window of a particular window. */ -static void rebuild_screen_reg(session_t *ps) { - get_screen_region(ps, &ps->screen_reg); -} - -/** - * Rebuild shadow_exclude_reg. - */ -static void rebuild_shadow_exclude_reg(session_t *ps) { - bool ret = parse_geometry(ps, ps->o.shadow_exclude_reg_str, &ps->shadow_exclude_reg); - if (!ret) - exit(1); -} - -/// Free up all the images and deinit the backend -static void destroy_backend(session_t *ps) { - win_stack_foreach_managed_safe(w, &ps->window_stack) { - // Wrapping up fading in progress - if (win_skip_fading(ps, w)) { - // `w` is freed by win_skip_fading - continue; - } - - if (ps->backend_data) { - if (w->state == WSTATE_MAPPED) { - win_release_images(ps->backend_data, w); - } else { - assert(!w->win_image); - assert(!w->shadow_image); - } - } - free_paint(ps, &w->paint); +xcb_window_t find_client_win(session_t *ps, xcb_window_t w) { + if (wid_has_prop(ps, w, ps->atoms->aWM_STATE)) { + return w; } - if (ps->backend_data && ps->root_image) { - ps->backend_data->ops->release_image(ps->backend_data, ps->root_image); - ps->root_image = NULL; + xcb_query_tree_reply_t *reply = + xcb_query_tree_reply(ps->c, xcb_query_tree(ps->c, w), NULL); + if (!reply) + return 0; + + xcb_window_t *children = xcb_query_tree_children(reply); + int nchildren = xcb_query_tree_children_length(reply); + int i; + xcb_window_t ret = 0; + + for (i = 0; i < nchildren; ++i) { + if ((ret = find_client_win(ps, children[i]))) + break; } - if (ps->backend_data) { - // deinit backend - if (ps->backend_blur_context) { - ps->backend_data->ops->destroy_blur_context( - ps->backend_data, ps->backend_blur_context); - ps->backend_blur_context = NULL; - } - ps->backend_data->ops->deinit(ps->backend_data); - ps->backend_data = NULL; - } -} + free(reply); -static bool initialize_blur(session_t *ps) { - struct kernel_blur_args kargs; - struct gaussian_blur_args gargs; - struct box_blur_args bargs; - - void *args = NULL; - switch (ps->o.blur_method) { - case BLUR_METHOD_BOX: - bargs.size = ps->o.blur_radius; - args = (void *)&bargs; - break; - case BLUR_METHOD_KERNEL: - kargs.kernel_count = ps->o.blur_kernel_count; - kargs.kernels = ps->o.blur_kerns; - args = (void *)&kargs; - break; - case BLUR_METHOD_GAUSSIAN: - gargs.size = ps->o.blur_radius; - gargs.deviation = ps->o.blur_deviation; - args = (void *)&gargs; - break; - default: return true; - } - - ps->backend_blur_context = ps->backend_data->ops->create_blur_context( - ps->backend_data, ps->o.blur_method, args); - return ps->backend_blur_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) { - assert(!ps->backend_data); - // Reinitialize win_data - assert(backend_list[ps->o.backend]); - ps->backend_data = backend_list[ps->o.backend]->init(ps); - if (!ps->backend_data) { - log_fatal("Failed to initialize backend, aborting..."); - quit(ps); - return false; - } - ps->backend_data->ops = backend_list[ps->o.backend]; - - if (!initialize_blur(ps)) { - log_fatal("Failed to prepare for background blur, aborting..."); - ps->backend_data->ops->deinit(ps->backend_data); - ps->backend_data = NULL; - quit(ps); - return false; - } - - // window_stack shouldn't include window that's - // not in the hash table at this point. Since - // there cannot be any fading windows. - HASH_ITER2(ps->windows, _w) { - if (!_w->managed) { - continue; - } - auto w = (struct managed_win *)_w; - assert(w->state == WSTATE_MAPPED || w->state == WSTATE_UNMAPPED); - if (w->state == WSTATE_MAPPED) { - // We need to reacquire image - log_debug("Marking window %#010x (%s) for update after " - "redirection", - w->base.id, w->name); - if (w->shadow) { - struct color c = { - .red = ps->o.shadow_red, - .green = ps->o.shadow_green, - .blue = ps->o.shadow_blue, - .alpha = ps->o.shadow_opacity, - }; - win_bind_shadow(ps->backend_data, w, c, - ps->gaussian_map); - } - - w->flags |= WIN_FLAGS_PIXMAP_STALE; - ps->pending_updates = true; - } - } - } - - // The old backends binds pixmap lazily, nothing to do here - return true; -} - -/// Handle configure event of the root window -static void configure_root(session_t *ps) { - auto r = XCB_AWAIT(xcb_get_geometry, ps->c, ps->root); - if (!r) { - log_fatal("Failed to fetch root geometry"); - abort(); - } - - log_info("Root configuration changed, new geometry: %dx%d", r->width, r->height); - bool has_root_change = false; - if (ps->redirected) { - // On root window changes - if (ps->o.experimental_backends) { - assert(ps->backend_data); - has_root_change = ps->backend_data->ops->root_change != NULL; - } else { - // Old backend can handle root change - has_root_change = true; - } - - if (!has_root_change) { - // deinit/reinit backend and free up resources if the backend - // cannot handle root change - destroy_backend(ps); - } - free_paint(ps, &ps->tgt_buffer); - } - - ps->root_width = r->width; - ps->root_height = r->height; - - rebuild_screen_reg(ps); - rebuild_shadow_exclude_reg(ps); - - // Invalidate reg_ignore from the top - auto top_w = win_stack_find_next_managed(ps, &ps->window_stack); - if (top_w) { - rc_region_unref(&top_w->reg_ignore); - top_w->reg_ignore_valid = false; - } - - if (ps->redirected) { - for (int i = 0; i < ps->ndamage; i++) { - pixman_region32_clear(&ps->damage_ring[i]); - } - ps->damage = ps->damage_ring + ps->ndamage - 1; -#ifdef CONFIG_OPENGL - // GLX root change callback - if (BKEND_GLX == ps->o.backend && !ps->o.experimental_backends) { - glx_on_root_change(ps); - } -#endif - if (has_root_change) { - if (ps->backend_data != NULL) { - ps->backend_data->ops->root_change(ps->backend_data, ps); - } - // Old backend's root_change is not a specific function - } else { - if (!initialize_backend(ps)) { - log_fatal("Failed to re-initialize backend after root " - "change, aborting..."); - ps->quit = true; - // TODO only event handlers should request ev_break, - // otherwise it's too hard to keep track of what can break - // the event loop - ev_break(ps->loop, EVBREAK_ALL); - return; - } - - // Re-acquire the root pixmap. - root_damaged(ps); - } - force_repaint(ps); - } - return; + return ret; } static void handle_root_flags(session_t *ps) { @@ -607,11 +415,6 @@ static void handle_root_flags(session_t *ps) { } ps->root_flags &= ~(uint64_t)ROOT_FLAGS_SCREEN_CHANGE; } - - if ((ps->root_flags & ROOT_FLAGS_CONFIGURED) != 0) { - configure_root(ps); - ps->root_flags &= ~(uint64_t)ROOT_FLAGS_CONFIGURED; - } } static struct managed_win *paint_preprocess(session_t *ps, bool *fade_running) { @@ -668,8 +471,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 @@ -678,56 +493,63 @@ static struct managed_win *paint_preprocess(session_t *ps, bool *fade_running) { } } - win_stack_foreach_managed(w, &ps->window_stack) { - bool posChanged = (w->oldX != -30000 && w->oldY != -30000 && w->oldW != 0 && w->oldH != 0) - && (w->g.x != w->newX || w->g.y != w->newY || w->g.width != w->newW || w->g.height != w->newH); + win_stack_foreach_managed(w, &ps->window_stack) { + bool posChanged = + (w->oldX != -10000 && w->oldY != -10000 && w->oldW != 0 && w->oldH != 0) && + (w->g.x != w->newX || w->g.y != w->newY || w->g.width != w->newW || + w->g.height != w->newH); - if (posChanged) { - float t = get_time_ms(); - float moveDx = (t - w->moveTimeX) / ps->o.transition_length; - float moveDy = (t - w->moveTimeY) / ps->o.transition_length; - float moveDw = (t - w->moveTimeW) / ps->o.transition_length; - float moveDh = (t - w->moveTimeH) / ps->o.transition_length; - if (moveDx >= 1.0) moveDx = 1.0; - if (moveDy >= 1.0) moveDy = 1.0; - if (moveDw >= 1.0) moveDw = 1.0; - if (moveDh >= 1.0) moveDh = 1.0; + if (posChanged) { + float t = get_time_ms(); + float moveDx = (t - w->moveTimeX) / ps->o.transition_length; + float moveDy = (t - w->moveTimeY) / ps->o.transition_length; + float moveDw = (t - w->moveTimeW) / ps->o.transition_length; + float moveDh = (t - w->moveTimeH) / ps->o.transition_length; + if (moveDx >= 1.0) + moveDx = 1.0; + if (moveDy >= 1.0) + moveDy = 1.0; + if (moveDw >= 1.0) + moveDw = 1.0; + if (moveDh >= 1.0) + moveDh = 1.0; - float q = pow (moveDx, ps->o.transition_pow_x); - float k = pow (moveDy, ps->o.transition_pow_y); - float g = pow (moveDw, ps->o.transition_pow_w); - float z = pow (moveDh, ps->o.transition_pow_h); + float q = pow(moveDx, ps->o.transition_pow_x); + float k = pow(moveDy, ps->o.transition_pow_y); + float g = pow(moveDw, ps->o.transition_pow_w); + float z = pow(moveDh, ps->o.transition_pow_h); - float x = (float) w->oldX * (1-q) + (float) w->newX * q; - float y = (float) w->oldY * (1-k) + (float) w->newY * k; - float W = (float) w->oldW * (1-g) + (float) w->newW * g; - float h = (float) w->oldH * (1-z) + (float) w->newH * z; + float x = (float)w->oldX * (1 - q) + (float)w->newX * q; + float y = (float)w->oldY * (1 - k) + (float)w->newY * k; + float W = (float)w->oldW * (1 - g) + (float)w->newW * g; + float h = (float)w->oldH * (1 - z) + (float)w->newH * z; - add_damage_from_win(ps, w); - w->g.x = (int) x; - w->g.y = (int) y; - if (ps->o.size_transition) { - w->g.width = (int) W; - w->g.height = (int) h; - } + add_damage_from_win(ps, w); + w->g.x = (int)x; + w->g.y = (int)y; + if (ps->o.size_transition) { + w->g.width = (int)W; + w->g.height = (int)h; + } - /* w->to_paint = true; */ - w->mode = WMODE_TRANS; - *fade_running = true; - } - // TODO - //if ((w->shadow && posChanged) || (ps->o.size_transition && w->pixmap_damaged)) { - // rc_region_unref(&w->extents); - // rc_region_unref(&w->border_size); - // w->extents = win_extents(ps, w); - // calc_win_size(ps, w); + /* w->to_paint = true; */ + w->mode = WMODE_TRANS; + *fade_running = true; + } + // TODO + // if ((w->shadow && posChanged) || (ps->o.size_transition && + // w->pixmap_damaged)) { + // rc_region_unref(&w->extents); + // rc_region_unref(&w->border_size); + // w->extents = win_extents(ps, w); + // calc_win_size(ps, w); - // if (ps->shape_exists && ps->o.shadow_ignore_shaped - // && ps->o.detect_rounded_corners && w->bounding_shaped) - // win_update_shape(ps, w); - //} - /* add_damage_win(ps, w); */ - } + // if (ps->shape_exists && ps->o.shadow_ignore_shaped + // && ps->o.detect_rounded_corners && w->bounding_shaped) + // win_update_shape(ps, w); + //} + /* add_damage_win(ps, w); */ + } // Opacity will not change, from now on. rc_region_t *last_reg_ignore = rc_region_new(); @@ -747,6 +569,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 @@ -801,10 +628,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); } @@ -894,6 +721,243 @@ static struct managed_win *paint_preprocess(session_t *ps, bool *fade_running) { return bottom; } +/** + * Rebuild cached screen_reg. + */ +static void rebuild_screen_reg(session_t *ps) { + get_screen_region(ps, &ps->screen_reg); +} + +/** + * Rebuild shadow_exclude_reg. + */ +static void rebuild_shadow_exclude_reg(session_t *ps) { + bool ret = parse_geometry(ps, ps->o.shadow_exclude_reg_str, &ps->shadow_exclude_reg); + if (!ret) + exit(1); +} + +/// Free up all the images and deinit the backend +static void destroy_backend(session_t *ps) { + win_stack_foreach_managed_safe(w, &ps->window_stack) { + // Wrapping up fading in progress + if (win_skip_fading(ps, w)) { + // `w` is freed by win_skip_fading + continue; + } + + if (ps->backend_data) { + if (w->state == WSTATE_MAPPED) { + win_release_images(ps->backend_data, w); + } else { + assert(!w->win_image); + assert(!w->shadow_image); + } + } + free_paint(ps, &w->paint); + } + + if (ps->backend_data && ps->root_image) { + ps->backend_data->ops->release_image(ps->backend_data, ps->root_image); + ps->root_image = NULL; + } + + if (ps->backend_data) { + // deinit backend + if (ps->backend_blur_context) { + ps->backend_data->ops->destroy_blur_context( + 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; + } +} + +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; + switch (ps->o.blur_method) { + case BLUR_METHOD_BOX: + bargs.size = ps->o.blur_radius; + args = (void *)&bargs; + break; + case BLUR_METHOD_KERNEL: + kargs.kernel_count = ps->o.blur_kernel_count; + kargs.kernels = ps->o.blur_kerns; + args = (void *)&kargs; + break; + case BLUR_METHOD_GAUSSIAN: + gargs.size = ps->o.blur_radius; + 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; + } + + ps->backend_blur_context = ps->backend_data->ops->create_blur_context( + ps->backend_data, ps->o.blur_method, args); + 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) { + assert(!ps->backend_data); + // Reinitialize win_data + assert(backend_list[ps->o.backend]); + ps->backend_data = backend_list[ps->o.backend]->init(ps); + if (!ps->backend_data) { + log_fatal("Failed to initialize backend, aborting..."); + quit(ps); + return false; + } + ps->backend_data->ops = backend_list[ps->o.backend]; + + if (!initialize_blur(ps)) { + log_fatal("Failed to prepare for background blur, aborting..."); + ps->backend_data->ops->deinit(ps->backend_data); + ps->backend_data = NULL; + quit(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. + HASH_ITER2(ps->windows, _w) { + if (!_w->managed) { + continue; + } + auto w = (struct managed_win *)_w; + assert(w->state == WSTATE_MAPPED || w->state == WSTATE_UNMAPPED); + if (w->state == WSTATE_MAPPED) { + // We need to reacquire image + log_debug("Marking window %#010x (%s) for update after " + "redirection", + w->base.id, w->name); + if (w->shadow) { + struct color c = { + .red = ps->o.shadow_red, + .green = ps->o.shadow_green, + .blue = ps->o.shadow_blue, + .alpha = ps->o.shadow_opacity, + }; + win_bind_shadow(ps->backend_data, w, c, + ps->gaussian_map); + } + + w->flags |= WIN_FLAGS_PIXMAP_STALE; + ps->pending_updates = true; + } + } + } + + // The old backends binds pixmap lazily, nothing to do here + return true; +} + +/// Handle configure event of a root window +void configure_root(session_t *ps, int width, int height) { + log_info("Root configuration changed, new geometry: %dx%d", width, height); + bool has_root_change = false; + if (ps->redirected) { + // On root window changes + if (ps->o.experimental_backends) { + assert(ps->backend_data); + has_root_change = ps->backend_data->ops->root_change != NULL; + } else { + // Old backend can handle root change + has_root_change = true; + } + + if (!has_root_change) { + // deinit/reinit backend and free up resources if the backend + // cannot handle root change + destroy_backend(ps); + } + free_paint(ps, &ps->tgt_buffer); + } + + ps->root_width = width; + ps->root_height = height; + + rebuild_screen_reg(ps); + rebuild_shadow_exclude_reg(ps); + + // Invalidate reg_ignore from the top + auto top_w = win_stack_find_next_managed(ps, &ps->window_stack); + if (top_w) { + rc_region_unref(&top_w->reg_ignore); + top_w->reg_ignore_valid = false; + } + + if (ps->redirected) { + for (int i = 0; i < ps->ndamage; i++) { + pixman_region32_clear(&ps->damage_ring[i]); + } + ps->damage = ps->damage_ring + ps->ndamage - 1; +#ifdef CONFIG_OPENGL + // GLX root change callback + if (BKEND_GLX == ps->o.backend && !ps->o.experimental_backends) { + glx_on_root_change(ps); + } +#endif + if (has_root_change) { + if (ps->backend_data != NULL) { + ps->backend_data->ops->root_change(ps->backend_data, ps); + } + // Old backend's root_change is not a specific function + } else { + if (!initialize_backend(ps)) { + log_fatal("Failed to re-initialize backend after root " + "change, aborting..."); + ps->quit = true; + // TODO only event handlers should request ev_break, + // otherwise it's too hard to keep track of what can break + // the event loop + ev_break(ps->loop, EVBREAK_ALL); + return; + } + + // Re-acquire the root pixmap. + root_damaged(ps); + } + force_repaint(ps); + } + return; +} + void root_damaged(session_t *ps) { if (ps->root_tile_paint.pixmap) { free_root_tile(ps); @@ -1277,7 +1341,6 @@ static bool redirect_start(session_t *ps) { // Re-detect driver since we now have a backend ps->drivers = detect_driver(ps->c, ps->backend_data, ps->root); - apply_driver_workarounds(ps, ps->drivers); root_damaged(ps); @@ -1363,6 +1426,12 @@ static void handle_new_windows(session_t *ps) { } static void refresh_windows(session_t *ps) { + win_stack_foreach_managed_safe(w, &ps->window_stack) { + win_process_updates(ps, w); + } +} + +static void refresh_stale_images(session_t *ps) { win_stack_foreach_managed(w, &ps->window_stack) { win_process_flags(ps, w); } @@ -1399,13 +1468,7 @@ static void handle_pending_updates(EV_P_ struct session *ps) { // Call fill_win on new windows handle_new_windows(ps); - // Handle screen changes - // This HAS TO be called before refresh_windows, as handle_root_flags - // could call configure_root, which will release images and mark them - // stale. - handle_root_flags(ps); - - // Process window flags + // Process window updates refresh_windows(ps); { @@ -1417,6 +1480,12 @@ static void handle_pending_updates(EV_P_ struct session *ps) { free(r); } + // Refresh pixmaps and shadows + refresh_stale_images(ps); + + // Handle screen changes + handle_root_flags(ps); + e = xcb_request_check(ps->c, xcb_ungrab_server_checked(ps->c)); if (e) { log_fatal_x_error(e, "failed to ungrab x server"); @@ -1670,9 +1739,11 @@ static session_t *session_init(int argc, char **argv, Display *dpy, .randr_exists = 0, .randr_event = 0, .randr_error = 0, +#ifdef CONFIG_OPENGL .glx_exists = false, .glx_event = 0, .glx_error = 0, +#endif .xrfilter_convolution_exists = false, .atoms_wintypes = {0}, @@ -1739,7 +1810,6 @@ static session_t *session_init(int argc, char **argv, Display *dpy, xcb_prefetch_extension_data(ps->c, &xcb_xinerama_id); xcb_prefetch_extension_data(ps->c, &xcb_present_id); xcb_prefetch_extension_data(ps->c, &xcb_sync_id); - xcb_prefetch_extension_data(ps->c, &xcb_glx_id); ext_info = xcb_get_extension_data(ps->c, &xcb_render_id); if (!ext_info || !ext_info->present) { @@ -1795,13 +1865,6 @@ static session_t *session_init(int argc, char **argv, Display *dpy, XCB_XFIXES_MINOR_VERSION) .sequence); - ext_info = xcb_get_extension_data(ps->c, &xcb_glx_id); - if (ext_info && ext_info->present) { - ps->glx_exists = true; - ps->glx_error = ext_info->first_error; - ps->glx_event = ext_info->first_event; - } - // Parse configuration file win_option_mask_t winopt_mask[NUM_WINTYPES] = {{0}}; bool shadow_enabled = false, fading_enable = false, hasneg = false; @@ -1869,6 +1932,8 @@ 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.focus_blacklist))) { log_error("Post-processing of conditionals failed, some of your rules " "might not work"); @@ -1925,24 +1990,23 @@ static session_t *session_init(int argc, char **argv, Display *dpy, } ps->sync_fence = XCB_NONE; - if (ps->xsync_exists) { + if (!ps->xsync_exists && ps->o.xrender_sync_fence) { + log_error("XSync extension not found. No XSync fence sync is " + "possible. (xrender-sync-fence can't be enabled)"); + ps->o.xrender_sync_fence = false; + } + + if (ps->o.xrender_sync_fence) { ps->sync_fence = x_new_id(ps->c); e = xcb_request_check( ps->c, xcb_sync_create_fence(ps->c, ps->root, ps->sync_fence, 0)); if (e) { - if (ps->o.xrender_sync_fence) { - log_error_x_error(e, "Failed to create a XSync fence. " - "xrender-sync-fence will be " - "disabled"); - ps->o.xrender_sync_fence = false; - } + 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); } - } else if (ps->o.xrender_sync_fence) { - log_error("XSync extension not found. No XSync fence sync is " - "possible. (xrender-sync-fence can't be enabled)"); - ps->o.xrender_sync_fence = false; } // Query X RandR @@ -2014,8 +2078,12 @@ static session_t *session_init(int argc, char **argv, Display *dpy, } } + // Target window must be initialized before the backend + // + // backend_operations::present == NULL means this backend doesn't need a target + // window; non experimental backends always need a target window + ps->drivers = detect_driver(ps->c, ps->backend_data, ps->root); - apply_driver_workarounds(ps, ps->drivers); // Initialize filters, must be preceded by OpenGL context creation if (!ps->o.experimental_backends && !init_render(ps)) { @@ -2132,7 +2200,6 @@ 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_x_error(e, "Failed to grab X server"); - free(e); goto err; } @@ -2151,7 +2218,6 @@ static session_t *session_init(int argc, char **argv, Display *dpy, if (e) { log_fatal_x_error(e, "Failed to ungrab server"); free(e); - goto err; } ps->server_grabbed = false; @@ -2241,6 +2307,8 @@ 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 tracked atom list { diff --git a/src/picom.h b/src/picom.h index fe39ded..45acdc3 100644 --- a/src/picom.h +++ b/src/picom.h @@ -25,11 +25,7 @@ #include "win.h" #include "x.h" -enum root_flags { - ROOT_FLAGS_SCREEN_CHANGE = 1, // Received RandR screen change notify, we - // use this to track refresh rate changes - ROOT_FLAGS_CONFIGURED = 2 // Received configure notify on the root window -}; +enum root_flags { ROOT_FLAGS_SCREEN_CHANGE = 1 }; // == Functions == // TODO move static inline functions that are only used in picom.c, into @@ -42,6 +38,11 @@ void add_damage(session_t *ps, const region_t *damage); uint32_t determine_evmask(session_t *ps, xcb_window_t wid, win_evmode_t mode); +xcb_window_t find_client_win(session_t *ps, xcb_window_t w); + +/// Handle configure event of a root window +void configure_root(session_t *ps, int width, int height); + void circulate_win(session_t *ps, xcb_circulate_notify_event_t *ce); void update_refresh_rate(session_t *ps); diff --git a/src/render.c b/src/render.c index ac4b232..39e90a4 100644 --- a/src/render.c +++ b/src/render.c @@ -48,14 +48,15 @@ static inline bool paint_bind_tex(session_t *ps, paint_t *ppaint, int wid, int h bool repeat, int depth, xcb_visualid_t visual, bool force) { #ifdef CONFIG_OPENGL // XXX This is a mess. But this will go away after the backend refactor. + static thread_local struct glx_fbconfig_info *argb_fbconfig = NULL; if (!ppaint->pixmap) return false; struct glx_fbconfig_info *fbcfg; if (!visual) { assert(depth == 32); - if (!ps->argb_fbconfig) { - ps->argb_fbconfig = + if (!argb_fbconfig) { + argb_fbconfig = glx_find_fbconfig(ps->dpy, ps->scr, (struct xvisual_info){.red_size = 8, .green_size = 8, @@ -63,11 +64,11 @@ static inline bool paint_bind_tex(session_t *ps, paint_t *ppaint, int wid, int h .alpha_size = 8, .visual_depth = 32}); } - if (!ps->argb_fbconfig) { + if (!argb_fbconfig) { log_error("Failed to find appropriate FBConfig for 32 bit depth"); return false; } - fbcfg = ps->argb_fbconfig; + fbcfg = argb_fbconfig; } else { auto m = x_get_visual_info(ps->c, visual); if (m.visual_depth < 0) { @@ -185,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 @@ -221,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); } /** @@ -258,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. */ @@ -529,8 +684,8 @@ static void paint_root(session_t *ps, const region_t *reg_paint) { * Generate shadow Picture for a window. */ static bool win_build_shadow(session_t *ps, struct managed_win *w, double opacity) { - /* const int width = w->widthb; */ - /* const int height = w->heightb; */ + //const int width = w->widthb; + //const int height = w->heightb; const int width = w->newW; // TODO! const int height = w->newH; // log_trace("(): building shadow for %s %d %d", w->name, width, height); @@ -613,9 +768,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); + } } /** @@ -635,7 +810,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]); @@ -680,7 +855,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); @@ -698,6 +874,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 @@ -729,13 +906,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); @@ -743,14 +934,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); @@ -852,7 +1043,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 @@ -881,7 +1074,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) @@ -908,12 +1101,25 @@ 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); reg_tmp = region; if (pixman_region32_not_empty(®_tmp) || true) { 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 || @@ -923,6 +1129,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); + } } } @@ -1011,8 +1223,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 @@ -1131,13 +1343,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 *); @@ -1176,6 +1390,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 92b71c8..e61783d 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/utils.h b/src/utils.h index 31bf274..657c0b2 100644 --- a/src/utils.h +++ b/src/utils.h @@ -33,6 +33,9 @@ safe_isnan(double a) { return __builtin_isnan(a); } +#define CASESTRRET(s) \ + case s: return #s + /// Same as assert(false), but make sure we abort _even in release builds_. /// Silence compiler warning caused by release builds making some code paths reachable. #define BUG() \ diff --git a/src/win.c b/src/win.c index 9151682..e1a2625 100644 --- a/src/win.c +++ b/src/win.c @@ -46,6 +46,9 @@ // TODO Make more window states internal struct managed_win_internal { struct managed_win base; + + /// A bit mask of unhandled window updates + uint_fast32_t pending_updates; }; #define OPAQUE (0xffffffff) @@ -53,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 *) @@ -109,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)); + } + } } /** @@ -170,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; @@ -190,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; @@ -216,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. @@ -244,7 +275,6 @@ static inline void win_release_pixmap(backend_t *base, struct managed_win *w) { if (w->win_image) { base->ops->release_image(base, w->win_image); w->win_image = NULL; - // Bypassing win_set_flags, because `w` might have been destroyed w->flags |= WIN_FLAGS_PIXMAP_NONE; } } @@ -254,7 +284,6 @@ static inline void win_release_shadow(backend_t *base, struct managed_win *w) { if (w->shadow_image) { base->ops->release_image(base, w->shadow_image); w->shadow_image = NULL; - // Bypassing win_set_flags, because `w` might have been destroyed w->flags |= WIN_FLAGS_SHADOW_NONE; } } @@ -275,11 +304,11 @@ static inline bool win_bind_pixmap(struct backend_base *b, struct managed_win *w b->ops->bind_pixmap(b, pixmap, x_get_visual_info(b->c, w->a.visual), true); if (!w->win_image) { log_error("Failed to bind pixmap"); - win_set_flags(w, WIN_FLAGS_IMAGE_ERROR); + w->flags |= WIN_FLAGS_IMAGE_ERROR; return false; } - win_clear_flags(w, WIN_FLAGS_PIXMAP_NONE); + w->flags &= ~WIN_FLAGS_PIXMAP_NONE; return true; } @@ -293,13 +322,13 @@ bool win_bind_shadow(struct backend_base *b, struct managed_win *w, struct color log_error("Failed to bind shadow image, shadow will be disabled for " "%#010x (%s)", w->base.id, w->name); - win_set_flags(w, WIN_FLAGS_SHADOW_NONE); + w->flags |= WIN_FLAGS_SHADOW_NONE; w->shadow = false; return false; } log_debug("New shadow for %#010x (%s)", w->base.id, w->name); - win_clear_flags(w, WIN_FLAGS_SHADOW_NONE); + w->flags &= ~WIN_FLAGS_SHADOW_NONE; return true; } @@ -309,38 +338,40 @@ void win_release_images(struct backend_base *backend, struct managed_win *w) { // But if we are not releasing any images anyway, we don't care about the stale // flags. - if (!win_check_flags_all(w, WIN_FLAGS_PIXMAP_NONE)) { - assert(!win_check_flags_all(w, WIN_FLAGS_PIXMAP_STALE)); + if ((w->flags & WIN_FLAGS_PIXMAP_NONE) == 0) { + assert((w->flags & WIN_FLAGS_PIXMAP_STALE) == 0); win_release_pixmap(backend, w); } - if (!win_check_flags_all(w, WIN_FLAGS_SHADOW_NONE)) { - assert(!win_check_flags_all(w, WIN_FLAGS_SHADOW_STALE)); + if ((w->flags & WIN_FLAGS_SHADOW_NONE) == 0) { + assert((w->flags & WIN_FLAGS_SHADOW_STALE) == 0); win_release_shadow(backend, w); } } void win_process_flags(session_t *ps, struct managed_win *w) { - if (win_check_flags_all(w, WIN_FLAGS_MAPPED)) { - map_win_start(ps, w); - win_clear_flags(w, WIN_FLAGS_MAPPED); + // Make sure all pending window updates are processed before this. Making this + // assumption simplifies some checks (e.g. whether window is mapped) + assert(((struct managed_win_internal *)w)->pending_updates == 0); + + if (!w->flags || (w->flags & WIN_FLAGS_IMAGE_ERROR) != 0) { + return; } // Not a loop - while (win_check_flags_any(w, WIN_FLAGS_IMAGES_STALE) && - !win_check_flags_all(w, WIN_FLAGS_IMAGE_ERROR)) { + while ((w->flags & WIN_FLAGS_IMAGES_STALE) != 0) { // Image needs to be updated, update it. if (!ps->backend_data) { // We are using legacy backend, nothing to do here. break; } - if (win_check_flags_all(w, WIN_FLAGS_PIXMAP_STALE)) { + if ((w->flags & WIN_FLAGS_PIXMAP_STALE) != 0) { // Check to make sure the window is still mapped, otherwise we // won't be able to rebind pixmap after releasing it, yet we might // still need the pixmap for rendering. assert(w->state != WSTATE_UNMAPPING && w->state != WSTATE_DESTROYING); - if (!win_check_flags_all(w, WIN_FLAGS_PIXMAP_NONE)) { + if ((w->flags & WIN_FLAGS_PIXMAP_NONE) == 0) { // Must release images first, otherwise breaks // NVIDIA driver win_release_pixmap(ps->backend_data, w); @@ -348,8 +379,8 @@ void win_process_flags(session_t *ps, struct managed_win *w) { win_bind_pixmap(ps->backend_data, w); } - if (win_check_flags_all(w, WIN_FLAGS_SHADOW_STALE)) { - if (!win_check_flags_all(w, WIN_FLAGS_SHADOW_NONE)) { + if ((w->flags & WIN_FLAGS_SHADOW_STALE) != 0) { + if ((w->flags & WIN_FLAGS_SHADOW_NONE) == 0) { win_release_shadow(ps->backend_data, w); } if (w->shadow) { @@ -367,14 +398,7 @@ void win_process_flags(session_t *ps, struct managed_win *w) { } // Clear stale image flags - if (win_check_flags_any(w, WIN_FLAGS_IMAGES_STALE)) { - win_clear_flags(w, WIN_FLAGS_IMAGES_STALE); - } - - if (win_check_flags_all(w, WIN_FLAGS_CLIENT_STALE)) { - win_recheck_client(ps, w); - win_clear_flags(w, WIN_FLAGS_CLIENT_STALE); - } + w->flags &= ~WIN_FLAGS_IMAGES_STALE; } /** @@ -542,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, @@ -586,17 +614,18 @@ winmode_t win_calc_mode(const struct managed_win *w) { * * @param ps current session * @param w struct _win object representing the window + * @param ignore_state whether window state should be ignored in opacity calculation * * @return target opacity */ -double win_calc_opacity_target(session_t *ps, const struct managed_win *w) { +double win_calc_opacity_target(session_t *ps, const struct managed_win *w, bool ignore_state) { double opacity = 1; - if (w->state == WSTATE_UNMAPPED) { + if (w->state == WSTATE_UNMAPPED && !ignore_state) { // be consistent return 0; } - if (w->state == WSTATE_UNMAPPING || w->state == WSTATE_DESTROYING) { + if ((w->state == WSTATE_UNMAPPING || w->state == WSTATE_DESTROYING) && !ignore_state) { return 0; } // Try obeying opacity property and window type opacity firstly @@ -697,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(win_check_flags_all(w, WIN_FLAGS_IMAGES_NONE)); + //assert(w->flags & WIN_FLAGS_IMAGES_NONE); return; } @@ -723,14 +752,14 @@ static void win_set_shadow(session_t *ps, struct managed_win *w, bool shadow_new // asserting the existence of the shadow image. if (w->shadow) { // Mark the new extents as damaged if the shadow is added - assert(!w->shadow_image || win_check_flags_all(w, WIN_FLAGS_SHADOW_STALE) || + assert(!w->shadow_image || (w->flags & WIN_FLAGS_SHADOW_STALE) || !ps->o.experimental_backends); pixman_region32_clear(&extents); win_extents(w, &extents); add_damage_from_win(ps, w); } else { // Mark the old extents as damaged if the shadow is removed - assert(w->shadow_image || win_check_flags_all(w, WIN_FLAGS_SHADOW_STALE) || + assert(w->shadow_image || (w->flags & WIN_FLAGS_SHADOW_STALE) || !ps->o.experimental_backends); add_damage(ps, &extents); } @@ -740,7 +769,7 @@ static void win_set_shadow(session_t *ps, struct managed_win *w, bool shadow_new // Delayed update of shadow image // By setting WIN_FLAGS_SHADOW_STALE, we ask win_process_flags to re-create or // release the shaodw in based on whether w->shadow is set. - win_set_flags(w, WIN_FLAGS_SHADOW_STALE); + w->flags |= WIN_FLAGS_SHADOW_STALE; ps->pending_updates = true; } @@ -878,6 +907,48 @@ 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); + } + + 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. */ @@ -904,7 +975,6 @@ void win_update_opacity_rule(session_t *ps, struct managed_win *w) { * TODO need better name */ void win_on_factor_change(session_t *ps, struct managed_win *w) { - log_debug("Window %#010x (%s) factor change", w->base.id, w->name); // Focus needs to be updated first, as other rules might depend on the focused // state of the window win_update_focused(ps, w); @@ -912,8 +982,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); - w->mode = win_calc_mode(w); - log_debug("Window mode changed to %d", w->mode); + 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); @@ -921,7 +990,15 @@ void win_on_factor_change(session_t *ps, struct managed_win *w) { w->unredir_if_possible_excluded = c2_match(ps, w, ps->o.unredir_if_possible_blacklist, NULL); - win_update_opacity_target(ps, w); + 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)); + } + } w->reg_ignore_valid = false; } @@ -940,7 +1017,7 @@ void win_on_win_size_change(session_t *ps, struct managed_win *w) { // Invalidate the shadow we built if (w->state == WSTATE_MAPPED || w->state == WSTATE_MAPPING || w->state == WSTATE_FADING) { - win_set_flags(w, WIN_FLAGS_IMAGES_STALE); + w->flags |= WIN_FLAGS_IMAGES_STALE; ps->pending_updates = true; } else { assert(w->state == WSTATE_UNMAPPED); @@ -1014,9 +1091,9 @@ void win_mark_client(session_t *ps, struct managed_win *w, xcb_window_t client) win_on_factor_change(ps, w); auto r = xcb_get_window_attributes_reply( - ps->c, xcb_get_window_attributes(ps->c, w->client_win), &e); + ps->c, xcb_get_window_attributes(ps->c, w->client_win), NULL); if (!r) { - log_error_x_error(e, "Failed to get client window attributes"); + log_error("Failed to get client window attributes"); return; } @@ -1032,8 +1109,6 @@ void win_mark_client(session_t *ps, struct managed_win *w, xcb_window_t client) */ void win_unmark_client(session_t *ps, struct managed_win *w) { xcb_window_t client = w->client_win; - log_debug("Detaching client window %#010x from frame %#010x (%s)", client, - w->base.id, w->name); w->client_win = XCB_NONE; @@ -1043,42 +1118,13 @@ void win_unmark_client(session_t *ps, struct managed_win *w) { (const uint32_t[]){determine_evmask(ps, client, WIN_EVMODE_UNKNOWN)}); } -/** - * Look for the client window of a particular window. - */ -static xcb_window_t find_client_win(session_t *ps, xcb_window_t w) { - if (wid_has_prop(ps, w, ps->atoms->aWM_STATE)) { - return w; - } - - xcb_query_tree_reply_t *reply = - xcb_query_tree_reply(ps->c, xcb_query_tree(ps->c, w), NULL); - if (!reply) - return 0; - - xcb_window_t *children = xcb_query_tree_children(reply); - int nchildren = xcb_query_tree_children_length(reply); - int i; - xcb_window_t ret = 0; - - for (i = 0; i < nchildren; ++i) { - if ((ret = find_client_win(ps, children[i]))) - break; - } - - free(reply); - - return ret; -} - /** * Recheck client window of a window. * * @param ps current session * @param w struct _win of the parent window */ -void win_recheck_client(session_t *ps, struct managed_win *w) { - assert(ps->server_grabbed); +static void win_recheck_client(session_t *ps, struct managed_win *w) { // Initialize wmwin to false w->wmwin = false; @@ -1088,14 +1134,14 @@ void win_recheck_client(session_t *ps, struct managed_win *w) { // sets override-redirect flags on all frame windows. xcb_window_t cw = find_client_win(ps, w->base.id); if (cw) { - log_debug("(%#010x): client %#010x", w->base.id, cw); + log_trace("(%#010x): client %#010x", w->base.id, cw); } // Set a window's client window to itself if we couldn't find a // client window if (!cw) { cw = w->base.id; w->wmwin = !w->a.override_redirect; - log_debug("(%#010x): client self (%s)", w->base.id, + log_trace("(%#010x): client self (%s)", w->base.id, (w->wmwin ? "wmwin" : "override-redirected")); } @@ -1191,10 +1237,10 @@ struct win *fill_win(session_t *ps, struct win *w) { .invert_color = false, .blur_background = false, - .oldX = -10000, - .oldY = -10000, - .oldW = 0, - .oldH = 0, + .oldX = -10000, + .oldY = -10000, + .oldW = 0, + .oldH = 0, .reg_ignore = NULL, // The following ones are updated for other reasons @@ -1262,6 +1308,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); @@ -1305,6 +1353,7 @@ struct win *fill_win(session_t *ps, struct win *w) { // Allocate and initialize the new win structure auto new_internal = cmalloc(struct managed_win_internal); auto new = (struct managed_win *)new_internal; + new_internal->pending_updates = 0; // Fill structure // We only need to initialize the part that are not initialized @@ -1550,7 +1599,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) @@ -1602,7 +1651,7 @@ void win_update_bounding_shape(session_t *ps, struct managed_win *w) { // Note we only do this when screen is redirected, because // otherwise win_data is not valid assert(w->state != WSTATE_UNMAPPING && w->state != WSTATE_DESTROYING); - win_set_flags(w, WIN_FLAGS_IMAGES_STALE); + w->flags |= WIN_FLAGS_IMAGES_STALE; ps->pending_updates = true; } free_paint(ps, &w->paint); @@ -1716,7 +1765,7 @@ static void unmap_win_finish(session_t *ps, struct managed_win *w) { free_paint(ps, &w->shadow_paint); // Try again at binding images when the window is mapped next time - win_clear_flags(w, WIN_FLAGS_IMAGE_ERROR); + w->flags &= ~WIN_FLAGS_IMAGE_ERROR; } /// Finish the destruction of a window (e.g. after fading has finished). @@ -1778,7 +1827,7 @@ static void destroy_win_finish(session_t *ps, struct win *w) { static void map_win_finish(struct managed_win *w) { w->in_openclose = false; - w->isOld = true; + w->isOld = true; w->state = WSTATE_MAPPED; } @@ -1890,15 +1939,14 @@ bool destroy_win_start(session_t *ps, struct win *w) { } if (w->managed) { - // Clear PIXMAP_STALE flag, since the window is destroyed there is no - // pixmap available so STALE doesn't make sense. - // Do this before changing the window state to destroying - win_clear_flags(mw, WIN_FLAGS_PIXMAP_STALE); - // Update state flags of a managed window mw->state = WSTATE_DESTROYING; mw->a.map_state = XCB_MAP_STATE_UNMAPPED; mw->in_openclose = true; + + // Clear PIXMAP_STALE flag, since the window is destroyed there is no + // pixmap available so STALE doesn't make sense. + mw->flags &= ~WIN_FLAGS_PIXMAP_STALE; } // don't need win_ev_stop because the window is gone anyway @@ -1918,6 +1966,7 @@ bool destroy_win_start(session_t *ps, struct win *w) { } void unmap_win_start(session_t *ps, struct managed_win *w) { + auto internal_w = (struct managed_win_internal *)w; assert(w); assert(w->base.managed); assert(w->a._class != XCB_WINDOW_CLASS_INPUT_ONLY); @@ -1930,9 +1979,8 @@ void unmap_win_start(session_t *ps, struct managed_win *w) { } if (unlikely(w->state == WSTATE_UNMAPPING || w->state == WSTATE_UNMAPPED)) { - if (win_check_flags_all(w, WIN_FLAGS_MAPPED)) { - // Clear the pending map as this window is now unmapped - win_clear_flags(w, WIN_FLAGS_MAPPED); + if (internal_w->pending_updates & WIN_UPDATE_MAP) { + internal_w->pending_updates &= ~(unsigned long)WIN_UPDATE_MAP; } else { log_warn("Trying to unmapping an already unmapped window %#010x " "\"%s\"", @@ -1947,12 +1995,11 @@ void unmap_win_start(session_t *ps, struct managed_win *w) { w->a.map_state = XCB_MAP_STATE_UNMAPPED; w->state = WSTATE_UNMAPPING; - w->opacity_target_old = fmax(w->opacity_target, w->opacity_target_old); - w->opacity_target = win_calc_opacity_target(ps, w); + w->opacity_target = win_calc_opacity_target(ps, w, false); // Clear PIXMAP_STALE flag, since the window is unmapped there is no pixmap // available so STALE doesn't make sense. - win_clear_flags(w, WIN_FLAGS_PIXMAP_STALE); + w->flags &= ~WIN_FLAGS_PIXMAP_STALE; // don't care about properties anymore win_ev_stop(ps, &w->base); @@ -2059,7 +2106,8 @@ void map_win_start(session_t *ps, struct managed_win *w) { } assert(w->state == WSTATE_UNMAPPED); - assert(win_check_flags_all(w, WIN_FLAGS_IMAGES_NONE) || !ps->o.experimental_backends); + assert((w->flags & WIN_FLAGS_IMAGES_NONE) == WIN_FLAGS_IMAGES_NONE || + !ps->o.experimental_backends); // We stopped processing window size change when we were unmapped, refresh the // size of the window @@ -2087,13 +2135,14 @@ void map_win_start(session_t *ps, struct managed_win *w) { // XXX Can we assume map_state is always viewable? w->a.map_state = XCB_MAP_STATE_VIEWABLE; - if (!w->isOld) { - w->oldX = -1000; - w->oldY = -1000; + if (!w->isOld) { + w->oldX = -10000; + w->oldY = -10000; w->oldW = 0; w->oldH = 0; } + win_update_screen(ps, w); // Set window event mask before reading properties so that no property @@ -2108,7 +2157,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 @@ -2139,13 +2188,13 @@ void map_win_start(session_t *ps, struct managed_win *w) { // XXX We need to make sure that win_data is available // iff `state` is MAPPED w->state = WSTATE_MAPPING; - w->opacity_target_old = 0; - w->opacity_target = win_calc_opacity_target(ps, w); + w->opacity_target = win_calc_opacity_target(ps, w, false); log_debug("Window %#010x has opacity %f, opacity target is %f", w->base.id, 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 @@ -2161,7 +2210,7 @@ void map_win_start(session_t *ps, struct managed_win *w) { // the window's image will be bound win_update_bounding_shape(ps, w); - assert(win_check_flags_all(w, WIN_FLAGS_IMAGES_STALE)); + assert((w->flags & WIN_FLAGS_IMAGES_STALE) == WIN_FLAGS_IMAGES_STALE); #ifdef CONFIG_DBUS // Send D-Bus signal @@ -2175,60 +2224,6 @@ void map_win_start(session_t *ps, struct managed_win *w) { } } -/** - * Update target window opacity depending on the current state. - */ -void win_update_opacity_target(session_t *ps, struct managed_win *w) { - auto opacity_target_old = w->opacity_target; - w->opacity_target = win_calc_opacity_target(ps, w); - - if (opacity_target_old == w->opacity_target) { - return; - } - - if (w->state == WSTATE_MAPPED) { - // Opacity target changed while MAPPED. Transition to FADING. - assert(w->opacity == opacity_target_old); - w->opacity_target_old = opacity_target_old; - w->state = WSTATE_FADING; - log_debug("Window %#010x (%s) opacity %f, opacity target %f, set " - "old target %f", - w->base.id, w->name, w->opacity, w->opacity_target, - w->opacity_target_old); - } else if (w->state == WSTATE_MAPPING) { - // Opacity target changed while fading in. - if (w->opacity >= w->opacity_target) { - // Already reached new target opacity. Transition to - // FADING. - map_win_finish(w); - w->opacity_target_old = fmax(opacity_target_old, w->opacity); - w->state = WSTATE_FADING; - log_debug("Window %#010x (%s) opacity %f already reached " - "new opacity target %f while mapping, set old " - "target %f", - w->base.id, w->name, w->opacity, w->opacity_target, - w->opacity_target_old); - } - } else if (w->state == WSTATE_FADING) { - // Opacity target changed while FADING. - if ((w->opacity < opacity_target_old && w->opacity > w->opacity_target) || - (w->opacity > opacity_target_old && w->opacity < w->opacity_target)) { - // Changed while fading in and will fade out or while - // fading out and will fade in. - w->opacity_target_old = opacity_target_old; - log_debug("Window %#010x (%s) opacity %f already reached " - "new opacity target %f while fading, set " - "old target %f", - w->base.id, w->name, w->opacity, w->opacity_target, - w->opacity_target_old); - } - } - - if (!ps->redirected) { - CHECK(!win_skip_fading(ps, w)); - } -} - /** * Find a managed window from window id in window linked list of the session. */ @@ -2284,13 +2279,13 @@ struct managed_win *find_toplevel(session_t *ps, xcb_window_t id) { } /** - * Find a managed window that is, or is a parent of `wid`. + * Find out the WM frame of a client window by querying X. * * @param ps current session * @param wid window ID * @return struct _win object of the found window, NULL if not found */ -struct managed_win *find_managed_window_or_parent(session_t *ps, xcb_window_t wid) { +struct managed_win *find_toplevel2(session_t *ps, xcb_window_t wid) { // TODO this should probably be an "update tree", then find_toplevel. // current approach is a bit more "racy" struct win *w = NULL; @@ -2351,35 +2346,31 @@ win_is_fullscreen_xcb(xcb_connection_t *c, const struct atom *a, const xcb_windo return false; } -/// Set flags on a window. Some sanity checks are performed -void win_set_flags(struct managed_win *w, uint64_t flags) { - log_debug("Set flags %lu to window %#010x (%s)", flags, w->base.id, w->name); - if (unlikely(w->state == WSTATE_DESTROYING)) { - log_error("Flags set on a destroyed window %#010x (%s)", w->base.id, w->name); +/// Queue an update on a window. A series of sanity checks are performed +void win_queue_update(struct managed_win *_w, enum win_update update) { + auto w = (struct managed_win_internal *)_w; + assert(popcount(update) == 1); + assert(update == WIN_UPDATE_MAP); // Currently the only supported update + + if (unlikely(_w->state == WSTATE_DESTROYING)) { + log_error("Updates queued on a destroyed window %#010x (%s)", _w->base.id, + _w->name); return; } - w->flags |= flags; + w->pending_updates |= update; } -/// Clear flags on a window. Some sanity checks are performed -void win_clear_flags(struct managed_win *w, uint64_t flags) { - log_debug("Clear flags %lu from window %#010x (%s)", flags, w->base.id, w->name); - if (unlikely(w->state == WSTATE_DESTROYING)) { - log_warn("Flags cleared on a destroyed window %#010x (%s)", w->base.id, - w->name); - return; +/// Process pending updates on a window. Has to be called in X critical section +void win_process_updates(struct session *ps, struct managed_win *_w) { + assert(ps->server_grabbed); + auto w = (struct managed_win_internal *)_w; + + if (w->pending_updates & WIN_UPDATE_MAP) { + map_win_start(ps, _w); } - w->flags = w->flags & (~flags); -} - -bool win_check_flags_any(struct managed_win *w, uint64_t flags) { - return (w->flags & flags) != 0; -} - -bool win_check_flags_all(struct managed_win *w, uint64_t flags) { - return (w->flags & flags) == flags; + w->pending_updates = 0; } /** @@ -2435,6 +2426,7 @@ win_stack_find_next_managed(const session_t *ps, const struct list_node *i) { /// Return whether this window is mapped on the X server side bool win_is_mapped_in_x(const struct managed_win *w) { + auto iw = (const struct managed_win_internal *)w; return w->state == WSTATE_MAPPING || w->state == WSTATE_FADING || - w->state == WSTATE_MAPPED || (w->flags & WIN_FLAGS_MAPPED); + w->state == WSTATE_MAPPED || (iw->pending_updates & WIN_UPDATE_MAP); } diff --git a/src/win.h b/src/win.h index 43f5c02..d5ba91e 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 @@ -131,7 +131,7 @@ struct managed_win { /// See above about coordinate systems. region_t bounding_shape; /// Window flags. Definitions above. - uint64_t flags; + int_fast16_t flags; /// The region of screen that will be obscured when windows above is painted, /// in global coordinates. /// We use this to reduce the pixels that needed to be paint when painting @@ -192,8 +192,6 @@ struct managed_win { double opacity; /// Target window opacity. double opacity_target; - /// Previous window opacity. - double opacity_target_old; /// true if window (or client window, for broken window managers /// not transferring client window's _NET_WM_OPACITY value) has opacity prop bool has_opacity_prop; @@ -204,6 +202,11 @@ 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]; + // Fading-related members /// Override value of window fade state. Set by D-Bus method calls. switch_t fade_force; @@ -248,21 +251,29 @@ struct managed_win { /// Whether to blur window background. bool blur_background; - /// Animation state - int oldX; int oldY; int oldW; int oldH; - int newX; int newY; int newW; int newH; - float moveTimeX; float moveTimeY; - float moveTimeW; float moveTimeH; - bool isOld; + /// Animation state + int oldX; int oldY; int oldW; int oldH; + int newX; int newY; int newW; int newH; + float moveTimeX; float moveTimeY; + float moveTimeW; float moveTimeH; + bool isOld; + #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 }; +/// Process pending updates on a window. Has to be called in X critical section +void win_process_updates(struct session *ps, struct managed_win *_w); /// Process pending images flags on a window. Has to be called in X critical section void win_process_flags(session_t *ps, struct managed_win *w); +/// Queue an update on a window. A series of sanity checks are performed +void win_queue_update(struct managed_win *_w, enum win_update update); /// Bind a shadow to the window, with color `c` and shadow kernel `kernel` bool win_bind_shadow(struct backend_base *b, struct managed_win *w, struct color c, struct conv *kernel); @@ -284,7 +295,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); @@ -296,7 +307,6 @@ void win_set_focused(session_t *ps, struct managed_win *w); bool attr_pure win_should_fade(session_t *ps, const struct managed_win *w); void win_update_prop_shadow_raw(session_t *ps, struct managed_win *w); void win_update_prop_shadow(session_t *ps, struct managed_win *w); -void win_update_opacity_target(session_t *ps, struct managed_win *w); void win_on_factor_change(session_t *ps, struct managed_win *w); /** * Update cache data in struct _win that depends on window size. @@ -305,7 +315,6 @@ void win_on_win_size_change(session_t *ps, struct managed_win *w); void win_update_wintype(session_t *ps, struct managed_win *w); void win_mark_client(session_t *ps, struct managed_win *w, xcb_window_t client); void win_unmark_client(session_t *ps, struct managed_win *w); -void win_recheck_client(session_t *ps, struct managed_win *w); bool win_get_class(session_t *ps, struct managed_win *w); /** @@ -318,10 +327,12 @@ bool win_get_class(session_t *ps, struct managed_win *w); * * @param ps current session * @param w struct _win object representing the window + * @param ignore_state whether window state should be ignored in opacity calculation * * @return target opacity */ -double attr_pure win_calc_opacity_target(session_t *ps, const struct managed_win *w); +double attr_pure win_calc_opacity_target(session_t *ps, const struct managed_win *w, + bool ignore_state); bool attr_pure win_should_dim(session_t *ps, const struct managed_win *w); void win_update_screen(session_t *, struct managed_win *); /** @@ -362,12 +373,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. */ @@ -407,13 +418,13 @@ struct managed_win *find_managed_win(session_t *ps, xcb_window_t id); struct win *find_win(session_t *ps, xcb_window_t id); struct managed_win *find_toplevel(session_t *ps, xcb_window_t id); /** - * Find a managed window that is, or is a parent of `wid`. + * Find out the WM frame of a client window by querying X. * * @param ps current session * @param wid window ID * @return struct _win object of the found window, NULL if not found */ -struct managed_win *find_managed_window_or_parent(session_t *ps, xcb_window_t wid); +struct managed_win *find_toplevel2(session_t *ps, xcb_window_t wid); /** * Check if a window is a fullscreen window. @@ -439,22 +450,29 @@ bool win_is_mapped_in_x(const struct managed_win *w); // Find the managed window immediately below `w` in the window stack struct managed_win *attr_pure win_stack_find_next_managed(const session_t *ps, const struct list_node *w); -/// Set flags on a window. Some sanity checks are performed -void win_set_flags(struct managed_win *w, uint64_t flags); -/// Clear flags on a window. Some sanity checks are performed -void win_clear_flags(struct managed_win *w, uint64_t flags); -/// Returns true if any of the flags in `flags` is set -bool win_check_flags_any(struct managed_win *w, uint64_t flags); -/// Returns true if all of the flags in `flags` are set -bool win_check_flags_all(struct managed_win *w, uint64_t flags); /// 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/win_defs.h b/src/win_defs.h index 6099ac2..2a18e4b 100644 --- a/src/win_defs.h +++ b/src/win_defs.h @@ -27,6 +27,11 @@ typedef enum { WMODE_SOLID, // The window is opaque including the frame } winmode_t; +/// Pending window updates +enum win_update { + WIN_UPDATE_MAP = 1, +}; + /// Transition table: /// (DESTROYED is when the win struct is destroyed and freed) /// ('o' means in all other cases) @@ -40,8 +45,8 @@ typedef enum { /// | DESTROYING | - | o | - | - | - | - | Fading | /// | | | | | | | |finished | /// +-------------+---------+----------+-------+-------+--------+--------+---------+ -/// | MAPPING | Window | Window | o |Opacity| - | Fading | - | -/// | |unmapped |destroyed | |change | |finished| | +/// | MAPPING | Window | Window | o | - | - | Fading | - | +/// | |unmapped |destroyed | | | |finished| | /// +-------------+---------+----------+-------+-------+--------+--------+---------+ /// | FADING | Window | Window | - | o | - | Fading | - | /// | |unmapped |destroyed | | | |finished| | @@ -81,13 +86,9 @@ enum win_flags { WIN_FLAGS_SHADOW_STALE = 8, /// shadow has not been generated WIN_FLAGS_SHADOW_NONE = 16, - /// the client window needs to be updated - WIN_FLAGS_CLIENT_STALE = 32, - /// the window is mapped by X, we need to call map_win_start for it - WIN_FLAGS_MAPPED = 64, }; -static const uint64_t WIN_FLAGS_IMAGES_STALE = +static const int_fast16_t WIN_FLAGS_IMAGES_STALE = WIN_FLAGS_PIXMAP_STALE | WIN_FLAGS_SHADOW_STALE; #define WIN_FLAGS_IMAGES_NONE (WIN_FLAGS_PIXMAP_NONE | WIN_FLAGS_SHADOW_NONE) diff --git a/src/x.c b/src/x.c index 0d97ff5..f195894 100644 --- a/src/x.c +++ b/src/x.c @@ -7,7 +7,6 @@ #include #include #include -#include #include #include #include @@ -168,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; @@ -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. */ @@ -338,80 +357,69 @@ _x_strerror(unsigned long serial, uint8_t major, uint16_t minor, uint8_t error_c int o = 0; const char *name = "Unknown"; -#define CASESTRRET(s) \ - case s: \ - name = #s; \ - break - #define CASESTRRET2(s) \ - case XCB_##s: name = #s; break + case s: name = #s; break // TODO separate error code out from session_t o = error_code - ps->xfixes_error; - switch (o) { CASESTRRET2(XFIXES_BAD_REGION); } + switch (o) { CASESTRRET2(XCB_XFIXES_BAD_REGION); } o = error_code - ps->damage_error; - switch (o) { CASESTRRET2(DAMAGE_BAD_DAMAGE); } + switch (o) { CASESTRRET2(XCB_DAMAGE_BAD_DAMAGE); } o = error_code - ps->render_error; switch (o) { - CASESTRRET2(RENDER_PICT_FORMAT); - CASESTRRET2(RENDER_PICTURE); - CASESTRRET2(RENDER_PICT_OP); - CASESTRRET2(RENDER_GLYPH_SET); - CASESTRRET2(RENDER_GLYPH); + CASESTRRET2(XCB_RENDER_PICT_FORMAT); + CASESTRRET2(XCB_RENDER_PICTURE); + CASESTRRET2(XCB_RENDER_PICT_OP); + CASESTRRET2(XCB_RENDER_GLYPH_SET); + CASESTRRET2(XCB_RENDER_GLYPH); } +#ifdef CONFIG_OPENGL if (ps->glx_exists) { o = error_code - ps->glx_error; switch (o) { + CASESTRRET2(GLX_BAD_SCREEN); + CASESTRRET2(GLX_BAD_ATTRIBUTE); + CASESTRRET2(GLX_NO_EXTENSION); + CASESTRRET2(GLX_BAD_VISUAL); CASESTRRET2(GLX_BAD_CONTEXT); - CASESTRRET2(GLX_BAD_CONTEXT_STATE); - CASESTRRET2(GLX_BAD_DRAWABLE); - CASESTRRET2(GLX_BAD_PIXMAP); - CASESTRRET2(GLX_BAD_CONTEXT_TAG); - CASESTRRET2(GLX_BAD_CURRENT_WINDOW); - CASESTRRET2(GLX_BAD_RENDER_REQUEST); - CASESTRRET2(GLX_BAD_LARGE_REQUEST); - CASESTRRET2(GLX_UNSUPPORTED_PRIVATE_REQUEST); - CASESTRRET2(GLX_BAD_FB_CONFIG); - CASESTRRET2(GLX_BAD_PBUFFER); - CASESTRRET2(GLX_BAD_CURRENT_DRAWABLE); - CASESTRRET2(GLX_BAD_WINDOW); - CASESTRRET2(GLX_GLX_BAD_PROFILE_ARB); + CASESTRRET2(GLX_BAD_VALUE); + CASESTRRET2(GLX_BAD_ENUM); } } +#endif if (ps->xsync_exists) { o = error_code - ps->xsync_error; switch (o) { - CASESTRRET(XSyncBadCounter); - CASESTRRET(XSyncBadAlarm); - CASESTRRET(XSyncBadFence); + CASESTRRET2(XSyncBadCounter); + CASESTRRET2(XSyncBadAlarm); + CASESTRRET2(XSyncBadFence); } } switch (error_code) { - CASESTRRET2(ACCESS); - CASESTRRET2(ALLOC); - CASESTRRET2(ATOM); - CASESTRRET2(COLORMAP); - CASESTRRET2(CURSOR); - CASESTRRET2(DRAWABLE); - CASESTRRET2(FONT); - CASESTRRET2(G_CONTEXT); - CASESTRRET2(ID_CHOICE); - CASESTRRET2(IMPLEMENTATION); - CASESTRRET2(LENGTH); - CASESTRRET2(MATCH); - CASESTRRET2(NAME); - CASESTRRET2(PIXMAP); - CASESTRRET2(REQUEST); - CASESTRRET2(VALUE); - CASESTRRET2(WINDOW); + CASESTRRET2(BadAccess); + CASESTRRET2(BadAlloc); + CASESTRRET2(BadAtom); + CASESTRRET2(BadColor); + CASESTRRET2(BadCursor); + CASESTRRET2(BadDrawable); + CASESTRRET2(BadFont); + CASESTRRET2(BadGC); + CASESTRRET2(BadIDChoice); + CASESTRRET2(BadImplementation); + CASESTRRET2(BadLength); + CASESTRRET2(BadMatch); + CASESTRRET2(BadName); + CASESTRRET2(BadPixmap); + CASESTRRET2(BadRequest); + CASESTRRET2(BadValue); + CASESTRRET2(BadWindow); } -#undef CASESTRRET #undef CASESTRRET2 thread_local static char buffer[256]; @@ -434,9 +442,6 @@ void x_print_error(unsigned long serial, uint8_t major, uint16_t minor, uint8_t * for multiple calls to this function, */ const char *x_strerror(xcb_generic_error_t *e) { - if (!e) { - return "No error"; - } return _x_strerror(e->full_sequence, e->major_code, e->minor_code, e->error_code); } diff --git a/src/x.h b/src/x.h index e408428..20709ee 100644 --- a/src/x.h +++ b/src/x.h @@ -172,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. */ @@ -261,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/configs/issue314.conf b/tests/configs/issue314.conf deleted file mode 100644 index bb3c4cb..0000000 --- a/tests/configs/issue314.conf +++ /dev/null @@ -1,6 +0,0 @@ -fading = true -fade-in-step = 0.01 -fade-out-step = 0.01 -inactive-opacity = 0 -blur-background = true -force-win-blend = true diff --git a/tests/configs/issue357.conf b/tests/configs/issue357.conf deleted file mode 100644 index 00c3ba6..0000000 --- a/tests/configs/issue357.conf +++ /dev/null @@ -1,3 +0,0 @@ -fading = true; -fade-in-step = 1; -fade-out-step = 0.01; diff --git a/tests/run_one_test.sh b/tests/run_one_test.sh index 4619359..13da3df 100755 --- a/tests/run_one_test.sh +++ b/tests/run_one_test.sh @@ -7,7 +7,7 @@ fi echo "Running test $2" # TODO keep the log file, and parse it to see if test is successful -($1 --dbus --experimental-backends --backend dummy --log-level=debug --log-file=$PWD/log --config=$2) & +($1 --experimental-backends --backend dummy --log-level=debug --log-file=$PWD/log --config=$2) & main_pid=$! $3 diff --git a/tests/run_tests.sh b/tests/run_tests.sh index 08203e0..9f64442 100755 --- a/tests/run_tests.sh +++ b/tests/run_tests.sh @@ -3,15 +3,8 @@ set -e exe=$(realpath $1) cd $(dirname $0) -eval `dbus-launch --sh-syntax` - ./run_one_test.sh $exe configs/empty.conf testcases/basic.py -./run_one_test.sh $exe configs/issue357.conf testcases/issue357.py ./run_one_test.sh $exe configs/issue239.conf testcases/issue239.py ./run_one_test.sh $exe configs/issue239_2.conf testcases/issue239_2.py ./run_one_test.sh $exe configs/issue239_3.conf testcases/issue239_3.py ./run_one_test.sh $exe configs/issue239_3.conf testcases/issue239_3_norefresh.py -./run_one_test.sh $exe configs/issue314.conf testcases/issue314.py -./run_one_test.sh $exe configs/issue314.conf testcases/issue314_2.py -./run_one_test.sh $exe configs/issue314.conf testcases/issue314_3.py -./run_one_test.sh $exe /dev/null testcases/issue299.py diff --git a/tests/testcases/common.py b/tests/testcases/common.py index 104a108..cef5ac3 100644 --- a/tests/testcases/common.py +++ b/tests/testcases/common.py @@ -1,35 +1,18 @@ import xcffib.xproto as xproto import xcffib.randr as randr -import xcffib import time import random import string - -def to_atom(conn, string): - return conn.core.InternAtom(False, len(string), string).reply().atom - def set_window_name(conn, wid, name): - prop_name = to_atom(conn, "_NET_WM_NAME") - str_type = to_atom(conn, "UTF8_STRING") - conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid, prop_name, str_type, 8, len(name), name).check() - prop_name = to_atom(conn, "WM_NAME") - str_type = to_atom(conn, "STRING") - conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid, prop_name, str_type, 8, len(name), name).check() - -def set_window_state(conn, wid, state): - prop_name = to_atom(conn, "WM_STATE") - conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid, prop_name, prop_name, 32, 2, [state, 0]).check() - -def set_window_class(conn, wid, name): - if not isinstance(name, bytearray): - name = name.encode() - name = name+b"\0"+name+b"\0" - prop_name = to_atom(conn, "WM_CLASS") - str_type = to_atom(conn, "STRING") + 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 = to_atom(conn, "WM_NAME") + 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() @@ -58,19 +41,3 @@ def trigger_root_configure(conn): 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() -def find_32bit_visual(conn): - setup = conn.get_setup() - render = conn(xcffib.render.key) - r = render.QueryPictFormats().reply() - pictfmt_ids = set() - for pictform in r.formats: - if (pictform.depth == 32 and - pictform.type == xcffib.render.PictType.Direct and - pictform.direct.alpha_mask != 0): - pictfmt_ids.add(pictform.id) - print(pictfmt_ids) - for screen in r.screens: - for depth in screen.depths: - for pv in depth.visuals: - if pv.format in pictfmt_ids: - return pv.visual diff --git a/tests/testcases/issue299.py b/tests/testcases/issue299.py deleted file mode 100755 index d8be70c..0000000 --- a/tests/testcases/issue299.py +++ /dev/null @@ -1,112 +0,0 @@ -#!/usr/bin/env python3 - -import xcffib.xproto as xproto -import xcffib -import time -import os -import subprocess -import asyncio -from dbus_next.aio import MessageBus -from dbus_next.message import Message, MessageType -from common import * - -display = os.environ["DISPLAY"].replace(":", "_") -conn = xcffib.connect() -setup = conn.get_setup() -root = setup.roots[0].root -visual = setup.roots[0].root_visual -depth = setup.roots[0].root_depth -x = xproto.xprotoExtension(conn) -visual32 = find_32bit_visual(conn) - -async def get_client_win_async(wid): - message = await bus.call(Message(destination='com.github.chjj.compton.'+display, - path='/', - interface='com.github.chjj.compton', - member='win_get', - signature='us', - body=[wid, 'client_win'])) - return message.body[0] - -def get_client_win(wid): - return loop.run_until_complete(get_client_win_async(wid)) - -def wait(): - time.sleep(0.5) - -def create_client_window(name): - client_win = conn.generate_id() - print("Window : ", hex(client_win)) - conn.core.CreateWindowChecked(depth, client_win, root, 0, 0, 100, 100, 0, - xproto.WindowClass.InputOutput, visual, 0, []).check() - set_window_name(conn, client_win, "Test window "+name) - set_window_class(conn, client_win, "Test windows") - set_window_state(conn, client_win, 1) - conn.core.MapWindowChecked(client_win).check() - return client_win - -loop = asyncio.get_event_loop() -bus = loop.run_until_complete(MessageBus().connect()) - -cmid = conn.generate_id() -colormap = conn.core.CreateColormapChecked(xproto.ColormapAlloc._None, cmid, root, visual32).check() - -# Create window -client_wins = [] -for i in range(0,2): - client_wins.append(create_client_window(str(i))) - -# Create frame window -frame_win = conn.generate_id() -print("Window : ", hex(frame_win)) -conn.core.CreateWindowChecked(depth, frame_win, root, 0, 0, 200, 200, 0, - xproto.WindowClass.InputOutput, visual, 0, []).check() -set_window_name(conn, frame_win, "Frame") -conn.core.MapWindowChecked(frame_win).check() - -# Scenario 1.1 -# 1. reparent placeholder to frame -conn.core.ReparentWindowChecked(client_wins[0], frame_win, 0, 0).check() -wait() -# 2. reparent real client to frame -conn.core.ReparentWindowChecked(client_wins[1], frame_win, 0, 0).check() -wait() -# 3. detach the placeholder -conn.core.ReparentWindowChecked(client_wins[0], root, 0, 0).check() -wait() -assert get_client_win(frame_win) == client_wins[1] - -# Scenario 1.2 -# 1. reparent placeholder to frame -conn.core.ReparentWindowChecked(client_wins[0], frame_win, 0, 0).check() -wait() -# 2. reparent real client to frame -conn.core.ReparentWindowChecked(client_wins[1], frame_win, 0, 0).check() -wait() -# 3. destroy the placeholder -conn.core.DestroyWindowChecked(client_wins[0]).check() -wait() -assert get_client_win(frame_win) == client_wins[1] - -client_wins[0] = create_client_window("0") - -# Scenario 2 -# 1. frame is unmapped -conn.core.UnmapWindowChecked(frame_win).check() -wait() -# 2. reparent placeholder to frame -conn.core.ReparentWindowChecked(client_wins[0], frame_win, 0, 0).check() -wait() -# 3. destroy placeholder, map frame and reparent real client to frame -conn.core.DestroyWindowChecked(client_wins[0]).check() -conn.core.MapWindowChecked(frame_win).check() -conn.core.ReparentWindowChecked(client_wins[1], frame_win, 0, 0).check() -wait() -assert get_client_win(frame_win) == client_wins[1] - -client_wins[0] = create_client_window("0") - -# Destroy the windows -for wid in client_wins: - conn.core.DestroyWindowChecked(wid).check() -conn.core.DestroyWindowChecked(frame_win).check() diff --git a/tests/testcases/issue314.py b/tests/testcases/issue314.py deleted file mode 100755 index ee29fc4..0000000 --- a/tests/testcases/issue314.py +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env python - -import xcffib.xproto as xproto -import xcffib -import time -from common import set_window_name, trigger_root_configure - -conn = xcffib.connect() -setup = conn.get_setup() -root = setup.roots[0].root -visual = setup.roots[0].root_visual -depth = setup.roots[0].root_depth -x = xproto.xprotoExtension(conn) - -# issue 314 is caused by changing a windows target opacity during its fade-in/-out transition -wid1 = conn.generate_id() -print("Window 1: ", hex(wid1)) -wid2 = conn.generate_id() -print("Window 2: ", hex(wid2)) - -# Create windows -conn.core.CreateWindowChecked(depth, wid1, root, 0, 0, 100, 100, 0, xproto.WindowClass.InputOutput, visual, 0, []).check() -conn.core.CreateWindowChecked(depth, wid2, root, 0, 0, 100, 100, 0, xproto.WindowClass.InputOutput, visual, 0, []).check() - -# Set Window names -set_window_name(conn, wid1, "Test window 1") -set_window_name(conn, wid2, "Test window 2") - -# Check updating opacity while UNMAPPING/DESTROYING windows -print("Mapping 1") -conn.core.MapWindowChecked(wid1).check() -print("Mapping 2") -conn.core.MapWindowChecked(wid2).check() -time.sleep(0.5) - -x.SetInputFocusChecked(0, wid1, xproto.Time.CurrentTime).check() -time.sleep(0.5) - -# Destroy the windows -print("Destroy 1 while fading out") -conn.core.DestroyWindowChecked(wid1).check() -x.SetInputFocusChecked(0, wid2, xproto.Time.CurrentTime).check() -time.sleep(1) -conn.core.DestroyWindowChecked(wid2).check() diff --git a/tests/testcases/issue314_2.py b/tests/testcases/issue314_2.py deleted file mode 100755 index 01192de..0000000 --- a/tests/testcases/issue314_2.py +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env python - -import xcffib.xproto as xproto -import xcffib -import time -from common import set_window_name, trigger_root_configure - -conn = xcffib.connect() -setup = conn.get_setup() -root = setup.roots[0].root -visual = setup.roots[0].root_visual -depth = setup.roots[0].root_depth -x = xproto.xprotoExtension(conn) - -opacity_80 = [int(0xffffffff * 0.8), ] -opacity_single = [int(0xffffffff * 0.002), ] - -# issue 314 is caused by changing a windows target opacity during its fade-in/-out transition -wid1 = conn.generate_id() -print("Window 1: ", hex(wid1)) - -atom = "_NET_WM_WINDOW_OPACITY" -opacity_atom = conn.core.InternAtom(False, len(atom), atom).reply().atom - -# Create windows -conn.core.CreateWindowChecked(depth, wid1, root, 0, 0, 100, 100, 0, xproto.WindowClass.InputOutput, visual, 0, []).check() - -# Set Window names -set_window_name(conn, wid1, "Test window 1") - -# Check updating opacity while MAPPING windows -print("Mapping window") -conn.core.MapWindowChecked(wid1).check() -time.sleep(0.5) - -print("Update opacity while fading in") -conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid1, opacity_atom, xproto.Atom.CARDINAL, 32, 1, opacity_80).check() -time.sleep(0.2) -conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid1, opacity_atom, xproto.Atom.CARDINAL, 32, 1, opacity_single).check() -time.sleep(1) - -conn.core.DeletePropertyChecked(wid1, opacity_atom).check() -time.sleep(0.5) - -# Destroy the windows -conn.core.DestroyWindowChecked(wid1).check() diff --git a/tests/testcases/issue314_3.py b/tests/testcases/issue314_3.py deleted file mode 100755 index 8e5d62d..0000000 --- a/tests/testcases/issue314_3.py +++ /dev/null @@ -1,63 +0,0 @@ -#!/usr/bin/env python - -import xcffib.xproto as xproto -import xcffib -import time -from common import set_window_name, trigger_root_configure - -conn = xcffib.connect() -setup = conn.get_setup() -root = setup.roots[0].root -visual = setup.roots[0].root_visual -depth = setup.roots[0].root_depth -x = xproto.xprotoExtension(conn) - -opacity_100 = [0xffffffff, ] -opacity_80 = [int(0xffffffff * 0.8), ] -opacity_single = [int(0xffffffff * 0.002), ] -opacity_0 = [0, ] - -# issue 314 is caused by changing a windows target opacity during its fade-in/-out transition -wid1 = conn.generate_id() -print("Window 1: ", hex(wid1)) - -atom = "_NET_WM_WINDOW_OPACITY" -opacity_atom = conn.core.InternAtom(False, len(atom), atom).reply().atom - -# Create windows -conn.core.CreateWindowChecked(depth, wid1, root, 0, 0, 100, 100, 0, xproto.WindowClass.InputOutput, visual, 0, []).check() - -# Set Window names -set_window_name(conn, wid1, "Test window 1") - -# Check updating opacity while FADING windows -print("Mapping window") -conn.core.MapWindowChecked(wid1).check() -time.sleep(1.2) - -print("Update opacity while fading out") -conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid1, opacity_atom, xproto.Atom.CARDINAL, 32, 1, opacity_single).check() -time.sleep(0.2) -conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid1, opacity_atom, xproto.Atom.CARDINAL, 32, 1, opacity_0).check() -time.sleep(1) - -print("Change from fading in to fading out") -conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid1, opacity_atom, xproto.Atom.CARDINAL, 32, 1, opacity_80).check() -time.sleep(0.5) -conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid1, opacity_atom, xproto.Atom.CARDINAL, 32, 1, opacity_0).check() -time.sleep(1) - -print("Update opacity while fading in") -conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid1, opacity_atom, xproto.Atom.CARDINAL, 32, 1, opacity_80).check() -time.sleep(0.2) -conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid1, opacity_atom, xproto.Atom.CARDINAL, 32, 1, opacity_100).check() -time.sleep(1) - -print("Change from fading out to fading in") -conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid1, opacity_atom, xproto.Atom.CARDINAL, 32, 1, opacity_0).check() -time.sleep(0.5) -conn.core.ChangePropertyChecked(xproto.PropMode.Replace, wid1, opacity_atom, xproto.Atom.CARDINAL, 32, 1, opacity_80).check() -time.sleep(1) - -# Destroy the windows -conn.core.DestroyWindowChecked(wid1).check() diff --git a/tests/testcases/issue357.py b/tests/testcases/issue357.py deleted file mode 100755 index ae637de..0000000 --- a/tests/testcases/issue357.py +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env python - -import xcffib.xproto as xproto -import xcffib -import time -from common import set_window_name, trigger_root_configure - -conn = xcffib.connect() -setup = conn.get_setup() -root = setup.roots[0].root -visual = setup.roots[0].root_visual -depth = setup.roots[0].root_depth - -# issue 357 is triggered when a window is destroyed right after configure_root -wid = conn.generate_id() -print("Window 1: ", hex(wid)) - -# Create a window -conn.core.CreateWindowChecked(depth, wid, root, 0, 0, 100, 100, 0, xproto.WindowClass.InputOutput, visual, 0, []).check() - -# Set Window name -set_window_name(conn, wid, "Test window 1") - -print("mapping 1") -conn.core.MapWindowChecked(wid).check() -time.sleep(0.5) - -trigger_root_configure(conn) - -# Destroy the windows -conn.core.DestroyWindowChecked(wid).check() - -time.sleep(1)