35 Commits

Author SHA1 Message Date
Arda Atci
d4f3ffb258 remove queue draw, does not work with animations
Some checks failed
coding-style / check (push) Has been cancelled
2023-12-19 22:41:55 +03:00
Arda Atci
7b2fa9d09e Merge remote-tracking branch 'yshui/pacing-fixes' into pacing-fixes 2023-12-19 17:44:35 +03:00
Arda Atci
cd203473fe Merge remote-tracking branch 'yshui/pacing-fixes' into pacing-fixes 2023-12-19 17:40:52 +03:00
Yuxuan Shui
a5b2ceb55c core: disable frame pacing when vsync is disabled
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-19 11:07:55 +00:00
Yuxuan Shui
c9840c1a7b core: make missing dpms extension non-fatal
We are not using it for anything at the moment, and it is breaking CI.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-19 10:58:20 +00:00
Yuxuan Shui
bd1c6e600e driver: choose sgi_video_sync scheduler for NVIDIA
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-19 10:58:19 +00:00
Yuxuan Shui
4080a8d254 core: add debug options to override the vblank scheduler
Useful for debugging.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-19 10:58:17 +00:00
Yuxuan Shui
5ee7b09542 vblank: add GLX_SGI_video_sync based scheduler
Present extension based scheduler doesn't work well on NVIDIA drivers.
GLX_SGI_video_sync is less accurate, but is rumoured to work. See
[kwin's usage](https://invent.kde.org/plasma/kwin/-/blob/master/src/
backends/x11/standalone/x11_standalone_sgivideosyncvsyncmonitor.cpp)

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-19 10:58:16 +00:00
Yuxuan Shui
75f89caf04 core: don't always delay schedule_render to vblank
It's kind of dumb anyway. If we get damage event right after a vblank
event, we would waste the whole vblank.

Instead improve the frame scheduling logic to target the right vblank
interval. This only affects smart_frame_pacing anyway.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-19 10:58:14 +00:00
Yuxuan Shui
057c8265b0 core: always check if render is finished at next vblank
Right now we rely on `reschedule_render_at_vblank` being scheduled for
the render finish check. Which means we will only know if a frame has
finished rendering the next time we queue a render. And we only check at
vblank boundary, which means when a render is queued we have to wait for
the next vblank, when had we checked earlier, we will be able to start
rendering immediately

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-19 10:58:13 +00:00
Yuxuan Shui
4574977287 vblank: winding down vblank events instead of stopping immediately
I noticed sometimes full frame rate video is rendered at half frame rate
sometimes. That is because the damage notify is sent very close to
vblank, and since we request vblank events when we get the damage, we
miss the vblank event immediately after, despite the damage happening
before the vblank.

       request  next  ......  next next
damage  vblank vblank          vblank
   |    |       |     ......      |
   v    v       v                 v
---------------------->>>>>>---------

`request vblank` is triggered by `damage`, but because it's too close to
`next vblank`, that vblank is missed despite we requested before it
happening, and we only get `next next vblank`. The result is we will
drop every other frame.

The solution in this commit is that we will keep requesting vblank
events, right after we received a vblank event, even when nobody is
asking for them. We would do that for a set number of vblanks before
stopping (currently 4).

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-19 10:58:11 +00:00
Yuxuan Shui
58150dfe55 x: fix x_request_vblank_event
present_notify_msc with divisor == 0 has undocumented special meaning,
it means async present notify, which means we could receive MSC
notifications from the past.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-19 10:58:10 +00:00
Yuxuan Shui
91a0ccc391 core: simplify pacing logic a bit more
Also, vblank event callback should call schedule_render to queue renders
instead of starting the draw timer directly, so that the CPU time
calculation will be correct.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-19 10:58:09 +00:00
Yuxuan Shui
8e35b33458 config: add debug options to enable timing based pacing
Disable timing estimation based pacing by default, as it might not work
well across drivers, and might have subtle bugs.

You can try setting `PICOM_DEBUG=smart_frame_pacing` if you want to try
it out.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-19 10:58:07 +00:00
Yuxuan Shui
5bcd34449c core: refactor frame pacing
Factored out vblank event generation, add abstraction over how vblank
events are generated. The goal is so we can request vblank events in
different ways based on the driver we are running on.

Tried to simplify the frame scheduling logic, we will see if I succeeded
or not.

Also, the logic to exclude vblank events for vblank interval estimation
when the screen off is dropped. It's too hard to get right, we need to
find something robust.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-19 10:57:56 +00:00
Yuxuan Shui
5d94b2a054 config: add a debug environment variable
This is where we keep temporary, short living, private debug options.

Adding and removing command line and config file options are
troublesome, and we don't want people adding these to their config
files.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-19 10:36:03 +00:00
Yuxuan Shui
ff691e6295 core: make missing dpms extension non-fatal
We are not using it for anything at the moment, and it is breaking CI.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-19 10:00:07 +00:00
Yuxuan Shui
fc62f6a4ed config: make naming consistent
*_VBLANK_SCHEDULER -> VBLANK_SCHEDULER_*

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-19 10:00:05 +00:00
Yuxuan Shui
0b45b3415b driver: choose sgi_video_sync scheduler for NVIDIA
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-19 10:00:04 +00:00
Yuxuan Shui
d7a2f8ade6 core: add a few debug logs
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-19 10:00:03 +00:00
Yuxuan Shui
ffaa42a019 core: add debug options to override the vblank scheduler
Useful for debugging.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-19 10:00:02 +00:00
Yuxuan Shui
e7c00108d1 vblank: add GLX_SGI_video_sync based scheduler
Present extension based scheduler doesn't work well on NVIDIA drivers.
GLX_SGI_video_sync is less accurate, but is rumoured to work. See
[kwin's usage](https://invent.kde.org/plasma/kwin/-/blob/master/src/
backends/x11/standalone/x11_standalone_sgivideosyncvsyncmonitor.cpp)

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-19 10:00:01 +00:00
Yuxuan Shui
72693b7550 core: don't always delay schedule_render to vblank
It's kind of dumb anyway. If we get damage event right after a vblank
event, we would waste the whole vblank.

Instead improve the frame scheduling logic to target the right vblank
interval. This only affects smart_frame_pacing anyway.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-19 09:59:59 +00:00
Yuxuan Shui
6986ba919a core: always check if render is finished at next vblank
Right now we rely on `reschedule_render_at_vblank` being scheduled for
the render finish check. Which means we will only know if a frame has
finished rendering the next time we queue a render. And we only check at
vblank boundary, which means when a render is queued we have to wait for
the next vblank, when had we checked earlier, we will be able to start
rendering immediately

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-19 09:59:58 +00:00
Yuxuan Shui
79bf7cd5c3 vblank: use table of function pointers instead of switch
Makes conditional compilation easier in the future.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-19 09:59:57 +00:00
Yuxuan Shui
91667d7747 vblank: winding down vblank events instead of stopping immediately
I noticed sometimes full frame rate video is rendered at half frame rate
sometimes. That is because the damage notify is sent very close to
vblank, and since we request vblank events when we get the damage, we
miss the vblank event immediately after, despite the damage happening
before the vblank.

       request  next  ......  next next
damage  vblank vblank          vblank
   |    |       |     ......      |
   v    v       v                 v
---------------------->>>>>>---------

`request vblank` is triggered by `damage`, but because it's too close to
`next vblank`, that vblank is missed despite we requested before it
happening, and we only get `next next vblank`. The result is we will
drop every other frame.

The solution in this commit is that we will keep requesting vblank
events, right after we received a vblank event, even when nobody is
asking for them. We would do that for a set number of vblanks before
stopping (currently 4).

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-19 09:59:56 +00:00
Yuxuan Shui
c6b48d7cbc x: fix x_request_vblank_event
present_notify_msc with divisor == 0 has undocumented special meaning,
it means async present notify, which means we could receive MSC
notifications from the past.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-19 09:59:54 +00:00
Yuxuan Shui
8384890d3a core: simplify pacing logic a bit more
Also, vblank event callback should call schedule_render to queue renders
instead of starting the draw timer directly, so that the CPU time
calculation will be correct.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-19 09:59:53 +00:00
Yuxuan Shui
f7b578dd54 config: add debug options to enable timing based pacing
Disable timing estimation based pacing by default, as it might not work
well across drivers, and might have subtle bugs.

You can try setting `PICOM_DEBUG=smart_frame_pacing` if you want to try
it out.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-19 09:59:52 +00:00
Yuxuan Shui
40ca6d7146 core: refactor frame pacing
Factored out vblank event generation, add abstraction over how vblank
events are generated. The goal is so we can request vblank events in
different ways based on the driver we are running on.

Tried to simplify the frame scheduling logic, we will see if I succeeded
or not.

Also, the logic to exclude vblank events for vblank interval estimation
when the screen off is dropped. It's too hard to get right, we need to
find something robust.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-19 09:59:50 +00:00
Yuxuan Shui
b3c12b8724 config: add a debug environment variable
This is where we keep temporary, short living, private debug options.

Adding and removing command line and config file options are
troublesome, and we don't want people adding these to their config
files.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-19 09:59:48 +00:00
Yuxuan Shui
3e8af9fb88 core: don't unredir when display is turned off
We unredirect because we receive bad vblank events, and also vblank
events at a different interval compared to when the screen is on. But it
is enough to just not record the vblank interval statistics when the
screen is off.

Although, unredirecting when display is off can also fix the problem
where use-damage causes the screen to flicker when the display is turned
off then back on. So we need something else for that.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-19 09:59:47 +00:00
Yuxuan Shui
2bc180c2a7 core: don't request vblank events when we are not rendering
Previously everytime we receive a vblank event, we always request a new
one. This made the logic somewhat simpler. But this generated many
useless vblank events, and wasted power. We only need vblank events for
two things:

1. after we rendered a frame, we need to know when it has been displayed
   on the screen.
2. estimating the refresh rate.

This commit makes sure we only request vblank events when it's actually
needed.

Fixes #1079

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-19 09:59:46 +00:00
Yuxuan Shui
580889488f core: simplify the pacing logic a little bit
Make it simpler to stop requesting PresentCompleteNotify when there is
nothing to render.

Related: #1079

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-19 09:59:43 +00:00
Yuxuan Shui
ce160cf432 core: don't call schedule_render too early
I mistakenly assumed that PresentCompleteNotify event signifies the end
of a vblank (or the start of scanout). But actually this event can in
theory in sent at any point during a vblank, with its timestamp pointing
to when the end of vblank is. (that's why we often find the timestamp to
be in the future).

Add a delay so schedule_render is actually called at the end of vblank,
so it doesn't mistakenly think the render is too slow to complete.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2023-12-18 04:37:03 +00:00
65 changed files with 1116 additions and 1313 deletions

View File

@@ -12,7 +12,6 @@ packages:
- uthash
- libconfig
- libglvnd
- libepoxy
- dbus
- pcre
sources:
@@ -20,7 +19,7 @@ sources:
tasks:
- setup: |
cd picom
CPPFLAGS="-I/usr/local/include" meson setup -Dunittest=true --werror build
CPPFLAGS="-I/usr/local/include" meson -Dunittest=true build
- build: |
cd picom
ninja -C build

View File

@@ -1,23 +0,0 @@
image: openbsd/latest
packages:
- libev
- xcb
- meson
- pkgconf
- cmake
- uthash
- libconfig
- dbus
- pcre2
sources:
- https://github.com/yshui/picom
tasks:
- setup: |
cd picom
CPPFLAGS="-I/usr/local/include" LDFLAGS="-L/usr/local/lib" meson setup -Dunittest=true --werror build
- build: |
cd picom
ninja -C build
- unittest: |
cd picom
ninja -C build test

View File

@@ -29,7 +29,7 @@ commands:
- ".git"
- run:
name: config
command: CC=<< parameters.cc >> meson setup << parameters.build-config >> -Dunittest=true --werror . build
command: CC=<< parameters.cc >> meson << parameters.build-config >> -Dunittest=true --werror . build
- run:
name: build
command: ninja -vC build

View File

@@ -23,9 +23,5 @@ CheckOptions:
value: 255.0;1.0;
- key: readability-function-cognitive-complexity.IgnoreMacros
value: true
- key: readability-function-cognitive-complexity.Threshold
value: 50
- key: readability-function-cognitive-complexity.DescribeBasicIncrements
value: true
- key: bugprone-signed-char-misuse.CharTypdefsToIgnore
value: int8_t

View File

@@ -3,6 +3,3 @@ root = true
indent_style = tab
indent_size = 8
max_line_length = 90
[*.nix]
indent_style = space
indent_size = 2

View File

@@ -18,11 +18,11 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
@@ -32,7 +32,7 @@ jobs:
# Autobuild
- name: Autobuild
uses: github/codeql-action/autobuild@v3
uses: github/codeql-action/autobuild@v2
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
uses: github/codeql-action/analyze@v2

View File

@@ -6,8 +6,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- run: git fetch --depth=1 origin ${{ github.event.pull_request.base.sha }}
- uses: yshui/git-clang-format-lint@v1.15
- uses: yshui/git-clang-format-lint@v1.14
with:
base: ${{ github.event.pull_request.base.sha }}

View File

@@ -6,9 +6,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
with:
fetch-depth: 2
- uses: yshui/git-clang-format-lint@v1.15
- uses: yshui/git-clang-format-lint@v1.14
with:
base: ${{ github.event.ref }}~1

1
.gitignore vendored
View File

@@ -1,6 +1,5 @@
# Build files
.deps
.direnv
aclocal.m4
autom4te.cache
config.log

View File

@@ -1,74 +0,0 @@
# Unreleased
## New features
* Allow `corner-radius-rules` to override `corner-radius = 0`. Previously setting corner radius to 0 globally disables rounded corners. (#1170)
## Notable changes
* Marginally improve performance when resizing/opening/closing windows. (#1190)
# v11.2 (2024-Feb-13)
## Build changes
* `picom` now depends on `libepoxy` for OpenGL symbol management.
## Bug fixes
* Workaround a NVIDIA problem that causes high CPU usage after suspend/resume (#1172, #1168)
* Fix building on OpenBSD (#1189, #1188)
* Fix occasional freezes (#1040, #1145, #1166)
* Fix `corner-radius-rules` not applying sometimes (#1177)
* Fix window shader not having an effect when frame opacity is enabled (#1174)
* Fix binding root pixmap in case of depth mismatch (#984)
# v11.1 (2024-Jan-28)
## Bug fixes
* Fix missing fading on window close for some window managers. (#704)
# v11 (2024-Jan-20)
## Build changes
* Due to some caveats discovered related to setting the `CAP_SYS_NICE` capability, it is now recommended to **NOT** set this capability for picom.
## Deprecations
* Uses of `--sw-opti`, and `--respect-prop-shadow` are now hard errors.
* `-F` has been removed completely. It was deprecated before the picom fork.
# v11-rc1 (2024-Jan-14)
## Notable features
* picom now uses dithering to prevent banding. Banding is most notable when using a strong background blur. (#952)
* Frame pacing. picom uses present feedback information to schedule new frames when it makes sense to do so. This improves latency, and replaces the `glFlush` and `GL_MaxFramesAllowed=1` hacks we used to do for NVIDIA. (#968 #1156)
* Some missing features have been implemented for the EGL backend (#1004 #1007)
## Bug fixes
* Many memory/resource leak fixes thanks to @absolutelynothelix . (#977 #978 #979 #980 #982 #985 #992 #1009 #1022)
* Fix tiling of wallpaper. (#1002)
* Fix some blur artifacts (#1095)
* Fix shadow color for transparent shadows (#1124)
* Don't spam logs when another compositor is running (#1104)
* Fix rounded corners showing as black with the xrender backend (#1003)
* Fix blur with rounded windows (#950)
## Build changes
* Dependency `pcre` has been replaced by `pcre2`.
* New dependency `xcb-util`.
* `xinerama` is no longer used.
* `picom` now tries to give itself a real-time scheduling priority. ~~Please consider giving `picom` the `CAP_SYS_NICE` capability when packaging it.~~
## Deprecations
* The `kawase` blur method is removed. Note this is just an alias to the `dual_kawase` method, which is still available. (#1102)
# Earlier versions
Please see the GitHub releases page.

View File

@@ -34,7 +34,6 @@ hasufell <julian.ospald at googlemail.com>
i-c-u-p
Ignacio Taranto <ignacio.taranto at eclypsium.com>
Istvan Petres
Ivan Malison <ivanmalison at gmail.com>
Jake <jakeroggenbuck2 at gmail.com>
James Cloos <cloos at jhcloos.com>
Jamey Sharp <jamey at minilop.net>
@@ -44,7 +43,6 @@ Javeed Shaikh <syscrash2k at gmail.com>
Jerónimo Navarro <jnavarro at ancasrl.com.ar>
jialeens <jialeadmin at 163.com>
Johnny Pribyl <pribylsnbits at gmail.com>
Jose Maldonado aka Yukiteru <josemald89 at gmail.com>
Keith Packard <keithp at keithp.com>
Kevin Kelley <kelleyk at kelleyk.net>
ktprograms <ktprograms at gmail.com>
@@ -70,7 +68,6 @@ Peter Mattern <matternp at arcor.de>
Phil Blundell <pb at reciva.com>
Que Quotion <quequotion at bugmenot.com>
Rafael Kitover <rkitover at gmail.com>
Reith
Richard Grenville <pyxlcy at gmail.com>
Rytis Karpuska <rytis.karpuska at gmail.com>
Samuel Hand <samuel.d.hand at gmail.com>

View File

@@ -41,7 +41,7 @@ Assuming you already have all the usual building tools installed (e.g. gcc, pyth
* pixman
* libdbus (optional, disable with the `-Ddbus=false` meson configure flag)
* libconfig (optional, disable with the `-Dconfig_file=false` meson configure flag)
* libGL, libEGL, libepoxy (optional, disable with the `-Dopengl=false` meson configure flag)
* libGL, libEGL (optional, disable with the `-Dopengl=false` meson configure flag)
* libpcre2 (optional, disable with the `-Dregex=false` meson configure flag)
* libev
* uthash
@@ -49,13 +49,13 @@ Assuming you already have all the usual building tools installed (e.g. gcc, pyth
On Debian based distributions (e.g. Ubuntu), the needed packages are
```
libconfig-dev libdbus-1-dev libegl-dev libev-dev libgl-dev libepoxy-dev libpcre2-dev libpixman-1-dev libx11-xcb-dev libxcb1-dev libxcb-composite0-dev libxcb-damage0-dev libxcb-dpms0-dev libxcb-glx0-dev libxcb-image0-dev libxcb-present-dev libxcb-randr0-dev libxcb-render0-dev libxcb-render-util0-dev libxcb-shape0-dev libxcb-util-dev libxcb-xfixes0-dev libxext-dev meson ninja-build uthash-dev
libconfig-dev libdbus-1-dev libegl-dev libev-dev libgl-dev libpcre2-dev libpixman-1-dev libx11-xcb-dev libxcb1-dev libxcb-composite0-dev libxcb-damage0-dev libxcb-dpms0-dev libxcb-glx0-dev libxcb-image0-dev libxcb-present-dev libxcb-randr0-dev libxcb-render0-dev libxcb-render-util0-dev libxcb-shape0-dev libxcb-util-dev libxcb-xfixes0-dev libxext-dev meson ninja-build uthash-dev
```
On Fedora, the needed packages are
```
dbus-devel gcc git libconfig-devel libdrm-devel libev-devel libX11-devel libX11-xcb libXext-devel libxcb-devel libGL-devel libEGL-devel libepoxy-devel meson pcre2-devel pixman-devel uthash-devel xcb-util-image-devel xcb-util-renderutil-devel xorg-x11-proto-devel xcb-util-devel
dbus-devel gcc git libconfig-devel libdrm-devel libev-devel libX11-devel libX11-xcb libXext-devel libxcb-devel libGL-devel libEGL-devel meson pcre2-devel pixman-devel uthash-devel xcb-util-image-devel xcb-util-renderutil-devel xorg-x11-proto-devel
```
To build the documents, you need `asciidoc`
@@ -96,12 +96,6 @@ $ ninja -C build install
Default install prefix is `/usr/local`, you can change it with `meson configure -Dprefix=<path> build`
## Running
To launch with all animations as a background process you can use:
`picom --animations -b`
To only have specific animations, enable them with cli flags (see `picom --help`) or add them to your picom config.
## How to Contribute
All contributions are welcome!

23
flake.lock generated
View File

@@ -5,11 +5,11 @@
"systems": "systems"
},
"locked": {
"lastModified": 1705309234,
"narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
"lastModified": 1689068808,
"narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
"rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4",
"type": "github"
},
"original": {
@@ -25,11 +25,11 @@
]
},
"locked": {
"lastModified": 1703887061,
"narHash": "sha256-gGPa9qWNc6eCXT/+Z5/zMkyYOuRZqeFZBDbopNZQkuY=",
"lastModified": 1660459072,
"narHash": "sha256-8DFJjXG8zqoONA1vXtgeKXy68KdJL5UaXR8NtVMUbx8=",
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "43e1aa1308018f37118e34d3a9cb4f5e75dc11d5",
"rev": "a20de23b925fd8264fd7fad6454652e142fd7f73",
"type": "github"
},
"original": {
@@ -41,12 +41,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1705856552,
"narHash": "sha256-JXfnuEf5Yd6bhMs/uvM67/joxYKoysyE3M2k6T3eWbg=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "612f97239e2cc474c13c9dafa0df378058c5ad8d",
"type": "github"
"lastModified": 1691186842,
"narHash": "sha256-wxBVCvZUwq+XS4N4t9NqsHV4E64cPVqQ2fdDISpjcw0=",
"path": "/nix/store/d42v5grfq77vr10r336kks0qjp0wij8d-source",
"rev": "18036c0be90f4e308ae3ebcab0e14aae0336fe42",
"type": "path"
},
"original": {
"id": "nixpkgs",

View File

@@ -7,55 +7,28 @@
};
};
outputs = {
self,
flake-utils,
nixpkgs,
git-ignore-nix,
...
}:
flake-utils.lib.eachDefaultSystem (system: let
overlay = self: super: {
picom = super.picom.overrideAttrs (oldAttrs: rec {
version = "11";
pname = "picom";
buildInputs =
[
self.pcre2
self.xorg.xcbutil
self.libepoxy
]
++ self.lib.remove self.xorg.libXinerama (
self.lib.remove self.pcre oldAttrs.buildInputs
);
src = git-ignore-nix.lib.gitignoreSource ./.;
});
};
pkgs = import nixpkgs {
inherit system overlays;
config.allowBroken = true;
};
overlays = [overlay];
in rec {
inherit
overlay
overlays
;
defaultPackage = pkgs.picom;
devShell = defaultPackage.overrideAttrs {
buildInputs =
defaultPackage.buildInputs
++ (with pkgs; [
clang-tools_17
llvmPackages_17.clang-unwrapped.python
]);
hardeningDisable = ["fortify"];
shellHook = ''
# Workaround a NixOS limitation on sanitizers:
# See: https://github.com/NixOS/nixpkgs/issues/287763
export LD_LIBRARY_PATH+=":/run/opengl-driver/lib"
'';
};
});
self, flake-utils, nixpkgs, git-ignore-nix, ...
}: flake-utils.lib.eachDefaultSystem (system: let
overlay = self: super: {
picom = super.picom.overrideAttrs (oldAttrs: rec {
pname = "picom";
buildInputs = [
self.pcre2 self.xorg.xcbutil
] ++ self.lib.remove self.xorg.libXinerama (
self.lib.remove self.pcre oldAttrs.buildInputs
);
src = git-ignore-nix.lib.gitignoreSource ./.;
});
};
pkgs = import nixpkgs { inherit system overlays; config.allowBroken = true; };
overlays = [ overlay ];
in rec {
inherit overlay overlays;
defaultPackage = pkgs.picom;
devShell = defaultPackage.overrideAttrs {
buildInputs = defaultPackage.buildInputs ++ [
pkgs.clang-tools
];
};
});
}

View File

@@ -49,6 +49,9 @@ OPTIONS
*-f*, *--fading*::
Fade windows in/out when opening/closing and when opacity changes, unless *--no-fading-openclose* is used.
*-F*::
Equals to *-f*. Deprecated.
*-i*, *--inactive-opacity*='OPACITY'::
Opacity of inactive windows. (0.1 - 1.0, defaults to 1.0)
@@ -390,7 +393,6 @@ uniform float corner_radius; // corner radius of the window (pixels)
uniform float border_width; // estimated border width of the window (pixels)
uniform bool invert_color; // whether to invert the color of the window
uniform sampler2D tex; // texture of the window
uniform vec2 effective_size; // effective dimensions of the texture (repeats pixels if larger than tex)
uniform sampler2D brightness; // estimated brightness of the window, 1x1 texture
uniform float max_brightness; // configured maximum brightness of the window (0.0 - 1.0)
uniform float time; // time in milliseconds, counting from an unspecified starting point
@@ -416,8 +418,7 @@ vec4 default_post_processing(vec4 c);
// 1) fetch the specified pixel
// 2) apply default post-processing
vec4 window_shader() {
vec2 texsize = textureSize(tex, 0);
vec4 c = texture2D(tex, texcoord / texsize, 0);
vec4 c = texelFetch(tex, ivec2(texcoord), 0);
return default_post_processing(c);
}
----

View File

@@ -1,4 +1,4 @@
project('picom', 'c', version: '11',
project('picom', 'c', version: '10',
default_options: ['c_std=c11', 'warning_level=1'])
cc = meson.get_compiler('c')

View File

@@ -2,20 +2,12 @@
#include <xcb/xcb.h>
#include "atom.h"
#include "cache.h"
#include "common.h"
#include "compiler.h"
#include "log.h"
#include "utils.h"
#include "log.h"
struct atom_entry {
struct cache_handle entry;
xcb_atom_t atom;
};
static inline int atom_getter(struct cache *cache attr_unused, const char *atom_name,
struct cache_handle **value, void *user_data) {
xcb_connection_t *c = user_data;
static inline void *atom_getter(void *ud, const char *atom_name, int *err) {
xcb_connection_t *c = ud;
xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(
c, xcb_intern_atom(c, 0, to_u16_checked(strlen(atom_name)), atom_name), NULL);
@@ -26,32 +18,9 @@ static inline int atom_getter(struct cache *cache attr_unused, const char *atom_
free(reply);
} else {
log_error("Failed to intern atoms");
return -1;
*err = 1;
}
struct atom_entry *entry = ccalloc(1, struct atom_entry);
entry->atom = atom;
*value = &entry->entry;
return 0;
}
static inline void
atom_entry_free(struct cache *cache attr_unused, struct cache_handle *handle) {
struct atom_entry *entry = cache_entry(handle, struct atom_entry, entry);
free(entry);
}
xcb_atom_t get_atom(struct atom *a, const char *key, xcb_connection_t *c) {
struct cache_handle *entry = NULL;
if (cache_get_or_fetch(&a->c, key, &entry, c, atom_getter) < 0) {
log_error("Failed to get atom %s", key);
return XCB_NONE;
}
return cache_entry(entry, struct atom_entry, entry)->atom;
}
xcb_atom_t get_atom_cached(struct atom *a, const char *key) {
return cache_entry(cache_get(&a->c, key), struct atom_entry, entry)->atom;
return (void *)(intptr_t)atom;
}
/**
@@ -59,15 +28,10 @@ xcb_atom_t get_atom_cached(struct atom *a, const char *key) {
*/
struct atom *init_atoms(xcb_connection_t *c) {
auto atoms = ccalloc(1, struct atom);
atoms->c = CACHE_INIT;
#define ATOM_GET(x) atoms->a##x = get_atom(atoms, #x, c)
atoms->c = new_cache((void *)c, atom_getter, NULL);
#define ATOM_GET(x) atoms->a##x = (xcb_atom_t)(intptr_t)cache_get(atoms->c, #x, NULL)
LIST_APPLY(ATOM_GET, SEP_COLON, ATOM_LIST1);
LIST_APPLY(ATOM_GET, SEP_COLON, ATOM_LIST2);
#undef ATOM_GET
return atoms;
}
void destroy_atoms(struct atom *a) {
cache_invalidate_all(&a->c, atom_entry_free);
free(a);
}

View File

@@ -1,4 +1,6 @@
#pragma once
#include <stdlib.h>
#include <xcb/xcb.h>
#include "cache.h"
@@ -21,7 +23,6 @@
WM_CLIENT_MACHINE, \
_NET_ACTIVE_WINDOW, \
_COMPTON_SHADOW, \
COMPTON_VERSION, \
_NET_WM_WINDOW_TYPE, \
_XROOTPMAP_ID, \
ESETROOT_PMAP_ID, \
@@ -51,18 +52,19 @@
#define ATOM_DEF(x) xcb_atom_t a##x
struct atom_entry;
struct atom {
struct cache c;
struct cache *c;
LIST_APPLY(ATOM_DEF, SEP_COLON, ATOM_LIST1);
LIST_APPLY(ATOM_DEF, SEP_COLON, ATOM_LIST2);
};
/// Create a new atom object with a xcb connection. `struct atom` does not hold
/// a reference to the connection.
struct atom *init_atoms(xcb_connection_t *c);
struct atom *init_atoms(xcb_connection_t *);
xcb_atom_t get_atom(struct atom *a, const char *key, xcb_connection_t *c);
xcb_atom_t get_atom_cached(struct atom *a, const char *key);
static inline xcb_atom_t get_atom(struct atom *a, const char *key) {
return (xcb_atom_t)(intptr_t)cache_get(a->c, key, NULL);
}
void destroy_atoms(struct atom *a);
static inline void destroy_atoms(struct atom *a) {
cache_free(a->c);
free(a);
}

View File

@@ -246,7 +246,7 @@ bool paint_all_new(session_t *ps, struct managed_win *const t) {
auto after_damage_us = (uint64_t)now.tv_sec * 1000000UL + (uint64_t)now.tv_nsec / 1000;
log_trace("Getting damage took %" PRIu64 " us", after_damage_us - after_sync_fence_us);
if (ps->next_render > 0) {
log_verbose("Render schedule deviation: %ld us (%s) %" PRIu64 " %" PRIu64,
log_verbose("Render schedule deviation: %ld us (%s) %" PRIu64 " %ld",
labs((long)after_damage_us - (long)ps->next_render),
after_damage_us < ps->next_render ? "early" : "late",
after_damage_us, ps->next_render);
@@ -527,7 +527,7 @@ bool paint_all_new(session_t *ps, struct managed_win *const t) {
&reg_paint_in_bound, &reg_visible, true);
} else {
if (is_animating && w->old_win_image) {
bool is_focused = win_is_focused_raw(w);
bool is_focused = win_is_focused_raw(ps, w);
if (!is_focused && w->focused && w->opacity_is_set)
is_focused = true;
assert(w->old_win_image);

View File

@@ -115,10 +115,6 @@ struct dual_kawase_blur_args {
int strength;
};
typedef struct {
// Intentionally left blank
} *image_handle;
struct backend_operations {
// =========== Initialization ===========
@@ -171,27 +167,31 @@ struct backend_operations {
* Paint the content of an image onto the rendering buffer.
*
* @param backend_data the backend data
* @param image the image to paint, cannot be NULL
* @param image_data the image to paint
* @param dst_x, dst_y the top left corner of the image in the target
* @param mask the mask image, the top left of the mask is aligned with
* the top left of the image. Optional, can be
* NULL.
* the top left of the image
* @param reg_paint the clip region, in target coordinates
* @param reg_visible the visible region, in target coordinates
*/
void (*compose)(backend_t *backend_data, image_handle image, coord_t image_dst,
image_handle mask, coord_t mask_dst, const region_t *reg_paint,
const region_t *reg_visible, bool lerp) attr_nonnull(1, 2, 6, 7);
void (*compose)(backend_t *backend_data, void *image_data, coord_t image_dst,
void *mask, coord_t mask_dst, const region_t *reg_paint,
const region_t *reg_visible, bool lerp);
void (*_compose)(backend_t *backend_data, void *image_data,
int dst_x1, int dst_y1, int dst_x2, int dst_y2,
const region_t *reg_paint, const region_t *reg_visible);
/// Fill rectangle of the rendering buffer, mostly for debug purposes, optional.
void (*fill)(backend_t *backend_data, struct color, const region_t *clip);
/// Blur a given region of the rendering buffer.
///
/// The blur can be limited by `mask`. `mask_dst` specifies the top left corner of
/// the mask. `mask` can be NULL.
bool (*blur)(backend_t *backend_data, double opacity, void *blur_ctx,
image_handle mask, coord_t mask_dst, const region_t *reg_blur,
/// The blur is limited by `mask`. `mask_dst` specifies the top left corner of the
/// mask is.
bool (*blur)(backend_t *backend_data, double opacity, void *blur_ctx, void *mask,
coord_t mask_dst, const region_t *reg_blur,
const region_t *reg_visible) attr_nonnull(1, 3, 6, 7);
/// Update part of the back buffer with the rendering buffer, then present the
@@ -207,15 +207,13 @@ struct backend_operations {
* Bind a X pixmap to the backend's internal image data structure.
*
* @param backend_data backend data
* @param pixmap X pixmap to bind
* @param fmt information of the pixmap's visual
* @param owned whether the ownership of the pixmap is transferred to the
* backend.
* @return backend specific image handle for the pixmap. May be
* NULL.
* @param pixmap X pixmap to bind
* @param fmt information of the pixmap's visual
* @param owned whether the ownership of the pixmap is transfered to the backend
* @return backend internal data structure bound with this pixmap
*/
image_handle (*bind_pixmap)(backend_t *backend_data, xcb_pixmap_t pixmap,
struct xvisual_info fmt, bool owned);
void *(*bind_pixmap)(backend_t *backend_data, xcb_pixmap_t pixmap,
struct xvisual_info fmt, bool owned);
/// Create a shadow context for rendering shadows with radius `radius`.
/// Default implementation: default_create_shadow_context
@@ -227,27 +225,21 @@ struct backend_operations {
struct backend_shadow_context *ctx);
/// Create a shadow image based on the parameters. Resulting image should have a
/// size of `width + radius * 2` x `height + radius * 2`. Radius is set when the
/// size of `width + radisu * 2` x `height + radius * 2`. Radius is set when the
/// shadow context is created.
/// Default implementation: default_render_shadow
///
/// @return the shadow image, may be NULL.
///
/// Required.
image_handle (*render_shadow)(backend_t *backend_data, int width, int height,
struct backend_shadow_context *ctx, struct color color);
void *(*render_shadow)(backend_t *backend_data, int width, int height,
struct backend_shadow_context *ctx, struct color color);
/// Create a shadow by blurring a mask. `size` is the size of the blur. The
/// backend can use whichever blur method is the fastest. The shadow produced
/// shoule be consistent with `render_shadow`.
///
/// @param mask the input mask, must not be NULL.
/// @return the shadow image, may be NULL.
///
/// Optional.
image_handle (*shadow_from_mask)(backend_t *backend_data, image_handle mask,
struct backend_shadow_context *ctx,
struct color color);
void *(*shadow_from_mask)(backend_t *backend_data, void *mask,
struct backend_shadow_context *ctx, struct color color);
/// Create a mask image from region `reg`. This region can be used to create
/// shadow, or used as a mask for composing. When used as a mask, it should mask
@@ -258,18 +250,13 @@ struct backend_operations {
/// and outside of the mask. Corner radius should exclude the corners from the
/// mask. Corner radius should be applied before the inversion.
///
/// @return the mask image, may be NULL.
///
/// Required.
image_handle (*make_mask)(backend_t *backend_data, geometry_t size,
const region_t *reg);
void *(*make_mask)(backend_t *backend_data, geometry_t size, const region_t *reg);
// ============ Resource management ===========
/// Free resources associated with an image data structure
///
/// @param image the image to be released, cannot be NULL.
void (*release_image)(backend_t *backend_data, image_handle image) attr_nonnull(1, 2);
void (*release_image)(backend_t *backend_data, void *img_data) attr_nonnull(1, 2);
/// Create a shader object from a shader source.
///
@@ -294,14 +281,12 @@ struct backend_operations {
/// This function is needed because some backend might change the content of the
/// window (e.g. when using a custom shader with the glx backend), so only the
/// backend knows if an image is transparent.
///
/// @param image the image to be checked, must not be NULL.
bool (*is_image_transparent)(backend_t *backend_data, image_handle image)
bool (*is_image_transparent)(backend_t *backend_data, void *image_data)
attr_nonnull(1, 2);
/// Get the age of the buffer content we are currently rendering on top
/// Get the age of the buffer content we are currently rendering ontop
/// of. The buffer that has just been `present`ed has a buffer age of 1.
/// Every time `present` is called, buffers get older. Return -1 if the
/// Everytime `present` is called, buffers get older. Return -1 if the
/// buffer is empty.
///
/// Optional
@@ -310,7 +295,7 @@ struct backend_operations {
/// Get the render time of the last frame. If the render is still in progress,
/// returns false. The time is returned in `ts`. Frames are delimited by the
/// present() calls. i.e. after a present() call, last_render_time() should start
/// reporting the time of the just presented frame.
/// reporting the time of the just presen1ted frame.
///
/// Optional, if not available, the most conservative estimation will be used.
bool (*last_render_time)(backend_t *backend_data, struct timespec *ts);
@@ -331,39 +316,35 @@ struct backend_operations {
*
* @param backend_data backend data
* @param prop the property to change
* @param image an image handle, cannot be NULL.
* @param image_data an image data structure returned by the backend
* @param args property value
* @return whether the operation is successful
* @return whether the operation is successful
*/
bool (*set_image_property)(backend_t *backend_data, enum image_properties prop,
image_handle image, void *args) attr_nonnull(1, 3);
void *image_data, void *args);
/**
* Manipulate an image. Image properties are untouched.
*
* @param backend_data backend data
* @param op the operation to perform
* @param image an image handle, cannot be NULL.
* @param image_data an image data structure returned by the backend
* @param reg_op the clip region, define the part of the image to be
* operated on.
* @param reg_visible define the part of the image that will eventually
* be visible on target. this is a hint to the backend
* for optimization purposes.
* @param args extra arguments, operation specific
* @return whether the operation is successful
* @return whether the operation is successful
*/
bool (*image_op)(backend_t *backend_data, enum image_operations op,
image_handle image, const region_t *reg_op,
const region_t *reg_visible, void *args) attr_nonnull(1, 3, 4, 5);
bool (*image_op)(backend_t *backend_data, enum image_operations op, void *image_data,
const region_t *reg_op, const region_t *reg_visible, void *args);
/// Create another instance of the `image`. The newly created image
/// inherits its content and all image properties from the image being
/// cloned. All `image_op` and `set_image_property` calls on the
/// returned image should not affect the original image.
///
/// @param image the image to be cloned, must not be NULL.
image_handle (*clone_image)(backend_t *base, image_handle image,
const region_t *reg_visible) attr_nonnull_all;
/// Create another instance of the `image_data`. All `image_op` and
/// `set_image_property` calls on the returned image should not affect the
/// original image
void *(*clone_image)(backend_t *base, const void *image_data,
const region_t *reg_visible);
/// Create a blur context that can be used to call `blur`
void *(*create_blur_context)(backend_t *base, enum blur_method, void *args);

View File

@@ -293,8 +293,8 @@ shadow_picture_err:
return false;
}
image_handle default_render_shadow(backend_t *backend_data, int width, int height,
struct backend_shadow_context *sctx, struct color color) {
void *default_render_shadow(backend_t *backend_data, int width, int height,
struct backend_shadow_context *sctx, struct color color) {
const conv *kernel = (void *)sctx;
xcb_render_picture_t shadow_pixel =
solid_picture(backend_data->c, true, 1, color.red, color.green, color.blue);
@@ -308,7 +308,7 @@ image_handle default_render_shadow(backend_t *backend_data, int width, int heigh
}
auto visual = x_get_visual_for_standard(backend_data->c, XCB_PICT_STANDARD_ARGB_32);
auto ret = backend_data->ops->bind_pixmap(
void *ret = backend_data->ops->bind_pixmap(
backend_data, shadow, x_get_visual_info(backend_data->c, visual), true);
x_free_picture(backend_data->c, pict);
x_free_picture(backend_data->c, shadow_pixel);
@@ -316,16 +316,16 @@ image_handle default_render_shadow(backend_t *backend_data, int width, int heigh
}
/// Implement render_shadow with shadow_from_mask
image_handle
void *
backend_render_shadow_from_mask(backend_t *backend_data, int width, int height,
struct backend_shadow_context *sctx, struct color color) {
region_t reg;
pixman_region32_init_rect(&reg, 0, 0, (unsigned int)width, (unsigned int)height);
auto mask = backend_data->ops->make_mask(
void *mask = backend_data->ops->make_mask(
backend_data, (geometry_t){.width = width, .height = height}, &reg);
pixman_region32_fini(&reg);
auto shadow = backend_data->ops->shadow_from_mask(backend_data, mask, sctx, color);
void *shadow = backend_data->ops->shadow_from_mask(backend_data, mask, sctx, color);
backend_data->ops->release_image(backend_data, mask);
return shadow;
}
@@ -458,17 +458,17 @@ struct dual_kawase_params *generate_dual_kawase_params(void *args) {
return params;
}
image_handle default_clone_image(backend_t *base attr_unused, image_handle image,
const region_t *reg_visible attr_unused) {
void *default_clone_image(backend_t *base attr_unused, const void *image_data,
const region_t *reg_visible attr_unused) {
auto new_img = ccalloc(1, struct backend_image);
*new_img = *(struct backend_image *)image;
*new_img = *(struct backend_image *)image_data;
new_img->inner->refcount++;
return (image_handle)new_img;
return new_img;
}
bool default_set_image_property(backend_t *base attr_unused, enum image_properties op,
image_handle image, void *arg) {
auto tex = (struct backend_image *)image;
void *image_data, void *arg) {
struct backend_image *tex = image_data;
int *iargs = arg;
bool *bargs = arg;
double *dargs = arg;
@@ -490,8 +490,8 @@ bool default_set_image_property(backend_t *base attr_unused, enum image_properti
return true;
}
bool default_is_image_transparent(backend_t *base attr_unused, image_handle image) {
auto img = (struct backend_image *)image;
bool default_is_image_transparent(backend_t *base attr_unused, void *image_data) {
struct backend_image *img = image_data;
return img->opacity < 1 || img->inner->has_alpha;
}

View File

@@ -54,11 +54,11 @@ solid_picture(struct x_connection *, bool argb, double a, double r, double g, do
xcb_image_t *make_shadow(struct x_connection *c, const conv *kernel, double opacity,
int width, int height);
image_handle default_render_shadow(backend_t *backend_data, int width, int height,
struct backend_shadow_context *sctx, struct color color);
void *default_render_shadow(backend_t *backend_data, int width, int height,
struct backend_shadow_context *sctx, struct color color);
/// Implement `render_shadow` with `shadow_from_mask`.
image_handle
void *
backend_render_shadow_from_mask(backend_t *backend_data, int width, int height,
struct backend_shadow_context *sctx, struct color color);
struct backend_shadow_context *
@@ -72,8 +72,8 @@ 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);
image_handle default_clone_image(backend_t *base, image_handle image, const region_t *reg);
bool default_is_image_transparent(backend_t *base attr_unused, image_handle image);
void *default_clone_image(backend_t *base, const void *image_data, const region_t *reg);
bool default_is_image_transparent(backend_t *base attr_unused, void *image_data);
bool default_set_image_property(backend_t *base attr_unused, enum image_properties op,
image_handle image, void *arg);
void *image_data, void *arg);
struct backend_image *default_new_backend_image(int w, int h);

View File

@@ -19,14 +19,11 @@ void apply_driver_workarounds(struct session *ps, enum driver driver) {
}
}
enum vblank_scheduler_type choose_vblank_scheduler(enum driver driver attr_unused) {
enum vblank_scheduler_type type = VBLANK_SCHEDULER_PRESENT;
#ifdef CONFIG_OPENGL
enum vblank_scheduler_type choose_vblank_scheduler(enum driver driver) {
if (driver & DRIVER_NVIDIA) {
type = VBLANK_SCHEDULER_SGI_VIDEO_SYNC;
return VBLANK_SCHEDULER_SGI_VIDEO_SYNC;
}
#endif
return type;
return VBLANK_SCHEDULER_PRESENT;
}
enum driver detect_driver(xcb_connection_t *c, backend_t *backend_data, xcb_window_t window) {

View File

@@ -50,9 +50,8 @@ void dummy_deinit(struct backend_base *data) {
free(dummy);
}
static void dummy_check_image(struct backend_base *base, image_handle image) {
static void dummy_check_image(struct backend_base *base, const struct dummy_image *img) {
auto dummy = (struct dummy_data *)base;
auto img = (struct dummy_image *)image;
if (img == (struct dummy_image *)&dummy->mask) {
return;
}
@@ -65,13 +64,13 @@ static void dummy_check_image(struct backend_base *base, image_handle image) {
assert(*tmp->refcount > 0);
}
void dummy_compose(struct backend_base *base, image_handle image, coord_t dst attr_unused,
image_handle mask attr_unused, coord_t mask_dst attr_unused,
void dummy_compose(struct backend_base *base, void *image, coord_t dst attr_unused,
void *mask attr_unused, coord_t mask_dst attr_unused,
const region_t *reg_paint attr_unused,
const region_t *reg_visible attr_unused, bool lerp attr_unused) {
auto dummy attr_unused = (struct dummy_data *)base;
dummy_check_image(base, image);
assert(mask == NULL || (struct backend_image *)mask == &dummy->mask);
assert(mask == NULL || mask == &dummy->mask);
}
void dummy_fill(struct backend_base *backend_data attr_unused, struct color c attr_unused,
@@ -79,20 +78,20 @@ void dummy_fill(struct backend_base *backend_data attr_unused, struct color c at
}
bool dummy_blur(struct backend_base *backend_data attr_unused, double opacity attr_unused,
void *blur_ctx attr_unused, image_handle mask attr_unused,
void *blur_ctx attr_unused, void *mask attr_unused,
coord_t mask_dst attr_unused, const region_t *reg_blur attr_unused,
const region_t *reg_visible attr_unused) {
return true;
}
image_handle dummy_bind_pixmap(struct backend_base *base, xcb_pixmap_t pixmap,
struct xvisual_info fmt, bool owned) {
void *dummy_bind_pixmap(struct backend_base *base, xcb_pixmap_t pixmap,
struct xvisual_info fmt, bool owned) {
auto dummy = (struct dummy_data *)base;
struct dummy_image *img = NULL;
HASH_FIND_INT(dummy->images, &pixmap, img);
if (img) {
(*img->refcount)++;
return (image_handle)img;
return img;
}
img = ccalloc(1, struct dummy_image);
@@ -103,12 +102,12 @@ image_handle dummy_bind_pixmap(struct backend_base *base, xcb_pixmap_t pixmap,
img->owned = owned;
HASH_ADD_INT(dummy->images, pixmap, img);
return (image_handle)img;
return (void *)img;
}
void dummy_release_image(backend_t *base, image_handle image) {
void dummy_release_image(backend_t *base, void *image) {
auto dummy = (struct dummy_data *)base;
if ((struct backend_image *)image == &dummy->mask) {
if (image == &dummy->mask) {
return;
}
auto img = (struct dummy_image *)image;
@@ -124,9 +123,10 @@ void dummy_release_image(backend_t *base, image_handle image) {
}
}
bool dummy_is_image_transparent(struct backend_base *base, image_handle image) {
dummy_check_image(base, image);
return ((struct dummy_image *)image)->transparent;
bool dummy_is_image_transparent(struct backend_base *base, void *image) {
auto img = (struct dummy_image *)image;
dummy_check_image(base, img);
return img->transparent;
}
int dummy_buffer_age(struct backend_base *base attr_unused) {
@@ -134,31 +134,29 @@ int dummy_buffer_age(struct backend_base *base attr_unused) {
}
bool dummy_image_op(struct backend_base *base, enum image_operations op attr_unused,
image_handle image, const region_t *reg_op attr_unused,
void *image, const region_t *reg_op attr_unused,
const region_t *reg_visible attr_unused, void *args attr_unused) {
dummy_check_image(base, image);
return true;
}
image_handle dummy_make_mask(struct backend_base *base, geometry_t size attr_unused,
const region_t *reg attr_unused) {
auto dummy = (struct dummy_data *)base;
auto mask = &dummy->mask;
return (image_handle)mask;
void *dummy_make_mask(struct backend_base *base, geometry_t size attr_unused,
const region_t *reg attr_unused) {
return &(((struct dummy_data *)base)->mask);
}
bool dummy_set_image_property(struct backend_base *base, enum image_properties prop attr_unused,
image_handle image, void *arg attr_unused) {
void *image, void *arg attr_unused) {
dummy_check_image(base, image);
return true;
}
image_handle dummy_clone_image(struct backend_base *base, image_handle image,
const region_t *reg_visible attr_unused) {
dummy_check_image(base, image);
auto image_impl = (struct dummy_image *)image;
(*image_impl->refcount)++;
return image;
void *dummy_clone_image(struct backend_base *base, const void *image,
const region_t *reg_visible attr_unused) {
auto img = (const struct dummy_image *)image;
dummy_check_image(base, img);
(*img->refcount)++;
return (void *)img;
}
void *dummy_create_blur_context(struct backend_base *base attr_unused,

View File

@@ -256,11 +256,10 @@ bool gl_dual_kawase_blur(double opacity, struct gl_blur_context *bctx, const rec
return true;
}
bool gl_blur_impl(double opacity, struct gl_blur_context *bctx,
struct backend_image *mask, coord_t mask_dst, const region_t *reg_blur,
const region_t *reg_visible attr_unused, GLuint source_texture,
geometry_t source_size, GLuint target_fbo, GLuint default_mask,
bool high_precision) {
bool gl_blur_impl(double opacity, struct gl_blur_context *bctx, void *mask, coord_t mask_dst,
const region_t *reg_blur, const region_t *reg_visible attr_unused,
GLuint source_texture, geometry_t source_size, GLuint target_fbo,
GLuint default_mask, bool high_precision) {
bool ret = false;
if (source_size.width != bctx->fb_width || source_size.height != bctx->fb_height) {
@@ -401,12 +400,12 @@ bool gl_blur_impl(double opacity, struct gl_blur_context *bctx,
return ret;
}
bool gl_blur(backend_t *base, double opacity, void *ctx, image_handle mask, coord_t mask_dst,
bool gl_blur(backend_t *base, double opacity, void *ctx, void *mask, coord_t mask_dst,
const region_t *reg_blur, const region_t *reg_visible attr_unused) {
auto gd = (struct gl_data *)base;
auto bctx = (struct gl_blur_context *)ctx;
return gl_blur_impl(opacity, bctx, (struct backend_image *)mask, mask_dst,
reg_blur, reg_visible, gd->back_texture,
return gl_blur_impl(opacity, bctx, mask, mask_dst, reg_blur, reg_visible,
gd->back_texture,
(geometry_t){.width = gd->width, .height = gd->height},
gd->back_fbo, gd->default_mask_texture, gd->dithered_present);
}
@@ -605,9 +604,9 @@ bool gl_create_kernel_blur_context(void *blur_context, GLfloat *projection,
bind_uniform(pass, mask_offset);
bind_uniform(pass, mask_inverted);
bind_uniform(pass, mask_corner_radius);
log_debug("Uniform locations: %d %d %d %d %d", pass->uniform_mask_tex,
pass->uniform_mask_offset, pass->uniform_mask_inverted,
pass->uniform_mask_corner_radius, pass->uniform_opacity);
log_info("Uniform locations: %d %d %d %d %d", pass->uniform_mask_tex,
pass->uniform_mask_offset, pass->uniform_mask_inverted,
pass->uniform_mask_corner_radius, pass->uniform_opacity);
pass->texorig_loc = glGetUniformLocationChecked(pass->prog, "texorig");
// Setup projection matrix

View File

@@ -36,6 +36,12 @@ struct egl_data {
EGLContext ctx;
};
static PFNGLEGLIMAGETARGETTEXSTORAGEEXTPROC glEGLImageTargetTexStorage = NULL;
static PFNEGLCREATEIMAGEKHRPROC eglCreateImageProc = NULL;
static PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageProc = NULL;
static PFNEGLGETPLATFORMDISPLAYPROC eglGetPlatformDisplayProc = NULL;
static PFNEGLCREATEPLATFORMWINDOWSURFACEPROC eglCreatePlatformWindowSurfaceProc = NULL;
const char *eglGetErrorString(EGLint error) {
#define CASE_STR(value) \
case value: return #value;
@@ -68,7 +74,7 @@ static void egl_release_image(backend_t *base, struct gl_texture *tex) {
struct egl_pixmap *p = tex->user_data;
// Release binding
if (p->image != EGL_NO_IMAGE) {
eglDestroyImage(gd->display, p->image);
eglDestroyImageProc(gd->display, p->image);
p->image = EGL_NO_IMAGE;
}
@@ -128,6 +134,18 @@ static backend_t *egl_init(session_t *ps, xcb_window_t target) {
bool success = false;
struct egl_data *gd = NULL;
#define get_proc(name, type) \
name##Proc = (type)eglGetProcAddress(#name); \
if (!name##Proc) { \
log_error("Failed to get EGL function " #name); \
goto end; \
}
get_proc(eglCreateImage, PFNEGLCREATEIMAGEKHRPROC);
get_proc(eglDestroyImage, PFNEGLDESTROYIMAGEKHRPROC);
get_proc(eglGetPlatformDisplay, PFNEGLGETPLATFORMDISPLAYPROC);
get_proc(eglCreatePlatformWindowSurface, PFNEGLCREATEPLATFORMWINDOWSURFACEPROC);
#undef get_proc
// Check if we have the X11 platform
const char *exts = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);
if (strstr(exts, "EGL_EXT_platform_x11") == NULL) {
@@ -136,12 +154,12 @@ static backend_t *egl_init(session_t *ps, xcb_window_t target) {
}
gd = ccalloc(1, struct egl_data);
gd->display = eglGetPlatformDisplayEXT(EGL_PLATFORM_X11_EXT, ps->c.dpy,
(EGLint[]){
EGL_PLATFORM_X11_SCREEN_EXT,
ps->c.screen,
EGL_NONE,
});
gd->display = eglGetPlatformDisplayProc(EGL_PLATFORM_X11_EXT, ps->c.dpy,
(EGLAttrib[]){
EGL_PLATFORM_X11_SCREEN_EXT,
ps->c.screen,
EGL_NONE,
});
if (gd->display == EGL_NO_DISPLAY) {
log_error("Failed to get EGL display.");
goto end;
@@ -194,7 +212,7 @@ static backend_t *egl_init(session_t *ps, xcb_window_t target) {
// clang-format on
gd->target_win =
eglCreatePlatformWindowSurfaceEXT(gd->display, config, &target, NULL);
eglCreatePlatformWindowSurfaceProc(gd->display, config, &target, NULL);
if (gd->target_win == EGL_NO_SURFACE) {
log_error("Failed to create EGL surface.");
goto end;
@@ -225,6 +243,14 @@ static backend_t *egl_init(session_t *ps, xcb_window_t target) {
goto end;
}
glEGLImageTargetTexStorage =
(PFNGLEGLIMAGETARGETTEXSTORAGEEXTPROC)eglGetProcAddress("glEGLImageTargetTexS"
"torageEXT");
if (glEGLImageTargetTexStorage == NULL) {
log_error("Failed to get glEGLImageTargetTexStorageEXT.");
goto end;
}
gd->gl.decouple_texture_user_data = egl_decouple_user_data;
gd->gl.release_user_data = egl_release_image;
@@ -249,7 +275,7 @@ end:
return &gd->gl.base;
}
static image_handle
static void *
egl_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, bool owned) {
struct egl_data *gd = (void *)base;
struct egl_pixmap *eglpixmap = NULL;
@@ -276,8 +302,9 @@ egl_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, b
eglpixmap = cmalloc(struct egl_pixmap);
eglpixmap->pixmap = pixmap;
eglpixmap->image = eglCreateImage(gd->display, EGL_NO_CONTEXT, EGL_NATIVE_PIXMAP_KHR,
(EGLClientBuffer)(uintptr_t)pixmap, NULL);
eglpixmap->image =
eglCreateImageProc(gd->display, EGL_NO_CONTEXT, EGL_NATIVE_PIXMAP_KHR,
(EGLClientBuffer)(uintptr_t)pixmap, NULL);
eglpixmap->owned = owned;
if (eglpixmap->image == EGL_NO_IMAGE) {
@@ -297,14 +324,14 @@ egl_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, b
wd->dim = 0;
wd->inner->refcount = 1;
glBindTexture(GL_TEXTURE_2D, inner->texture);
glEGLImageTargetTexStorageEXT(GL_TEXTURE_2D, eglpixmap->image, NULL);
glEGLImageTargetTexStorage(GL_TEXTURE_2D, eglpixmap->image, NULL);
glBindTexture(GL_TEXTURE_2D, 0);
gl_check_err();
return (image_handle)wd;
return wd;
err:
if (eglpixmap && eglpixmap->image) {
eglDestroyImage(gd->display, eglpixmap->image);
eglDestroyImageProc(gd->display, eglpixmap->image);
}
free(eglpixmap);
@@ -395,6 +422,41 @@ struct backend_operations egl_ops = {
.max_buffer_age = 5, // Why?
};
PFNEGLGETDISPLAYDRIVERNAMEPROC eglGetDisplayDriverName;
/**
* Check if a EGL extension exists.
*/
static inline bool egl_has_extension(EGLDisplay dpy, const char *ext) {
const char *egl_exts = eglQueryString(dpy, EGL_EXTENSIONS);
if (!egl_exts) {
log_error("Failed get EGL extension list.");
return false;
}
auto inlen = strlen(ext);
const char *curr = egl_exts;
bool match = false;
while (curr && !match) {
const char *end = strchr(curr, ' ');
if (!end) {
// Last extension string
match = strcmp(ext, curr) == 0;
} else if (curr + inlen == end) {
// Length match, do match string
match = strncmp(ext, curr, (unsigned long)(end - curr)) == 0;
}
curr = end ? end + 1 : NULL;
}
if (!match) {
log_info("Missing EGL extension %s.", ext);
} else {
log_info("Found EGL extension %s.", ext);
}
return match;
}
struct eglext_info eglext = {0};
void eglext_init(EGLDisplay dpy) {
@@ -402,10 +464,7 @@ void eglext_init(EGLDisplay dpy) {
return;
}
eglext.initialized = true;
#define check_ext(name) \
eglext.has_##name = epoxy_has_egl_extension(dpy, #name); \
log_info("Extension " #name " - %s", eglext.has_##name ? "present" : "absent")
#define check_ext(name) eglext.has_##name = egl_has_extension(dpy, #name)
check_ext(EGL_EXT_buffer_age);
check_ext(EGL_EXT_create_context_robustness);
check_ext(EGL_KHR_image_pixmap);
@@ -413,4 +472,16 @@ void eglext_init(EGLDisplay dpy) {
check_ext(EGL_MESA_query_driver);
#endif
#undef check_ext
// Checking if the returned function pointer is NULL is not really necessary,
// or maybe not even useful, since eglGetProcAddress might always return
// something. We are doing it just for completeness' sake.
#ifdef EGL_MESA_query_driver
eglGetDisplayDriverName =
(PFNEGLGETDISPLAYDRIVERNAMEPROC)eglGetProcAddress("eglGetDisplayDriverName");
if (!eglGetDisplayDriverName) {
eglext.has_EGL_MESA_query_driver = false;
}
#endif
}

View File

@@ -1,8 +1,10 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) Yuxuan Shui <yshuiv7@gmail.com>
#pragma once
#include <epoxy/egl.h>
#include <epoxy/gl.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GL/gl.h>
#include <GL/glext.h>
#include <stdbool.h>
#include <xcb/render.h>
#include <xcb/xcb.h>
@@ -22,4 +24,8 @@ struct eglext_info {
extern struct eglext_info eglext;
#ifdef EGL_MESA_query_driver
extern PFNEGLGETDISPLAYDRIVERNAMEPROC eglGetDisplayDriverName;
#endif
void eglext_init(EGLDisplay);

View File

@@ -1,6 +1,7 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) Yuxuan Shui <yshuiv7@gmail.com>
#include <epoxy/gl.h>
#include <GL/gl.h>
#include <GL/glext.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
@@ -184,7 +185,7 @@ void gl_destroy_window_shader(backend_t *backend_data attr_unused, void *shader)
* @note In order to reduce number of textures which needs to be
* allocated and deleted during this recursive render
* we reuse the same two textures for render source and
* destination simply by alternating between them.
* destination simply by alterating between them.
* Unfortunately on first iteration source_texture might
* be read-only. In this case we will select auxiliary_texture as
* destination_texture in order not to touch that read-only source
@@ -252,7 +253,7 @@ _gl_average_texture_color(backend_t *base, GLuint source_texture, GLuint destina
/*
* @brief Builds a 1x1 texture which has color corresponding to the average of all
* pixels of img by recursively rendering into texture of quarter the size (half
* pixels of img by recursively rendering into texture of quorter the size (half
* width and half height).
* Returned texture must not be deleted, since it's owned by the gl_image. It will be
* deleted when the gl_image is released.
@@ -389,10 +390,6 @@ static void _gl_compose(backend_t *base, struct backend_image *img, GLuint targe
if (win_shader->uniform_tex >= 0) {
glUniform1i(win_shader->uniform_tex, 0);
}
if (win_shader->uniform_effective_size >= 0) {
glUniform2f(win_shader->uniform_effective_size, (float)img->ewidth,
(float)img->eheight);
}
if (win_shader->uniform_dim >= 0) {
glUniform1f(win_shader->uniform_dim, (float)img->dim);
}
@@ -552,12 +549,11 @@ void x_rect_to_coords(int nrects, const rect_t *rects, coord_t image_dst,
}
// TODO(yshui) make use of reg_visible
void gl_compose(backend_t *base, image_handle image_data, coord_t image_dst, image_handle mask_,
void gl_compose(backend_t *base, void *image_data, coord_t image_dst, void *mask,
coord_t mask_dst, const region_t *reg_tgt,
const region_t *reg_visible attr_unused, bool lerp) {
auto gd = (struct gl_data *)base;
auto img = (struct backend_image *)image_data;
auto mask = (struct backend_image *)mask_;
struct backend_image *img = image_data;
auto inner = (struct gl_texture *)img->inner;
// Painting
@@ -612,7 +608,6 @@ static bool gl_win_shader_from_stringv(const char **vshader_strv,
bind_uniform(ret, opacity);
bind_uniform(ret, invert_color);
bind_uniform(ret, tex);
bind_uniform(ret, effective_size);
bind_uniform(ret, dim);
bind_uniform(ret, brightness);
bind_uniform(ret, max_brightness);
@@ -719,7 +714,7 @@ void gl_fill(backend_t *base, struct color c, const region_t *clip) {
return _gl_fill(base, c, clip, gd->back_fbo, gd->height, true);
}
image_handle gl_make_mask(backend_t *base, geometry_t size, const region_t *reg) {
void *gl_make_mask(backend_t *base, geometry_t size, const region_t *reg) {
auto tex = ccalloc(1, struct gl_texture);
auto img = default_new_backend_image(size.width, size.height);
tex->width = size.width;
@@ -750,7 +745,7 @@ image_handle gl_make_mask(backend_t *base, geometry_t size, const region_t *reg)
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glDeleteFramebuffers(1, &fbo);
return (image_handle)img;
return img;
}
static void gl_release_image_inner(backend_t *base, struct gl_texture *inner) {
@@ -766,8 +761,8 @@ static void gl_release_image_inner(backend_t *base, struct gl_texture *inner) {
gl_check_err();
}
void gl_release_image(backend_t *base, image_handle image) {
auto wd = (struct backend_image *)image;
void gl_release_image(backend_t *base, void *image_data) {
struct backend_image *wd = image_data;
auto inner = (struct gl_texture *)wd->inner;
inner->refcount--;
assert(inner->refcount >= 0);
@@ -894,35 +889,24 @@ bool gl_init(struct gl_data *gd, session_t *ps) {
glUseProgram(0);
gd->dithered_present = ps->o.dithered_present;
gd->dummy_prog =
gl_create_program_from_strv((const char *[]){present_vertex_shader, NULL},
(const char *[]){dummy_frag, NULL});
if (!gd->dummy_prog) {
log_error("Failed to create the dummy shader");
return false;
}
if (gd->dithered_present) {
gd->present_prog = gl_create_program_from_strv(
(const char *[]){present_vertex_shader, NULL},
(const char *[]){present_frag, dither_glsl, NULL});
} else {
gd->present_prog = gd->dummy_prog;
gd->present_prog = gl_create_program_from_strv(
(const char *[]){present_vertex_shader, NULL},
(const char *[]){dummy_frag, NULL});
}
if (!gd->present_prog) {
log_error("Failed to create the present shader");
return false;
}
pml = glGetUniformLocationChecked(gd->dummy_prog, "projection");
glUseProgram(gd->dummy_prog);
glUniform1i(glGetUniformLocationChecked(gd->dummy_prog, "tex"), 0);
pml = glGetUniformLocationChecked(gd->present_prog, "projection");
glUseProgram(gd->present_prog);
glUniform1i(glGetUniformLocationChecked(gd->present_prog, "tex"), 0);
glUniformMatrix4fv(pml, 1, false, projection_matrix[0]);
if (gd->present_prog != gd->dummy_prog) {
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->shadow_shader.prog =
gl_create_program_from_str(present_vertex_shader, shadow_colorization_frag);
@@ -932,6 +916,7 @@ bool gl_init(struct gl_data *gd, session_t *ps) {
glUseProgram(gd->shadow_shader.prog);
glUniform1i(glGetUniformLocationChecked(gd->shadow_shader.prog, "tex"), 0);
glUniformMatrix4fv(pml, 1, false, projection_matrix[0]);
glUseProgram(0);
glBindFragDataLocation(gd->shadow_shader.prog, 0, "out_color");
gd->brightness_shader.prog =
@@ -944,7 +929,6 @@ bool gl_init(struct gl_data *gd, session_t *ps) {
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 and format of the back texture
@@ -981,8 +965,8 @@ bool gl_init(struct gl_data *gd, session_t *ps) {
} else {
gd->is_nvidia = false;
}
gd->has_robustness = epoxy_has_gl_extension("GL_ARB_robustness");
gd->has_egl_image_storage = epoxy_has_gl_extension("GL_EXT_EGL_image_storage");
gd->has_robustness = gl_has_extension("GL_ARB_robustness");
gd->has_egl_image_storage = gl_has_extension("GL_EXT_EGL_image_storage");
gl_check_err();
return true;
@@ -998,25 +982,10 @@ void gl_deinit(struct gl_data *gd) {
gl_destroy_window_shader(&gd->base, gd->default_shader);
gd->default_shader = NULL;
}
glDeleteProgram(gd->dummy_prog);
if (gd->present_prog != gd->dummy_prog) {
glDeleteProgram(gd->present_prog);
}
gd->dummy_prog = 0;
gd->present_prog = 0;
glDeleteProgram(gd->fill_shader.prog);
glDeleteProgram(gd->brightness_shader.prog);
glDeleteProgram(gd->shadow_shader.prog);
gd->fill_shader.prog = 0;
gd->brightness_shader.prog = 0;
gd->shadow_shader.prog = 0;
glDeleteTextures(1, &gd->default_mask_texture);
glDeleteTextures(1, &gd->back_texture);
glDeleteQueries(2, gd->frame_timing);
gl_check_err();
}
@@ -1048,11 +1017,9 @@ static inline void gl_image_decouple(backend_t *base, struct backend_image *img)
auto new_tex = ccalloc(1, struct gl_texture);
new_tex->texture = gl_new_texture(GL_TEXTURE_2D);
new_tex->y_inverted = inner->y_inverted;
new_tex->has_alpha = inner->has_alpha;
new_tex->y_inverted = true;
new_tex->height = inner->height;
new_tex->width = inner->width;
new_tex->shader = inner->shader;
new_tex->refcount = 1;
new_tex->user_data = gd->decouple_texture_user_data(base, inner->user_data);
@@ -1061,7 +1028,8 @@ static inline void gl_image_decouple(backend_t *base, struct backend_image *img)
GL_BGRA, GL_UNSIGNED_BYTE, NULL);
glBindTexture(GL_TEXTURE_2D, 0);
glUseProgram(gd->dummy_prog);
assert(gd->present_prog);
glUseProgram(gd->present_prog);
glBindTexture(GL_TEXTURE_2D, inner->texture);
GLuint fbo;
@@ -1231,9 +1199,9 @@ bool gl_last_render_time(backend_t *base, struct timespec *ts) {
return true;
}
bool gl_image_op(backend_t *base, enum image_operations op, image_handle image,
bool gl_image_op(backend_t *base, enum image_operations op, void *image_data,
const region_t *reg_op, const region_t *reg_visible attr_unused, void *arg) {
auto tex = (struct backend_image *)image;
struct backend_image *tex = image_data;
switch (op) {
case IMAGE_OP_APPLY_ALPHA:
gl_image_decouple(base, tex);
@@ -1246,12 +1214,12 @@ bool gl_image_op(backend_t *base, enum image_operations op, image_handle image,
}
bool gl_set_image_property(backend_t *backend_data, enum image_properties prop,
image_handle image, void *args) {
void *image_data, void *args) {
if (prop != IMAGE_PROPERTY_CUSTOM_SHADER) {
return default_set_image_property(backend_data, prop, image, args);
return default_set_image_property(backend_data, prop, image_data, args);
}
auto img = (struct backend_image *)image;
struct backend_image *img = image_data;
auto inner = (struct gl_texture *)img->inner;
inner->shader = args;
return true;
@@ -1290,12 +1258,12 @@ void gl_destroy_shadow_context(backend_t *base attr_unused, struct backend_shado
free(ctx_);
}
image_handle gl_shadow_from_mask(backend_t *base, image_handle mask_,
struct backend_shadow_context *sctx, struct color color) {
void *gl_shadow_from_mask(backend_t *base, void *mask,
struct backend_shadow_context *sctx, struct color color) {
log_debug("Create shadow from mask");
auto gd = (struct gl_data *)base;
auto mask = (struct backend_image *)mask_;
auto inner = (struct gl_texture *)mask->inner;
auto img = (struct backend_image *)mask;
auto inner = (struct gl_texture *)img->inner;
auto gsctx = (struct gl_shadow_context *)sctx;
int radius = (int)gsctx->radius;
@@ -1325,7 +1293,7 @@ image_handle gl_shadow_from_mask(backend_t *base, image_handle mask_,
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
source_texture, 0);
glDrawBuffer(GL_COLOR_ATTACHMENT0);
if (mask->color_inverted) {
if (img->color_inverted) {
// If the mask is inverted, clear the source_texture to white, so the
// "outside" of the mask would be correct
glClearColor(1, 1, 1, 1);
@@ -1434,7 +1402,7 @@ image_handle gl_shadow_from_mask(backend_t *base, image_handle mask_,
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glDeleteFramebuffers(1, &fbo);
gl_check_err();
return (image_handle)new_img;
return new_img;
}
enum device_status gl_device_status(backend_t *base) {

View File

@@ -1,12 +1,12 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) Yuxuan Shui <yshuiv7@gmail.com>
#pragma once
#include <epoxy/gl.h>
#include <GL/gl.h>
#include <GL/glext.h>
#include <stdbool.h>
#include <string.h>
#include "backend/backend.h"
#include "backend/backend_common.h"
#include "log.h"
#include "region.h"
@@ -29,11 +29,12 @@ static inline GLint glGetUniformLocationChecked(GLuint p, const char *name) {
// Program and uniforms for window shader
typedef struct {
UT_hash_handle hh;
uint32_t id;
GLuint prog;
GLint uniform_opacity;
GLint uniform_invert_color;
GLint uniform_tex;
GLint uniform_effective_size;
GLint uniform_dim;
GLint uniform_brightness;
GLint uniform_max_brightness;
@@ -76,7 +77,7 @@ typedef struct {
GLint color_loc;
} gl_fill_shader_t;
/// @brief Wrapper of a bound GL texture.
/// @brief Wrapper of a binded GL texture.
struct gl_texture {
int refcount;
bool has_alpha;
@@ -110,7 +111,6 @@ struct gl_data {
GLuint frame_timing[2];
int current_frame_timing;
GLuint present_prog;
GLuint dummy_prog;
bool dithered_present;
@@ -144,13 +144,13 @@ void *gl_create_window_shader(backend_t *backend_data, const char *source);
void gl_destroy_window_shader(backend_t *backend_data, void *shader);
uint64_t gl_get_shader_attributes(backend_t *backend_data, void *shader);
bool gl_set_image_property(backend_t *backend_data, enum image_properties prop,
image_handle image, void *args);
void *image_data, void *args);
bool gl_last_render_time(backend_t *backend_data, struct timespec *time);
/**
* @brief Render a region with texture data.
*/
void gl_compose(backend_t *, image_handle image_data, coord_t image_dst, image_handle mask_,
void gl_compose(backend_t *, void *image_data, coord_t image_dst, void *mask,
coord_t mask_dst, const region_t *reg_tgt, const region_t *reg_visible, bool lerp);
void gl_resize(struct gl_data *, int width, int height);
@@ -160,32 +160,32 @@ void gl_deinit(struct gl_data *gd);
GLuint gl_new_texture(GLenum target);
bool gl_image_op(backend_t *base, enum image_operations op, image_handle image,
bool gl_image_op(backend_t *base, enum image_operations op, void *image_data,
const region_t *reg_op, const region_t *reg_visible, void *arg);
void gl_release_image(backend_t *base, image_handle image);
image_handle gl_make_mask(backend_t *base, geometry_t size, const region_t *reg);
void gl_release_image(backend_t *base, void *image_data);
void *gl_make_mask(backend_t *base, geometry_t size, const region_t *reg);
image_handle gl_clone(backend_t *base, image_handle image, const region_t *reg_visible);
void *gl_clone(backend_t *base, const void *image_data, const region_t *reg_visible);
bool gl_blur(backend_t *base, double opacity, void *ctx, image_handle mask,
coord_t mask_dst, const region_t *reg_blur, const region_t *reg_visible);
bool gl_blur_impl(double opacity, struct gl_blur_context *bctx,
struct backend_image *mask, coord_t mask_dst, const region_t *reg_blur,
const region_t *reg_visible attr_unused, GLuint source_texture,
geometry_t source_size, GLuint target_fbo, GLuint default_mask,
bool high_precision);
bool gl_blur(backend_t *base, double opacity, void *ctx, void *mask, coord_t mask_dst,
const region_t *reg_blur, const region_t *reg_visible);
bool gl_blur_impl(double opacity, struct gl_blur_context *bctx, void *mask, coord_t mask_dst,
const region_t *reg_blur, const region_t *reg_visible attr_unused,
GLuint source_texture, geometry_t source_size, GLuint target_fbo,
GLuint default_mask, bool high_precision);
void *gl_create_blur_context(backend_t *base, enum blur_method, void *args);
void gl_destroy_blur_context(backend_t *base, void *ctx);
struct backend_shadow_context *gl_create_shadow_context(backend_t *base, double radius);
void gl_destroy_shadow_context(backend_t *base attr_unused, struct backend_shadow_context *ctx);
image_handle gl_shadow_from_mask(backend_t *base, image_handle mask,
struct backend_shadow_context *sctx, struct color color);
void *gl_shadow_from_mask(backend_t *base, void *mask,
struct backend_shadow_context *sctx, struct color color);
void gl_get_blur_size(void *blur_context, int *width, int *height);
void gl_fill(backend_t *base, struct color, const region_t *clip);
void gl_present(backend_t *base, const region_t *);
bool gl_read_pixel(backend_t *base, void *image_data, int x, int y, struct color *output);
enum device_status gl_device_status(backend_t *base);
/**
@@ -264,6 +264,26 @@ static inline bool gl_check_fb_complete_(const char *func, int line, GLenum fb)
#define gl_check_fb_complete(fb) gl_check_fb_complete_(__func__, __LINE__, (fb))
/**
* Check if a GL extension exists.
*/
static inline bool gl_has_extension(const char *ext) {
int nexts = 0;
glGetIntegerv(GL_NUM_EXTENSIONS, &nexts);
for (int i = 0; i < nexts || !nexts; i++) {
const char *exti = (const char *)glGetStringi(GL_EXTENSIONS, (GLuint)i);
if (exti == NULL) {
break;
}
if (strcmp(ext, exti) == 0) {
return true;
}
}
gl_clear_err();
log_info("Missing GL extension %s.", ext);
return false;
}
static const GLuint vert_coord_loc = 0;
static const GLuint vert_in_texcoord_loc = 1;

View File

@@ -17,10 +17,8 @@
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <uthash.h>
#include <xcb/composite.h>
#include <xcb/xcb.h>
#include <xcb/xcb_aux.h>
#include "backend/backend.h"
#include "backend/backend_common.h"
@@ -46,13 +44,6 @@ struct _glx_data {
struct gl_data gl;
xcb_window_t target_win;
GLXContext ctx;
struct glx_fbconfig_cache *cached_fbconfigs;
};
struct glx_fbconfig_cache {
UT_hash_handle hh;
struct xvisual_info visual_info;
struct glx_fbconfig_info info;
};
#define glXGetFBConfigAttribChecked(a, b, attr, c) \
@@ -63,12 +54,9 @@ struct glx_fbconfig_cache {
} \
} while (0)
bool glx_find_fbconfig(struct x_connection *c, struct xvisual_info m,
struct glx_fbconfig_info *info) {
log_debug("Looking for FBConfig for RGBA%d%d%d%d, depth: %d, visual id: %#x", m.red_size,
m.blue_size, m.green_size, m.alpha_size, m.visual_depth, m.visual);
info->cfg = NULL;
struct glx_fbconfig_info *glx_find_fbconfig(struct x_connection *c, struct xvisual_info m) {
log_debug("Looking for FBConfig for RGBA%d%d%d%d, depth %d", m.red_size,
m.blue_size, m.green_size, m.alpha_size, m.visual_depth);
int ncfg;
// clang-format off
@@ -126,8 +114,7 @@ bool glx_find_fbconfig(struct x_connection *c, struct xvisual_info m,
int visual;
glXGetFBConfigAttribChecked(c->dpy, cfg[i], GLX_VISUAL_ID, &visual);
if (m.visual_depth != -1 &&
xcb_aux_get_depth_of_visual(c->screen_info, (xcb_visualid_t)visual) !=
m.visual_depth) {
x_get_visual_depth(c, (xcb_visualid_t)visual) != m.visual_depth) {
// FBConfig and the correspondent X Visual might not have the same
// depth. (e.g. 32 bit FBConfig with a 24 bit Visual). This is
// quite common, seen in both open source and proprietary drivers.
@@ -156,13 +143,16 @@ bool glx_find_fbconfig(struct x_connection *c, struct xvisual_info m,
min_cost = depthbuf + stencil + bufsize * (doublebuf + 1);
}
free(cfg);
if (found) {
info->cfg = ret;
info->texture_tgts = texture_tgts;
info->texture_fmt = texture_fmt;
info->y_inverted = y_inverted;
if (!found) {
return NULL;
}
return found;
auto info = cmalloc(struct glx_fbconfig_info);
info->cfg = ret;
info->texture_tgts = texture_tgts;
info->texture_fmt = texture_fmt;
info->y_inverted = y_inverted;
return info;
}
/**
@@ -207,12 +197,6 @@ void glx_deinit(backend_t *base) {
gd->ctx = 0;
}
struct glx_fbconfig_cache *cached_fbconfig = NULL, *tmp = NULL;
HASH_ITER(hh, gd->cached_fbconfigs, cached_fbconfig, tmp) {
HASH_DEL(gd->cached_fbconfigs, cached_fbconfig);
free(cached_fbconfig);
}
free(gd);
}
@@ -380,10 +364,9 @@ end:
return &gd->gl.base;
}
static image_handle
static void *
glx_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, bool owned) {
struct _glx_pixmap *glxpixmap = NULL;
auto gd = (struct _glx_data *)base;
// Retrieve pixmap parameters, if they aren't provided
if (fmt.visual_depth > OPENGL_MAX_DEPTH) {
log_error("Requested depth %d higher than max possible depth %d.",
@@ -411,52 +394,38 @@ glx_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, b
wd->inner = (struct backend_image_inner_base *)inner;
free(r);
struct glx_fbconfig_cache *cached_fbconfig = NULL;
HASH_FIND(hh, gd->cached_fbconfigs, &fmt, sizeof(fmt), cached_fbconfig);
if (!cached_fbconfig) {
struct glx_fbconfig_info fbconfig;
if (!glx_find_fbconfig(base->c, fmt, &fbconfig)) {
log_error("Couldn't find FBConfig with requested visual %#x",
fmt.visual);
goto err;
}
cached_fbconfig = cmalloc(struct glx_fbconfig_cache);
cached_fbconfig->visual_info = fmt;
cached_fbconfig->info = fbconfig;
HASH_ADD(hh, gd->cached_fbconfigs, visual_info, sizeof(fmt), cached_fbconfig);
} else {
log_debug("Found cached FBConfig for RGBA%d%d%d%d, depth: %d, visual id: "
"%#x",
fmt.red_size, fmt.blue_size, fmt.green_size, fmt.alpha_size,
fmt.visual_depth, fmt.visual);
auto fbcfg = glx_find_fbconfig(base->c, fmt);
if (!fbcfg) {
log_error("Couldn't find FBConfig with requested visual %x", fmt.visual);
goto err;
}
struct glx_fbconfig_info *fbconfig = &cached_fbconfig->info;
// Choose a suitable texture target for our pixmap.
// Refer to GLX_EXT_texture_om_pixmap spec to see what are the mean
// of the bits in texture_tgts
if (!(fbconfig->texture_tgts & GLX_TEXTURE_2D_BIT_EXT)) {
if (!(fbcfg->texture_tgts & GLX_TEXTURE_2D_BIT_EXT)) {
log_error("Cannot bind pixmap to GL_TEXTURE_2D, giving up");
goto err;
}
log_debug("depth %d, rgba %d", fmt.visual_depth,
(fbconfig->texture_fmt == GLX_TEXTURE_FORMAT_RGBA_EXT));
(fbcfg->texture_fmt == GLX_TEXTURE_FORMAT_RGBA_EXT));
GLint attrs[] = {
GLX_TEXTURE_FORMAT_EXT,
fbconfig->texture_fmt,
fbcfg->texture_fmt,
GLX_TEXTURE_TARGET_EXT,
GLX_TEXTURE_2D_EXT,
0,
};
inner->y_inverted = fbconfig->y_inverted;
inner->y_inverted = fbcfg->y_inverted;
glxpixmap = cmalloc(struct _glx_pixmap);
glxpixmap->pixmap = pixmap;
glxpixmap->glpixmap = glXCreatePixmap(base->c->dpy, fbconfig->cfg, pixmap, attrs);
glxpixmap->glpixmap = glXCreatePixmap(base->c->dpy, fbcfg->cfg, pixmap, attrs);
glxpixmap->owned = owned;
free(fbcfg);
if (!glxpixmap->glpixmap) {
log_error("Failed to create glpixmap for pixmap %#010x", pixmap);
@@ -475,7 +444,7 @@ glx_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, b
glBindTexture(GL_TEXTURE_2D, 0);
gl_check_err();
return (image_handle)wd;
return wd;
err:
if (glxpixmap && glxpixmap->glpixmap) {
glXDestroyPixmap(base->c->dpy, glxpixmap->glpixmap);
@@ -576,17 +545,62 @@ struct backend_operations glx_ops = {
.max_buffer_age = 5, // Why?
};
/**
* Check if a GLX extension exists.
*/
static inline bool glx_has_extension(Display *dpy, int screen, const char *ext) {
const char *glx_exts = glXQueryExtensionsString(dpy, screen);
if (!glx_exts) {
log_error("Failed get GLX extension list.");
return false;
}
auto inlen = strlen(ext);
const char *curr = glx_exts;
bool match = false;
while (curr && !match) {
const char *end = strchr(curr, ' ');
if (!end) {
// Last extension string
match = strcmp(ext, curr) == 0;
} else if (curr + inlen == end) {
// Length match, do match string
match = strncmp(ext, curr, (unsigned long)(end - curr)) == 0;
}
curr = end ? end + 1 : NULL;
}
if (!match) {
log_info("Missing GLX extension %s.", ext);
} else {
log_info("Found GLX extension %s.", ext);
}
return match;
}
struct glxext_info glxext = {0};
PFNGLXGETVIDEOSYNCSGIPROC glXGetVideoSyncSGI;
PFNGLXWAITVIDEOSYNCSGIPROC glXWaitVideoSyncSGI;
PFNGLXGETSYNCVALUESOMLPROC glXGetSyncValuesOML;
PFNGLXWAITFORMSCOMLPROC glXWaitForMscOML;
PFNGLXSWAPINTERVALEXTPROC glXSwapIntervalEXT;
PFNGLXSWAPINTERVALSGIPROC glXSwapIntervalSGI;
PFNGLXSWAPINTERVALMESAPROC glXSwapIntervalMESA;
PFNGLXBINDTEXIMAGEEXTPROC glXBindTexImageEXT;
PFNGLXRELEASETEXIMAGEEXTPROC glXReleaseTexImageEXT;
PFNGLXCREATECONTEXTATTRIBSARBPROC glXCreateContextAttribsARB;
#ifdef GLX_MESA_query_renderer
PFNGLXQUERYCURRENTRENDERERINTEGERMESAPROC glXQueryCurrentRendererIntegerMESA;
#endif
void glxext_init(Display *dpy, int screen) {
if (glxext.initialized) {
return;
}
glxext.initialized = true;
#define check_ext(name) \
glxext.has_##name = epoxy_has_glx_extension(dpy, screen, #name); \
log_info("Extension " #name " - %s", glxext.has_##name ? "present" : "absent")
#define check_ext(name) glxext.has_##name = glx_has_extension(dpy, screen, #name)
check_ext(GLX_SGI_video_sync);
check_ext(GLX_SGI_swap_control);
check_ext(GLX_OML_sync_control);
@@ -600,4 +614,36 @@ void glxext_init(Display *dpy, int screen) {
check_ext(GLX_MESA_query_renderer);
#endif
#undef check_ext
#define lookup(name) ((name) = (__typeof__(name))glXGetProcAddress((GLubyte *)#name))
// Checking if the returned function pointer is NULL is not really necessary,
// or maybe not even useful, since glXGetProcAddress might always return
// something. We are doing it just for completeness' sake.
if (!lookup(glXGetVideoSyncSGI) || !lookup(glXWaitVideoSyncSGI)) {
glxext.has_GLX_SGI_video_sync = false;
}
if (!lookup(glXSwapIntervalEXT)) {
glxext.has_GLX_EXT_swap_control = false;
}
if (!lookup(glXSwapIntervalMESA)) {
glxext.has_GLX_MESA_swap_control = false;
}
if (!lookup(glXSwapIntervalSGI)) {
glxext.has_GLX_SGI_swap_control = false;
}
if (!lookup(glXWaitForMscOML) || !lookup(glXGetSyncValuesOML)) {
glxext.has_GLX_OML_sync_control = false;
}
if (!lookup(glXBindTexImageEXT) || !lookup(glXReleaseTexImageEXT)) {
glxext.has_GLX_EXT_texture_from_pixmap = false;
}
if (!lookup(glXCreateContextAttribsARB)) {
glxext.has_GLX_ARB_create_context = false;
}
#ifdef GLX_MESA_query_renderer
if (!lookup(glXQueryCurrentRendererIntegerMESA)) {
glxext.has_GLX_MESA_query_renderer = false;
}
#endif
#undef lookup
}

View File

@@ -1,9 +1,17 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) Yuxuan Shui <yshuiv7@gmail.com>
#pragma once
#include <X11/Xlib.h>
#include <epoxy/glx.h>
#include <stdbool.h>
// Older version of glx.h defines function prototypes for these extensions...
// Rename them to avoid conflicts
#define glXSwapIntervalMESA glXSwapIntervalMESA_
#define glXBindTexImageEXT glXBindTexImageEXT_
#define glXReleaseTexImageEXT glXReleaseTexImageEXT
#include <GL/glx.h>
#undef glXSwapIntervalMESA
#undef glXBindTexImageEXT
#undef glXReleaseTexImageEXT
#include <X11/Xlib.h>
#include <xcb/render.h>
#include <xcb/xcb.h>
@@ -19,8 +27,21 @@ struct glx_fbconfig_info {
int y_inverted;
};
bool glx_find_fbconfig(struct x_connection *c, struct xvisual_info m,
struct glx_fbconfig_info *info);
/// The search criteria for glx_find_fbconfig
struct glx_fbconfig_criteria {
/// Bit width of the red component
int red_size;
/// Bit width of the green component
int green_size;
/// Bit width of the blue component
int blue_size;
/// Bit width of the alpha component
int alpha_size;
/// The depth of X visual
int visual_depth;
};
struct glx_fbconfig_info *glx_find_fbconfig(struct x_connection *, struct xvisual_info);
struct glxext_info {
bool initialized;
@@ -38,4 +59,19 @@ struct glxext_info {
extern struct glxext_info glxext;
extern PFNGLXGETVIDEOSYNCSGIPROC glXGetVideoSyncSGI;
extern PFNGLXWAITVIDEOSYNCSGIPROC glXWaitVideoSyncSGI;
extern PFNGLXGETSYNCVALUESOMLPROC glXGetSyncValuesOML;
extern PFNGLXWAITFORMSCOMLPROC glXWaitForMscOML;
extern PFNGLXSWAPINTERVALEXTPROC glXSwapIntervalEXT;
extern PFNGLXSWAPINTERVALSGIPROC glXSwapIntervalSGI;
extern PFNGLXSWAPINTERVALMESAPROC glXSwapIntervalMESA;
extern PFNGLXBINDTEXIMAGEEXTPROC glXBindTexImageEXT;
extern PFNGLXRELEASETEXIMAGEEXTPROC glXReleaseTexImageEXT;
extern PFNGLXCREATECONTEXTATTRIBSARBPROC glXCreateContextAttribsARB;
#ifdef GLX_MESA_query_renderer
extern PFNGLXQUERYCURRENTRENDERERINTEGERMESAPROC glXQueryCurrentRendererIntegerMESA;
#endif
void glxext_init(Display *, int screen);

View File

@@ -97,7 +97,6 @@ const char win_shader_glsl[] = GLSL(330,
uniform bool invert_color;
in vec2 texcoord;
uniform sampler2D tex;
uniform vec2 effective_size;
uniform sampler2D brightness;
uniform float max_brightness;
// Signed distance field for rectangle center at (0, 0), with size of
@@ -131,7 +130,7 @@ const char win_shader_glsl[] = GLSL(330,
// Using mix() to avoid a branch here.
vec4 rim_color = mix(c, border_color, clamp(border_width, 0.0f, 1.0f));
vec2 outer_size = effective_size;
vec2 outer_size = vec2(textureSize(tex, 0));
vec2 inner_size = outer_size - vec2(corner_radius) * 2.0f;
float rect_distance = rectangle_sdf(texcoord - outer_size / 2.0f,
inner_size / 2.0f) - corner_radius;
@@ -158,8 +157,7 @@ const char win_shader_default[] = GLSL(330,
uniform sampler2D tex;
vec4 default_post_processing(vec4 c);
vec4 window_shader() {
vec2 texsize = textureSize(tex, 0);
vec4 c = texture2D(tex, texcoord / texsize, 0);
vec4 c = texelFetch(tex, ivec2(texcoord), 0);
return default_post_processing(c);
}
);

View File

@@ -14,7 +14,6 @@
#include "backend/backend.h"
#include "backend/backend_common.h"
#include "common.h"
#include "compiler.h"
#include "config.h"
#include "kernel.h"
#include "log.h"
@@ -57,10 +56,6 @@ typedef struct _xrender_data {
int target_width, target_height;
xcb_special_event_t *present_event;
/// Cache an X region to avoid creating and destroying it every frame. A
/// workaround for yshui/picom#1166.
xcb_xfixes_region_t present_region;
} xrender_data;
struct _xrender_blur_context {
@@ -360,12 +355,10 @@ compose_impl(struct _xrender_data *xd, struct xrender_image *xrimg, coord_t dst,
pixman_region32_fini(&reg);
}
static void compose(backend_t *base, image_handle image_, coord_t dst, image_handle mask_,
coord_t mask_dst, const region_t *reg_paint, const region_t *reg_visible, attr_unused bool lerp) {
static void compose(backend_t *base, void *img_data, coord_t dst, void *mask, coord_t mask_dst,
const region_t *reg_paint, const region_t *reg_visible, bool lerp attr_unused) {
struct _xrender_data *xd = (void *)base;
auto image = (struct xrender_image *)image_;
auto mask = (struct xrender_image *)mask_;
return compose_impl(xd, image, dst, mask, mask_dst, reg_paint, reg_visible,
return compose_impl(xd, img_data, dst, mask, mask_dst, reg_paint, reg_visible,
xd->back[2]);
}
@@ -387,10 +380,9 @@ static void fill(backend_t *base, struct color c, const region_t *clip) {
.height = to_u16_checked(extent->y2 - extent->y1)}});
}
static bool blur(backend_t *backend_data, double opacity, void *ctx_, image_handle mask_,
static bool blur(backend_t *backend_data, double opacity, void *ctx_, void *mask,
coord_t mask_dst, const region_t *reg_blur, const region_t *reg_visible) {
auto bctx = (struct _xrender_blur_context *)ctx_;
auto mask = (struct xrender_image *)mask_;
struct _xrender_blur_context *bctx = ctx_;
if (bctx->method == BLUR_METHOD_NONE) {
return true;
}
@@ -521,7 +513,7 @@ static bool blur(backend_t *backend_data, double opacity, void *ctx_, image_hand
return true;
}
static image_handle
static void *
bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, bool owned) {
xcb_generic_error_t *e;
auto r = xcb_get_geometry_reply(base->c->c, xcb_get_geometry(base->c->c, pixmap), &e);
@@ -556,7 +548,7 @@ bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, bool
free(img);
return NULL;
}
return (image_handle)img;
return img;
}
static void release_image_inner(backend_t *base, struct _xrender_image_data_inner *inner) {
x_free_picture(base->c, inner->pict);
@@ -580,13 +572,13 @@ release_rounded_corner_cache(backend_t *base, struct xrender_rounded_rectangle_c
}
}
static void release_image(backend_t *base, image_handle image) {
auto img = (struct xrender_image *)image;
static void release_image(backend_t *base, void *image) {
struct xrender_image *img = image;
release_rounded_corner_cache(base, img->rounded_rectangle);
img->rounded_rectangle = NULL;
img->base.inner->refcount -= 1;
if (img->base.inner->refcount == 0) {
release_image_inner(base, (struct _xrender_image_data_inner *)img->base.inner);
release_image_inner(base, (void *)img->base.inner);
}
free(img);
}
@@ -605,7 +597,6 @@ static void deinit(backend_t *backend_data) {
xcb_free_pixmap(xd->base.c->c, xd->back_pixmap[i]);
}
}
x_destroy_region(xd->base.c, xd->present_region);
if (xd->present_event) {
xcb_unregister_for_special_event(xd->base.c->c, xd->present_event);
}
@@ -633,15 +624,16 @@ static void present(backend_t *base, const region_t *region) {
XCB_NONE, xd->back[xd->curr_back], orig_x, orig_y, 0,
0, orig_x, orig_y, region_width, region_height);
auto xregion = x_create_region(base->c, region);
// Make sure we got reply from PresentPixmap before waiting for events,
// to avoid deadlock
auto e = xcb_request_check(
base->c->c,
xcb_present_pixmap_checked(
base->c->c, xd->target_win, xd->back_pixmap[xd->curr_back], 0, XCB_NONE,
x_set_region(base->c, xd->present_region, region) ? xd->present_region
: XCB_NONE,
0, 0, XCB_NONE, XCB_NONE, XCB_NONE, 0, 0, 0, 0, 0, NULL));
base->c->c, xcb_present_pixmap_checked(
xd->base.c->c, xd->target_win,
xd->back_pixmap[xd->curr_back], 0, XCB_NONE, xregion, 0,
0, XCB_NONE, XCB_NONE, XCB_NONE, 0, 0, 0, 0, 0, NULL));
x_destroy_region(base->c, xregion);
if (e) {
log_error("Failed to present pixmap");
free(e);
@@ -715,7 +707,7 @@ new_inner(backend_t *base, int w, int h, xcb_visualid_t visual, uint8_t depth) {
return new_inner;
}
static image_handle make_mask(backend_t *base, geometry_t size, const region_t *reg) {
static void *make_mask(backend_t *base, geometry_t size, const region_t *reg) {
struct _xrender_data *xd = (void *)base;
// Give the mask a 1 pixel wide border to emulate the clamp to border behavior of
// OpenGL textures.
@@ -758,7 +750,7 @@ static image_handle make_mask(backend_t *base, geometry_t size, const region_t *
img->base.dim = 0;
img->base.inner = (struct backend_image_inner_base *)inner;
img->rounded_rectangle = NULL;
return (image_handle)img;
return img;
}
static bool decouple_image(backend_t *base, struct backend_image *img, const region_t *reg) {
@@ -786,10 +778,10 @@ static bool decouple_image(backend_t *base, struct backend_image *img, const reg
return true;
}
static bool image_op(backend_t *base, enum image_operations op, image_handle image,
static bool image_op(backend_t *base, enum image_operations op, void *image,
const region_t *reg_op, const region_t *reg_visible, void *arg) {
struct _xrender_data *xd = (void *)base;
auto img = (struct backend_image *)image;
struct backend_image *img = image;
region_t reg;
double *dargs = arg;
@@ -937,10 +929,6 @@ static backend_t *backend_xrender_init(session_t *ps, xcb_window_t target) {
xd->vsync = false;
}
if (xd->vsync) {
xd->present_region = x_create_region(&ps->c, &ps->screen_reg);
}
// We might need to do double buffering for vsync, and buffer 0 and 1 are for
// double buffering.
int first_buffer_index = xd->vsync ? 0 : 2;
@@ -968,19 +956,19 @@ err:
return NULL;
}
image_handle clone_image(backend_t *base attr_unused, image_handle image,
const region_t *reg_visible attr_unused) {
void *clone_image(backend_t *base attr_unused, const void *image_data,
const region_t *reg_visible attr_unused) {
auto new_img = ccalloc(1, struct xrender_image);
*new_img = *(struct xrender_image *)image;
*new_img = *(struct xrender_image *)image_data;
new_img->base.inner->refcount++;
if (new_img->rounded_rectangle) {
new_img->rounded_rectangle->refcount++;
}
return (image_handle)new_img;
return new_img;
}
static bool set_image_property(backend_t *base, enum image_properties op,
image_handle image, void *args) {
static bool
set_image_property(backend_t *base, enum image_properties op, void *image, void *args) {
auto xrimg = (struct xrender_image *)image;
if (op == IMAGE_PROPERTY_CORNER_RADIUS &&
((double *)args)[0] != xrimg->base.corner_radius) {

View File

@@ -1047,7 +1047,7 @@ static bool c2_l_postprocess(session_t *ps, c2_l_t *pleaf) {
// Get target atom if it's not a predefined one
if (pleaf->predef == C2_L_PUNDEFINED) {
pleaf->tgtatom = get_atom(ps->atoms, pleaf->tgt, ps->c.c);
pleaf->tgtatom = get_atom(ps->atoms, pleaf->tgt);
if (!pleaf->tgtatom) {
log_error("Failed to get atom for target \"%s\".", pleaf->tgt);
return false;
@@ -1380,10 +1380,14 @@ static inline void c2_match_once_leaf(session_t *ps, const struct managed_win *w
case C2_L_PWIDTHB: predef_target = w->widthb; break;
case C2_L_PHEIGHTB: predef_target = w->heightb; break;
case C2_L_PBDW: predef_target = w->g.border_width; break;
case C2_L_PFULLSCREEN: predef_target = w->is_fullscreen; break;
case C2_L_PFULLSCREEN:
predef_target = win_is_fullscreen(ps, w);
break;
case C2_L_POVREDIR: predef_target = w->a.override_redirect; break;
case C2_L_PARGB: predef_target = win_has_alpha(w); break;
case C2_L_PFOCUSED: predef_target = win_is_focused_raw(w); break;
case C2_L_PFOCUSED:
predef_target = win_is_focused_raw(ps, w);
break;
case C2_L_PWMWIN: predef_target = w->wmwin; break;
case C2_L_PBSHAPED: predef_target = w->bounding_shaped; break;
case C2_L_PROUNDED: predef_target = w->rounded_corners; break;
@@ -1509,8 +1513,7 @@ static inline void c2_match_once_leaf(session_t *ps, const struct managed_win *w
else {
char **strlst = NULL;
int nstr = 0;
if (wid_get_text_prop(&ps->c, ps->atoms, wid, pleaf->tgtatom,
&strlst, &nstr)) {
if (wid_get_text_prop(ps, wid, pleaf->tgtatom, &strlst, &nstr)) {
if (pleaf->index < 0 && nstr > 0 && strlen(strlst[0]) > 0) {
ntargets = to_u32_checked(nstr);
targets = (const char **)strlst;

View File

@@ -1,43 +1,95 @@
#include <uthash.h>
#include "compiler.h"
#include "utils.h"
#include "cache.h"
struct cache_handle *cache_get(struct cache *c, const char *key) {
struct cache_handle *e;
struct cache_entry {
char *key;
void *value;
UT_hash_handle hh;
};
struct cache {
cache_getter_t getter;
cache_free_t free;
void *user_data;
struct cache_entry *entries;
};
void cache_set(struct cache *c, const char *key, void *data) {
struct cache_entry *e = NULL;
HASH_FIND_STR(c->entries, key, e);
return e;
CHECK(!e);
e = ccalloc(1, struct cache_entry);
e->key = strdup(key);
e->value = data;
HASH_ADD_STR(c->entries, key, e);
}
int cache_get_or_fetch(struct cache *c, const char *key, struct cache_handle **value,
void *user_data, cache_getter_t getter) {
*value = cache_get(c, key);
if (*value) {
return 0;
void *cache_get(struct cache *c, const char *key, int *err) {
struct cache_entry *e;
HASH_FIND_STR(c->entries, key, e);
if (e) {
return e->value;
}
int err = getter(c, key, value, user_data);
assert(err <= 0);
if (err < 0) {
return err;
int tmperr;
if (!err) {
err = &tmperr;
}
(*value)->key = strdup(key);
HASH_ADD_STR(c->entries, key, *value);
return 1;
*err = 0;
e = ccalloc(1, struct cache_entry);
e->key = strdup(key);
e->value = c->getter(c->user_data, key, err);
if (*err) {
free(e->key);
free(e);
return NULL;
}
HASH_ADD_STR(c->entries, key, e);
return e->value;
}
static inline void
cache_invalidate_impl(struct cache *c, struct cache_handle *e, cache_free_t free_fn) {
static inline void _cache_invalidate(struct cache *c, struct cache_entry *e) {
if (c->free) {
c->free(c->user_data, e->value);
}
free(e->key);
HASH_DEL(c->entries, e);
if (free_fn) {
free_fn(c, e);
free(e);
}
void cache_invalidate(struct cache *c, const char *key) {
struct cache_entry *e;
HASH_FIND_STR(c->entries, key, e);
if (e) {
_cache_invalidate(c, e);
}
}
void cache_invalidate_all(struct cache *c, cache_free_t free_fn) {
struct cache_handle *e, *tmpe;
void cache_invalidate_all(struct cache *c) {
struct cache_entry *e, *tmpe;
HASH_ITER(hh, c->entries, e, tmpe) {
cache_invalidate_impl(c, e, free_fn);
_cache_invalidate(c, e);
}
}
void *cache_free(struct cache *c) {
void *ret = c->user_data;
cache_invalidate_all(c);
free(c);
return ret;
}
struct cache *new_cache(void *ud, cache_getter_t getter, cache_free_t f) {
auto c = ccalloc(1, struct cache);
c->user_data = ud;
c->getter = getter;
c->free = f;
return c;
}

View File

@@ -1,43 +1,32 @@
#pragma once
#include <uthash.h>
#include "utils.h"
#define cache_entry(ptr, type, member) container_of(ptr, type, member)
struct cache;
struct cache_handle;
/// User-provided function to fetch a value for the cache, when it's not present.
/// Should return 0 if the value is fetched successfully, and a negative number if the
/// value cannot be fetched. Getter doesn't need to initialize fields of `struct
/// cache_handle`.
typedef int (*cache_getter_t)(struct cache *, const char *key,
struct cache_handle **value, void *user_data);
typedef void (*cache_free_t)(struct cache *, struct cache_handle *value);
typedef void *(*cache_getter_t)(void *user_data, const char *key, int *err);
typedef void (*cache_free_t)(void *user_data, void *data);
struct cache {
struct cache_handle *entries;
};
/// Create a cache with `getter`, and a free function `f` which is used to free the cache
/// value when they are invalidated.
///
/// `user_data` will be passed to `getter` and `f` when they are called.
struct cache *new_cache(void *user_data, cache_getter_t getter, cache_free_t f);
static const struct cache CACHE_INIT = {NULL};
struct cache_handle {
char *key;
UT_hash_handle hh;
};
/// Get a value from the cache. If the value doesn't present in the cache yet, the
/// Fetch a value from the cache. If the value doesn't present in the cache yet, the
/// getter will be called, and the returned value will be stored into the cache.
/// Returns 0 if the value is already present in the cache, 1 if the value is fetched
/// successfully, and a negative number if the value cannot be fetched.
int cache_get_or_fetch(struct cache *, const char *key, struct cache_handle **value,
void *user_data, cache_getter_t getter);
void *cache_get(struct cache *, const char *key, int *err);
/// Get a value from the cache. If the value doesn't present in the cache, NULL will be
/// returned.
struct cache_handle *cache_get(struct cache *, const char *key);
/// Invalidate a value in the cache.
void cache_invalidate(struct cache *, const char *key);
/// Invalidate all values in the cache. After this call, `struct cache` holds no allocated
/// memory, and can be discarded.
void cache_invalidate_all(struct cache *, cache_free_t free_fn);
/// Invalidate all values in the cache.
void cache_invalidate_all(struct cache *);
/// Invalidate all values in the cache and free it. Returns the user data passed to
/// `new_cache`
void *cache_free(struct cache *);
/// Insert a key-value pair into the cache. Only used for internal testing. Takes
/// ownership of `data`
///
/// If `key` already exists in the cache, this function will abort the program.
void cache_set(struct cache *c, const char *key, void *data);

View File

@@ -190,7 +190,7 @@ typedef struct session {
/// Picture of the root window background.
paint_t root_tile_paint;
/// The backend data the root pixmap bound to
image_handle root_image;
void *root_image;
/// A region of the size of the screen.
region_t screen_reg;
/// Picture of root window. Destination of painting in no-DBE painting
@@ -208,7 +208,7 @@ 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;
struct glx_fbconfig_info *argb_fbconfig;
#endif
/// Sync fence to sync draw operations
xcb_sync_fence_t sync_fence;
@@ -244,8 +244,8 @@ typedef struct session {
/// Either the backend is currently rendering a frame, or a frame has been
/// rendered but has yet to be presented. In either case, we should not start
/// another render right now. As if we start issuing rendering commands now, we
/// will have to wait for either the current render to finish, or the current
/// back buffer to become available again. In either case, we will be wasting
/// will have to wait for either the the current render to finish, or the current
/// back buffer to be become available again. In either case, we will be wasting
/// time.
bool backend_busy;
/// Whether a render is queued. This generally means there are pending updates
@@ -279,7 +279,7 @@ typedef struct session {
struct x_convolution_kernel **blur_kerns_cache;
/// If we should quit
bool quit : 1;
// TODO(yshui) use separate flags for different kinds of updates so we don't
// TODO(yshui) use separate flags for dfferent kinds of updates so we don't
// waste our time.
/// Whether there are pending updates, like window creation, etc.
bool pending_updates : 1;

View File

@@ -52,7 +52,7 @@
#else
# define attr_warn_unused_result
#endif
// An alias for convenience
// An alias for conveninence
#define must_use attr_warn_unused_result
#if __has_attribute(nonnull)
@@ -105,9 +105,9 @@
#ifndef unreachable
# if defined(__GNUC__) || defined(__clang__)
# define unreachable() assert(false); __builtin_unreachable()
# define unreachable() __builtin_unreachable()
# else
# define unreachable() assert(false); do {} while(0)
# define unreachable() do {} while(0)
# endif
#endif

View File

@@ -43,7 +43,10 @@ const char *xdg_config_home(void) {
return NULL;
}
xdgh = mstrjoin(home, default_dir);
xdgh = cvalloc(strlen(home) + strlen(default_dir) + 1);
strcpy(xdgh, home);
strcat(xdgh, default_dir);
} else {
xdgh = strdup(xdgh);
}
@@ -560,7 +563,7 @@ static char *locate_auxiliary_file_at(const char *base, const char *scope, const
}
/**
* Get a path of an auxiliary file to read, could be a shader file, or any supplementary
* Get a path of an auxiliary file to read, could be a shader file, or any supplimenrary
* file.
*
* Follows the XDG specification to search for the shader file in configuration locations.
@@ -595,17 +598,15 @@ char *locate_auxiliary_file(const char *scope, const char *path, const char *inc
// Fall back to searching in user config directory
scoped_charp picom_scope = mstrjoin("/picom/", scope);
scoped_charp config_home = (char *)xdg_config_home();
if (config_home) {
char *ret = locate_auxiliary_file_at(config_home, picom_scope, path);
if (ret) {
return ret;
}
char *ret = locate_auxiliary_file_at(config_home, picom_scope, path);
if (ret) {
return ret;
}
// Fall back to searching in system config directory
auto config_dirs = xdg_config_dirs();
for (int i = 0; config_dirs[i]; i++) {
char *ret = locate_auxiliary_file_at(config_dirs[i], picom_scope, path);
ret = locate_auxiliary_file_at(config_dirs[i], picom_scope, path);
if (ret) {
free(config_dirs);
return ret;
@@ -613,7 +614,7 @@ char *locate_auxiliary_file(const char *scope, const char *path, const char *inc
}
free(config_dirs);
return NULL;
return ret;
}
struct debug_options_entry {

View File

@@ -366,7 +366,7 @@ char **xdg_config_dirs(void);
/// Parse a configuration file
/// Returns the actually config_file name used, allocated on heap
/// Outputs:
/// shadow_enable = whether shadow is enabled globally
/// shadow_enable = whether shaodw is enabled globally
/// fading_enable = whether fading is enabled globally
/// win_option_mask = whether option overrides for specific window type is set for given
/// options

View File

@@ -81,19 +81,17 @@ FILE *open_config_file(const char *cpath, char **ppath) {
// First search for config file in user config directory
auto config_home = xdg_config_home();
if (config_home) {
auto ret = open_config_file_at(config_home, ppath);
free((void *)config_home);
if (ret) {
return ret;
}
auto ret = open_config_file_at(config_home, ppath);
free((void *)config_home);
if (ret) {
return ret;
}
// Fall back to legacy config file in user home directory
const char *home = getenv("HOME");
if (home && strlen(home)) {
auto path = mstrjoin(home, config_filename_legacy);
auto ret = fopen(path, "r");
ret = fopen(path, "r");
if (ret && ppath) {
*ppath = path;
} else {
@@ -107,7 +105,7 @@ FILE *open_config_file(const char *cpath, char **ppath) {
// Fall back to config file in system config directory
auto config_dirs = xdg_config_dirs();
for (int i = 0; config_dirs[i]; i++) {
auto ret = open_config_file_at(config_dirs[i], ppath);
ret = open_config_file_at(config_dirs[i], ppath);
if (ret) {
free(config_dirs);
return ret;
@@ -487,8 +485,7 @@ char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shad
}
// --sw-opti
if (lcfg_lookup_bool(&cfg, "sw-opti", &bval)) {
log_error("The sw-opti %s", deprecation_message);
goto err;
log_warn("The sw-opti %s", deprecation_message);
}
// --use-ewmh-active-win
lcfg_lookup_bool(&cfg, "use-ewmh-active-win", &opt->use_ewmh_active_win);

View File

@@ -906,7 +906,7 @@ cdbus_process_window_property_get(session_t *ps, DBusMessage *msg, cdbus_window_
}
if (!strcmp("RawFocused", target)) {
cdbus_reply(ps, msg, cdbus_append_bool_variant,
(bool[]){win_is_focused_raw(w)});
(bool[]){win_is_focused_raw(ps, w)});
return true;
}
@@ -976,7 +976,7 @@ static bool cdbus_process_win_get(session_t *ps, DBusMessage *msg) {
cdbus_m_win_get_do(wmwin, cdbus_reply_bool);
cdbus_m_win_get_do(leader, cdbus_reply_wid);
if (!strcmp("focused_raw", target)) {
cdbus_reply_bool(ps, msg, win_is_focused_raw(w));
cdbus_reply_bool(ps, msg, win_is_focused_raw(ps, w));
return true;
}
cdbus_m_win_get_do(fade_force, cdbus_reply_enum);

View File

@@ -1,7 +1,6 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2019, Yuxuan Shui <yshuiv7@gmail.com>
#include <stdint.h>
#include <stdio.h>
#include <X11/Xlibint.h>
@@ -30,7 +29,7 @@
/// made the query when those events were already in the queue. so the reply you got is
/// more up-to-date than the events). Also, handling events when other client are making
/// concurrent requests is not good. Because the server states are changing without you
/// knowing them. This is super racy, and can cause lots of potential problems.
/// knowning them. This is super racy, and can cause lots of potential problems.
///
/// All of above mandates we do these things:
/// 1. Grab server when handling events
@@ -48,14 +47,8 @@
/// When top half finished, we enter the render stage, where no server state should be
/// queried. All rendering should be done with our internal knowledge of the server state.
///
/// P.S. There is another reason to avoid sending any request to the server as much as
/// possible. To make sure requests are sent, flushes are needed. And `xcb_flush`/`XFlush`
/// functions may read more events from the server into their queues. This is
/// undesirable, see the comments on `handle_queued_x_events` in picom.c for more details.
// TODO(yshui) the things described above. This is mostly done, maybe some of
// the functions here is still making unnecessary queries, we need
// to do some auditing to be sure.
// TODO(yshui) the things described above
/**
* Get a window's name from window ID.
@@ -331,7 +324,7 @@ static inline void ev_reparent_notify(session_t *ps, xcb_reparent_notify_event_t
}
if (ev->parent == ps->c.screen_info->root) {
// X will generate reparent notify even if the parent didn't actually
// X will generate reparent notifiy even if the parent didn't actually
// change (i.e. reparent again to current parent). So we check if that's
// the case
auto w = find_win(ps, ev->window);
@@ -357,14 +350,19 @@ static inline void ev_reparent_notify(session_t *ps, xcb_reparent_notify_event_t
}
// Reset event mask in case something wrong happens
uint32_t evmask = determine_evmask(ps, ev->window, WIN_EVMODE_UNKNOWN);
xcb_change_window_attributes(
ps->c.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);
evmask |= XCB_EVENT_MASK_PROPERTY_CHANGE;
xcb_change_window_attributes(
ps->c.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 &&
@@ -388,8 +386,6 @@ static inline void ev_reparent_notify(session_t *ps, xcb_reparent_notify_event_t
}
}
}
XCB_AWAIT_VOID(xcb_change_window_attributes, ps->c.c, ev->window,
XCB_CW_EVENT_MASK, (const uint32_t[]){evmask});
}
}
@@ -469,7 +465,7 @@ static inline void ev_property_notify(session_t *ps, xcb_property_notify_event_t
}
}
// Unconcerned about any other properties on root window
// Unconcerned about any other proprties on root window
return;
}
@@ -479,10 +475,9 @@ static inline void ev_property_notify(session_t *ps, xcb_property_notify_event_t
// Check whether it could be a client window
if (!find_toplevel(ps, ev->window)) {
// Reset event mask anyway
const uint32_t evmask =
determine_evmask(ps, ev->window, WIN_EVMODE_UNKNOWN);
XCB_AWAIT_VOID(xcb_change_window_attributes, ps->c.c, ev->window,
XCB_CW_EVENT_MASK, (const uint32_t[]){evmask});
xcb_change_window_attributes(ps->c.c, ev->window, XCB_CW_EVENT_MASK,
(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
@@ -497,14 +492,14 @@ static inline void ev_property_notify(session_t *ps, xcb_property_notify_event_t
// If _NET_WM_WINDOW_TYPE changes... God knows why this would happen, but
// there are always some stupid applications. (#144)
if (ev->atom == ps->atoms->a_NET_WM_WINDOW_TYPE) {
struct managed_win *w = find_toplevel(ps, ev->window);
if (w) {
struct managed_win *w = NULL;
if ((w = find_toplevel(ps, ev->window))) {
win_set_property_stale(w, ev->atom);
}
}
if (ev->atom == ps->atoms->a_NET_WM_BYPASS_COMPOSITOR) {
// Unnecessary until we remove the queue_redraw in ev_handle
// Unnecessay until we remove the queue_redraw in ev_handle
queue_redraw(ps);
}
@@ -565,13 +560,6 @@ static inline void ev_property_notify(session_t *ps, xcb_property_notify_event_t
}
}
if (!ps->o.no_ewmh_fullscreen && ev->atom == ps->atoms->a_NET_WM_STATE) {
auto w = find_toplevel(ps, ev->window);
if (w) {
win_set_property_stale(w, ev->atom);
}
}
// Check for other atoms we are tracking
for (latom_t *platom = ps->track_atom_lst; platom; platom = platom->next) {
if (platom->atom == ev->atom) {
@@ -598,28 +586,16 @@ static inline void repair_win(session_t *ps, struct managed_win *w) {
region_t parts;
pixman_region32_init(&parts);
// If this is the first time this window is damaged, we would redraw the
// whole window, so we don't need to fetch the damage region. But we still need
// to make sure the X server receives the DamageSubtract request, hence the
// `xcb_request_check` here.
// Otherwise, we fetch the damage regions. That means we will receive a reply
// from the X server, which implies it has received our DamageSubtract request.
if (!w->ever_damaged) {
auto e = xcb_request_check(
ps->c.c, xcb_damage_subtract(ps->c.c, w->damage, XCB_NONE, XCB_NONE));
if (e) {
if (ps->o.show_all_xerrors) {
x_print_error(e->sequence, e->major_code, e->minor_code,
e->error_code);
}
free(e);
}
win_extents(w, &parts);
} else {
auto cookie =
xcb_damage_subtract(ps->c.c, w->damage, XCB_NONE, ps->damaged_region);
if (!ps->o.show_all_xerrors) {
set_ignore_cookie(&ps->c, cookie);
set_ignore_cookie(&ps->c, xcb_damage_subtract(ps->c.c, w->damage,
XCB_NONE, XCB_NONE));
}
} else {
if (!ps->o.show_all_xerrors) {
set_ignore_cookie(&ps->c, xcb_damage_subtract(ps->c.c, w->damage, XCB_NONE,
ps->damaged_region));
}
x_fetch_region(&ps->c, ps->damaged_region, &parts);
pixman_region32_translate(&parts, w->g.x + w->g.border_width,

View File

@@ -158,7 +158,7 @@ bool file_watch_add(void *_fwr, const char *filename, file_watch_cb_t cb, void *
fflags |= NOTE_CLOSE_WRITE;
#else
// NOTE_WRITE will receive notification more frequent than necessary, so is less
// preferable
// preferrable
fflags |= NOTE_WRITE;
#endif
struct kevent ev = {

View File

@@ -96,7 +96,7 @@ static inline double estimate_first_row_sum(double size, double r) {
// `a` is gaussian at (size, 0)
double a = exp(-0.5 * size * size / (r * r)) / sqrt(2 * M_PI) / r;
// The sum of the whole kernel is normalized to 1, i.e. each element is divided by
// factor squared. So the sum of the first row is a * factor / factor^2 = a /
// factor sqaured. So the sum of the first row is a * factor / factor^2 = a /
// factor
return a / factor;
}

View File

@@ -2,7 +2,18 @@
#include <stdbool.h>
#include <stddef.h>
#include "utils.h"
/**
* container_of - cast a member of a structure out to the containing structure
* @ptr: the pointer to the member.
* @type: the type of the container struct this is embedded in.
* @member: the name of the member within the struct.
*
*/
#define container_of(ptr, type, member) \
({ \
const __typeof__(((type *)0)->member) *__mptr = (ptr); \
(type *)((char *)__mptr - offsetof(type, member)); \
})
struct list_node {
struct list_node *next, *prev;

View File

@@ -9,7 +9,7 @@
#include <unistd.h>
#ifdef CONFIG_OPENGL
#include <epoxy/gl.h>
#include <GL/gl.h>
#include "backend/gl/gl_common.h"
#include "backend/gl/glx.h"
#endif
@@ -338,14 +338,21 @@ struct log_target *stderr_logger_new(void) {
}
#ifdef CONFIG_OPENGL
/// An opengl logger that can be used for logging into opengl debugging tools,
/// such as apitrace
struct gl_string_marker_logger {
struct log_target tgt;
PFNGLSTRINGMARKERGREMEDYPROC gl_string_marker;
};
static void gl_string_marker_logger_write(struct log_target *tgt attr_unused,
const char *str, size_t len) {
static void
gl_string_marker_logger_write(struct log_target *tgt, const char *str, size_t len) {
auto g = (struct gl_string_marker_logger *)tgt;
// strip newlines at the end of the string
while (len > 0 && str[len - 1] == '\n') {
len--;
}
glStringMarkerGREMEDY((GLsizei)len, str);
g->gl_string_marker((GLsizei)len, str);
}
static const struct log_ops gl_string_marker_logger_ops = {
@@ -354,16 +361,20 @@ static const struct log_ops gl_string_marker_logger_ops = {
.destroy = logger_trivial_destroy,
};
/// Create an opengl logger that can be used for logging into opengl debugging tools,
/// such as apitrace
struct log_target *gl_string_marker_logger_new(void) {
if (!epoxy_has_gl_extension("GL_GREMEDY_string_marker")) {
if (!gl_has_extension("GL_GREMEDY_string_marker")) {
return NULL;
}
auto ret = cmalloc(struct log_target);
ret->ops = &gl_string_marker_logger_ops;
return ret;
void *fnptr = glXGetProcAddress((GLubyte *)"glStringMarkerGREMEDY");
if (!fnptr) {
return NULL;
}
auto ret = cmalloc(struct gl_string_marker_logger);
ret->tgt.ops = &gl_string_marker_logger_ops;
ret->gl_string_marker = fnptr;
return &ret->tgt;
}
#else

View File

@@ -23,7 +23,7 @@ required_xcb_packages = [
# Some XCB packages are here because their versioning differs (see check below).
required_packages = [
'pixman-1', 'x11', 'x11-xcb', 'xcb-image', 'xcb-renderutil', 'xcb-util',
'xext', 'threads',
'xext'
]
foreach i : required_packages
@@ -58,8 +58,8 @@ if get_option('vsync_drm')
endif
if get_option('opengl')
cflags += ['-DCONFIG_OPENGL']
deps += [dependency('epoxy', required: true)]
cflags += ['-DCONFIG_OPENGL', '-DGL_GLEXT_PROTOTYPES']
deps += [dependency('gl', required: true), dependency('egl', required: true), dependency('threads', required:true)]
srcs += [ 'opengl.c' ]
endif

View File

@@ -181,7 +181,7 @@ bool glx_init(session_t *ps, bool need_render) {
// must precede FBConfig fetching
if (need_render) {
psglx->has_texture_non_power_of_two =
epoxy_has_gl_extension("GL_ARB_texture_non_power_of_two");
gl_has_extension("GL_ARB_texture_non_power_of_two");
}
// Render preparations
@@ -279,7 +279,7 @@ void glx_destroy(session_t *ps) {
free(ps->psglx);
ps->psglx = NULL;
ps->argb_fbconfig = (struct glx_fbconfig_info){0};
ps->argb_fbconfig = NULL;
}
/**
@@ -1129,7 +1129,7 @@ glx_blur_dst_end:
// TODO(bhagwan) 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 literally non existent...
// documentation for the xcb_xrender extension is literaly non existent...
//
// NOTE(yshui) There is no consistent way to get the "border" color of a X window. From
// the WM's perspective there are multiple ways to implement window borders. Using

View File

@@ -18,9 +18,9 @@
#include "render.h"
#include "win.h"
#include <GL/gl.h>
#include <GL/glx.h>
#include <ctype.h>
#include <epoxy/gl.h>
#include <epoxy/glx.h>
#include <locale.h>
#include <stdlib.h>
#include <string.h>
@@ -75,7 +75,7 @@ typedef struct glx_session {
glx_round_pass_t *round_passes;
} glx_session_t;
/// @brief Wrapper of a bound GLX texture.
/// @brief Wrapper of a binded GLX texture.
typedef struct _glx_texture {
GLuint texture;
GLXPixmap glpixmap;
@@ -121,9 +121,9 @@ bool glx_bind_texture(session_t *ps, glx_texture_t **pptex, int x, int y, int wi
void glx_paint_pre(session_t *ps, region_t *preg) attr_nonnull(1, 2);
/**
* Check if a texture is bound, or is bound to the given pixmap.
* Check if a texture is binded, or is binded to the given pixmap.
*/
static inline bool glx_tex_bound(const glx_texture_t *ptex, xcb_pixmap_t pixmap) {
static inline bool glx_tex_binded(const glx_texture_t *ptex, xcb_pixmap_t pixmap) {
return ptex && ptex->glpixmap && ptex->texture && (!pixmap || pixmap == ptex->pixmap);
}
@@ -228,7 +228,10 @@ 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 = (struct glx_fbconfig_info){0};
#ifdef CONFIG_OPENGL
free(ppaint->fbcfg);
#endif
ppaint->fbcfg = NULL;
}
/**

View File

@@ -315,7 +315,7 @@ static void usage(const char *argv0, int ret) {
}
}
static const char *shortopts = "D:I:O:r:o:m:l:t:i:e:hscnfCazGb";
static const char *shortopts = "D:I:O:r:o:m:l:t:i:e:hscnfFCazGb";
/// Get config options that are needed to parse the rest of the options
/// Return true if we should quit
@@ -325,9 +325,9 @@ bool get_early_config(int argc, char *const *argv, char **config_file, bool *all
int o = 0, longopt_idx = -1;
// Pre-parse the command line arguments to check for --config and invalid
// Pre-parse the commandline arguments to check for --config and invalid
// switches
// Must reset optind to 0 here in case we reread the command line
// Must reset optind to 0 here in case we reread the commandline
// arguments
optind = 1;
*config_file = NULL;
@@ -379,7 +379,7 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable,
// instead of commas in atof().
setlocale(LC_NUMERIC, "C");
// Parse command line arguments. Range checking will be done later.
// Parse commandline arguments. Range checking will be done later.
bool failed = false;
const char *deprecation_message attr_unused =
@@ -435,6 +435,7 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable,
opt->wintype_option[WINTYPE_DROPDOWN_MENU].opacity = tmp;
break;
case 'f':
case 'F':
fading_enable = true;
break;
P_CASEINT('r', shadow_radius);
@@ -519,9 +520,8 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable,
"--crop-shadow-to-monitor instead.");
break;
case 274:
log_error("--sw-opti has been deprecated, please remove it from the "
log_warn("--sw-opti has been deprecated, please remove it from the "
"command line options");
failed = true;
break;
case 275:
// --vsync-aggressive
@@ -532,10 +532,9 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable,
P_CASEBOOL(276, use_ewmh_active_win);
case 277:
// --respect-prop-shadow
log_error("--respect-prop-shadow option has been deprecated, its "
log_warn("--respect-prop-shadow option has been deprecated, its "
"functionality will always be enabled. Please remove it "
"from the command line options");
failed = true;
break;
P_CASEBOOL(278, unredir_if_possible);
case 279:
@@ -731,7 +730,7 @@ bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable,
opt->blur_strength = atoi(optarg);
break;
case 333:
// --corner-radius
// --cornor-radius
opt->corner_radius = atoi(optarg);
break;
case 334:

View File

@@ -1,12 +1,10 @@
// SPDX-License-Identifier: MIT
/*
* picom - a compositor for X11
* Compton - a compositor for X11
*
* Based on `compton` - Copyright (c) 2011-2013, Christopher Jeffrey
* Based on `xcompmgr` - Copyright (c) 2003, Keith Packard
*
* Copyright (c) 2019-2023, Yuxuan Shui
*
* Copyright (c) 2011-2013, Christopher Jeffrey
* See LICENSE-mit for more information.
*
*/
@@ -18,8 +16,6 @@
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <math.h>
#include <pthread.h>
#include <sched.h>
#include <stddef.h>
#include <stdio.h>
@@ -35,7 +31,6 @@
#include <xcb/randr.h>
#include <xcb/render.h>
#include <xcb/sync.h>
#include <xcb/xcb_aux.h>
#include <xcb/xfixes.h>
#include <ev.h>
@@ -149,10 +144,10 @@ static inline struct managed_win *find_win_all(session_t *ps, const xcb_window_t
return w;
}
enum vblank_callback_action check_render_finish(struct vblank_event *e attr_unused, void *ud) {
void check_render_finish(struct vblank_event *e attr_unused, void *ud) {
auto ps = (session_t *)ud;
if (!ps->backend_busy) {
return VBLANK_CALLBACK_DONE;
return;
}
struct timespec render_time;
@@ -164,7 +159,8 @@ enum vblank_callback_action check_render_finish(struct vblank_event *e attr_unus
log_debug("Last render did not complete during vblank, msc: "
"%" PRIu64,
ps->last_msc);
return VBLANK_CALLBACK_AGAIN;
vblank_scheduler_schedule(ps->vblank_scheduler, check_render_finish, ud);
return;
}
// The frame has been finished and presented, record its render time.
@@ -178,20 +174,22 @@ enum vblank_callback_action check_render_finish(struct vblank_event *e attr_unus
render_time_us, (int)ps->last_schedule_delay, ps->last_msc);
}
ps->backend_busy = false;
return VBLANK_CALLBACK_DONE;
}
enum vblank_callback_action
collect_vblank_interval_statistics(struct vblank_event *e, void *ud) {
void collect_vblank_interval_statistics(struct vblank_event *e, void *ud) {
auto ps = (session_t *)ud;
double vblank_interval = NAN;
assert(ps->frame_pacing);
assert(ps->vblank_scheduler);
if (ps->last_msc == e->msc) {
// Already collected statistics for this vblank
return;
}
if (!ps->o.debug_options.smart_frame_pacing) {
// We don't need to collect statistics if we are not doing smart frame
// pacing.
return VBLANK_CALLBACK_DONE;
return;
}
// TODO(yshui): this naive method of estimating vblank interval does not handle
@@ -202,44 +200,29 @@ collect_vblank_interval_statistics(struct vblank_event *e, void *ud) {
// samples when the monitor is off, but I had a hard time to get it
// working reliably, there are just too many corner cases.
// Don't add sample again if we already collected statistics for this vblank
if (ps->last_msc < e->msc) {
if (ps->last_msc_instant != 0) {
auto frame_count = e->msc - ps->last_msc;
auto frame_time =
(int)((e->ust - ps->last_msc_instant) / frame_count);
if (frame_count == 1) {
render_statistics_add_vblank_time_sample(
&ps->render_stats, frame_time);
log_trace("Frame count %" PRIu64 ", frame time: %d us, "
"ust: "
"%" PRIu64,
frame_count, frame_time, e->ust);
} else {
log_trace("Frame count %" PRIu64 ", frame time: %d us, "
"msc: "
"%" PRIu64 ", not adding sample.",
frame_count, frame_time, e->ust);
}
if (ps->last_msc_instant != 0) {
auto frame_count = e->msc - ps->last_msc;
int frame_time = (int)((e->ust - ps->last_msc_instant) / frame_count);
if (frame_count == 1) {
render_statistics_add_vblank_time_sample(&ps->render_stats, frame_time);
log_trace("Frame count %lu, frame time: %d us, ust: %" PRIu64 "",
frame_count, frame_time, e->ust);
} else {
log_trace("Frame count %lu, frame time: %d us, msc: %" PRIu64
", not adding sample.",
frame_count, frame_time, e->ust);
}
ps->last_msc_instant = e->ust;
ps->last_msc = e->msc;
} else if (ps->last_msc > e->msc) {
log_warn("PresentCompleteNotify msc is going backwards, last_msc: "
"%" PRIu64 ", current msc: %" PRIu64,
ps->last_msc, e->msc);
ps->last_msc_instant = 0;
ps->last_msc = 0;
}
vblank_interval = render_statistics_get_vblank_time(&ps->render_stats);
ps->last_msc_instant = e->ust;
ps->last_msc = e->msc;
double vblank_interval = render_statistics_get_vblank_time(&ps->render_stats);
log_trace("Vblank interval estimate: %f us", vblank_interval);
if (vblank_interval == 0) {
// We don't have enough data for vblank interval estimate, schedule
// another vblank event.
return VBLANK_CALLBACK_AGAIN;
vblank_scheduler_schedule(ps->vblank_scheduler,
collect_vblank_interval_statistics, ud);
}
return VBLANK_CALLBACK_DONE;
}
void schedule_render(session_t *ps, bool triggered_by_vblank);
@@ -247,7 +230,7 @@ void schedule_render(session_t *ps, bool triggered_by_vblank);
/// vblank callback scheduled by schedule_render, when a render is ongoing.
///
/// Check if previously queued render has finished, and reschedule render if it has.
enum vblank_callback_action reschedule_render_at_vblank(struct vblank_event *e, void *ud) {
void reschedule_render_at_vblank(struct vblank_event *e, void *ud) {
auto ps = (session_t *)ud;
assert(ps->frame_pacing);
assert(ps->render_queued);
@@ -259,11 +242,12 @@ enum vblank_callback_action reschedule_render_at_vblank(struct vblank_event *e,
check_render_finish(e, ud);
if (ps->backend_busy) {
return VBLANK_CALLBACK_AGAIN;
vblank_scheduler_schedule(ps->vblank_scheduler,
reschedule_render_at_vblank, ud);
return;
}
schedule_render(ps, false);
return VBLANK_CALLBACK_DONE;
}
/// How many seconds into the future should we start rendering the next frame.
@@ -274,7 +258,7 @@ enum vblank_callback_action reschedule_render_at_vblank(struct vblank_event *e,
/// is no render currently scheduled. i.e. render_queued == false.
/// 2. then, we need to figure out the best time to start rendering. we need to
/// at least know when the next vblank will start, as we can't start render
/// before the current rendered frame is displayed on screen. we have this
/// before the current rendered frame is diplayed on screen. we have this
/// information from the vblank scheduler, it will notify us when that happens.
/// we might also want to delay the rendering even further to reduce latency,
/// this is discussed below, in FUTURE WORKS.
@@ -479,29 +463,27 @@ static double fade_timeout(session_t *ps) {
*/
static bool run_fade(session_t *ps, struct managed_win **_w, long long steps) {
auto w = *_w;
log_trace("Process fading for window %s (%#010x), steps: %lld", w->name,
w->base.id, steps);
if (w->state == WSTATE_MAPPED || w->state == WSTATE_UNMAPPED) {
// We are not fading
assert(w->opacity_target == w->opacity);
log_trace("|- not fading");
return false;
}
if (!win_should_fade(ps, w)) {
log_trace("|- in transition but doesn't need fading");
log_debug("Window %#010x %s doesn't need fading", w->base.id, w->name);
w->opacity = w->opacity_target;
}
if (w->opacity == w->opacity_target) {
// We have reached target opacity.
// We don't call win_check_fade_finished here because that could destroy
// the window, but we still need the damage info from this window
log_trace("|- was fading but finished");
log_debug("Fading finished for window %#010x %s", w->base.id, w->name);
return false;
}
log_trace("|- fading, opacity: %lf", w->opacity);
if (steps) {
log_trace("Window %#010x (%s) opacity was: %lf", w->base.id, w->name,
w->opacity);
if (w->opacity < w->opacity_target) {
w->opacity = clamp(w->opacity + ps->o.fade_in_step * (double)steps,
0.0, w->opacity_target);
@@ -509,7 +491,7 @@ static bool run_fade(session_t *ps, struct managed_win **_w, long long steps) {
w->opacity = clamp(w->opacity - ps->o.fade_out_step * (double)steps,
w->opacity_target, 1);
}
log_trace("|- opacity updated: %lf", w->opacity);
log_trace("... updated to: %lf", w->opacity);
}
// Note even if opacity == opacity_target here, we still want to run preprocess
@@ -791,8 +773,6 @@ err:
/// Handle configure event of the root window
static void configure_root(session_t *ps) {
// TODO(yshui) re-initializing backend should be done outside of the
// critical section. Probably set a flag and do it in draw_callback_impl.
auto r = XCB_AWAIT(xcb_get_geometry, ps->c.c, ps->c.screen_info->root);
if (!r) {
log_fatal("Failed to fetch root geometry");
@@ -832,13 +812,6 @@ static void configure_root(session_t *ps) {
top_w->reg_ignore_valid = false;
}
// Whether a window is fullscreen depends on the new screen
// size. So we need to refresh the fullscreen state of all
// windows.
win_stack_foreach_managed(w, &ps->window_stack) {
win_update_is_fullscreen(ps, w);
}
if (ps->redirected) {
for (int i = 0; i < ps->ndamage; i++) {
pixman_region32_clear(&ps->damage_ring[i]);
@@ -1072,10 +1045,8 @@ paint_preprocess(session_t *ps, bool *fade_running, bool *animation_running) {
pixman_region32_init_rect(&w->bounding_shape, 0, 0,
(uint)w->widthb, (uint)w->heightb);
if (w->state != WSTATE_UNMAPPED && w->state != WSTATE_DESTROYING && w->state != WSTATE_UNMAPPING) {
win_clear_flags(w, WIN_FLAGS_PIXMAP_STALE);
win_process_image_flags(ps, w);
}
win_clear_flags(w, WIN_FLAGS_PIXMAP_STALE);
win_process_image_flags(ps, w);
}
// Mark new window region with damage
if (was_painted && geometry_changed) {
@@ -1161,19 +1132,13 @@ paint_preprocess(session_t *ps, bool *fade_running, bool *animation_running) {
}
// log_trace("%d %d %s", w->a.map_state, w->ever_damaged, w->name);
log_trace("Checking whether window %#010x (%s) should be painted",
w->base.id, w->name);
// Give up if it's not damaged or invisible, or it's unmapped and its
// pixmap is gone (for example due to a ConfigureNotify), or when it's
// excluded
if (w->state == WSTATE_UNMAPPED) {
log_trace("|- is unmapped");
to_paint = false;
} else if (unlikely(ps->debug_window != XCB_NONE) &&
(w->base.id == ps->debug_window ||
w->client_win == ps->debug_window)) {
log_trace("|- is the debug window");
if (w->state == WSTATE_UNMAPPED ||
unlikely(w->base.id == ps->debug_window ||
w->client_win == ps->debug_window)) {
to_paint = false;
} else if (!w->ever_damaged && w->state != WSTATE_UNMAPPING &&
w->state != WSTATE_DESTROYING) {
@@ -1181,23 +1146,33 @@ paint_preprocess(session_t *ps, bool *fade_running, bool *animation_running) {
// is fading out means it must have been damaged when it was still
// mapped (because unmap_win_start will skip fading if it wasn't),
// so we still need to paint it.
log_trace("|- has not received any damages");
log_trace("Window %#010x (%s) will not be painted because it has "
"not received any damages",
w->base.id, w->name);
to_paint = false;
} else if (unlikely(w->g.x + w->g.width < 1 || w->g.y + w->g.height < 1 ||
w->g.x >= ps->root_width || w->g.y >= ps->root_height)) {
log_trace("|- is positioned outside of the screen");
log_trace("Window %#010x (%s) will not be painted because it is "
"positioned outside of the screen",
w->base.id, w->name);
to_paint = false;
} else if (unlikely((double)w->opacity * MAX_ALPHA < 1 && !w->blur_background)) {
/* TODO(yshui) for consistency, even a window has 0 opacity, we
* still probably need to blur its background, so to_paint
* shouldn't be false for them. */
log_trace("|- has 0 opacity");
log_trace("Window %#010x (%s) will not be painted because it has "
"0 opacity",
w->base.id, w->name);
to_paint = false;
} else if (w->paint_excluded) {
log_trace("|- is excluded from painting");
log_trace("Window %#010x (%s) will not be painted because it is "
"excluded from painting",
w->base.id, w->name);
to_paint = false;
} else if (unlikely((w->flags & WIN_FLAGS_IMAGE_ERROR) != 0)) {
log_trace("|- has image errors");
log_trace("Window %#010x (%s) will not be painted because it has "
"image errors",
w->base.id, w->name);
to_paint = false;
}
// log_trace("%s %d %d %d", w->name, to_paint, w->opacity,
@@ -1212,12 +1187,10 @@ paint_preprocess(session_t *ps, bool *fade_running, bool *animation_running) {
// to_paint will never change after this point
if (!to_paint) {
log_trace("|- will not be painted");
goto skip_window;
}
log_trace("|- will be painted");
log_verbose("Window %#010x (%s) will be painted", w->base.id, w->name);
log_trace("Window %#010x (%s) will be painted", w->base.id, w->name);
// Calculate shadow opacity
w->shadow_opacity = ps->o.shadow_opacity * w->opacity * ps->o.frame_opacity;
@@ -1256,7 +1229,7 @@ paint_preprocess(session_t *ps, bool *fade_running, bool *animation_running) {
// is not correctly set.
if (ps->o.unredir_if_possible && is_highest) {
if (w->mode == WMODE_SOLID && !ps->o.force_win_blend &&
w->is_fullscreen && !w->unredir_if_possible_excluded) {
win_is_fullscreen(ps, w) && !w->unredir_if_possible_excluded) {
unredir_possible = true;
}
}
@@ -1345,43 +1318,14 @@ void root_damaged(session_t *ps) {
}
auto pixmap = x_get_root_back_pixmap(&ps->c, ps->atoms);
if (pixmap != XCB_NONE) {
xcb_get_geometry_reply_t *r = xcb_get_geometry_reply(
ps->c.c, xcb_get_geometry(ps->c.c, pixmap), NULL);
if (!r) {
goto err;
}
// We used to assume that pixmaps pointed by the root background
// pixmap atoms are owned by the root window and have the same
// depth and hence the same visual that we can use to bind them.
// However, some applications break this assumption, e.g. the
// Xfce's desktop manager xfdesktop that sets the _XROOTPMAP_ID
// atom to a pixmap owned by it that seems to always have 32 bpp
// depth when the common root window's depth is 24 bpp. So use the
// root window's visual only if the root background pixmap's depth
// matches the root window's depth. Otherwise, find a suitable
// visual for the root background pixmap's depth and use it.
//
// We can't obtain a suitable visual for the root background
// pixmap the same way as the win_bind_pixmap function because it
// requires a window and we have only a pixmap. We also can't not
// bind the root background pixmap in case of depth mismatch
// because some options rely on it's content, e.g.
// transparent-clipping.
xcb_visualid_t visual =
r->depth == ps->c.screen_info->root_depth
? ps->c.screen_info->root_visual
: x_get_visual_for_depth(ps->c.screen_info, r->depth);
free(r);
ps->root_image = ps->backend_data->ops->bind_pixmap(
ps->backend_data, pixmap, x_get_visual_info(&ps->c, visual), false);
ps->backend_data, pixmap,
x_get_visual_info(&ps->c, ps->c.screen_info->root_visual), false);
if (ps->root_image) {
ps->backend_data->ops->set_image_property(
ps->backend_data, IMAGE_PROPERTY_EFFECTIVE_SIZE,
ps->root_image, (int[]){ps->root_width, ps->root_height});
} else {
err:
log_error("Failed to bind root back pixmap");
}
}
@@ -1501,10 +1445,11 @@ static int register_cm(session_t *ps) {
}
// Set COMPTON_VERSION
e = xcb_request_check(ps->c.c, xcb_change_property_checked(
ps->c.c, XCB_PROP_MODE_REPLACE, ps->reg_win,
ps->atoms->aCOMPTON_VERSION, XCB_ATOM_STRING, 8,
(uint32_t)strlen(PICOM_VERSION), PICOM_VERSION));
e = xcb_request_check(
ps->c.c, xcb_change_property_checked(
ps->c.c, XCB_PROP_MODE_REPLACE, ps->reg_win,
get_atom(ps->atoms, "COMPTON_VERSION"), XCB_ATOM_STRING, 8,
(uint32_t)strlen(PICOM_VERSION), PICOM_VERSION));
if (e) {
log_error_x_error(e, "Failed to set COMPTON_VERSION.");
free(e);
@@ -1520,7 +1465,7 @@ static int register_cm(session_t *ps) {
log_fatal("Failed to allocate memory");
return -1;
}
atom = get_atom(ps->atoms, buf, ps->c.c);
atom = get_atom(ps->atoms, buf);
free(buf);
xcb_get_selection_owner_reply_t *reply = xcb_get_selection_owner_reply(
@@ -1684,7 +1629,7 @@ static bool redirect_start(session_t *ps) {
return false;
}
xcb_aux_sync(ps->c.c);
x_sync(&ps->c);
if (!initialize_backend(ps)) {
return false;
@@ -1703,7 +1648,8 @@ static bool redirect_start(session_t *ps) {
pixman_region32_init(&ps->damage_ring[i]);
}
ps->frame_pacing = !ps->o.no_frame_pacing && ps->o.vsync;
ps->frame_pacing = !ps->o.no_frame_pacing;
// if ((ps->o.legacy_backends || ps->o.animations || ps->o.benchmark || !ps->backend_data->ops->last_render_time) &&
if ((ps->o.legacy_backends || ps->o.benchmark || !ps->backend_data->ops->last_render_time) &&
ps->frame_pacing) {
// Disable frame pacing if we are using a legacy backend or if we are in
@@ -1743,7 +1689,7 @@ static bool redirect_start(session_t *ps) {
}
// Must call XSync() here
xcb_aux_sync(ps->c.c);
x_sync(&ps->c);
ps->redirected = true;
ps->first_frame = true;
@@ -1786,38 +1732,15 @@ static void unredirect(session_t *ps) {
}
// Must call XSync() here
xcb_aux_sync(ps->c.c);
x_sync(&ps->c);
ps->redirected = false;
log_debug("Screen unredirected.");
}
/// Handle queued events before we go to sleep.
///
/// This function is called by ev_prepare watcher, which is called just before
/// the event loop goes to sleep. X damage events are incremental, which means
/// if we don't handle the ones X server already sent us, we won't get new ones.
/// And if we don't get new ones, we won't render, i.e. we would freeze. libxcb
/// keeps an internal queue of events, so we have to be 100% sure no events are
/// left in that queue before we go to sleep.
// Handle queued events before we go to sleep
static void handle_queued_x_events(EV_P attr_unused, ev_prepare *w, int revents attr_unused) {
session_t *ps = session_ptr(w, event_check);
// Flush because if we go into sleep when there is still requests in the
// outgoing buffer, they will not be sent for an indefinite amount of
// time. Use XFlush here too, we might still use some Xlib functions
// because OpenGL.
//
// Also note, after we have flushed here, we won't flush again in this
// function before going into sleep. This is because `xcb_flush`/`XFlush`
// may _read_ more events from the server (yes, this is ridiculous, I
// know). And we can't have that, see the comments above this function.
//
// This means if functions called ev_handle need to send some events,
// they need to carefully make sure those events are flushed, one way or
// another.
XFlush(ps->c.dpy);
xcb_flush(ps->c.c);
if (ps->vblank_scheduler) {
vblank_handle_x_events(ps->vblank_scheduler);
}
@@ -1827,6 +1750,13 @@ static void handle_queued_x_events(EV_P attr_unused, ev_prepare *w, int revents
ev_handle(ps, ev);
free(ev);
};
// Flush because if we go into sleep when there is still
// requests in the outgoing buffer, they will not be sent
// for an indefinite amount of time.
// Use XFlush here too, we might still use some Xlib functions
// because OpenGL.
XFlush(ps->c.dpy);
xcb_flush(ps->c.c);
int err = xcb_connection_has_error(ps->c.c);
if (err) {
log_fatal("X11 server connection broke (error %d)", err);
@@ -2069,9 +1999,12 @@ static void draw_callback_impl(EV_P_ session_t *ps, int revents attr_unused) {
ps->render_queued = false;
// TODO(yshui) Investigate how big the X critical section needs to be. There are
// suggestions that rendering should be in the critical section as well.
if (ps->vblank_scheduler) {
// Even if we might not want to render during next vblank, we want to keep
// `backend_busy` up to date, so when the next render comes, we can
// immediately know if we can render.
vblank_scheduler_schedule(ps->vblank_scheduler, check_render_finish, ps);
}
}
static void draw_callback(EV_P_ ev_timer *w, int revents) {
@@ -2099,7 +2032,7 @@ static void x_event_callback(EV_P attr_unused, ev_io *w, int revents attr_unused
/**
* Turn on the program reset flag.
*
* This will result in the compositor resetting itself after next paint.
* This will result in the compostior resetting itself after next paint.
*/
static void reset_enable(EV_P_ ev_signal *w attr_unused, int revents attr_unused) {
log_info("picom is resetting...");
@@ -2176,11 +2109,11 @@ static bool load_shader_source_for_condition(const c2_lptr_t *cond, void *data)
/**
* Initialize a session.
*
* @param argc number of command line arguments
* @param argv command line arguments
* @param argc number of commandline arguments
* @param argv commandline arguments
* @param dpy the X Display
* @param config_file the path to the config file
* @param all_xerrors whether we should report all X errors
* @param all_xerros whether we should report all X errors
* @param fork whether we will fork after initialization
*/
static session_t *session_init(int argc, char **argv, Display *dpy,
@@ -2454,7 +2387,6 @@ static session_t *session_init(int argc, char **argv, Display *dpy,
c2_list_postprocess(ps, ps->o.opacity_rules) &&
c2_list_postprocess(ps, ps->o.rounded_corners_blacklist) &&
c2_list_postprocess(ps, ps->o.focus_blacklist) &&
c2_list_postprocess(ps, ps->o.corner_radius_rules) &&
c2_list_postprocess(ps, ps->o.animation_blacklist))) {
log_error("Post-processing of conditionals failed, some of your rules "
"might not work");
@@ -2733,7 +2665,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy,
ps->server_grabbed = true;
// We are going to pull latest information from X server now, events sent by X
// earlier is irrelevant at this point.
// earlier is irrelavant at this point.
// A better solution is probably grabbing the server from the very start. But I
// think there still could be race condition that mandates discarding the events.
x_discard_events(&ps->c);
@@ -2794,24 +2726,21 @@ void set_rr_scheduling(void) {
int ret;
struct sched_param param;
int old_policy;
ret = pthread_getschedparam(pthread_self(), &old_policy, &param);
ret = sched_getparam(0, &param);
if (ret != 0) {
log_debug("Failed to get old scheduling priority");
return;
}
param.sched_priority = priority;
ret = pthread_setschedparam(pthread_self(), SCHED_RR, &param);
ret = sched_setscheduler(0, SCHED_RR, &param);
if (ret != 0) {
log_info("Failed to set real-time scheduling priority to %d. Consider "
"giving picom the CAP_SYS_NICE capability or equivalent "
"support.",
"giving picom the CAP_SYS_NICE capability",
priority);
return;
}
log_info("Set real-time scheduling priority to %d", priority);
}
@@ -2828,6 +2757,11 @@ static void session_destroy(session_t *ps) {
unredirect(ps);
}
#ifdef CONFIG_OPENGL
free(ps->argb_fbconfig);
ps->argb_fbconfig = NULL;
#endif
file_watch_destroy(ps->loop, ps->file_watch_handle);
ps->file_watch_handle = NULL;
@@ -2870,7 +2804,6 @@ static void session_destroy(session_t *ps) {
c2_list_free(&ps->o.paint_blacklist, NULL);
c2_list_free(&ps->o.unredir_if_possible_blacklist, NULL);
c2_list_free(&ps->o.rounded_corners_blacklist, NULL);
c2_list_free(&ps->o.corner_radius_rules, NULL);
c2_list_free(&ps->o.window_shader_fg_rules, free);
// Free tracked atom list
@@ -2972,7 +2905,7 @@ static void session_destroy(session_t *ps) {
#endif
// Flush all events
xcb_aux_sync(ps->c.c);
x_sync(&ps->c);
ev_io_stop(ps->loop, &ps->xiow);
if (ps->o.legacy_backends) {
free_conv((conv *)ps->shadow_context);

View File

@@ -6,7 +6,6 @@
#include <xcb/composite.h>
#include <xcb/render.h>
#include <xcb/sync.h>
#include <xcb/xcb_aux.h>
#include <xcb/xcb_image.h>
#include <xcb/xcb_renderutil.h>
@@ -56,20 +55,19 @@ static inline bool paint_bind_tex(session_t *ps, paint_t *ppaint, int wid, int h
struct glx_fbconfig_info *fbcfg;
if (!visual) {
assert(depth == 32);
if (!ps->argb_fbconfig.cfg) {
glx_find_fbconfig(&ps->c,
(struct xvisual_info){.red_size = 8,
.green_size = 8,
.blue_size = 8,
.alpha_size = 8,
.visual_depth = 32},
&ps->argb_fbconfig);
if (!ps->argb_fbconfig) {
ps->argb_fbconfig = glx_find_fbconfig(
&ps->c, (struct xvisual_info){.red_size = 8,
.green_size = 8,
.blue_size = 8,
.alpha_size = 8,
.visual_depth = 32});
}
if (!ps->argb_fbconfig.cfg) {
if (!ps->argb_fbconfig) {
log_error("Failed to find appropriate FBConfig for 32 bit depth");
return false;
}
fbcfg = &ps->argb_fbconfig;
fbcfg = ps->argb_fbconfig;
} else {
auto m = x_get_visual_info(&ps->c, visual);
if (m.visual_depth < 0) {
@@ -81,17 +79,17 @@ static inline bool paint_bind_tex(session_t *ps, paint_t *ppaint, int wid, int h
return false;
}
if (!ppaint->fbcfg.cfg) {
glx_find_fbconfig(&ps->c, m, &ppaint->fbcfg);
if (!ppaint->fbcfg) {
ppaint->fbcfg = glx_find_fbconfig(&ps->c, m);
}
if (!ppaint->fbcfg.cfg) {
if (!ppaint->fbcfg) {
log_error("Failed to find appropriate FBConfig for X pixmap");
return false;
}
fbcfg = &ppaint->fbcfg;
fbcfg = ppaint->fbcfg;
}
if (force || !glx_tex_bound(ppaint->ptex, ppaint->pixmap)) {
if (force || !glx_tex_binded(ppaint->ptex, ppaint->pixmap)) {
return glx_bind_pixmap(ps, &ppaint->ptex, ppaint->pixmap, wid, hei,
repeat, fbcfg);
}
@@ -380,7 +378,7 @@ static inline bool paint_isvalid(session_t *ps, const paint_t *ppaint) {
}
#ifdef CONFIG_OPENGL
if (BKEND_GLX == ps->o.backend && !glx_tex_bound(ppaint->ptex, XCB_NONE)) {
if (BKEND_GLX == ps->o.backend && !glx_tex_binded(ppaint->ptex, XCB_NONE)) {
return false;
}
#endif
@@ -606,27 +604,20 @@ static bool get_root_tile(session_t *ps) {
bool fill = false;
xcb_pixmap_t pixmap = x_get_root_back_pixmap(&ps->c, ps->atoms);
xcb_get_geometry_reply_t *r;
if (pixmap) {
r = xcb_get_geometry_reply(ps->c.c, xcb_get_geometry(ps->c.c, pixmap), NULL);
// Make sure the pixmap we got is valid
if (pixmap && !x_validate_pixmap(&ps->c, pixmap)) {
pixmap = XCB_NONE;
}
// Create a pixmap if there isn't any
xcb_visualid_t visual;
if (!pixmap || !r) {
if (!pixmap) {
pixmap =
x_create_pixmap(&ps->c, (uint8_t)ps->c.screen_info->root_depth, 1, 1);
if (pixmap == XCB_NONE) {
log_error("Failed to create pixmaps for root tile.");
return false;
}
visual = ps->c.screen_info->root_visual;
fill = true;
} else {
visual = r->depth == ps->c.screen_info->root_depth
? ps->c.screen_info->root_visual
: x_get_visual_for_depth(ps->c.screen_info, r->depth);
free(r);
}
// Create Picture
@@ -634,7 +625,7 @@ static bool get_root_tile(session_t *ps) {
.repeat = true,
};
ps->root_tile_paint.pict = x_create_picture_with_visual_and_pixmap(
&ps->c, visual, pixmap, XCB_RENDER_CP_REPEAT, &pa);
&ps->c, ps->c.screen_info->root_visual, pixmap, XCB_RENDER_CP_REPEAT, &pa);
// Fill pixmap if needed
if (fill) {
@@ -655,7 +646,8 @@ static bool get_root_tile(session_t *ps) {
ps->root_tile_paint.pixmap = pixmap;
#ifdef CONFIG_OPENGL
if (BKEND_GLX == ps->o.backend) {
return paint_bind_tex(ps, &ps->root_tile_paint, 0, 0, true, 0, visual, false);
return paint_bind_tex(ps, &ps->root_tile_paint, 0, 0, true, 0,
ps->c.screen_info->root_visual, false);
}
#endif
@@ -1231,7 +1223,7 @@ void paint_all(session_t *ps, struct managed_win *t) {
if (ps->o.vsync) {
// Make sure all previous requests are processed to achieve best
// effect
xcb_aux_sync(ps->c.c);
x_sync(&ps->c);
#ifdef CONFIG_OPENGL
if (glx_has_context(ps)) {
if (ps->o.vsync_use_glfinish) {
@@ -1290,7 +1282,7 @@ void paint_all(session_t *ps, struct managed_win *t) {
break;
#ifdef CONFIG_OPENGL
case BKEND_XR_GLX_HYBRID:
xcb_aux_sync(ps->c.c);
x_sync(&ps->c);
if (ps->o.vsync_use_glfinish) {
glFinish();
} else {
@@ -1315,7 +1307,7 @@ void paint_all(session_t *ps, struct managed_win *t) {
default: assert(0);
}
xcb_aux_sync(ps->c.c);
x_sync(&ps->c);
#ifdef CONFIG_OPENGL
if (glx_has_context(ps)) {
@@ -1530,7 +1522,7 @@ void deinit_render(session_t *ps) {
free_root_tile(ps);
#ifdef CONFIG_OPENGL
ps->root_tile_paint.fbcfg = (struct glx_fbconfig_info){0};
free(ps->root_tile_paint.fbcfg);
if (bkend_use_glx(ps)) {
glx_destroy(ps);
}

View File

@@ -21,7 +21,7 @@ typedef struct paint {
xcb_render_picture_t pict;
glx_texture_t *ptex;
#ifdef CONFIG_OPENGL
struct glx_fbconfig_info fbcfg;
struct glx_fbconfig_info *fbcfg;
#endif
} paint_t;

View File

@@ -51,6 +51,10 @@ void render_statistics_add_render_time_sample(struct render_statistics *rs, int
}
/// How much time budget we should give to the backend for rendering, in microseconds.
///
/// A `divisor` is also returned, indicating the target framerate. The divisor is
/// the number of vblanks we should wait between each frame. A divisor of 1 means
/// full framerate, 2 means half framerate, etc.
unsigned int render_statistics_get_budget(struct render_statistics *rs) {
if (rs->render_times.nelem < rs->render_times.window_size) {
// No valid render time estimates yet. Assume maximum budget.

View File

@@ -141,10 +141,10 @@ void rolling_max_pop_front(struct rolling_max *rm, int front) {
}
void rolling_max_push_back(struct rolling_max *rm, int val) {
// Update the priority queue.
// Update the prority queue.
// Remove all elements smaller than the new element from the queue. Because
// the new element will become the maximum element before them, and since they
// come before the new element, they will have been popped before the new
// come b1efore the new element, they will have been popped before the new
// element, so they will never become the maximum element.
while (rm->np) {
int p_tail = IDX(rm->p_head + rm->np - 1);

View File

@@ -59,11 +59,11 @@ safe_isnan(double a) {
/// being always true or false.
#define ASSERT_IN_RANGE(var, lower, upper) \
do { \
auto __assert_in_range_tmp attr_unused = (var); \
auto __tmp attr_unused = (var); \
_Pragma("GCC diagnostic push"); \
_Pragma("GCC diagnostic ignored \"-Wtype-limits\""); \
assert(__assert_in_range_tmp >= lower); \
assert(__assert_in_range_tmp <= upper); \
assert(__tmp >= lower); \
assert(__tmp <= upper); \
_Pragma("GCC diagnostic pop"); \
} while (0)
@@ -113,26 +113,11 @@ safe_isnan(double a) {
#define to_u32_checked(val) \
({ \
auto __to_tmp = (val); \
int64_t __to_u32_max attr_unused = UINT32_MAX; /* silence clang \
tautological \
comparison warning */ \
ASSERT_IN_RANGE(__to_tmp, 0, __to_u32_max); \
int64_t max attr_unused = UINT32_MAX; /* silence clang tautological \
comparison warning*/ \
ASSERT_IN_RANGE(__to_tmp, 0, max); \
(uint32_t) __to_tmp; \
})
/**
* container_of - cast a member of a structure out to the containing structure
* @ptr: the pointer to the member.
* @type: the type of the container struct this is embedded in.
* @member: the name of the member within the struct.
*
*/
#define container_of(ptr, type, member) \
({ \
const __typeof__(((type *)0)->member) *__mptr = (ptr); \
(type *)((char *)__mptr - offsetof(type, member)); \
})
/**
* Normalize an int value to a specific range.
*
@@ -251,7 +236,7 @@ allocchk_(const char *func_name, const char *file, unsigned int line, void *ptr)
((type *)allocchk(calloc((size_t)tmp, sizeof(type)))); \
})
/// @brief Wrapper of realloc().
/// @brief Wrapper of ealloc().
#define crealloc(ptr, nmemb) \
({ \
auto tmp = (nmemb); \

View File

@@ -11,13 +11,14 @@
#ifdef CONFIG_OPENGL
// Enable sgi_video_sync_vblank_scheduler
#include <GL/glx.h>
#include <X11/X.h>
#include <X11/Xlib-xcb.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <epoxy/glx.h>
#include <pthread.h>
#include "backend/gl/glx.h"
#endif
#include "compiler.h"
@@ -26,7 +27,7 @@
#include "vblank.h"
#include "x.h"
struct vblank_closure {
struct vblank_callback {
vblank_callback_t fn;
void *user_data;
};
@@ -36,7 +37,7 @@ struct vblank_closure {
struct vblank_scheduler {
struct x_connection *c;
size_t callback_capacity, callback_count;
struct vblank_closure *callbacks;
struct vblank_callback *callbacks;
struct ev_loop *loop;
/// Request extra vblank events even when no callbacks are scheduled.
/// This is because when callbacks are scheduled too close to a vblank,
@@ -62,9 +63,9 @@ struct present_vblank_scheduler {
struct vblank_scheduler_ops {
size_t size;
bool (*init)(struct vblank_scheduler *self);
void (*init)(struct vblank_scheduler *self);
void (*deinit)(struct vblank_scheduler *self);
bool (*schedule)(struct vblank_scheduler *self);
void (*schedule)(struct vblank_scheduler *self);
bool (*handle_x_events)(struct vblank_scheduler *self);
};
@@ -77,14 +78,13 @@ struct sgi_video_sync_vblank_scheduler {
// Since glXWaitVideoSyncSGI blocks, we need to run it in a separate thread.
// ... and all the thread shenanigans that come with it.
_Atomic unsigned int current_msc;
_Atomic uint64_t current_ust;
_Atomic unsigned int last_msc;
_Atomic uint64_t last_ust;
ev_async notify;
pthread_t sync_thread;
bool running, error;
unsigned int last_msc;
/// Protects `running`, and `base.vblank_event_requested`
/// Protects `running`, `error` and `base.vblank_event_requested`
pthread_mutex_t vblank_requested_mtx;
pthread_cond_t vblank_requested_cnd;
};
@@ -110,6 +110,11 @@ static bool check_sgi_video_sync_extension(Display *dpy, int screen) {
return false;
}
glXWaitVideoSyncSGI = (PFNGLXWAITVIDEOSYNCSGIPROC)(void *)glXGetProcAddress(
(const GLubyte *)"glXWaitVideoSyncSGI");
if (!glXWaitVideoSyncSGI) {
return false;
}
return true;
}
@@ -202,8 +207,8 @@ static void *sgi_video_sync_thread(void *data) {
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
atomic_store(&self->current_msc, last_msc);
atomic_store(&self->current_ust,
atomic_store(&self->last_msc, last_msc);
atomic_store(&self->last_ust,
(uint64_t)(now.tv_sec * 1000000 + now.tv_nsec / 1000));
ev_async_send(self->base.loop, &self->notify);
pthread_mutex_lock(&self->vblank_requested_mtx);
@@ -234,30 +239,34 @@ cleanup:
return NULL;
}
static bool sgi_video_sync_scheduler_schedule(struct vblank_scheduler *base) {
static void sgi_video_sync_scheduler_schedule(struct vblank_scheduler *base) {
auto self = (struct sgi_video_sync_vblank_scheduler *)base;
if (self->error) {
return false;
}
log_verbose("Requesting vblank event for msc %d", self->current_msc + 1);
log_verbose("Requesting vblank event for msc %d", self->last_msc + 1);
pthread_mutex_lock(&self->vblank_requested_mtx);
assert(!base->vblank_event_requested);
base->vblank_event_requested = true;
pthread_cond_signal(&self->vblank_requested_cnd);
pthread_mutex_unlock(&self->vblank_requested_mtx);
return true;
}
static void
sgi_video_sync_scheduler_callback(EV_P attr_unused, ev_async *w, int attr_unused revents);
sgi_video_sync_scheduler_callback(EV_P attr_unused, ev_async *w, int attr_unused revents) {
auto sched = container_of(w, struct sgi_video_sync_vblank_scheduler, notify);
auto event = (struct vblank_event){
.msc = atomic_load(&sched->last_msc),
.ust = atomic_load(&sched->last_ust),
};
sched->base.vblank_event_requested = false;
log_verbose("Received vblank event for msc %lu", event.msc);
vblank_scheduler_invoke_callbacks(&sched->base, &event);
}
static bool sgi_video_sync_scheduler_init(struct vblank_scheduler *base) {
static void sgi_video_sync_scheduler_init(struct vblank_scheduler *base) {
auto self = (struct sgi_video_sync_vblank_scheduler *)base;
auto args = (struct sgi_video_sync_thread_args){
.self = self,
.start_status = -1,
};
bool succeeded = true;
pthread_mutex_init(&args.start_mtx, NULL);
pthread_cond_init(&args.start_cnd, NULL);
@@ -277,15 +286,11 @@ static bool sgi_video_sync_scheduler_init(struct vblank_scheduler *base) {
if (args.start_status != 0) {
log_fatal("Failed to start sgi_video_sync_thread, error code: %d",
args.start_status);
succeeded = false;
} else {
log_info("Started sgi_video_sync_thread");
abort();
}
self->error = !succeeded;
self->last_msc = 0;
pthread_mutex_destroy(&args.start_mtx);
pthread_cond_destroy(&args.start_cnd);
return succeeded;
log_info("Started sgi_video_sync_thread");
}
static void sgi_video_sync_scheduler_deinit(struct vblank_scheduler *base) {
@@ -301,45 +306,15 @@ static void sgi_video_sync_scheduler_deinit(struct vblank_scheduler *base) {
pthread_mutex_destroy(&self->vblank_requested_mtx);
pthread_cond_destroy(&self->vblank_requested_cnd);
}
static void
sgi_video_sync_scheduler_callback(EV_P attr_unused, ev_async *w, int attr_unused revents) {
auto sched = container_of(w, struct sgi_video_sync_vblank_scheduler, notify);
auto msc = atomic_load(&sched->current_msc);
if (sched->last_msc == msc) {
// NVIDIA spams us with duplicate vblank events after a suspend/resume
// cycle. Recreating the X connection and GLX context seems to fix this.
// Oh NVIDIA.
log_warn("Duplicate vblank event found with msc %d. Possible NVIDIA bug?", msc);
log_warn("Resetting the vblank scheduler");
sgi_video_sync_scheduler_deinit(&sched->base);
sched->base.vblank_event_requested = false;
if (!sgi_video_sync_scheduler_init(&sched->base)) {
log_error("Failed to reset the vblank scheduler");
} else {
sgi_video_sync_scheduler_schedule(&sched->base);
}
return;
}
auto event = (struct vblank_event){
.msc = msc,
.ust = atomic_load(&sched->current_ust),
};
sched->base.vblank_event_requested = false;
sched->last_msc = msc;
log_verbose("Received vblank event for msc %" PRIu64, event.msc);
vblank_scheduler_invoke_callbacks(&sched->base, &event);
}
#endif
static bool present_vblank_scheduler_schedule(struct vblank_scheduler *base) {
static void present_vblank_scheduler_schedule(struct vblank_scheduler *base) {
auto self = (struct present_vblank_scheduler *)base;
log_verbose("Requesting vblank event for window 0x%08x, msc %" PRIu64,
base->target_window, self->last_msc + 1);
assert(!base->vblank_event_requested);
x_request_vblank_event(base->c, base->target_window, self->last_msc + 1);
base->vblank_event_requested = true;
return true;
}
static void present_vblank_callback(EV_P attr_unused, ev_timer *w, int attr_unused revents) {
@@ -352,7 +327,7 @@ static void present_vblank_callback(EV_P attr_unused, ev_timer *w, int attr_unus
vblank_scheduler_invoke_callbacks(&sched->base, &event);
}
static bool present_vblank_scheduler_init(struct vblank_scheduler *base) {
static void present_vblank_scheduler_init(struct vblank_scheduler *base) {
auto self = (struct present_vblank_scheduler *)base;
base->type = VBLANK_SCHEDULER_PRESENT;
ev_timer_init(&self->callback_timer, present_vblank_callback, 0, 0);
@@ -364,7 +339,6 @@ static bool present_vblank_scheduler_init(struct vblank_scheduler *base) {
set_cant_fail_cookie(base->c, select_input);
self->event =
xcb_register_for_special_xge(base->c->c, &xcb_present_id, self->event_id, NULL);
return true;
}
static void present_vblank_scheduler_deinit(struct vblank_scheduler *base) {
@@ -411,7 +385,7 @@ static void handle_present_complete_notify(struct present_vblank_scheduler *self
auto now_us = (unsigned long)(now.tv_sec * 1000000L + now.tv_nsec / 1000);
double delay_sec = 0.0;
if (now_us < cne->ust) {
log_trace("The end of this vblank is %" PRIu64 " us into the "
log_trace("The end of this vblank is %lu us into the "
"future",
cne->ust - now_us);
delay_sec = (double)(cne->ust - now_us) / 1000000.0;
@@ -465,19 +439,17 @@ static const struct vblank_scheduler_ops vblank_scheduler_ops[LAST_VBLANK_SCHEDU
#endif
};
static bool vblank_scheduler_schedule_internal(struct vblank_scheduler *self) {
static void vblank_scheduler_schedule_internal(struct vblank_scheduler *self) {
assert(self->type < LAST_VBLANK_SCHEDULER);
auto fn = vblank_scheduler_ops[self->type].schedule;
assert(fn != NULL);
return fn(self);
fn(self);
}
bool vblank_scheduler_schedule(struct vblank_scheduler *self,
vblank_callback_t vblank_callback, void *user_data) {
if (self->callback_count == 0 && self->wind_down == 0) {
if (!vblank_scheduler_schedule_internal(self)) {
return false;
}
vblank_scheduler_schedule_internal(self);
}
if (self->callback_count == self->callback_capacity) {
size_t new_capacity =
@@ -490,7 +462,7 @@ bool vblank_scheduler_schedule(struct vblank_scheduler *self,
self->callbacks = new_buffer;
self->callback_capacity = new_capacity;
}
self->callbacks[self->callback_count++] = (struct vblank_closure){
self->callbacks[self->callback_count++] = (struct vblank_callback){
.fn = vblank_callback,
.user_data = user_data,
};
@@ -501,30 +473,19 @@ static void
vblank_scheduler_invoke_callbacks(struct vblank_scheduler *self, struct vblank_event *event) {
// callbacks might be added during callback invocation, so we need to
// copy the callback_count.
size_t count = self->callback_count, write_head = 0;
size_t count = self->callback_count;
if (count == 0) {
self->wind_down--;
} else {
self->wind_down = VBLANK_WIND_DOWN;
}
for (size_t i = 0; i < count; i++) {
auto action = self->callbacks[i].fn(event, self->callbacks[i].user_data);
switch (action) {
case VBLANK_CALLBACK_AGAIN:
if (i != write_head) {
self->callbacks[write_head] = self->callbacks[i];
}
write_head++;
case VBLANK_CALLBACK_DONE:
default: // nothing to do
break;
}
self->callbacks[i].fn(event, self->callbacks[i].user_data);
}
memset(self->callbacks + write_head, 0,
(count - write_head) * sizeof(*self->callbacks));
assert(count == self->callback_count && "callbacks should not be added when "
"callbacks are being invoked.");
self->callback_count = write_head;
// remove the callbacks that we have called, keep the newly added ones.
memmove(self->callbacks, self->callbacks + count,
(self->callback_count - count) * sizeof(*self->callbacks));
self->callback_count -= count;
if (self->callback_count || self->wind_down) {
vblank_scheduler_schedule_internal(self);
}

View File

@@ -19,15 +19,7 @@ struct vblank_event {
uint64_t ust;
};
enum vblank_callback_action {
/// The callback should be called again in the next vblank.
VBLANK_CALLBACK_AGAIN,
/// The callback is done and should not be called again.
VBLANK_CALLBACK_DONE,
};
typedef enum vblank_callback_action (*vblank_callback_t)(struct vblank_event *event,
void *user_data);
typedef void (*vblank_callback_t)(struct vblank_event *event, void *user_data);
/// Schedule a vblank event.
///

191
src/win.c
View File

@@ -73,11 +73,6 @@ static void
win_update_frame_extents(session_t *ps, struct managed_win *w, xcb_window_t client);
static void win_update_prop_shadow_raw(session_t *ps, struct managed_win *w);
static void win_update_prop_shadow(session_t *ps, struct managed_win *w);
/**
* Update window EWMH fullscreen state.
*/
bool win_update_prop_fullscreen(struct x_connection *c, const struct atom *atoms,
struct managed_win *w);
/**
* Update leader of a window.
*/
@@ -139,10 +134,10 @@ static inline bool attr_pure win_is_real_visible(const struct managed_win *w) {
* Update focused state of a window.
*/
static void win_update_focused(session_t *ps, struct managed_win *w) {
if (w->focused_force != UNSET) {
if (UNSET != w->focused_force) {
w->focused = w->focused_force;
} else {
w->focused = win_is_focused_raw(w);
w->focused = win_is_focused_raw(ps, w);
// Use wintype_focus, and treat WM windows and override-redirected
// windows specially
@@ -210,7 +205,7 @@ static inline bool group_is_focused(session_t *ps, xcb_window_t leader) {
continue;
}
auto mw = (struct managed_win *)w;
if (win_get_leader(ps, mw) == leader && win_is_focused_raw(mw)) {
if (win_get_leader(ps, mw) == leader && win_is_focused_raw(ps, mw)) {
return true;
}
}
@@ -470,12 +465,6 @@ static void win_update_properties(session_t *ps, struct managed_win *w) {
win_update_prop_shadow(ps, w);
}
if (win_fetch_and_unset_property_stale(w, ps->atoms->a_NET_WM_STATE)) {
if (win_update_prop_fullscreen(&ps->c, ps->atoms, w)) {
win_set_flags(w, WIN_FLAGS_FACTOR_CHANGED);
}
}
if (win_fetch_and_unset_property_stale(w, ps->atoms->aWM_CLIENT_LEADER) ||
win_fetch_and_unset_property_stale(w, ps->atoms->aWM_TRANSIENT_FOR)) {
win_update_leader(ps, w);
@@ -495,19 +484,18 @@ static void init_animation(session_t *ps, struct managed_win *w) {
}
static double *anim_x, *anim_y, *anim_w, *anim_h;
enum open_window_animation animation;
animation = ps->o.animation_for_open_window;
if (ps->o.wintype_option[w->window_type].animation != OPEN_WINDOW_ANIMATION_INVALID
&& !w->dwm_mask) {
animation = ps->o.wintype_option[w->window_type].animation;
}
else
animation = ps->o.animation_for_open_window;
if (w->window_type != WINTYPE_TOOLTIP &&
wid_has_prop(ps, w->client_win, ps->atoms->aWM_TRANSIENT_FOR)) {
animation = ps->o.animation_for_transient_window;
}
if (ps->o.wintype_option[w->window_type].animation != OPEN_WINDOW_ANIMATION_INVALID
&& !w->dwm_mask) {
animation = ps->o.wintype_option[w->window_type].animation;
}
anim_x = &w->animation_center_x, anim_y = &w->animation_center_y;
anim_w = &w->animation_w, anim_h = &w->animation_h;
@@ -656,9 +644,8 @@ void win_process_update_flags(session_t *ps, struct managed_win *w) {
// Whether the window was visible before we process the mapped flag. i.e.
// is the window just mapped.
bool was_visible = win_is_real_visible(w);
log_trace("Processing flags for window %#010x (%s), was visible: %d, flags: "
"%#" PRIx64,
w->base.id, w->name, was_visible, w->flags);
log_trace("Processing flags for window %#010x (%s), was visible: %d", w->base.id,
w->name, was_visible);
if (win_check_flags_all(w, WIN_FLAGS_MAPPED)) {
map_win_start(ps, w);
@@ -766,9 +753,6 @@ void win_process_update_flags(session_t *ps, struct managed_win *w) {
w->g = w->pending_g;
}
// Whether a window is fullscreen changes based on its geometry
win_update_is_fullscreen(ps, w);
if (win_check_flags_all(w, WIN_FLAGS_SIZE_STALE)) {
win_on_win_size_change(ps, w);
win_update_bounding_shape(ps, w);
@@ -904,14 +888,12 @@ int win_update_name(session_t *ps, struct managed_win *w) {
return 0;
}
if (!(wid_get_text_prop(&ps->c, ps->atoms, w->client_win,
ps->atoms->a_NET_WM_NAME, &strlst, &nstr))) {
if (!(wid_get_text_prop(ps, w->client_win, ps->atoms->a_NET_WM_NAME, &strlst, &nstr))) {
log_debug("(%#010x): _NET_WM_NAME unset, falling back to "
"WM_NAME.",
w->client_win);
if (!wid_get_text_prop(&ps->c, ps->atoms, w->client_win,
ps->atoms->aWM_NAME, &strlst, &nstr)) {
if (!wid_get_text_prop(ps, w->client_win, ps->atoms->aWM_NAME, &strlst, &nstr)) {
log_debug("Unsetting window name for %#010x", w->client_win);
free(w->name);
w->name = NULL;
@@ -938,8 +920,7 @@ static int win_update_role(session_t *ps, struct managed_win *w) {
char **strlst = NULL;
int nstr = 0;
if (!wid_get_text_prop(&ps->c, ps->atoms, w->client_win,
ps->atoms->aWM_WINDOW_ROLE, &strlst, &nstr)) {
if (!wid_get_text_prop(ps, w->client_win, ps->atoms->aWM_WINDOW_ROLE, &strlst, &nstr)) {
return -1;
}
@@ -1096,7 +1077,7 @@ double win_calc_opacity_target(session_t *ps, const struct managed_win *w) {
} else {
// Respect active_opacity only when the window is physically
// focused
if (win_is_focused_raw(w)) {
if (win_is_focused_raw(ps, w)) {
opacity = ps->o.active_opacity;
} else if (!w->focused) {
// Respect inactive_opacity in some cases
@@ -1240,7 +1221,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 shadow in based on whether w->shadow
// re-create or release the shaodw in based on whether w->shadow
// is set.
win_set_flags(w, WIN_FLAGS_SHADOW_STALE);
@@ -1297,30 +1278,6 @@ void win_update_prop_shadow(session_t *ps, struct managed_win *w) {
}
}
/**
* Update window EWMH fullscreen state.
*/
bool win_update_prop_fullscreen(struct x_connection *c, const struct atom *atoms,
struct managed_win *w) {
auto prop = x_get_prop(c, w->client_win, atoms->a_NET_WM_STATE, 12, XCB_ATOM_ATOM, 0);
if (!prop.nitems) {
return false;
}
bool is_fullscreen = false;
for (uint32_t i = 0; i < prop.nitems; i++) {
if (prop.atom[i] == atoms->a_NET_WM_STATE_FULLSCREEN) {
is_fullscreen = true;
break;
}
}
free_winprop(&prop);
bool changed = w->is_ewmh_fullscreen != is_fullscreen;
w->is_ewmh_fullscreen = is_fullscreen;
return changed;
}
static void win_determine_clip_shadow_above(session_t *ps, struct managed_win *w) {
bool should_crop = (ps->o.wintype_option[w->window_type].clip_shadow_above ||
c2_match(ps, w, ps->o.shadow_clip_list, NULL));
@@ -1449,19 +1406,19 @@ static void win_determine_blur_background(session_t *ps, struct managed_win *w)
* Determine if a window should have rounded corners.
*/
static void win_determine_rounded_corners(session_t *ps, struct managed_win *w) {
if (ps->o.corner_radius == 0) {
w->corner_radius = 0;
return;
}
void *radius_override = NULL;
if (c2_match(ps, w, ps->o.corner_radius_rules, &radius_override)) {
log_debug("Matched corner rule! %d", w->corner_radius);
}
if (ps->o.corner_radius == 0 && !radius_override) {
w->corner_radius = 0;
return;
}
// Don't round full screen windows & excluded windows,
// unless we find a corner override in corner_radius_rules
if (!radius_override && ((w && w->is_fullscreen) ||
if (!radius_override && ((w && win_is_fullscreen(ps, w)) ||
c2_match(ps, w, ps->o.rounded_corners_blacklist, NULL))) {
w->corner_radius = 0;
log_debug("Not rounding corners for window %#010x", w->base.id);
@@ -1528,10 +1485,9 @@ void win_update_opacity_rule(session_t *ps, struct managed_win *w) {
*/
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 and is_fullscreen needs to be updated first, as other rules might depend
// on the focused state of the window
// Focus needs to be updated first, as other rules might depend on the
// focused state of the window
win_update_focused(ps, w);
win_update_is_fullscreen(ps, w);
win_determine_shadow(ps, w);
win_determine_clip_shadow_above(ps, w);
@@ -1564,10 +1520,6 @@ void win_on_factor_change(session_t *ps, struct managed_win *w) {
* Update cache data in struct _win that depends on window size.
*/
void win_on_win_size_change(session_t *ps, struct managed_win *w) {
log_trace("Window %#010x (%s) size changed, was %dx%d, now %dx%d", w->base.id,
w->name, w->widthb, w->heightb, w->g.width + w->g.border_width * 2,
w->g.height + w->g.border_width * 2);
w->widthb = w->g.width + w->g.border_width * 2;
w->heightb = w->g.height + w->g.border_width * 2;
w->shadow_dx = ps->o.shadow_offset_x;
@@ -1581,12 +1533,8 @@ void win_on_win_size_change(session_t *ps, struct managed_win *w) {
w->state != WSTATE_UNMAPPING);
// Invalidate the shadow we built
// Do not set flags if window is unmapping and animation is running
if (w->state != WSTATE_UNMAPPED && w->state != WSTATE_DESTROYING &&
w->state != WSTATE_UNMAPPING) {
win_set_flags(w, WIN_FLAGS_IMAGES_STALE);
win_release_mask(ps->backend_data, w);
}
win_set_flags(w, WIN_FLAGS_IMAGES_STALE);
win_release_mask(ps->backend_data, w);
ps->pending_updates = true;
free_paint(ps, &w->shadow_paint);
}
@@ -2043,7 +1991,6 @@ struct win *fill_win(session_t *ps, struct win *w) {
ps->atoms->a_NET_WM_NAME, ps->atoms->aWM_CLASS,
ps->atoms->aWM_WINDOW_ROLE, ps->atoms->a_COMPTON_SHADOW,
ps->atoms->aWM_CLIENT_LEADER, ps->atoms->aWM_TRANSIENT_FOR,
ps->atoms->a_NET_WM_STATE,
};
win_set_properties_stale(new, init_stale_props, ARR_SIZE(init_stale_props));
@@ -2073,7 +2020,7 @@ static inline void win_set_leader(session_t *ps, struct managed_win *w, xcb_wind
// Update the old and new window group and active_leader if the
// window could affect their state.
xcb_window_t cache_leader = win_get_leader(ps, w);
if (win_is_focused_raw(w) && cache_leader_old != cache_leader) {
if (win_is_focused_raw(ps, w) && cache_leader_old != cache_leader) {
ps->active_leader = cache_leader;
group_on_factor_change(ps, cache_leader_old);
@@ -2157,8 +2104,7 @@ bool win_update_class(session_t *ps, struct managed_win *w) {
w->class_general = NULL;
// Retrieve the property string list
if (!wid_get_text_prop(&ps->c, ps->atoms, w->client_win, ps->atoms->aWM_CLASS,
&strlst, &nstr)) {
if (!wid_get_text_prop(ps, w->client_win, ps->atoms->aWM_CLASS, &strlst, &nstr)) {
return false;
}
@@ -2187,7 +2133,7 @@ static void win_on_focus_change(session_t *ps, struct managed_win *w) {
xcb_window_t leader = win_get_leader(ps, w);
// If the window gets focused, replace the old active_leader
if (win_is_focused_raw(w) && leader != ps->active_leader) {
if (win_is_focused_raw(ps, w) && leader != ps->active_leader) {
xcb_window_t active_leader_old = ps->active_leader;
ps->active_leader = leader;
@@ -2196,7 +2142,7 @@ static void win_on_focus_change(session_t *ps, struct managed_win *w) {
group_on_factor_change(ps, leader);
}
// If the group get unfocused, remove it from active_leader
else if (!win_is_focused_raw(w) && leader &&
else if (!win_is_focused_raw(ps, w) && leader &&
leader == ps->active_leader && !group_is_focused(ps, leader)) {
ps->active_leader = XCB_NONE;
group_on_factor_change(ps, leader);
@@ -2209,7 +2155,7 @@ static void win_on_focus_change(session_t *ps, struct managed_win *w) {
#ifdef CONFIG_DBUS
// Send D-Bus signal
if (ps->o.dbus) {
if (win_is_focused_raw(w)) {
if (win_is_focused_raw(ps, w)) {
cdbus_ev_win_focusin(ps, &w->base);
} else {
cdbus_ev_win_focusout(ps, &w->base);
@@ -2227,18 +2173,15 @@ void win_set_focused(session_t *ps, struct managed_win *w) {
return;
}
if (w->is_ewmh_focused) {
assert(ps->active_win == w);
if (win_is_focused_raw(ps, w)) {
return;
}
auto old_active_win = ps->active_win;
ps->active_win = w;
w->is_ewmh_focused = true;
assert(win_is_focused_raw(ps, w));
if (old_active_win) {
assert(old_active_win->is_ewmh_focused);
old_active_win->is_ewmh_focused = false;
win_on_focus_change(ps, old_active_win);
}
win_on_focus_change(ps, w);
@@ -2311,7 +2254,7 @@ void win_update_bounding_shape(session_t *ps, struct managed_win *w) {
// Add border width because we are using a different origin.
// X thinks the top left of the inner window is the origin
// (for the bounding shape, although xcb_get_geometry thinks
// (for the bounding shape, althought xcb_get_geometry thinks
// the outer top left (outer means outside of the window
// border) is the origin),
// We think the top left of the border is the origin
@@ -2331,12 +2274,14 @@ void win_update_bounding_shape(session_t *ps, struct managed_win *w) {
// Window shape changed, we should free old wpaint and shadow pict
// log_trace("free out dated pict");
win_set_flags(w, WIN_FLAGS_IMAGES_STALE | WIN_FLAGS_FACTOR_CHANGED);
win_set_flags(w, WIN_FLAGS_IMAGES_STALE);
win_release_mask(ps->backend_data, w);
ps->pending_updates = true;
free_paint(ps, &w->paint);
free_paint(ps, &w->shadow_paint);
win_on_factor_change(ps, w);
}
/**
@@ -2652,7 +2597,7 @@ bool destroy_win_start(session_t *ps, struct win *w) {
HASH_DEL(ps->windows, w);
if (!w->managed || mw->state == WSTATE_UNMAPPED) {
// Window is already unmapped, or is an unmanaged window, just
// Window is already unmapped, or is an unmanged window, just
// destroy it
destroy_win_finish(ps, w);
return true;
@@ -2677,13 +2622,6 @@ bool destroy_win_start(session_t *ps, struct win *w) {
add_damage_from_win(ps, mw);
}
if (win_check_flags_all(mw, WIN_FLAGS_CLIENT_STALE)) {
mw->client_win = mw->base.id;
mw->wmwin = !mw->a.override_redirect;
log_debug("(%#010x): client self (%s)", mw->base.id,
(mw->wmwin ? "wmwin" : "override-redirected"));
}
// Clear some flags about stale window information. Because now
// the window is destroyed, we can't update them anyway.
win_clear_flags(mw, WIN_FLAGS_SIZE_STALE | WIN_FLAGS_POSITION_STALE |
@@ -3073,6 +3011,41 @@ struct managed_win *find_managed_window_or_parent(session_t *ps, xcb_window_t wi
return (struct managed_win *)w;
}
/**
* Check if a rectangle includes the whole screen.
*/
static inline bool rect_is_fullscreen(const session_t *ps, int x, int y, int wid, int hei) {
return (x <= 0 && y <= 0 && (x + wid) >= ps->root_width && (y + hei) >= ps->root_height);
}
/**
* Check if a window is fulscreen using EWMH
*
* TODO(yshui) cache this property
*/
static inline bool
win_is_fullscreen_xcb(xcb_connection_t *c, const struct atom *a, const xcb_window_t w) {
xcb_get_property_cookie_t prop =
xcb_get_property(c, 0, w, a->a_NET_WM_STATE, XCB_ATOM_ATOM, 0, 12);
xcb_get_property_reply_t *reply = xcb_get_property_reply(c, prop, NULL);
if (!reply) {
return false;
}
if (reply->length) {
xcb_atom_t *val = xcb_get_property_value(reply);
for (uint32_t i = 0; i < reply->length; i++) {
if (val[i] != a->a_NET_WM_STATE_FULLSCREEN) {
continue;
}
free(reply);
return true;
}
}
free(reply);
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 %" PRIu64 " to window %#010x (%s)", flags, w->base.id, w->name);
@@ -3157,15 +3130,13 @@ bool win_check_flags_all(struct managed_win *w, uint64_t flags) {
*
* It's not using w->border_size for performance measures.
*/
void win_update_is_fullscreen(const session_t *ps, struct managed_win *w) {
if (!ps->o.no_ewmh_fullscreen && w->is_ewmh_fullscreen) {
w->is_fullscreen = true;
return;
bool win_is_fullscreen(const session_t *ps, const struct managed_win *w) {
if (!ps->o.no_ewmh_fullscreen &&
win_is_fullscreen_xcb(ps->c.c, ps->atoms, w->client_win)) {
return true;
}
w->is_fullscreen = w->g.x <= 0 && w->g.y <= 0 &&
(w->g.x + w->widthb) >= ps->root_width &&
(w->g.y + w->heightb) >= ps->root_height &&
(!w->bounding_shaped || w->rounded_corners);
return rect_is_fullscreen(ps, w->g.x, w->g.y, w->widthb, w->heightb) &&
(!w->bounding_shaped || w->rounded_corners);
}
/**
@@ -3191,8 +3162,8 @@ bool win_is_bypassing_compositor(const session_t *ps, const struct managed_win *
* Check if a window is focused, without using any focus rules or forced focus
* settings
*/
bool win_is_focused_raw(const struct managed_win *w) {
return w->a.map_state == XCB_MAP_STATE_VIEWABLE && w->is_ewmh_focused;
bool win_is_focused_raw(const session_t *ps, const struct managed_win *w) {
return w->a.map_state == XCB_MAP_STATE_VIEWABLE && ps->active_win == w;
}
// Find the managed window immediately below `i` in the window stack

View File

@@ -113,10 +113,10 @@ struct managed_win {
struct win base;
/// backend data attached to this window. Only available when
/// `state` is not UNMAPPED
image_handle win_image;
image_handle old_win_image; // Old window image for interpolating window contents during animations
image_handle shadow_image;
image_handle mask_image;
void *win_image;
void *old_win_image; // Old window image for interpolating window contents during animations
void *shadow_image;
void *mask_image;
/// Pointer to the next higher window to paint.
struct managed_win *prev_trans;
/// Number of windows above this window
@@ -233,14 +233,6 @@ struct managed_win {
char *class_general;
/// <code>WM_WINDOW_ROLE</code> value of the window.
char *role;
/// Whether the window sets the EWMH fullscreen property.
bool is_ewmh_fullscreen;
/// Whether the window should be considered fullscreen. Based on
/// `is_ewmh_fullscreen`, or the windows spatial relation with the
/// root window. Which one is used is determined by user configuration.
bool is_fullscreen;
/// Whether the window is the EWMH active window.
bool is_ewmh_focused;
// Opacity-related members
/// Current window opacity.
@@ -286,13 +278,13 @@ struct managed_win {
switch_t shadow_force;
/// Opacity of the shadow. Affected by window opacity and frame opacity.
double shadow_opacity;
/// X offset of shadow. Affected by command line argument.
/// X offset of shadow. Affected by commandline argument.
int shadow_dx;
/// Y offset of shadow. Affected by command line argument.
/// Y offset of shadow. Affected by commandline argument.
int shadow_dy;
/// Width of shadow. Affected by window size and command line argument.
/// Width of shadow. Affected by window size and commandline argument.
int shadow_width;
/// Height of shadow. Affected by window size and command line argument.
/// Height of shadow. Affected by window size and commandline argument.
int shadow_height;
/// Picture to render shadow. Affected by window size.
paint_t shadow_paint;
@@ -391,8 +383,6 @@ void win_update_monitor(struct x_monitors *monitors, struct managed_win *mw);
*/
// XXX was win_border_size
void win_update_bounding_shape(session_t *ps, struct managed_win *w);
/// Recheck if a window is fullscreen
void win_update_is_fullscreen(const session_t *ps, struct managed_win *w);
/**
* Check if a window has BYPASS_COMPOSITOR property set
*/
@@ -468,10 +458,17 @@ struct managed_win *find_toplevel(session_t *ps, xcb_window_t id);
*/
struct managed_win *find_managed_window_or_parent(session_t *ps, xcb_window_t wid);
/**
* Check if a window is a fullscreen window.
*
* It's not using w->border_size for performance measures.
*/
bool attr_pure win_is_fullscreen(const session_t *ps, const struct managed_win *w);
/**
* Check if a window is focused, without using any focus rules or forced focus settings
*/
bool attr_pure win_is_focused_raw(const struct managed_win *w);
bool attr_pure win_is_focused_raw(const session_t *ps, const struct managed_win *w);
/// check if window has ARGB visual
bool attr_pure win_has_alpha(const struct managed_win *w);

View File

@@ -68,7 +68,7 @@ typedef enum {
} winstate_t;
enum win_flags {
// Note: *_NONE flags are mostly redundant and meant for detecting logical errors
// Note: *_NONE flags are mostly redudant and meant for detecting logical errors
// in the code
/// pixmap is out of date, will be update in win_process_flags

114
src/x.c
View File

@@ -16,7 +16,6 @@
#include <xcb/render.h>
#include <xcb/sync.h>
#include <xcb/xcb.h>
#include <xcb/xcb_aux.h>
#include <xcb/xcb_renderutil.h>
#include <xcb/xfixes.h>
@@ -95,7 +94,7 @@ void x_connection_init(struct x_connection *c, Display *dpy) {
c->previous_xerror_handler = XSetErrorHandler(xerror);
c->screen = DefaultScreen(dpy);
c->screen_info = xcb_aux_get_screen(c->c, c->screen);
c->screen_info = x_screen_of_display(c, c->screen);
}
/**
@@ -182,9 +181,10 @@ xcb_window_t wid_get_prop_window(struct x_connection *c, xcb_window_t wid, xcb_a
/**
* Get the value of a text property of a window.
*/
bool wid_get_text_prop(struct x_connection *c, struct atom *atoms, xcb_window_t wid,
xcb_atom_t prop, char ***pstrlst, int *pnstr) {
auto prop_info = x_get_prop_info(c, wid, prop);
bool wid_get_text_prop(session_t *ps, xcb_window_t wid, xcb_atom_t prop, char ***pstrlst,
int *pnstr) {
assert(ps->server_grabbed);
auto prop_info = x_get_prop_info(&ps->c, wid, prop);
auto type = prop_info.type;
auto format = prop_info.format;
auto length = prop_info.length;
@@ -193,7 +193,8 @@ bool wid_get_text_prop(struct x_connection *c, struct atom *atoms, xcb_window_t
return false;
}
if (type != XCB_ATOM_STRING && type != atoms->aUTF8_STRING && type != atoms->aC_STRING) {
if (type != XCB_ATOM_STRING && type != ps->atoms->aUTF8_STRING &&
type != ps->atoms->aC_STRING) {
log_warn("Text property %d of window %#010x has unsupported type: %d",
prop, wid, type);
return false;
@@ -208,7 +209,7 @@ bool wid_get_text_prop(struct x_connection *c, struct atom *atoms, xcb_window_t
xcb_generic_error_t *e = NULL;
auto word_count = (length + 4 - 1) / 4;
auto r = xcb_get_property_reply(
c->c, xcb_get_property(c->c, 0, wid, prop, type, 0, word_count), &e);
ps->c.c, xcb_get_property(ps->c.c, 0, wid, prop, type, 0, word_count), &e);
if (!r) {
log_debug_x_error(e, "Failed to get window property for %#010x", wid);
free(e);
@@ -320,17 +321,6 @@ xcb_visualid_t x_get_visual_for_standard(struct x_connection *c, xcb_pict_standa
return x_get_visual_for_pictfmt(g_pictfmts, pictfmt->id);
}
xcb_visualid_t x_get_visual_for_depth(xcb_screen_t *screen, uint8_t depth) {
xcb_depth_iterator_t depth_it = xcb_screen_allowed_depths_iterator(screen);
for (; depth_it.rem; xcb_depth_next(&depth_it)) {
if (depth_it.data->depth == depth) {
return xcb_depth_visuals_iterator(depth_it.data).data->visual_id;
}
}
return XCB_NONE;
}
xcb_render_pictformat_t
x_get_pictfmt_for_standard(struct x_connection *c, xcb_pict_standard_t std) {
x_get_server_pictfmts(c);
@@ -340,6 +330,24 @@ x_get_pictfmt_for_standard(struct x_connection *c, xcb_pict_standard_t std) {
return pictfmt->id;
}
int x_get_visual_depth(struct x_connection *c, xcb_visualid_t visual) {
auto setup = xcb_get_setup(c->c);
for (auto screen = xcb_setup_roots_iterator(setup); screen.rem;
xcb_screen_next(&screen)) {
for (auto depth = xcb_screen_allowed_depths_iterator(screen.data);
depth.rem; xcb_depth_next(&depth)) {
const int len = xcb_depth_visuals_length(depth.data);
const xcb_visualtype_t *visuals = xcb_depth_visuals(depth.data);
for (int i = 0; i < len; i++) {
if (visual == visuals[i].visual_id) {
return depth.data->depth;
}
}
}
}
return -1;
}
xcb_render_picture_t
x_create_picture_with_pictfmt_and_pixmap(struct x_connection *c,
const xcb_render_pictforminfo_t *pictfmt,
@@ -451,34 +459,6 @@ bool x_fetch_region(struct x_connection *c, xcb_xfixes_region_t r, pixman_region
return ret;
}
bool x_set_region(struct x_connection *c, xcb_xfixes_region_t dst, const region_t *src) {
if (!src || dst == XCB_NONE) {
return false;
}
int32_t nrects = 0;
const rect_t *rects = pixman_region32_rectangles((region_t *)src, &nrects);
if (!rects || nrects < 1) {
return false;
}
xcb_rectangle_t *xrects = ccalloc(nrects, xcb_rectangle_t);
for (int32_t i = 0; i < nrects; i++) {
xrects[i] =
(xcb_rectangle_t){.x = to_i16_checked(rects[i].x1),
.y = to_i16_checked(rects[i].y1),
.width = to_u16_checked(rects[i].x2 - rects[i].x1),
.height = to_u16_checked(rects[i].y2 - rects[i].y1)};
}
bool success =
XCB_AWAIT_VOID(xcb_xfixes_set_region, c->c, dst, to_u32_checked(nrects), xrects);
free(xrects);
return success;
}
uint32_t x_create_region(struct x_connection *c, const region_t *reg) {
if (!reg) {
return XCB_NONE;
@@ -582,7 +562,9 @@ _x_strerror(unsigned long serial, uint8_t major, uint16_t minor, uint8_t error_c
const char *name = "Unknown";
#define CASESTRRET(s) \
case s: name = #s; break
case s: \
name = #s; \
break
#define CASESTRRET2(s) \
case XCB_##s: name = #s; break
@@ -707,6 +689,27 @@ xcb_pixmap_t x_create_pixmap(struct x_connection *c, uint8_t depth, int width, i
return XCB_NONE;
}
/**
* Validate a pixmap.
*
* Detect whether the pixmap is valid with XGetGeometry. Well, maybe there
* are better ways.
*/
bool x_validate_pixmap(struct x_connection *c, xcb_pixmap_t pixmap) {
if (pixmap == XCB_NONE) {
return false;
}
auto r = xcb_get_geometry_reply(c->c, xcb_get_geometry(c->c, pixmap), NULL);
if (!r) {
return false;
}
bool ret = r->width && r->height;
free(r);
return ret;
}
/// We don't use the _XSETROOT_ID root window property as a source of the background
/// pixmap because it most likely points to a dummy pixmap used to keep the colormap
/// associated with the background pixmap alive but we listen for it's changes and update
@@ -853,8 +856,8 @@ void x_create_convolution_kernel(const conv *kernel, double center,
/// Returns {-1, -1, -1, -1, -1, 0} on failure
struct xvisual_info x_get_visual_info(struct x_connection *c, xcb_visualid_t visual) {
auto pictfmt = x_get_pictform_for_visual(c, visual);
auto depth = xcb_aux_get_depth_of_visual(c->screen_info, visual);
if (!pictfmt || depth == 0) {
auto depth = x_get_visual_depth(c, visual);
if (!pictfmt || depth == -1) {
log_error("Invalid visual %#03x", visual);
return (struct xvisual_info){-1, -1, -1, -1, -1, 0};
}
@@ -879,6 +882,19 @@ struct xvisual_info x_get_visual_info(struct x_connection *c, xcb_visualid_t vis
};
}
xcb_screen_t *x_screen_of_display(struct x_connection *c, int screen) {
xcb_screen_iterator_t iter;
iter = xcb_setup_roots_iterator(xcb_get_setup(c->c));
for (; iter.rem; --screen, xcb_screen_next(&iter)) {
if (screen == 0) {
return iter.data;
}
}
return NULL;
}
void x_update_monitors(struct x_connection *c, struct x_monitors *m) {
x_free_monitor_info(m);

26
src/x.h
View File

@@ -27,7 +27,6 @@ typedef struct winprop {
int16_t *p16;
int32_t *p32;
uint32_t *c32; // 32bit cardinal
xcb_atom_t *atom;
};
unsigned long nitems;
xcb_atom_t type;
@@ -208,6 +207,17 @@ void x_discard_pending(struct x_connection *c, uint32_t sequence);
/// This function logs X errors, or aborts the program based on severity of the error.
void x_handle_error(struct x_connection *c, xcb_generic_error_t *ev);
/**
* Send a request to X server and get the reply to make sure all previous
* requests are processed, and their replies received
*
* xcb_get_input_focus is used here because it is the same request used by
* libX11
*/
static inline void x_sync(struct x_connection *c) {
free(xcb_get_input_focus_reply(c->c, xcb_get_input_focus(c->c), NULL));
}
/**
* Get a specific attribute of a window.
*
@@ -261,11 +271,12 @@ xcb_window_t wid_get_prop_window(struct x_connection *c, xcb_window_t wid, xcb_a
* array
* @param[out] pnstr Number of strings in the array
*/
bool wid_get_text_prop(struct x_connection *c, struct atom *atoms, xcb_window_t wid,
xcb_atom_t prop, char ***pstrlst, int *pnstr);
bool wid_get_text_prop(session_t *ps, xcb_window_t wid, xcb_atom_t prop, char ***pstrlst,
int *pnstr);
const xcb_render_pictforminfo_t *
x_get_pictform_for_visual(struct x_connection *, xcb_visualid_t);
int x_get_visual_depth(struct x_connection *, xcb_visualid_t);
xcb_render_picture_t
x_create_picture_with_pictfmt_and_pixmap(struct x_connection *,
@@ -310,9 +321,6 @@ x_create_picture_with_visual(struct x_connection *, int w, int h, xcb_visualid_t
/// Fetch a X region and store it in a pixman region
bool x_fetch_region(struct x_connection *, xcb_xfixes_region_t r, region_t *res);
/// Set an X region to a pixman region
bool x_set_region(struct x_connection *c, xcb_xfixes_region_t dst, const region_t *src);
/// Create a X region from a pixman region
uint32_t x_create_region(struct x_connection *c, const region_t *reg);
@@ -348,6 +356,8 @@ const char *x_strerror(xcb_generic_error_t *e);
xcb_pixmap_t x_create_pixmap(struct x_connection *, uint8_t depth, int width, int height);
bool x_validate_pixmap(struct x_connection *, xcb_pixmap_t pxmap);
/**
* Free a <code>winprop_t</code>.
*
@@ -398,11 +408,11 @@ struct xvisual_info x_get_visual_info(struct x_connection *c, xcb_visualid_t vis
xcb_visualid_t x_get_visual_for_standard(struct x_connection *c, xcb_pict_standard_t std);
xcb_visualid_t x_get_visual_for_depth(xcb_screen_t *screen, uint8_t depth);
xcb_render_pictformat_t
x_get_pictfmt_for_standard(struct x_connection *c, xcb_pict_standard_t std);
xcb_screen_t *x_screen_of_display(struct x_connection *c, int screen);
/// Populates a `struct x_monitors` with the current monitor configuration.
void x_update_monitors(struct x_connection *, struct x_monitors *);
/// Free memory allocated for a `struct x_monitors`.

View File

@@ -58,14 +58,6 @@ struct test_file_metadata __attribute__((weak)) * test_file_head;
} \
} while (0)
#define TEST_NOTEQUAL(a, b) \
do { \
if ((a) == (b)) { \
SET_FAILURE(#a " == " #b, false); \
return; \
} \
} while (0)
#define TEST_TRUE(a) \
do { \
if (!(a)) { \
@@ -77,13 +69,11 @@ struct test_file_metadata __attribute__((weak)) * test_file_head;
#define TEST_STREQUAL(a, b) \
do { \
if (strcmp(a, b) != 0) { \
const char *test_strequal__part2 = " != " #b; \
size_t test_strequal__len = \
strlen(a) + strlen(test_strequal__part2) + 3; \
char *test_strequal__buf = malloc(test_strequal__len); \
snprintf(test_strequal__buf, test_strequal__len, "\"%s\"%s", a, \
test_strequal__part2); \
SET_FAILURE(test_strequal__buf, true); \
const char *part2 = " != " #b; \
size_t len = strlen(a) + strlen(part2) + 3; \
char *buf = malloc(len); \
snprintf(buf, len, "\"%s\"%s", a, part2); \
SET_FAILURE(buf, true); \
return; \
} \
} while (0)
@@ -91,27 +81,11 @@ struct test_file_metadata __attribute__((weak)) * test_file_head;
#define TEST_STRNEQUAL(a, b, len) \
do { \
if (strncmp(a, b, len) != 0) { \
const char *test_strnequal__part2 = " != " #b; \
size_t test_strnequal__len2 = \
len + strlen(test_strnequal__part2) + 3; \
char *test_strnequal__buf = malloc(test_strnequal__len2); \
snprintf(test_strnequal__buf, test_strnequal__len2, \
"\"%.*s\"%s", (int)len, a, test_strnequal__part2); \
SET_FAILURE(test_strnequal__buf, true); \
return; \
} \
} while (0)
#define TEST_STREQUAL3(str, expected, len) \
do { \
if (len != strlen(expected) || strncmp(str, expected, len) != 0) { \
const char *test_strequal3__part2 = " != " #expected; \
size_t test_strequal3__len2 = \
len + strlen(test_strequal3__part2) + 3; \
char *test_strequal3__buf = malloc(test_strequal3__len2); \
snprintf(test_strequal3__buf, test_strequal3__len2, \
"\"%.*s\"%s", (int)len, str, test_strequal3__part2); \
SET_FAILURE(test_strequal3__buf, true); \
const char *part2 = " != " #b; \
size_t len2 = len + strlen(part2) + 3; \
char *buf = malloc(len2); \
snprintf(buf, len2, "\"%.*s\"%s", (int)len, a, part2); \
SET_FAILURE(buf, true); \
return; \
} \
} while (0)
@@ -225,9 +199,6 @@ static inline void __attribute__((constructor(102))) run_tests(void) {
#define TEST_EQUAL(a, b) \
(void)(a); \
(void)(b)
#define TEST_NOTEQUAL(a, b) \
(void)(a); \
(void)(b)
#define TEST_TRUE(a) (void)(a)
#define TEST_STREQUAL(a, b) \
(void)(a); \
@@ -236,8 +207,5 @@ static inline void __attribute__((constructor(102))) run_tests(void) {
(void)(a); \
(void)(b); \
(void)(len)
#define TEST_STREQUAL3(str, expected, len) \
(void)(str); \
(void)(expected); \
(void)(len)
#endif