Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
Integrate local scenefx fork renamed to cce-fx, add native GPU fade_inset shader, and add standalone wallpaper client stub
.gitignore | 1 +
Cargo.toml | 8 +-
Makefile | 11 +-
build.rs | 37 +-
scenefx/.editorconfig | 14 +
scenefx/.gitignore | 3 +
scenefx/LICENSE | 20 +
scenefx/README.md | 36 +
scenefx/examples/meson.build | 24 +
scenefx/examples/scene-graph.c | 224 ++
scenefx/flake.lock | 27 +
scenefx/flake.nix | 85 +
scenefx/include/meson.build | 6 +
scenefx/include/render/egl.h | 120 +
scenefx/include/render/fx_renderer/fx_renderer.h | 199 ++
scenefx/include/render/fx_renderer/shaders.h | 174 ++
scenefx/include/render/fx_renderer/util.h | 11 +
scenefx/include/render/pass.h | 16 +
scenefx/include/render/pixel_format.h | 66 +
.../render/fx_renderer/fx_effect_framebuffers.h | 30 +
.../scenefx/render/fx_renderer/fx_renderer.h | 37 +
scenefx/include/scenefx/render/pass.h | 163 +
scenefx/include/scenefx/types/fx/blur_data.h | 24 +
scenefx/include/scenefx/types/fx/clipped_region.h | 16 +
scenefx/include/scenefx/types/fx/corner_location.h | 26 +
scenefx/include/scenefx/types/wlr_scene.h | 891 ++++++
scenefx/include/types/wlr_output.h | 10 +
scenefx/include/types/wlr_scene.h | 10 +
scenefx/include/util/array.h | 18 +
scenefx/include/util/env.h | 23 +
scenefx/include/util/matrix.h | 49 +
scenefx/include/util/time.h | 35 +
scenefx/meson.build | 164 +
scenefx/meson_options.txt | 2 +
scenefx/protocol/meson.build | 52 +
scenefx/render/egl.c | 1132 +++++++
.../render/fx_renderer/fx_effect_framebuffers.c | 57 +
scenefx/render/fx_renderer/fx_framebuffer.c | 186 ++
scenefx/render/fx_renderer/fx_pass.c | 1184 ++++++++
scenefx/render/fx_renderer/fx_renderer.c | 546 ++++
scenefx/render/fx_renderer/fx_texture.c | 435 +++
scenefx/render/fx_renderer/gles2/meson.build | 2 +
.../render/fx_renderer/gles2/shaders/blur1.frag | 19 +
.../render/fx_renderer/gles2/shaders/blur2.frag | 23 +
.../fx_renderer/gles2/shaders/blur_effects.frag | 58 +
.../fx_renderer/gles2/shaders/box_shadow.frag | 96 +
.../render/fx_renderer/gles2/shaders/common.vert | 13 +
.../fx_renderer/gles2/shaders/corner_alpha.frag | 35 +
scenefx/render/fx_renderer/gles2/shaders/embed.sh | 11 +
.../render/fx_renderer/gles2/shaders/gradient.frag | 41 +
.../render/fx_renderer/gles2/shaders/meson.build | 30 +
scenefx/render/fx_renderer/gles2/shaders/quad.frag | 31 +
.../fx_renderer/gles2/shaders/quad_grad.frag | 25 +
.../fx_renderer/gles2/shaders/quad_grad_round.frag | 46 +
.../fx_renderer/gles2/shaders/quad_round.frag | 64 +
scenefx/render/fx_renderer/gles2/shaders/tex.frag | 65 +
scenefx/render/fx_renderer/gles3/meson.build | 1 +
.../render/fx_renderer/gles3/shaders/blur1.frag | 23 +
.../render/fx_renderer/gles3/shaders/blur2.frag | 27 +
.../fx_renderer/gles3/shaders/blur_effects.frag | 58 +
.../fx_renderer/gles3/shaders/box_shadow.frag | 96 +
.../render/fx_renderer/gles3/shaders/common.vert | 15 +
.../fx_renderer/gles3/shaders/corner_alpha.frag | 35 +
scenefx/render/fx_renderer/gles3/shaders/embed.sh | 11 +
.../render/fx_renderer/gles3/shaders/gradient.frag | 41 +
.../render/fx_renderer/gles3/shaders/meson.build | 30 +
scenefx/render/fx_renderer/gles3/shaders/quad.frag | 31 +
.../fx_renderer/gles3/shaders/quad_grad.frag | 25 +
.../fx_renderer/gles3/shaders/quad_grad_round.frag | 46 +
.../fx_renderer/gles3/shaders/quad_round.frag | 64 +
scenefx/render/fx_renderer/gles3/shaders/tex.frag | 65 +
scenefx/render/fx_renderer/meson.build | 46 +
scenefx/render/fx_renderer/pixel_format.c | 163 +
scenefx/render/fx_renderer/shaders.c | 388 +++
scenefx/render/fx_renderer/util.c | 110 +
scenefx/render/meson.build | 6 +
scenefx/render/pixel_format.c | 277 ++
scenefx/tinywl/.gitignore | 3 +
scenefx/tinywl/LICENSE | 125 +
scenefx/tinywl/Makefile | 27 +
scenefx/tinywl/README.md | 45 +
scenefx/tinywl/meson.build | 5 +
scenefx/tinywl/tinywl.c | 1281 ++++++++
scenefx/types/fx/blur_data.c | 27 +
scenefx/types/fx/clipped_region.c | 10 +
scenefx/types/fx/corner_location.c | 76 +
scenefx/types/fx/meson.build | 5 +
scenefx/types/meson.build | 6 +
scenefx/types/output/wlr_output.c | 23 +
scenefx/types/scene/wlr_scene.c | 3183 ++++++++++++++++++++
scenefx/util/array.c | 40 +
scenefx/util/env.c | 38 +
scenefx/util/matrix.c | 165 +
scenefx/util/meson.build | 6 +
scenefx/util/time.c | 33 +
src/bin/cce.rs | 6 +-
src/bin/wallpaper.rs | 10 +
src/server/output.rs | 45 +-
98 files changed, 13395 insertions(+), 53 deletions(-)
diff --git a/.gitignore b/.gitignore
index 00420f1..5552c02 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,4 @@
.gemini/
*.log
check_errors.txt
+/scenefx/build/
diff --git a/Cargo.toml b/Cargo.toml
index 30f885e..accf691 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,5 +1,5 @@
[package]
-name = "cce"
+name = "cce-fx"
version = "0.1.0"
edition = "2021"
@@ -26,9 +26,13 @@ pkg-config = "0.3"
cc = "1.0"
[[bin]]
-name = "cce"
+name = "cce-fx"
path = "src/bin/cce.rs"
+[[bin]]
+name = "cce-wallpaper"
+path = "src/bin/wallpaper.rs"
+
[profile.release]
debug = true
diff --git a/Makefile b/Makefile
index 0f59205..9da2ed4 100644
--- a/Makefile
+++ b/Makefile
@@ -5,16 +5,19 @@ build:
install: build
mkdir -p ~/.local/bin
- @if [ -f ../target/release/cce ]; then \
- install -m 755 ../target/release/cce ~/.local/bin/cce; \
+ @if [ -f ../target/release/cce-fx ]; then \
+ install -m 755 ../target/release/cce-fx ~/.local/bin/cce-fx; \
else \
- echo "Error: cce binary not found"; exit 1; \
+ echo "Error: cce-fx binary not found"; exit 1; \
+ fi
+ @if [ -f ../target/release/cce-wallpaper ]; then \
+ install -m 755 ../target/release/cce-wallpaper ~/.local/bin/cce-wallpaper; \
fi
install -m 755 scripts/cce-desktop-menu ~/.local/bin/cce-desktop-menu
install -m 755 scripts/cce-app-menu ~/.local/bin/cce-app-menu
run:
- cargo run --bin cce
+ cargo run --bin cce-fx
clean:
cargo clean
diff --git a/build.rs b/build.rs
index 319daaf..6089ae5 100644
--- a/build.rs
+++ b/build.rs
@@ -8,11 +8,34 @@ fn main() {
println!("cargo:rerun-if-changed=protocol/river-layer-shell-v1.xml");
println!("cargo:rerun-if-changed=protocol/river-input-management-v1.xml");
- // Probe system libraries
- let scenefx = pkg_config::Config::new()
- .atleast_version("0.4.0")
- .probe("scenefx-0.4")
- .expect("scenefx-0.4 is required");
+ // Build local scenefx
+ let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
+ if !std::path::Path::new("scenefx/build").exists() {
+ let status = std::process::Command::new("meson")
+ .args(&["setup", "build", "--buildtype=release", "--default-library=static"])
+ .current_dir("scenefx")
+ .status()
+ .expect("Failed to run meson setup");
+ assert!(status.success(), "meson setup failed");
+ }
+ let status = std::process::Command::new("meson")
+ .args(&["compile", "-C", "build"])
+ .current_dir("scenefx")
+ .status()
+ .expect("Failed to run meson compile");
+ assert!(status.success(), "meson compile failed");
+
+ let scenefx_inc1 = PathBuf::from(&manifest_dir).join("scenefx/include");
+ let scenefx_inc2 = PathBuf::from(&manifest_dir).join("scenefx/build/include");
+ let scenefx_inc3 = PathBuf::from(&manifest_dir).join("scenefx/build/protocol");
+ let scenefx_include_paths = vec![scenefx_inc1, scenefx_inc2, scenefx_inc3];
+
+ println!("cargo:rustc-link-search=native={}/scenefx/build", manifest_dir);
+ println!("cargo:rustc-link-lib=static=scenefx-0.4");
+ println!("cargo:rustc-link-lib=dylib=GLESv2");
+ println!("cargo:rustc-link-lib=dylib=EGL");
+ println!("cargo:rustc-link-lib=dylib=drm");
+ println!("cargo:rustc-link-lib=dylib=gbm");
let wlroots = pkg_config::Config::new()
.atleast_version("0.19.0")
@@ -133,7 +156,7 @@ fn main() {
}
// Add include paths for scenefx, wlroots, and wayland-server
- for path in &scenefx.include_paths {
+ for path in &scenefx_include_paths {
build.include(path);
}
for path in &wlroots.include_paths {
@@ -154,7 +177,7 @@ fn main() {
// Pass include paths to bindgen clang argument parser
let mut include_paths = vec![out_dir.clone()];
- include_paths.extend(scenefx.include_paths.clone());
+ include_paths.extend(scenefx_include_paths.clone());
include_paths.extend(wlroots.include_paths.clone());
include_paths.extend(wl_server.include_paths.clone());
include_paths.extend(xkb.include_paths.clone());
diff --git a/scenefx/.editorconfig b/scenefx/.editorconfig
new file mode 100644
index 0000000..9dcb30d
--- /dev/null
+++ b/scenefx/.editorconfig
@@ -0,0 +1,14 @@
+root = true
+
+[*]
+end_of_line = lf
+insert_final_newline = true
+charset = utf-8
+trim_trailing_whitespace = true
+indent_style = tab
+indent_size = 4
+max_line_length = 80
+
+[*.xml]
+indent_style = space
+indent_size = 2
diff --git a/scenefx/.gitignore b/scenefx/.gitignore
new file mode 100644
index 0000000..cb9554a
--- /dev/null
+++ b/scenefx/.gitignore
@@ -0,0 +1,3 @@
+/subprojects/
+build/
+.cache/
diff --git a/scenefx/LICENSE b/scenefx/LICENSE
new file mode 100644
index 0000000..b75a8c4
--- /dev/null
+++ b/scenefx/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2017, 2018 Drew DeVault
+Copyright (c) 2014 Jari Vetoniemi
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/scenefx/README.md b/scenefx/README.md
new file mode 100644
index 0000000..8bdca76
--- /dev/null
+++ b/scenefx/README.md
@@ -0,0 +1,36 @@
+# scenefx
+
+wlroots is the de-facto library for building wayland compositors, and its scene api is a great stride in simplifying wayland compositor development. The problem with the scene api (for compositors looking for eye candy), however, is that it forces you to use the wlr renderer, which is powerful yet simple. SceneFX is a project that takes the scene api and replaces the wlr renderer with our own fx renderer, capable of rendering surfaces with eye-candy effects including blur, shadows, and rounded corners, while maintaining the benefits of simplicity gained from using the scene api.
+
+**Please note: while SceneFX is in use by SwayFX version 0.4, it is not yet ready for usage by other compositors. Please refer to the [1.0 milestone](https://github.com/wlrfx/scenefx/milestone/2) to track the remaining tasks for our stable 1.0 release**
+
+## Installation
+<a href="https://repology.org/project/scenefx/versions"><img src="https://repology.org/badge/vertical-allrepos/scenefx.svg"/></a>
+
+
+## Compiling From Source
+Install dependencies:
+* meson \*
+* wlroots
+* wayland
+* wayland-protocols \*
+* EGL and GLESv2
+* libdrm
+* pixman
+
+_\* Compile-time dep_
+
+Run these commands:
+```sh
+meson setup build/
+ninja -C build/
+```
+
+Install like so:
+```sh
+sudo ninja -C build/ install
+```
+
+
+---
+[Join our Discord](https://discord.gg/qsSx397rkh)
diff --git a/scenefx/examples/meson.build b/scenefx/examples/meson.build
new file mode 100644
index 0000000..dc6f171
--- /dev/null
+++ b/scenefx/examples/meson.build
@@ -0,0 +1,24 @@
+# Only needed for drm_fourcc.h
+libdrm_header = dependency('libdrm').partial_dependency(compile_args: true, includes: true)
+
+compositors = {
+ 'scene-graph': {
+ 'src': 'scene-graph.c',
+ 'proto': ['xdg-shell'],
+ },
+}
+
+foreach name, info : compositors
+ extra_src = []
+ foreach p : info.get('proto', [])
+ extra_src += protocols_server_header[p]
+ endforeach
+
+ executable(
+ name,
+ [info.get('src'), extra_src],
+ dependencies: [scenefx, libdrm_header, info.get('dep', [])],
+ build_by_default: get_option('examples'),
+ )
+endforeach
+
diff --git a/scenefx/examples/scene-graph.c b/scenefx/examples/scene-graph.c
new file mode 100644
index 0000000..910d60b
--- /dev/null
+++ b/scenefx/examples/scene-graph.c
@@ -0,0 +1,224 @@
+#include <assert.h>
+#include <getopt.h>
+#include <scenefx/render/fx_renderer/fx_renderer.h>
+#include <scenefx/types/fx/corner_location.h>
+#include <scenefx/types/wlr_scene.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+#include <wayland-server-core.h>
+#include <wlr/backend.h>
+#include <wlr/render/allocator.h>
+#include <wlr/render/wlr_renderer.h>
+#include <wlr/types/wlr_compositor.h>
+#include <wlr/types/wlr_output.h>
+#include <wlr/types/wlr_xdg_shell.h>
+#include <wlr/util/log.h>
+
+/* Simple compositor making use of the scene-graph API. Input is unimplemented.
+ *
+ * New surfaces are stacked on top of the existing ones as they appear. */
+
+static const int border_width = 3;
+static const int corner_radius = 0; // TODO
+
+struct server {
+ struct wl_display *display;
+ struct wlr_backend *backend;
+ struct wlr_renderer *renderer;
+ struct wlr_allocator *allocator;
+ struct wlr_scene *scene;
+
+ uint32_t surface_offset;
+
+ struct wl_listener new_output;
+ struct wl_listener new_surface;
+};
+
+struct surface {
+ struct wlr_surface *wlr;
+ struct wlr_scene_surface *scene_surface;
+ struct wlr_scene_rect *border;
+ struct wlr_scene_shadow *shadow;
+ struct wl_list link;
+
+ struct wl_listener commit;
+ struct wl_listener destroy;
+};
+
+struct output {
+ struct wl_list link;
+ struct server *server;
+ struct wlr_output *wlr;
+ struct wlr_scene_output *scene_output;
+
+ struct wl_listener frame;
+};
+
+static void output_handle_frame(struct wl_listener *listener, void *data) {
+ struct output *output = wl_container_of(listener, output, frame);
+
+ if (!wlr_scene_output_commit(output->scene_output, NULL)) {
+ return;
+ }
+
+ struct timespec now;
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ wlr_scene_output_send_frame_done(output->scene_output, &now);
+}
+
+static void server_handle_new_output(struct wl_listener *listener, void *data) {
+ struct server *server = wl_container_of(listener, server, new_output);
+ struct wlr_output *wlr_output = data;
+
+ wlr_output_init_render(wlr_output, server->allocator, server->renderer);
+
+ struct output *output = calloc(1, sizeof(*output));
+ output->wlr = wlr_output;
+ output->server = server;
+ output->frame.notify = output_handle_frame;
+ wl_signal_add(&wlr_output->events.frame, &output->frame);
+
+ output->scene_output = wlr_scene_output_create(server->scene, wlr_output);
+
+ struct wlr_output_state state;
+ wlr_output_state_init(&state);
+ wlr_output_state_set_enabled(&state, true);
+ struct wlr_output_mode *mode = wlr_output_preferred_mode(wlr_output);
+ if (mode != NULL) {
+ wlr_output_state_set_mode(&state, mode);
+ }
+ wlr_output_commit_state(wlr_output, &state);
+ wlr_output_state_finish(&state);
+
+ wlr_output_create_global(wlr_output, server->display);
+}
+
+static void surface_handle_commit(struct wl_listener *listener, void *data) {
+ struct surface *surface = wl_container_of(listener, surface, commit);
+ wlr_scene_rect_set_size(surface->border,
+ surface->wlr->current.width + 2 * border_width,
+ surface->wlr->current.height + 2 * border_width);
+ wlr_scene_shadow_set_size(surface->shadow,
+ surface->wlr->current.width + 2 * (surface->shadow->blur_sigma + border_width),
+ surface->wlr->current.height + 2 * (surface->shadow->blur_sigma + border_width));
+}
+
+static void surface_handle_destroy(struct wl_listener *listener, void *data) {
+ struct surface *surface = wl_container_of(listener, surface, destroy);
+ wlr_scene_node_destroy(&surface->scene_surface->buffer->node);
+ wlr_scene_node_destroy(&surface->border->node);
+ wlr_scene_node_destroy(&surface->shadow->node);
+ wl_list_remove(&surface->destroy.link);
+ wl_list_remove(&surface->link);
+ free(surface);
+}
+
+static void server_handle_new_surface(struct wl_listener *listener,
+ void *data) {
+ struct server *server = wl_container_of(listener, server, new_surface);
+ struct wlr_surface *wlr_surface = data;
+
+ int pos = server->surface_offset;
+ server->surface_offset += 50;
+
+ struct surface *surface = calloc(1, sizeof(*surface));
+ surface->wlr = wlr_surface;
+ surface->commit.notify = surface_handle_commit;
+ wl_signal_add(&wlr_surface->events.commit, &surface->commit);
+ surface->destroy.notify = surface_handle_destroy;
+ wl_signal_add(&wlr_surface->events.destroy, &surface->destroy);
+
+ /* Border dimensions will be set in surface.commit handler */
+ surface->border = wlr_scene_rect_create(&server->scene->tree,
+ 0, 0, (float[4]){ 0.5f, 0.5f, 0.5f, 1 });
+ wlr_scene_node_set_position(&surface->border->node, pos, pos);
+
+ /* Shadow dimensions will be set in surface.commit handler */
+ float blur_sigma = 20.0f;
+ surface->shadow = wlr_scene_shadow_create(&server->scene->tree,
+ 0, 0, corner_radius, blur_sigma, (float[4]){ 1.0f, 0.f, 0.f, 1.0f });
+ wlr_scene_node_set_position(&surface->shadow->node,
+ pos - blur_sigma, pos - blur_sigma);
+ surface->scene_surface =
+ wlr_scene_surface_create(&server->scene->tree, wlr_surface);
+ wlr_scene_buffer_set_corner_radius(
+ surface->scene_surface->buffer, corner_radius, CORNER_LOCATION_ALL);
+
+ wlr_scene_node_set_position(&surface->scene_surface->buffer->node,
+ pos + border_width, pos + border_width);
+}
+
+int main(int argc, char *argv[]) {
+ wlr_log_init(WLR_DEBUG, NULL);
+
+ char *startup_cmd = NULL;
+
+ int c;
+ while ((c = getopt(argc, argv, "s:")) != -1) {
+ switch (c) {
+ case 's':
+ startup_cmd = optarg;
+ break;
+ default:
+ printf("usage: %s [-s startup-command]\n", argv[0]);
+ return EXIT_FAILURE;
+ }
+ }
+ if (optind < argc) {
+ printf("usage: %s [-s startup-command]\n", argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ struct server server = {0};
+ server.surface_offset = 0;
+ server.display = wl_display_create();
+ server.backend = wlr_backend_autocreate(wl_display_get_event_loop(server.display), NULL);
+ server.scene = wlr_scene_create();
+
+ server.renderer = fx_renderer_create(server.backend);
+ wlr_renderer_init_wl_display(server.renderer, server.display);
+
+ server.allocator = wlr_allocator_autocreate(server.backend,
+ server.renderer);
+
+ struct wlr_compositor *compositor =
+ wlr_compositor_create(server.display, 5, server.renderer);
+
+ wlr_xdg_shell_create(server.display, 2);
+
+ server.new_output.notify = server_handle_new_output;
+ wl_signal_add(&server.backend->events.new_output, &server.new_output);
+
+ server.new_surface.notify = server_handle_new_surface;
+ wl_signal_add(&compositor->events.new_surface, &server.new_surface);
+
+ const char *socket = wl_display_add_socket_auto(server.display);
+ if (!socket) {
+ wl_display_destroy(server.display);
+ return EXIT_FAILURE;
+ }
+
+ if (!wlr_backend_start(server.backend)) {
+ wl_display_destroy(server.display);
+ return EXIT_FAILURE;
+ }
+
+ setenv("WAYLAND_DISPLAY", socket, true);
+ if (startup_cmd != NULL) {
+ if (fork() == 0) {
+ execl("/bin/sh", "/bin/sh", "-c", startup_cmd, (void *)NULL);
+ }
+ }
+
+ wlr_log(WLR_INFO, "Running Wayland compositor on WAYLAND_DISPLAY=%s",
+ socket);
+ wl_display_run(server.display);
+
+ wl_display_destroy_clients(server.display);
+ wl_display_destroy(server.display);
+ return EXIT_SUCCESS;
+}
+
diff --git a/scenefx/flake.lock b/scenefx/flake.lock
new file mode 100644
index 0000000..2e0310d
--- /dev/null
+++ b/scenefx/flake.lock
@@ -0,0 +1,27 @@
+{
+ "nodes": {
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1750605355,
+ "narHash": "sha256-xT8cPLTxlktxI9vSdoBlAVK7dXgd8IK59j7ZwzkkhnI=",
+ "owner": "nixos",
+ "repo": "nixpkgs",
+ "rev": "3078b9a9e75f1790e6d6ef9955fdc6a2d1740cc6",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nixos",
+ "ref": "nixpkgs-unstable",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "root": {
+ "inputs": {
+ "nixpkgs": "nixpkgs"
+ }
+ }
+ },
+ "root": "root",
+ "version": 7
+}
diff --git a/scenefx/flake.nix b/scenefx/flake.nix
new file mode 100644
index 0000000..34b9257
--- /dev/null
+++ b/scenefx/flake.nix
@@ -0,0 +1,85 @@
+{
+ description = "Scenefx development environment";
+ inputs.nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
+ outputs =
+ { self, nixpkgs, ... }:
+ let
+ mkPackages = pkgs: {
+ scenefx = pkgs.callPackage (
+ { wlroots_0_19, ... }:
+ pkgs.stdenv.mkDerivation {
+ pname = "scenefx";
+ version = "0.4.0-git";
+ src = ./.;
+ outputs = [
+ "out"
+ "lib"
+ ];
+
+ nativeBuildInputs = with pkgs; [
+ pkg-config
+ meson
+ cmake
+ ninja
+ scdoc
+ wayland-scanner
+ ];
+
+ buildInputs = with pkgs; [
+ libdrm
+ libxkbcommon
+ pixman
+ libGL # egl
+ mesa # gbm
+ wayland # wayland-server
+ wayland-protocols
+ wlroots_0_19
+ ];
+
+ meta = with pkgs.lib; {
+ description = "A drop-in replacement for the wlroots scene API that allows wayland compositors to render surfaces with eye-candy effects";
+ homepage = "https://github.com/wlrfx/scenefx";
+ license = licenses.mit;
+ platforms = platforms.linux;
+ };
+ }
+ ) { };
+ };
+
+ targetSystems = [
+ "aarch64-linux"
+ "x86_64-linux"
+ ];
+ pkgsFor = system: import nixpkgs { inherit system; };
+ forEachSystem = f: nixpkgs.lib.genAttrs targetSystems (system: f (pkgsFor system));
+ in
+ {
+ overlays = rec {
+ default = insert;
+ override = _: prev: mkPackages prev;
+ insert = _: prev: mkPackages (pkgsFor prev.system);
+ };
+
+ packages = forEachSystem (
+ pkgs: (mkPackages pkgs) // { default = self.packages.${pkgs.system}.scenefx; }
+ );
+
+ devShells = forEachSystem (pkgs: {
+ default = pkgs.mkShell {
+ name = "scenefx-shell";
+ inputsFrom = [
+ self.packages.${pkgs.system}.scenefx
+ pkgs.wlroots_0_19
+ ];
+ shellHook = ''
+ (
+ # Copy the nix version of wlroots into the project
+ mkdir -p "$PWD/subprojects" && cd "$PWD/subprojects"
+ cp -R --no-preserve=mode,ownership ${pkgs.wlroots_0_19.src} wlroots
+ )'';
+ };
+ });
+
+ formatter = forEachSystem (pkgs: pkgs.nixfmt-rfc-style);
+ };
+}
diff --git a/scenefx/include/meson.build b/scenefx/include/meson.build
new file mode 100644
index 0000000..4fee8be
--- /dev/null
+++ b/scenefx/include/meson.build
@@ -0,0 +1,6 @@
+exclude_files = ['meson.build']
+
+install_subdir('scenefx',
+ install_dir: get_option('includedir') / versioned_name,
+ exclude_files: exclude_files,
+)
diff --git a/scenefx/include/render/egl.h b/scenefx/include/render/egl.h
new file mode 100644
index 0000000..103ab57
--- /dev/null
+++ b/scenefx/include/render/egl.h
@@ -0,0 +1,120 @@
+#ifndef RENDER_EGL_H
+#define RENDER_EGL_H
+
+#include <wlr/render/egl.h>
+
+struct wlr_egl {
+ EGLDisplay display;
+ EGLContext context;
+ EGLDeviceEXT device; // may be EGL_NO_DEVICE_EXT
+ struct gbm_device *gbm_device;
+
+ struct {
+ // Display extensions
+ bool KHR_image_base;
+ bool EXT_image_dma_buf_import;
+ bool EXT_image_dma_buf_import_modifiers;
+ bool IMG_context_priority;
+ bool EXT_create_context_robustness;
+
+ // Device extensions
+ bool EXT_device_drm;
+ bool EXT_device_drm_render_node;
+
+ // Client extensions
+ bool EXT_device_query;
+ bool KHR_platform_gbm;
+ bool EXT_platform_device;
+ bool KHR_display_reference;
+ } exts;
+
+ struct {
+ PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT;
+ PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR;
+ PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR;
+ PFNEGLQUERYDMABUFFORMATSEXTPROC eglQueryDmaBufFormatsEXT;
+ PFNEGLQUERYDMABUFMODIFIERSEXTPROC eglQueryDmaBufModifiersEXT;
+ PFNEGLDEBUGMESSAGECONTROLKHRPROC eglDebugMessageControlKHR;
+ PFNEGLQUERYDISPLAYATTRIBEXTPROC eglQueryDisplayAttribEXT;
+ PFNEGLQUERYDEVICESTRINGEXTPROC eglQueryDeviceStringEXT;
+ PFNEGLQUERYDEVICESEXTPROC eglQueryDevicesEXT;
+ PFNEGLCREATESYNCKHRPROC eglCreateSyncKHR;
+ PFNEGLDESTROYSYNCKHRPROC eglDestroySyncKHR;
+ PFNEGLDUPNATIVEFENCEFDANDROIDPROC eglDupNativeFenceFDANDROID;
+ PFNEGLWAITSYNCKHRPROC eglWaitSyncKHR;
+ } procs;
+
+ bool has_modifiers;
+ struct wlr_drm_format_set dmabuf_texture_formats;
+ struct wlr_drm_format_set dmabuf_render_formats;
+};
+
+struct wlr_egl_context {
+ EGLDisplay display;
+ EGLContext context;
+ EGLSurface draw_surface;
+ EGLSurface read_surface;
+};
+
+/**
+ * Initializes an EGL context for the given DRM FD.
+ *
+ * Will attempt to load all possibly required API functions.
+ */
+struct wlr_egl *wlr_egl_create_with_drm_fd(int drm_fd);
+
+/**
+ * Frees all related EGL resources, makes the context not-current and
+ * unbinds a bound wayland display.
+ */
+void wlr_egl_destroy(struct wlr_egl *egl);
+
+/**
+ * Creates an EGL image from the given dmabuf attributes. Check usability
+ * of the dmabuf with wlr_egl_check_import_dmabuf once first.
+ */
+EGLImageKHR wlr_egl_create_image_from_dmabuf(struct wlr_egl *egl,
+ struct wlr_dmabuf_attributes *attributes, bool *external_only);
+
+/**
+ * Get DMA-BUF formats suitable for sampling usage.
+ */
+const struct wlr_drm_format_set *wlr_egl_get_dmabuf_texture_formats(
+ struct wlr_egl *egl);
+/**
+ * Get DMA-BUF formats suitable for rendering usage.
+ */
+const struct wlr_drm_format_set *wlr_egl_get_dmabuf_render_formats(
+ struct wlr_egl *egl);
+
+/**
+ * Destroys an EGL image created with the given wlr_egl.
+ */
+bool wlr_egl_destroy_image(struct wlr_egl *egl, EGLImageKHR image);
+
+int wlr_egl_dup_drm_fd(struct wlr_egl *egl);
+
+/**
+ * Restore EGL context that was previously saved using wlr_egl_save_current().
+ */
+bool wlr_egl_restore_context(struct wlr_egl_context *context);
+
+/**
+ * Make the EGL context current.
+ *
+ * The old EGL context is saved. Callers are expected to clear the current
+ * context when they are done by calling wlr_egl_restore_context().
+ */
+bool wlr_egl_make_current(struct wlr_egl *egl, struct wlr_egl_context *save_context);
+
+bool wlr_egl_unset_current(struct wlr_egl *egl);
+
+EGLSyncKHR wlr_egl_create_sync(struct wlr_egl *egl, int fence_fd);
+
+void wlr_egl_destroy_sync(struct wlr_egl *egl, EGLSyncKHR sync);
+
+int wlr_egl_dup_fence_fd(struct wlr_egl *egl, EGLSyncKHR sync);
+
+bool wlr_egl_wait_sync(struct wlr_egl *egl, EGLSyncKHR sync);
+
+#endif
diff --git a/scenefx/include/render/fx_renderer/fx_renderer.h b/scenefx/include/render/fx_renderer/fx_renderer.h
new file mode 100644
index 0000000..ea8e7fb
--- /dev/null
+++ b/scenefx/include/render/fx_renderer/fx_renderer.h
@@ -0,0 +1,199 @@
+#ifndef _FX_OPENGL_H
+#define _FX_OPENGL_H
+
+#include <GLES2/gl2.h>
+#include <scenefx/render/fx_renderer/fx_renderer.h>
+#include <stdbool.h>
+#include <time.h>
+#include <wlr/render/egl.h>
+#include <wlr/render/interface.h>
+#include <wlr/render/swapchain.h>
+#include <wlr/render/wlr_texture.h>
+#include <wlr/util/addon.h>
+#include <wlr/util/box.h>
+
+#include "render/fx_renderer/shaders.h"
+
+struct fx_framebuffer;
+
+struct fx_pixel_format {
+ uint32_t drm_format;
+ // optional field, if empty then internalformat = format
+ GLint gl_internalformat;
+ GLint gl_format, gl_type;
+};
+
+bool is_fx_pixel_format_supported(const struct fx_renderer *renderer, const struct fx_pixel_format *format);
+const struct fx_pixel_format *get_fx_format_from_drm(uint32_t fmt);
+const struct fx_pixel_format *get_fx_format_from_gl(GLint gl_format, GLint gl_type, bool alpha);
+void get_fx_shm_formats(const struct fx_renderer *renderer, struct wlr_drm_format_set *out);
+
+GLuint fx_framebuffer_get_fbo(struct fx_framebuffer *buffer);
+
+///
+/// fx_framebuffer
+///
+
+struct fx_framebuffer {
+ struct wlr_buffer *buffer;
+ struct fx_renderer *renderer;
+ struct wl_list link; // fx_renderer.buffers
+ bool external_only;
+
+ EGLImageKHR image;
+ GLuint rbo;
+ GLuint fbo;
+ GLuint tex;
+ GLuint sb; // Stencil
+
+ struct wlr_addon addon;
+};
+
+/** Should only be used with custom fbs */
+void fx_framebuffer_get_or_create_custom(struct fx_renderer *fx_renderer,
+ struct wlr_output *output, struct wlr_swapchain *swapchain,
+ struct fx_framebuffer **fx_buffer, bool *failed);
+
+struct fx_framebuffer *fx_framebuffer_get_or_create(struct fx_renderer *renderer,
+ struct wlr_buffer *wlr_buffer);
+
+void fx_framebuffer_bind(struct fx_framebuffer *buffer);
+
+void fx_framebuffer_destroy(struct fx_framebuffer *buffer);
+
+///
+/// fx_texture
+///
+
+struct fx_texture {
+ struct wlr_texture wlr_texture;
+ struct fx_renderer *fx_renderer;
+ struct wl_list link; // fx_renderer.textures
+
+ GLuint target;
+
+ // If this texture is imported from a buffer, the texture is does not own
+ // these states. These cannot be destroyed along with the texture in this
+ // case.
+ GLuint tex;
+ GLuint fbo;
+
+ bool has_alpha;
+
+ uint32_t drm_format; // for mutable textures only, used to interpret upload data
+ struct fx_framebuffer *buffer; // for DMA-BUF imports only
+};
+
+struct fx_texture *fx_get_texture(struct wlr_texture *wlr_texture);
+
+void fx_texture_destroy(struct fx_texture *texture);
+
+bool wlr_texture_is_fx(struct wlr_texture *wlr_texture);
+
+bool wlr_renderer_is_fx(struct wlr_renderer *wlr_renderer);
+
+struct fx_render_timer *fx_get_render_timer(struct wlr_render_timer *timer);
+
+struct fx_texture *fx_get_texture(struct wlr_texture *wlr_texture);
+
+struct wlr_renderer *fx_renderer_create_egl(struct wlr_egl *egl);
+
+struct wlr_egl *wlr_fx_renderer_get_egl(struct wlr_renderer *renderer);
+
+void push_fx_debug_(struct fx_renderer *renderer, const char *file, const char *func);
+
+#define push_fx_debug(renderer) push_fx_debug_(renderer, _WLR_FILENAME, __func__)
+
+void pop_fx_debug(struct fx_renderer *renderer);
+
+///
+/// Render Timer
+///
+
+struct fx_render_timer {
+ struct wlr_render_timer base;
+ struct fx_renderer *renderer;
+ struct timespec cpu_start;
+ struct timespec cpu_end;
+ GLuint id;
+ GLint64 gl_cpu_end;
+};
+
+bool wlr_render_timer_is_fx(struct wlr_render_timer *timer);
+
+///
+/// fx_renderer
+///
+
+/**
+ * OpenGL ES 2 renderer.
+ *
+ * Care must be taken to avoid stepping each other's toes with EGL contexts:
+ * the current EGL is global state. The GLES2 renderer operations will save
+ * and restore any previous EGL context when called. A render pass is seen as
+ * a single operation.
+ *
+ * The GLES2 renderer doesn't support arbitrarily nested render passes. It
+ * supports a subset only: after a nested render pass is created, any parent
+ * render pass can't be used before the nested render pass is submitted.
+ */
+
+struct fx_renderer {
+ struct wlr_renderer wlr_renderer;
+
+ struct wlr_egl *egl;
+ int drm_fd;
+
+ struct wlr_drm_format_set shm_texture_formats;
+
+ const char *exts_str;
+ struct {
+ bool EXT_read_format_bgra;
+ bool KHR_debug;
+ bool OES_egl_image_external;
+ bool OES_egl_image;
+ bool EXT_texture_type_2_10_10_10_REV;
+ bool OES_texture_half_float_linear;
+ bool EXT_texture_norm16;
+ bool EXT_disjoint_timer_query;
+ } exts;
+
+ struct {
+ PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES;
+ PFNGLDEBUGMESSAGECALLBACKKHRPROC glDebugMessageCallbackKHR;
+ PFNGLDEBUGMESSAGECONTROLKHRPROC glDebugMessageControlKHR;
+ PFNGLPOPDEBUGGROUPKHRPROC glPopDebugGroupKHR;
+ PFNGLPUSHDEBUGGROUPKHRPROC glPushDebugGroupKHR;
+ PFNGLEGLIMAGETARGETRENDERBUFFERSTORAGEOESPROC glEGLImageTargetRenderbufferStorageOES;
+ PFNGLGETGRAPHICSRESETSTATUSKHRPROC glGetGraphicsResetStatusKHR;
+ PFNGLGENQUERIESEXTPROC glGenQueriesEXT;
+ PFNGLDELETEQUERIESEXTPROC glDeleteQueriesEXT;
+ PFNGLQUERYCOUNTEREXTPROC glQueryCounterEXT;
+ PFNGLGETQUERYOBJECTIVEXTPROC glGetQueryObjectivEXT;
+ PFNGLGETQUERYOBJECTUI64VEXTPROC glGetQueryObjectui64vEXT;
+ PFNGLGETINTEGER64VEXTPROC glGetInteger64vEXT;
+ } procs;
+
+ struct {
+ struct quad_shader quad;
+ struct quad_grad_shader quad_grad;
+ struct quad_round_shader quad_round;
+ struct quad_grad_round_shader quad_grad_round;
+ struct tex_shader tex_rgba;
+ struct tex_shader tex_rgbx;
+ struct tex_shader tex_ext;
+ struct box_shadow_shader box_shadow;
+ struct blur_shader blur1;
+ struct blur_shader blur2;
+ struct blur_effects_shader blur_effects;
+ } shaders;
+
+ struct wl_list buffers; // fx_framebuffer.link
+ struct wl_list textures; // fx_texture.link
+
+ // Set to true when 'wlr_renderer_begin_buffer_pass' is called instead of
+ // our custom 'fx_renderer_begin_buffer_pass' function
+ bool basic_renderer;
+};
+
+#endif
diff --git a/scenefx/include/render/fx_renderer/shaders.h b/scenefx/include/render/fx_renderer/shaders.h
new file mode 100644
index 0000000..650b6e4
--- /dev/null
+++ b/scenefx/include/render/fx_renderer/shaders.h
@@ -0,0 +1,174 @@
+#ifndef _FX_SHADERS_H
+#define _FX_SHADERS_H
+
+#include <GLES2/gl2.h>
+#include <stdbool.h>
+
+struct fx_renderer;
+
+GLuint compile_shader(GLuint type, const GLchar *src);
+
+GLuint link_program(const GLchar *frag_src, GLint client_version);
+
+bool check_gl_ext(const char *exts, const char *ext);
+
+void load_gl_proc(void *proc_ptr, const char *name);
+
+enum fx_tex_shader_source {
+ SHADER_SOURCE_TEXTURE_RGBA = 1,
+ SHADER_SOURCE_TEXTURE_RGBX = 2,
+ SHADER_SOURCE_TEXTURE_EXTERNAL = 3,
+};
+
+struct quad_shader {
+ GLuint program;
+ GLint proj;
+ GLint color;
+ GLint pos_attrib;
+
+ GLint clip_size;
+ GLint clip_position;
+ GLint clip_radius_top_left;
+ GLint clip_radius_top_right;
+ GLint clip_radius_bottom_left;
+ GLint clip_radius_bottom_right;
+};
+
+bool link_quad_program(struct quad_shader *shader, GLint client_version);
+
+struct quad_grad_shader {
+ int max_len;
+
+ GLuint program;
+ GLint proj;
+ GLint colors;
+ GLint size;
+ GLint degree;
+ GLint grad_box;
+ GLint pos_attrib;
+ GLint linear;
+ GLint origin;
+ GLint count;
+ GLint blend;
+};
+
+bool link_quad_grad_program(struct quad_grad_shader *shader, GLint client_version, int max_len);
+
+struct quad_round_shader {
+ GLuint program;
+ GLint proj;
+ GLint color;
+ GLint pos_attrib;
+ GLint size;
+ GLint position;
+
+ GLint radius_top_left;
+ GLint radius_top_right;
+ GLint radius_bottom_left;
+ GLint radius_bottom_right;
+
+ GLint clip_size;
+ GLint clip_position;
+ GLint clip_radius_top_left;
+ GLint clip_radius_top_right;
+ GLint clip_radius_bottom_left;
+ GLint clip_radius_bottom_right;
+ GLint fade_inset;
+};
+
+bool link_quad_round_program(struct quad_round_shader *shader, GLint client_version);
+
+struct quad_grad_round_shader {
+ GLuint program;
+ GLint proj;
+ GLint color;
+ GLint pos_attrib;
+ GLint size;
+ GLint position;
+
+ GLint colors;
+ GLint grad_size;
+ GLint degree;
+ GLint grad_box;
+ GLint linear;
+ GLint origin;
+ GLint count;
+ GLint blend;
+
+ GLint radius_top_left;
+ GLint radius_top_right;
+ GLint radius_bottom_left;
+ GLint radius_bottom_right;
+
+ int max_len;
+};
+
+bool link_quad_grad_round_program(struct quad_grad_round_shader *shader, GLint client_version, int max_len);
+
+struct tex_shader {
+ GLuint program;
+ GLint proj;
+ GLint tex_proj;
+ GLint tex;
+ GLint alpha;
+ GLint pos_attrib;
+ GLint size;
+ GLint position;
+ GLint radius_top_left;
+ GLint radius_top_right;
+ GLint radius_bottom_left;
+ GLint radius_bottom_right;
+
+ GLint discard_transparent;
+};
+
+bool link_tex_program(struct tex_shader *shader, GLint client_version, enum fx_tex_shader_source source);
+
+struct box_shadow_shader {
+ GLuint program;
+ GLint proj;
+ GLint color;
+ GLint pos_attrib;
+ GLint position;
+ GLint size;
+ GLint blur_sigma;
+ GLint corner_radius;
+
+ GLint clip_position;
+ GLint clip_size;
+ GLint clip_radius_top_left;
+ GLint clip_radius_top_right;
+ GLint clip_radius_bottom_left;
+ GLint clip_radius_bottom_right;
+};
+
+bool link_box_shadow_program(struct box_shadow_shader *shader, GLint client_version);
+
+struct blur_shader {
+ GLuint program;
+ GLint proj;
+ GLint tex_proj;
+ GLint tex;
+ GLint pos_attrib;
+ GLint radius;
+ GLint halfpixel;
+};
+
+bool link_blur1_program(struct blur_shader *shader, GLint client_version);
+bool link_blur2_program(struct blur_shader *shader, GLint client_version);
+
+struct blur_effects_shader {
+ GLuint program;
+ GLint proj;
+ GLint tex_proj;
+ GLint tex;
+ GLint pos_attrib;
+ GLfloat noise;
+ GLfloat brightness;
+ GLfloat contrast;
+ GLfloat saturation;
+};
+
+bool link_blur_effects_program(struct blur_effects_shader *shader, GLint client_version);
+
+#endif
diff --git a/scenefx/include/render/fx_renderer/util.h b/scenefx/include/render/fx_renderer/util.h
new file mode 100644
index 0000000..c0afc69
--- /dev/null
+++ b/scenefx/include/render/fx_renderer/util.h
@@ -0,0 +1,11 @@
+#ifndef _FX_UTIL_H
+#define _FX_UTIL_H
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <wlr/backend/interface.h>
+
+bool open_preferred_drm_fd(struct wlr_backend *backend, int *drm_fd_ptr,
+ bool *own_drm_fd);
+
+#endif
diff --git a/scenefx/include/render/pass.h b/scenefx/include/render/pass.h
new file mode 100644
index 0000000..0ee9550
--- /dev/null
+++ b/scenefx/include/render/pass.h
@@ -0,0 +1,16 @@
+#ifndef FX_RENDER_PASS_H
+#define FX_RENDER_PASS_H
+
+#include <scenefx/render/pass.h>
+#include <stdbool.h>
+#include <wlr/render/pass.h>
+#include <wlr/util/box.h>
+#include <wlr/render/interface.h>
+
+struct fx_render_texture_options fx_render_texture_options_default(
+ const struct wlr_render_texture_options *base);
+
+struct fx_render_rect_options fx_render_rect_options_default(
+ const struct wlr_render_rect_options *base);
+
+#endif
diff --git a/scenefx/include/render/pixel_format.h b/scenefx/include/render/pixel_format.h
new file mode 100644
index 0000000..0b20aa5
--- /dev/null
+++ b/scenefx/include/render/pixel_format.h
@@ -0,0 +1,66 @@
+#ifndef FX_RENDER_PIXEL_FORMAT_H
+#define FX_RENDER_PIXEL_FORMAT_H
+
+#include <wayland-server-protocol.h>
+
+/**
+ * Information about a pixel format.
+ *
+ * A pixel format is identified via its DRM four character code (see <drm_fourcc.h>).
+ *
+ * Simple formats have a block size of 1×1 pixels and bytes_per_block contains
+ * the number of bytes per pixel (including padding).
+ *
+ * Tiled formats (e.g. sub-sampled YCbCr) are described with a block size
+ * greater than 1×1 pixels. A block is a rectangle of pixels which are stored
+ * next to each other in a byte-aligned memory region.
+ */
+struct wlr_pixel_format_info {
+ uint32_t drm_format;
+
+ /* Equivalent of the format if it has an alpha channel,
+ * DRM_FORMAT_INVALID (0) if NA
+ */
+ uint32_t opaque_substitute;
+
+ /* Bytes per block (including padding) */
+ uint32_t bytes_per_block;
+ /* Size of a block in pixels (zero for 1×1) */
+ uint32_t block_width, block_height;
+};
+
+/**
+ * Get pixel format information from a DRM FourCC.
+ *
+ * NULL is returned if the pixel format is unknown.
+ */
+const struct wlr_pixel_format_info *drm_get_pixel_format_info(uint32_t fmt);
+/**
+ * Get the number of pixels per block for a pixel format.
+ */
+uint32_t pixel_format_info_pixels_per_block(const struct wlr_pixel_format_info *info);
+/**
+ * Get the minimum stride for a given pixel format and width.
+ */
+int32_t pixel_format_info_min_stride(const struct wlr_pixel_format_info *info, int32_t width);
+/**
+ * Check whether a stride is large enough for a given pixel format and width.
+ */
+bool pixel_format_info_check_stride(const struct wlr_pixel_format_info *info,
+ int32_t stride, int32_t width);
+
+/**
+ * Convert an enum wl_shm_format to a DRM FourCC.
+ */
+uint32_t convert_wl_shm_format_to_drm(enum wl_shm_format fmt);
+/**
+ * Convert a DRM FourCC to an enum wl_shm_format.
+ */
+enum wl_shm_format convert_drm_format_to_wl_shm(uint32_t fmt);
+
+/**
+ * Return true if the DRM FourCC fmt has an alpha channel, false otherwise.
+ */
+bool pixel_format_has_alpha(uint32_t fmt);
+
+#endif
diff --git a/scenefx/include/scenefx/render/fx_renderer/fx_effect_framebuffers.h b/scenefx/include/scenefx/render/fx_renderer/fx_effect_framebuffers.h
new file mode 100644
index 0000000..0938db4
--- /dev/null
+++ b/scenefx/include/scenefx/render/fx_renderer/fx_effect_framebuffers.h
@@ -0,0 +1,30 @@
+#ifndef _FX_EFFECT_FRAMEBUFFERS_H
+#define _FX_EFFECT_FRAMEBUFFERS_H
+
+#include <wlr/types/wlr_output.h>
+#include <wlr/util/addon.h>
+
+/**
+ * Used to add effect framebuffers per output instead of every output sharing
+ * them.
+ */
+struct fx_effect_framebuffers {
+ struct wlr_addon addon;
+
+ // Contains the blurred background for tiled windows
+ struct fx_framebuffer *optimized_blur_buffer;
+ // Contains the original pixels to draw over the areas where artifact are visible
+ struct fx_framebuffer *blur_saved_pixels_buffer;
+ // Blur swaps between the two effects buffers everytime it scales the image
+ // Buffer used for effects
+ struct fx_framebuffer *effects_buffer;
+ // Swap buffer used for effects
+ struct fx_framebuffer *effects_buffer_swapped;
+
+ // The region where there's blur
+ pixman_region32_t blur_padding_region;
+};
+
+struct fx_effect_framebuffers *fx_effect_framebuffers_try_get(struct wlr_output *output);
+
+#endif
diff --git a/scenefx/include/scenefx/render/fx_renderer/fx_renderer.h b/scenefx/include/scenefx/render/fx_renderer/fx_renderer.h
new file mode 100644
index 0000000..cbd312d
--- /dev/null
+++ b/scenefx/include/scenefx/render/fx_renderer/fx_renderer.h
@@ -0,0 +1,37 @@
+#ifndef SCENEFX_FX_OPENGL_H
+#define SCENEFX_FX_OPENGL_H
+
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+#include <wlr/backend.h>
+#include <wlr/render/interface.h>
+#include <wlr/types/wlr_buffer.h>
+
+struct fx_renderer;
+
+struct wlr_renderer *fx_renderer_create_with_drm_fd(int drm_fd);
+struct wlr_renderer *fx_renderer_create(struct wlr_backend *backend);
+
+struct fx_renderer *fx_get_renderer(struct wlr_renderer *wlr_renderer);
+
+bool fx_renderer_check_ext(struct wlr_renderer *renderer, const char *ext);
+GLuint fx_renderer_get_buffer_fbo(struct wlr_renderer *renderer, struct wlr_buffer *buffer);
+
+//
+// fx_texture
+//
+
+struct fx_texture_attribs {
+ GLenum target; /* either GL_TEXTURE_2D or GL_TEXTURE_EXTERNAL_OES */
+ GLuint tex;
+
+ bool has_alpha;
+};
+
+struct wlr_texture *fx_texture_from_buffer(struct wlr_renderer *wlr_renderer,
+ struct wlr_buffer *buffer);
+
+void fx_texture_get_attribs(struct wlr_texture *texture,
+ struct fx_texture_attribs *attribs);
+
+#endif
diff --git a/scenefx/include/scenefx/render/pass.h b/scenefx/include/scenefx/render/pass.h
new file mode 100644
index 0000000..e8adb68
--- /dev/null
+++ b/scenefx/include/scenefx/render/pass.h
@@ -0,0 +1,163 @@
+#ifndef SCENE_FX_RENDER_PASS_H
+#define SCENE_FX_RENDER_PASS_H
+
+#include <stdbool.h>
+#include <wlr/render/pass.h>
+#include <wlr/render/interface.h>
+#include <wlr/render/swapchain.h>
+
+#include "render/egl.h"
+#include "scenefx/types/fx/clipped_region.h"
+#include "scenefx/types/fx/corner_location.h"
+
+struct fx_gles_render_pass {
+ struct wlr_render_pass base;
+ struct fx_framebuffer *buffer;
+ struct fx_effect_framebuffers *fx_effect_framebuffers;
+ struct wlr_output *output;
+ float projection_matrix[9];
+ struct wlr_egl_context prev_ctx;
+ struct fx_render_timer *timer;
+ struct wlr_drm_syncobj_timeline *signal_timeline;
+ uint64_t signal_point;
+};
+
+struct fx_buffer_pass_options {
+ const struct wlr_buffer_pass_options *base;
+
+ struct wlr_swapchain *swapchain;
+};
+
+/**
+ * Begin a new render pass with the supplied destination buffer.
+ *
+ * Callers must call wlr_render_pass_submit() once they are done with the
+ * render pass.
+ */
+struct fx_gles_render_pass *fx_renderer_begin_buffer_pass(struct wlr_renderer *wlr_renderer,
+ struct wlr_buffer *wlr_buffer, struct wlr_output *output,
+ const struct fx_buffer_pass_options *options);
+
+struct fx_gradient {
+ float degree;
+ /* The full area the gradient fit too, for borders use the window size */
+ struct wlr_box range;
+ /* The center of the gradient, {0.5, 0.5} for normal*/
+ float origin[2];
+ /* 1 = Linear, 2 = Conic */
+ int linear;
+ /* Whether or not to blend the colors */
+ int blend;
+ int count;
+ float *colors;
+};
+
+struct fx_render_texture_options {
+ struct wlr_render_texture_options base;
+ const struct wlr_box *clip_box; // Used to clip csd. Ignored if NULL
+ enum corner_location corners;
+ int corner_radius;
+ bool discard_transparent;
+};
+
+struct fx_render_rect_options {
+ struct wlr_render_rect_options base;
+ struct clipped_region clipped_region;
+};
+
+struct fx_render_rect_grad_options {
+ struct wlr_render_rect_options base;
+ struct fx_gradient gradient;
+};
+
+struct fx_render_rounded_rect_options {
+ struct wlr_render_rect_options base;
+ int corner_radius;
+ enum corner_location corners;
+ struct clipped_region clipped_region;
+ int fade_inset;
+};
+
+struct fx_render_rounded_rect_grad_options {
+ struct wlr_render_rect_options base;
+ struct fx_gradient gradient;
+ int corner_radius;
+ enum corner_location corners;
+};
+
+struct fx_render_box_shadow_options {
+ struct wlr_box box;
+ struct clipped_region clipped_region;
+ /* Clip region, leave NULL to disable clipping */
+ const pixman_region32_t *clip;
+
+ float blur_sigma;
+ int corner_radius;
+ struct wlr_render_color color;
+};
+
+struct fx_render_blur_pass_options {
+ struct fx_render_texture_options tex_options;
+ pixman_region32_t *opaque_region;
+ struct fx_framebuffer *current_buffer;
+ struct blur_data *blur_data;
+ bool use_optimized_blur;
+ bool ignore_transparent;
+};
+
+/**
+ * Render a fx texture.
+ */
+void fx_render_pass_add_texture(struct fx_gles_render_pass *render_pass,
+ const struct fx_render_texture_options *options);
+
+/**
+ * Render a rectangle.
+ */
+void fx_render_pass_add_rect(struct fx_gles_render_pass *render_pass,
+ const struct fx_render_rect_options *options);
+
+/**
+ * Render a rectangle with a gradient.
+ */
+void fx_render_pass_add_rect_grad(struct fx_gles_render_pass *render_pass,
+ const struct fx_render_rect_grad_options *options);
+
+/**
+ * Render a rounded rectangle.
+ */
+void fx_render_pass_add_rounded_rect(struct fx_gles_render_pass *render_pass,
+ const struct fx_render_rounded_rect_options *options);
+
+/**
+ * Render a rounded rectangle with a gradient.
+ */
+void fx_render_pass_add_rounded_rect_grad(struct fx_gles_render_pass *render_pass,
+ const struct fx_render_rounded_rect_grad_options *options);
+
+/**
+ * Render a box shadow.
+ */
+void fx_render_pass_add_box_shadow(struct fx_gles_render_pass *pass,
+ const struct fx_render_box_shadow_options *options);
+
+/**
+ * Render blur.
+ */
+void fx_render_pass_add_blur(struct fx_gles_render_pass *pass,
+ struct fx_render_blur_pass_options *fx_options);
+
+/**
+ * Render optimized blur.
+ */
+bool fx_render_pass_add_optimized_blur(struct fx_gles_render_pass *pass,
+ struct fx_render_blur_pass_options *fx_options);
+
+/**
+ * Render from one buffer to another
+ */
+void fx_renderer_read_to_buffer(struct fx_gles_render_pass *pass,
+ pixman_region32_t *region, struct fx_framebuffer *dst_buffer,
+ struct fx_framebuffer *src_buffer);
+
+#endif
diff --git a/scenefx/include/scenefx/types/fx/blur_data.h b/scenefx/include/scenefx/types/fx/blur_data.h
new file mode 100644
index 0000000..82b3c98
--- /dev/null
+++ b/scenefx/include/scenefx/types/fx/blur_data.h
@@ -0,0 +1,24 @@
+#ifndef TYPES_FX_BLUR_DATA_H
+#define TYPES_FX_BLUR_DATA_H
+
+#include <stdbool.h>
+#include <wlr/util/addon.h>
+
+struct blur_data {
+ int num_passes;
+ int radius;
+ float noise;
+ float brightness;
+ float contrast;
+ float saturation;
+};
+
+struct blur_data blur_data_get_default(void);
+
+bool is_scene_blur_enabled(struct blur_data *blur_data);
+
+bool blur_data_should_parameters_blur_effects(struct blur_data *blur_data);
+
+int blur_data_calc_size(struct blur_data *blur_data);
+
+#endif
diff --git a/scenefx/include/scenefx/types/fx/clipped_region.h b/scenefx/include/scenefx/types/fx/clipped_region.h
new file mode 100644
index 0000000..8449004
--- /dev/null
+++ b/scenefx/include/scenefx/types/fx/clipped_region.h
@@ -0,0 +1,16 @@
+#ifndef TYPES_FX_CLIPPED_REGION_H
+#define TYPES_FX_CLIPPED_REGION_H
+
+#include <wlr/util/box.h>
+
+#include "scenefx/types/fx/corner_location.h"
+
+struct clipped_region {
+ struct wlr_box area;
+ int corner_radius;
+ enum corner_location corners;
+};
+
+struct clipped_region clipped_region_get_default(void);
+
+#endif // !TYPES_FX_CLIPPED_REGION_H
diff --git a/scenefx/include/scenefx/types/fx/corner_location.h b/scenefx/include/scenefx/types/fx/corner_location.h
new file mode 100644
index 0000000..cd94046
--- /dev/null
+++ b/scenefx/include/scenefx/types/fx/corner_location.h
@@ -0,0 +1,26 @@
+#ifndef TYPES_FX_CORNER_LOCATION_H
+#define TYPES_FX_CORNER_LOCATION_H
+
+#include <wayland-server-protocol.h>
+
+#define CORNER_LOCATION_LAST (1 << 3) + 1
+#define CORNER_LOCATION_COUNT 5
+
+enum corner_location {
+ CORNER_LOCATION_NONE = 0,
+ CORNER_LOCATION_TOP_LEFT = 1 << 0,
+ CORNER_LOCATION_TOP_RIGHT = 1 << 1,
+ CORNER_LOCATION_BOTTOM_RIGHT = 1 << 2,
+ CORNER_LOCATION_BOTTOM_LEFT = 1 << 3,
+ CORNER_LOCATION_TOP = CORNER_LOCATION_TOP_LEFT | CORNER_LOCATION_TOP_RIGHT,
+ CORNER_LOCATION_BOTTOM = CORNER_LOCATION_BOTTOM_LEFT | CORNER_LOCATION_BOTTOM_RIGHT,
+ CORNER_LOCATION_LEFT = CORNER_LOCATION_TOP_LEFT | CORNER_LOCATION_BOTTOM_LEFT,
+ CORNER_LOCATION_RIGHT = CORNER_LOCATION_TOP_RIGHT | CORNER_LOCATION_BOTTOM_RIGHT,
+ CORNER_LOCATION_ALL = CORNER_LOCATION_TOP_LEFT | CORNER_LOCATION_TOP_RIGHT
+ | CORNER_LOCATION_BOTTOM_LEFT | CORNER_LOCATION_BOTTOM_RIGHT,
+};
+
+void corner_location_transform(enum wl_output_transform transform,
+ enum corner_location *corners);
+
+#endif // !TYPES_FX_CORNER_LOCATION_H
diff --git a/scenefx/include/scenefx/types/wlr_scene.h b/scenefx/include/scenefx/types/wlr_scene.h
new file mode 100644
index 0000000..d1011c7
--- /dev/null
+++ b/scenefx/include/scenefx/types/wlr_scene.h
@@ -0,0 +1,891 @@
+/*
+ * This an unstable interface of wlroots. No guarantees are made regarding the
+ * future consistency of this API.
+ */
+#ifndef WLR_USE_UNSTABLE
+#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features"
+#endif
+
+#ifndef WLR_TYPES_WLR_SCENE_H
+#define WLR_TYPES_WLR_SCENE_H
+
+/**
+ * The scene-graph API provides a declarative way to display surfaces. The
+ * compositor creates a scene, adds surfaces, then renders the scene on
+ * outputs.
+ *
+ * The scene-graph API only supports basic 2D composition operations (like the
+ * KMS API or the Wayland protocol does). For anything more complicated,
+ * compositors need to implement custom rendering logic.
+ */
+
+#include <pixman.h>
+#include <time.h>
+#include <wayland-server-core.h>
+#include <wlr/render/wlr_renderer.h>
+#include <wlr/types/wlr_damage_ring.h>
+#include <wlr/types/wlr_linux_dmabuf_v1.h>
+#include <wlr/util/addon.h>
+#include <wlr/util/box.h>
+
+#include "scenefx/types/fx/blur_data.h"
+#include "scenefx/types/fx/clipped_region.h"
+#include "scenefx/types/fx/corner_location.h"
+
+struct wlr_output;
+struct wlr_output_layout;
+struct wlr_output_layout_output;
+struct wlr_xdg_surface;
+struct wlr_layer_surface_v1;
+struct wlr_drag_icon;
+struct wlr_surface;
+
+struct wlr_scene_node;
+struct wlr_scene_buffer;
+struct wlr_scene_output_layout;
+
+struct wlr_presentation;
+struct wlr_linux_dmabuf_v1;
+struct wlr_output_state;
+
+typedef bool (*wlr_scene_buffer_point_accepts_input_func_t)(
+ struct wlr_scene_buffer *buffer, double *sx, double *sy);
+
+typedef void (*wlr_scene_buffer_iterator_func_t)(
+ struct wlr_scene_buffer *buffer, int sx, int sy, void *user_data);
+
+enum wlr_scene_node_type {
+ WLR_SCENE_NODE_TREE,
+ WLR_SCENE_NODE_RECT,
+ WLR_SCENE_NODE_SHADOW,
+ WLR_SCENE_NODE_BUFFER,
+ WLR_SCENE_NODE_OPTIMIZED_BLUR,
+};
+
+/** A node is an object in the scene. */
+struct wlr_scene_node {
+ enum wlr_scene_node_type type;
+ struct wlr_scene_tree *parent;
+
+ struct wl_list link; // wlr_scene_tree.children
+
+ bool enabled;
+ int x, y; // relative to parent
+
+ struct {
+ struct wl_signal destroy;
+ } events;
+
+ void *data;
+
+ struct wlr_addon_set addons;
+
+ struct {
+ pixman_region32_t visible;
+ } WLR_PRIVATE;
+};
+
+enum wlr_scene_debug_damage_option {
+ WLR_SCENE_DEBUG_DAMAGE_NONE,
+ WLR_SCENE_DEBUG_DAMAGE_RERENDER,
+ WLR_SCENE_DEBUG_DAMAGE_HIGHLIGHT
+};
+
+/** A sub-tree in the scene-graph. */
+struct wlr_scene_tree {
+ struct wlr_scene_node node;
+
+ struct wl_list children; // wlr_scene_node.link
+};
+
+/** The root scene-graph node. */
+struct wlr_scene {
+ struct wlr_scene_tree tree;
+
+ struct wl_list outputs; // wlr_scene_output.link
+
+ // May be NULL
+ struct wlr_linux_dmabuf_v1 *linux_dmabuf_v1;
+ struct wlr_gamma_control_manager_v1 *gamma_control_manager_v1;
+
+ struct {
+ struct wl_listener linux_dmabuf_v1_destroy;
+ struct wl_listener gamma_control_manager_v1_destroy;
+ struct wl_listener gamma_control_manager_v1_set_gamma;
+
+ enum wlr_scene_debug_damage_option debug_damage_option;
+ bool direct_scanout;
+ bool calculate_visibility;
+ bool highlight_transparent_region;
+
+ struct blur_data blur_data;
+ } WLR_PRIVATE;
+};
+
+/** A scene-graph node displaying a single surface. */
+struct wlr_scene_surface {
+ struct wlr_scene_buffer *buffer;
+ struct wlr_surface *surface;
+
+ struct {
+ struct wlr_box clip;
+
+ struct wlr_addon addon;
+
+ struct wl_listener outputs_update;
+ struct wl_listener output_enter;
+ struct wl_listener output_leave;
+ struct wl_listener output_sample;
+ struct wl_listener frame_done;
+ struct wl_listener surface_destroy;
+ struct wl_listener surface_commit;
+ } WLR_PRIVATE;
+};
+
+/** A scene-graph node displaying a solid-colored rectangle */
+struct wlr_scene_rect {
+ struct wlr_scene_node node;
+ int width, height;
+ float color[4];
+ int corner_radius;
+ enum corner_location corners;
+ bool backdrop_blur;
+ bool backdrop_blur_optimized;
+
+ bool accepts_input;
+ struct clipped_region clipped_region;
+ int fade_inset;
+};
+
+/** A scene-graph node displaying a shadow */
+struct wlr_scene_shadow {
+ struct wlr_scene_node node;
+ int width, height;
+ int corner_radius;
+ float color[4];
+ float blur_sigma;
+
+ struct clipped_region clipped_region;
+};
+
+/** A scene-graph node telling SceneFX to render the optimized blur */
+struct wlr_scene_optimized_blur {
+ struct wlr_scene_node node;
+ int width, height;
+
+ bool dirty;
+};
+
+struct wlr_scene_outputs_update_event {
+ struct wlr_scene_output **active;
+ size_t size;
+};
+
+struct wlr_scene_output_sample_event {
+ struct wlr_scene_output *output;
+ bool direct_scanout;
+};
+
+/** A scene-graph node displaying a buffer */
+struct wlr_scene_buffer {
+ struct wlr_scene_node node;
+
+ // May be NULL
+ struct wlr_buffer *buffer;
+
+ struct {
+ struct wl_signal outputs_update; // struct wlr_scene_outputs_update_event
+ struct wl_signal output_enter; // struct wlr_scene_output
+ struct wl_signal output_leave; // struct wlr_scene_output
+ struct wl_signal output_sample; // struct wlr_scene_output_sample_event
+ struct wl_signal frame_done; // struct timespec
+ } events;
+
+ // May be NULL
+ wlr_scene_buffer_point_accepts_input_func_t point_accepts_input;
+
+ /**
+ * The output that the largest area of this buffer is displayed on.
+ * This may be NULL if the buffer is not currently displayed on any
+ * outputs. This is the output that should be used for frame callbacks,
+ * presentation feedback, etc.
+ */
+ struct wlr_scene_output *primary_output;
+
+ int corner_radius;
+ bool backdrop_blur;
+ bool backdrop_blur_optimized;
+ bool backdrop_blur_ignore_transparent;
+ enum corner_location corners;
+
+ float opacity;
+ enum wlr_scale_filter_mode filter_mode;
+ struct wlr_fbox src_box;
+ int dst_width, dst_height;
+ enum wl_output_transform transform;
+ pixman_region32_t opaque_region;
+
+ struct {
+ uint64_t active_outputs;
+ struct wlr_texture *texture;
+ struct wlr_linux_dmabuf_feedback_v1_init_options prev_feedback_options;
+
+ bool own_buffer;
+ int buffer_width, buffer_height;
+ bool buffer_is_opaque;
+
+ struct wlr_drm_syncobj_timeline *wait_timeline;
+ uint64_t wait_point;
+
+ struct wl_listener buffer_release;
+ struct wl_listener renderer_destroy;
+
+ // True if the underlying buffer is a wlr_single_pixel_buffer_v1
+ bool is_single_pixel_buffer;
+ // If is_single_pixel_buffer is set, contains the color of the buffer
+ // as {R, G, B, A} where the max value of each component is UINT32_MAX
+ uint32_t single_pixel_buffer_color[4];
+ } WLR_PRIVATE;
+};
+
+/** A viewport for an output in the scene-graph */
+struct wlr_scene_output {
+ struct wlr_output *output;
+ struct wl_list link; // wlr_scene.outputs
+ struct wlr_scene *scene;
+ struct wlr_addon addon;
+
+ struct wlr_damage_ring damage_ring;
+
+ int x, y;
+
+ struct {
+ struct wl_signal destroy;
+ } events;
+
+ struct {
+ pixman_region32_t pending_commit_damage;
+
+ uint8_t index;
+
+ /**
+ * When scanout is applicable, we increment this every time a frame is rendered until
+ * DMABUF_FEEDBACK_DEBOUNCE_FRAMES is hit to debounce the scanout dmabuf feedback. Likewise,
+ * when scanout is no longer applicable, we decrement this until zero is hit to debounce
+ * composition dmabuf feedback.
+ */
+ uint8_t dmabuf_feedback_debounce;
+ bool prev_scanout;
+
+ bool gamma_lut_changed;
+ struct wlr_gamma_control_v1 *gamma_lut;
+
+ struct wl_listener output_commit;
+ struct wl_listener output_damage;
+ struct wl_listener output_needs_frame;
+
+ struct wl_list damage_highlight_regions;
+
+ struct wl_array render_list;
+
+ struct wlr_drm_syncobj_timeline *in_timeline;
+ uint64_t in_point;
+ } WLR_PRIVATE;
+};
+
+struct wlr_scene_timer {
+ int64_t pre_render_duration;
+ struct wlr_render_timer *render_timer;
+};
+
+/** A layer shell scene helper */
+struct wlr_scene_layer_surface_v1 {
+ struct wlr_scene_tree *tree;
+ struct wlr_layer_surface_v1 *layer_surface;
+
+ struct {
+ struct wl_listener tree_destroy;
+ struct wl_listener layer_surface_destroy;
+ struct wl_listener layer_surface_map;
+ struct wl_listener layer_surface_unmap;
+ } WLR_PRIVATE;
+};
+
+/**
+ * Immediately destroy the scene-graph node.
+ */
+void wlr_scene_node_destroy(struct wlr_scene_node *node);
+/**
+ * Enable or disable this node. If a node is disabled, all of its children are
+ * implicitly disabled as well.
+ */
+void wlr_scene_node_set_enabled(struct wlr_scene_node *node, bool enabled);
+/**
+ * Set the position of the node relative to its parent.
+ */
+void wlr_scene_node_set_position(struct wlr_scene_node *node, int x, int y);
+/**
+ * Move the node right above the specified sibling.
+ * Asserts that node and sibling are distinct and share the same parent.
+ */
+void wlr_scene_node_place_above(struct wlr_scene_node *node,
+ struct wlr_scene_node *sibling);
+/**
+ * Move the node right below the specified sibling.
+ * Asserts that node and sibling are distinct and share the same parent.
+ */
+void wlr_scene_node_place_below(struct wlr_scene_node *node,
+ struct wlr_scene_node *sibling);
+/**
+ * Move the node above all of its sibling nodes.
+ */
+void wlr_scene_node_raise_to_top(struct wlr_scene_node *node);
+/**
+ * Move the node below all of its sibling nodes.
+ */
+void wlr_scene_node_lower_to_bottom(struct wlr_scene_node *node);
+/**
+ * Move the node to another location in the tree.
+ */
+void wlr_scene_node_reparent(struct wlr_scene_node *node,
+ struct wlr_scene_tree *new_parent);
+/**
+ * Get the node's layout-local coordinates.
+ *
+ * True is returned if the node and all of its ancestors are enabled.
+ */
+bool wlr_scene_node_coords(struct wlr_scene_node *node, int *lx, int *ly);
+/**
+ * Call `iterator` on each buffer in the scene-graph, with the buffer's
+ * position in layout coordinates. The function is called from root to leaves
+ * (in rendering order).
+ */
+void wlr_scene_node_for_each_buffer(struct wlr_scene_node *node,
+ wlr_scene_buffer_iterator_func_t iterator, void *user_data);
+/**
+ * Find the topmost node in this scene-graph that contains the point at the
+ * given layout-local coordinates. (For surface nodes, this means accepting
+ * input events at that point.) Returns the node and coordinates relative to the
+ * returned node, or NULL if no node is found at that location.
+ */
+struct wlr_scene_node *wlr_scene_node_at(struct wlr_scene_node *node,
+ double lx, double ly, double *nx, double *ny);
+
+/**
+ * Create a new scene-graph.
+ *
+ * The graph is also a struct wlr_scene_node. Associated resources can be
+ * destroyed through wlr_scene_node_destroy().
+ */
+struct wlr_scene *wlr_scene_create(void);
+
+
+// Sets the global blur parameters
+void wlr_scene_set_blur_data(struct wlr_scene *scene, int num_passes,
+ int radius, float noise, float brightness, float contrast, float saturation);
+
+// Sets the global blur num_passes parameter
+void wlr_scene_set_blur_num_passes(struct wlr_scene *scene, int num_passes);
+
+// Sets the global blur radius parameter
+void wlr_scene_set_blur_radius(struct wlr_scene *scene, int radius);
+
+// Sets the global blur noise parameter
+void wlr_scene_set_blur_noise(struct wlr_scene *scene, float noise);
+
+// Sets the global blur brightness parameter
+void wlr_scene_set_blur_brightness(struct wlr_scene *scene, float brightness);
+
+// Sets the global blur contrast parameter
+void wlr_scene_set_blur_contrast(struct wlr_scene *scene, float contrast);
+
+// Sets the global blur saturation parameter
+void wlr_scene_set_blur_saturation(struct wlr_scene *scene, float saturation);
+
+/**
+ * Handles linux_dmabuf_v1 feedback for all surfaces in the scene.
+ *
+ * Asserts that a struct wlr_linux_dmabuf_v1 hasn't already been set for the scene.
+ */
+void wlr_scene_set_linux_dmabuf_v1(struct wlr_scene *scene,
+ struct wlr_linux_dmabuf_v1 *linux_dmabuf_v1);
+
+/**
+ * Handles gamma_control_v1 for all outputs in the scene.
+ *
+ * Asserts that a struct wlr_gamma_control_manager_v1 hasn't already been set
+ * for the scene.
+ */
+void wlr_scene_set_gamma_control_manager_v1(struct wlr_scene *scene,
+ struct wlr_gamma_control_manager_v1 *gamma_control);
+
+
+/**
+ * Add a node displaying nothing but its children.
+ */
+struct wlr_scene_tree *wlr_scene_tree_create(struct wlr_scene_tree *parent);
+
+/**
+ * Add a node displaying a single surface to the scene-graph.
+ *
+ * The child sub-surfaces are ignored. See wlr_scene_subsurface_tree_create()
+ *
+ * Note that this helper does multiple things on behalf of the compositor. Some
+ * of these include protocol implementations where compositors just need to enable
+ * the protocols:
+ * - wp_viewporter
+ * - wp_presentation_time
+ * - wp_fractional_scale_v1
+ * - wp_alpha_modifier_v1
+ * - wp_linux_drm_syncobj_v1
+ * - zwp_linux_dmabuf_v1 presentation feedback with wlr_scene_set_linux_dmabuf_v1()
+ *
+ * This helper will also transparently:
+ * - Send preferred buffer scale¹
+ * - Send preferred buffer transform¹
+ * - Restack xwayland surfaces. See wlr_xwayland_surface_restack()²
+ * - Send output enter/leave events.
+ *
+ * ¹ Note that scale and transform sent to the surface will be based on the output
+ * which has the largest visible surface area. Intelligent visibility calculations
+ * influence this.
+ * ² xwayland stacking order is undefined when the xwayland surfaces do not
+ * intersect.
+ */
+struct wlr_scene_surface *wlr_scene_surface_create(struct wlr_scene_tree *parent,
+ struct wlr_surface *surface);
+
+/**
+ * If this node represents a wlr_scene_buffer, that buffer will be returned. It
+ * is not legal to feed a node that does not represent a wlr_scene_buffer.
+ */
+struct wlr_scene_buffer *wlr_scene_buffer_from_node(struct wlr_scene_node *node);
+
+/**
+ * If this node represents a wlr_scene_tree, that tree will be returned. It
+ * is not legal to feed a node that does not represent a wlr_scene_tree.
+ */
+struct wlr_scene_tree *wlr_scene_tree_from_node(struct wlr_scene_node *node);
+
+/**
+ * If this node represents a wlr_scene_rect, that rect will be returned. It
+ * is not legal to feed a node that does not represent a wlr_scene_rect.
+ */
+struct wlr_scene_rect *wlr_scene_rect_from_node(struct wlr_scene_node *node);
+
+/**
+ * If this node represents a wlr_scene_shadow, that shadow will be returned. It
+ * is not legal to feed a node that does not represent a wlr_scene_shadow.
+ */
+struct wlr_scene_shadow *wlr_scene_shadow_from_node(struct wlr_scene_node *node);
+
+/**
+ * If this buffer is backed by a surface, then the struct wlr_scene_surface is
+ * returned. If not, NULL will be returned.
+ */
+struct wlr_scene_surface *wlr_scene_surface_try_from_buffer(
+ struct wlr_scene_buffer *scene_buffer);
+
+/**
+ * Add a node displaying a solid-colored rectangle to the scene-graph.
+ *
+ * The color argument must be a premultiplied color value.
+ */
+struct wlr_scene_rect *wlr_scene_rect_create(struct wlr_scene_tree *parent,
+ int width, int height, const float color[static 4]);
+
+/**
+ * Change the width and height of an existing rectangle node.
+ */
+void wlr_scene_rect_set_size(struct wlr_scene_rect *rect, int width, int height);
+
+/**
+ * Change the corner radius of an existing rectangle node.
+ */
+void wlr_scene_rect_set_corner_radius(struct wlr_scene_rect *rect, int corner_radius,
+ enum corner_location corners);
+
+/**
+ * Sets the region where to clip the rect.
+ *
+ * For there to be corner rounding of the clipped region, the corner radius and
+ * corners must be non-zero.
+ *
+ * NOTE: The positioning is node-relative.
+ */
+void wlr_scene_rect_set_clipped_region(struct wlr_scene_rect *rect,
+ struct clipped_region clipped_region);
+
+/**
+ * Change the color of an existing rectangle node.
+ *
+ * The color argument must be a premultiplied color value.
+ */
+void wlr_scene_rect_set_color(struct wlr_scene_rect *rect, const float color[static 4]);
+
+/**
+* Sets whether or not the buffer should render backdrop blur
+*/
+void wlr_scene_rect_set_backdrop_blur(struct wlr_scene_rect *rect,
+ bool enabled);
+
+/**
+* Sets whether the backdrop blur should use optimized blur or not
+*/
+void wlr_scene_rect_set_backdrop_blur_optimized(struct wlr_scene_rect *rect,
+ bool enabled);
+
+/**
+ * Sets the fade inset size for the rectangle's borders.
+ */
+void wlr_scene_rect_set_fade_inset(struct wlr_scene_rect *rect, int fade_inset);
+
+/**
+ * Add a node displaying a shadow to the scene-graph.
+ */
+struct wlr_scene_shadow *wlr_scene_shadow_create(struct wlr_scene_tree *parent,
+ int width, int height, int corner_radius, float blur_sigma,
+ const float color[static 4]);
+
+/**
+ * Change the width and height of an existing shadow node.
+ */
+void wlr_scene_shadow_set_size(struct wlr_scene_shadow *shadow, int width, int height);
+
+/**
+ * Change the corner radius of an existing shadow node.
+ */
+void wlr_scene_shadow_set_corner_radius(struct wlr_scene_shadow *shadow, int corner_radius);
+
+/**
+ * Change the blur_sigma of an existing shadow node.
+ */
+void wlr_scene_shadow_set_blur_sigma(struct wlr_scene_shadow *shadow, float blur_sigma);
+
+/**
+ * Change the color of an existing shadow node.
+ */
+void wlr_scene_shadow_set_color(struct wlr_scene_shadow *shadow, const float color[static 4]);
+
+/**
+ * Sets the region where to clip the shadow.
+ *
+ * For there to be corner rounding of the clipped region, the corner radius and
+ * corners must be non-zero.
+ *
+ * NOTE: The positioning is node-relative.
+ */
+void wlr_scene_shadow_set_clipped_region(struct wlr_scene_shadow *shadow,
+ struct clipped_region clipped_region);
+
+/**
+ * If this node represents a wlr_scene_optimized_blur node, that node will
+ * be returned. It is not legal to feed a node that does not represent
+ * a wlr_scene_optimized_blur.
+ */
+struct wlr_scene_optimized_blur *wlr_scene_optimized_blur_from_node(
+ struct wlr_scene_node *node);
+
+/**
+ * Add a node indicating to the renderer to render optimized blur to the scene-graph.
+ * NOTE: Has to be positioned where to draw the optimized blur. This allows
+ * for unique effects like only blurring half of the output.
+ */
+struct wlr_scene_optimized_blur *wlr_scene_optimized_blur_create(struct wlr_scene_tree *parent,
+ int width, int height);
+
+/**
+ * Change the width and height of an existing blur node.
+ * This calls `wlr_scene_optimized_blur_mark_dirty` as well
+ */
+void wlr_scene_optimized_blur_set_size(struct wlr_scene_optimized_blur *blur_node,
+ int width, int height);
+
+/**
+ * Tells the renderer to re-render the optimized blur.
+ * This is very expensive so should only be called when needed.
+ *
+ * An example use would be to call this when a "static" node changes, like a
+ * wallpaper.
+ */
+void wlr_scene_optimized_blur_mark_dirty(struct wlr_scene_optimized_blur *blur_node);
+
+/**
+ * Add a node displaying a buffer to the scene-graph.
+ *
+ * If the buffer is NULL, this node will not be displayed.
+ */
+struct wlr_scene_buffer *wlr_scene_buffer_create(struct wlr_scene_tree *parent,
+ struct wlr_buffer *buffer);
+
+/**
+ * Sets the buffer's backing buffer.
+ *
+ * If the buffer is NULL, the buffer node will not be displayed.
+ */
+void wlr_scene_buffer_set_buffer(struct wlr_scene_buffer *scene_buffer,
+ struct wlr_buffer *buffer);
+
+/**
+ * Sets the buffer's backing buffer with a custom damage region.
+ *
+ * The damage region is in buffer-local coordinates. If the region is NULL,
+ * the whole buffer node will be damaged.
+ */
+void wlr_scene_buffer_set_buffer_with_damage(struct wlr_scene_buffer *scene_buffer,
+ struct wlr_buffer *buffer, const pixman_region32_t *region);
+
+/**
+ * Options for wlr_scene_buffer_set_buffer_with_options().
+ */
+struct wlr_scene_buffer_set_buffer_options {
+ // The damage region is in buffer-local coordinates. If the region is NULL,
+ // the whole buffer node will be damaged.
+ const pixman_region32_t *damage;
+
+ // Wait for a timeline synchronization point before reading from the buffer.
+ struct wlr_drm_syncobj_timeline *wait_timeline;
+ uint64_t wait_point;
+};
+
+/**
+ * Sets the buffer's backing buffer.
+ *
+ * If the buffer is NULL, the buffer node will not be displayed. If options is
+ * NULL, empty options are used.
+ */
+void wlr_scene_buffer_set_buffer_with_options(struct wlr_scene_buffer *scene_buffer,
+ struct wlr_buffer *buffer, const struct wlr_scene_buffer_set_buffer_options *options);
+
+/**
+ * Sets the buffer's opaque region. This is an optimization hint used to
+ * determine if buffers which reside under this one need to be rendered or not.
+ */
+void wlr_scene_buffer_set_opaque_region(struct wlr_scene_buffer *scene_buffer,
+ const pixman_region32_t *region);
+
+/**
+ * Set the source rectangle describing the region of the buffer which will be
+ * sampled to render this node. This allows cropping the buffer.
+ *
+ * If NULL, the whole buffer is sampled. By default, the source box is NULL.
+ */
+void wlr_scene_buffer_set_source_box(struct wlr_scene_buffer *scene_buffer,
+ const struct wlr_fbox *box);
+
+/**
+ * Set the destination size describing the region of the scene-graph the buffer
+ * will be painted onto. This allows scaling the buffer.
+ *
+ * If zero, the destination size will be the buffer size. By default, the
+ * destination size is zero.
+ */
+void wlr_scene_buffer_set_dest_size(struct wlr_scene_buffer *scene_buffer,
+ int width, int height);
+
+/**
+ * Set a transform which will be applied to the buffer.
+ */
+void wlr_scene_buffer_set_transform(struct wlr_scene_buffer *scene_buffer,
+ enum wl_output_transform transform);
+
+/**
+* Sets the opacity of this buffer
+*/
+void wlr_scene_buffer_set_opacity(struct wlr_scene_buffer *scene_buffer,
+ float opacity);
+
+/**
+* Sets the filter mode to use when scaling the buffer
+*/
+void wlr_scene_buffer_set_filter_mode(struct wlr_scene_buffer *scene_buffer,
+ enum wlr_scale_filter_mode filter_mode);
+
+/**
+* Sets the corner radius and which corners to round of this buffer
+*/
+void wlr_scene_buffer_set_corner_radius(struct wlr_scene_buffer *scene_buffer,
+ int radii, enum corner_location corners);
+
+/**
+* Sets whether or not the buffer should render backdrop blur
+*/
+void wlr_scene_buffer_set_backdrop_blur(struct wlr_scene_buffer *scene_buffer,
+ bool enabled);
+
+/**
+* Sets whether the backdrop blur should use optimized blur or not
+*/
+void wlr_scene_buffer_set_backdrop_blur_optimized(struct wlr_scene_buffer *scene_buffer,
+ bool enabled);
+
+/**
+* Sets whether the backdrop blur should not render in fully transparent
+* segments.
+*/
+void wlr_scene_buffer_set_backdrop_blur_ignore_transparent(
+ struct wlr_scene_buffer *scene_buffer, bool enabled);
+
+/**
+ * Calls the buffer's frame_done signal.
+ */
+void wlr_scene_buffer_send_frame_done(struct wlr_scene_buffer *scene_buffer,
+ struct timespec *now);
+
+/**
+ * Add a viewport for the specified output to the scene-graph.
+ *
+ * An output can only be added once to the scene-graph.
+ */
+struct wlr_scene_output *wlr_scene_output_create(struct wlr_scene *scene,
+ struct wlr_output *output);
+/**
+ * Destroy a scene-graph output.
+ */
+void wlr_scene_output_destroy(struct wlr_scene_output *scene_output);
+/**
+ * Set the output's position in the scene-graph.
+ */
+void wlr_scene_output_set_position(struct wlr_scene_output *scene_output,
+ int lx, int ly);
+
+struct wlr_scene_output_state_options {
+ struct wlr_scene_timer *timer;
+ struct wlr_color_transform *color_transform;
+
+ /**
+ * Allows use of a custom swapchain. This can be useful when trying out an
+ * output configuration. The swapchain dimensions must match the respective
+ * wlr_output_state or output size if not specified.
+ */
+ struct wlr_swapchain *swapchain;
+};
+
+/**
+ * Returns true if scene wants to render a new frame. False, if no new frame
+ * is needed and an output commit can be skipped for the current frame.
+ */
+bool wlr_scene_output_needs_frame(struct wlr_scene_output *scene_output);
+
+/**
+ * Render and commit an output.
+ */
+bool wlr_scene_output_commit(struct wlr_scene_output *scene_output,
+ const struct wlr_scene_output_state_options *options);
+
+/**
+ * Render and populate given output state.
+ */
+bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output,
+ struct wlr_output_state *state, const struct wlr_scene_output_state_options *options);
+
+/**
+ * Retrieve the duration in nanoseconds between the last wlr_scene_output_commit() call and the end
+ * of its operations, including those on the GPU that may have finished after the call returned.
+ *
+ * Returns -1 if the duration is unavailable.
+ */
+int64_t wlr_scene_timer_get_duration_ns(struct wlr_scene_timer *timer);
+void wlr_scene_timer_finish(struct wlr_scene_timer *timer);
+
+/**
+ * Call wlr_surface_send_frame_done() on all surfaces in the scene rendered by
+ * wlr_scene_output_commit() for which wlr_scene_surface.primary_output
+ * matches the given scene_output.
+ */
+void wlr_scene_output_send_frame_done(struct wlr_scene_output *scene_output,
+ struct timespec *now);
+/**
+ * Call `iterator` on each buffer in the scene-graph visible on the output,
+ * with the buffer's position in layout coordinates. The function is called
+ * from root to leaves (in rendering order).
+ */
+void wlr_scene_output_for_each_buffer(struct wlr_scene_output *scene_output,
+ wlr_scene_buffer_iterator_func_t iterator, void *user_data);
+/**
+ * Get a scene-graph output from a struct wlr_output.
+ *
+ * If the output hasn't been added to the scene-graph, returns NULL.
+ */
+struct wlr_scene_output *wlr_scene_get_scene_output(struct wlr_scene *scene,
+ struct wlr_output *output);
+
+/**
+ * Attach an output layout to a scene.
+ *
+ * The resulting scene output layout allows to synchronize the positions of scene
+ * outputs with the positions of corresponding layout outputs.
+ *
+ * It is automatically destroyed when the scene or the output layout is destroyed.
+ */
+struct wlr_scene_output_layout *wlr_scene_attach_output_layout(struct wlr_scene *scene,
+ struct wlr_output_layout *output_layout);
+
+/**
+ * Add an output to the scene output layout.
+ *
+ * When the layout output is repositioned, the scene output will be repositioned
+ * accordingly.
+ */
+void wlr_scene_output_layout_add_output(struct wlr_scene_output_layout *sol,
+ struct wlr_output_layout_output *lo, struct wlr_scene_output *so);
+
+/**
+ * Add a node displaying a surface and all of its sub-surfaces to the
+ * scene-graph.
+ */
+struct wlr_scene_tree *wlr_scene_subsurface_tree_create(
+ struct wlr_scene_tree *parent, struct wlr_surface *surface);
+
+/**
+ * Sets a cropping region for any subsurface trees that are children of this
+ * scene node. The clip coordinate space will be that of the root surface of
+ * the subsurface tree.
+ *
+ * A NULL or empty clip will disable clipping
+ */
+void wlr_scene_subsurface_tree_set_clip(struct wlr_scene_node *node,
+ const struct wlr_box *clip);
+
+/**
+ * Add a node displaying an xdg_surface and all of its sub-surfaces to the
+ * scene-graph.
+ *
+ * The origin of the returned scene-graph node will match the top-left corner
+ * of the xdg_surface window geometry.
+ */
+struct wlr_scene_tree *wlr_scene_xdg_surface_create(
+ struct wlr_scene_tree *parent, struct wlr_xdg_surface *xdg_surface);
+
+/**
+ * Add a node displaying a layer_surface_v1 and all of its sub-surfaces to the
+ * scene-graph.
+ *
+ * The origin of the returned scene-graph node will match the top-left corner
+ * of the layer surface.
+ */
+struct wlr_scene_layer_surface_v1 *wlr_scene_layer_surface_v1_create(
+ struct wlr_scene_tree *parent, struct wlr_layer_surface_v1 *layer_surface);
+
+/**
+ * Configure a layer_surface_v1, position its scene node in accordance to its
+ * current state, and update the remaining usable area.
+ *
+ * full_area represents the entire area that may be used by the layer surface
+ * if its exclusive_zone is -1, and is usually the output dimensions.
+ * usable_area represents what remains of full_area that can be used if
+ * exclusive_zone is >= 0. usable_area is updated if the surface has a positive
+ * exclusive_zone, so that it can be used for the next layer surface.
+ */
+void wlr_scene_layer_surface_v1_configure(
+ struct wlr_scene_layer_surface_v1 *scene_layer_surface,
+ const struct wlr_box *full_area, struct wlr_box *usable_area);
+
+/**
+ * Add a node displaying a drag icon and all its sub-surfaces to the
+ * scene-graph.
+ */
+struct wlr_scene_tree *wlr_scene_drag_icon_create(
+ struct wlr_scene_tree *parent, struct wlr_drag_icon *drag_icon);
+
+#endif
diff --git a/scenefx/include/types/wlr_output.h b/scenefx/include/types/wlr_output.h
new file mode 100644
index 0000000..b239b6a
--- /dev/null
+++ b/scenefx/include/types/wlr_output.h
@@ -0,0 +1,10 @@
+#ifndef TYPES_WLR_OUTPUT_H
+#define TYPES_WLR_OUTPUT_H
+
+#include <wlr/render/drm_format_set.h>
+#include <wlr/types/wlr_output.h>
+
+void output_pending_resolution(struct wlr_output *output,
+ const struct wlr_output_state *state, int *width, int *height);
+
+#endif
diff --git a/scenefx/include/types/wlr_scene.h b/scenefx/include/types/wlr_scene.h
new file mode 100644
index 0000000..80dcfd1
--- /dev/null
+++ b/scenefx/include/types/wlr_scene.h
@@ -0,0 +1,10 @@
+#ifndef TYPES_WLR_SCENE_H
+#define TYPES_WLR_SCENE_H
+
+#include <wlr/types/wlr_scene.h>
+
+struct wlr_scene *scene_node_get_root(struct wlr_scene_node *node);
+
+void scene_surface_set_clip(struct wlr_scene_surface *surface, struct wlr_box *clip);
+
+#endif
diff --git a/scenefx/include/util/array.h b/scenefx/include/util/array.h
new file mode 100644
index 0000000..a51bdb6
--- /dev/null
+++ b/scenefx/include/util/array.h
@@ -0,0 +1,18 @@
+#ifndef UTIL_ARRAY_H
+#define UTIL_ARRAY_H
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include <wayland-util.h>
+
+/**
+ * Remove a chunk of memory of the specified size at the specified offset.
+ */
+void array_remove_at(struct wl_array *arr, size_t offset, size_t size);
+
+/**
+ * Grow or shrink the array to fit the specifized size.
+ */
+bool array_realloc(struct wl_array *arr, size_t size);
+
+#endif
diff --git a/scenefx/include/util/env.h b/scenefx/include/util/env.h
new file mode 100644
index 0000000..e271f4b
--- /dev/null
+++ b/scenefx/include/util/env.h
@@ -0,0 +1,23 @@
+#ifndef UTIL_ENV_H
+#define UTIL_ENV_H
+
+#include <stdbool.h>
+#include <unistd.h>
+
+/**
+ * Parse a bool from an environment variable.
+ *
+ * On success, the parsed value is returned. On error, false is returned.
+ */
+bool env_parse_bool(const char *option);
+
+/**
+ * Pick a choice from an environment variable.
+ *
+ * On success, the choice index is returned. On error, zero is returned.
+ *
+ * switches is a NULL-terminated array.
+ */
+size_t env_parse_switch(const char *option, const char **switches);
+
+#endif
diff --git a/scenefx/include/util/matrix.h b/scenefx/include/util/matrix.h
new file mode 100644
index 0000000..c45c99a
--- /dev/null
+++ b/scenefx/include/util/matrix.h
@@ -0,0 +1,49 @@
+#ifndef UTIL_MATRIX_H
+#define UTIL_MATRIX_H
+
+#include <wayland-server-protocol.h>
+
+struct wlr_box;
+
+/** Writes the identity matrix into mat */
+void wlr_matrix_identity(float mat[static 9]);
+
+/** mat ← a × b */
+void wlr_matrix_multiply(float mat[static 9], const float a[static 9],
+ const float b[static 9]);
+
+/** Writes a 2D translation matrix to mat of magnitude (x, y) */
+void wlr_matrix_translate(float mat[static 9], float x, float y);
+
+/** Writes a 2D scale matrix to mat of magnitude (x, y) */
+void wlr_matrix_scale(float mat[static 9], float x, float y);
+
+/** Writes a transformation matrix which applies the specified
+ * wl_output_transform to mat */
+void wlr_matrix_transform(float mat[static 9],
+ enum wl_output_transform transform);
+
+/** Shortcut for the various matrix operations involved in projecting the
+ * specified wlr_box onto a given orthographic projection with a given
+ * rotation. The result is written to mat, which can be applied to each
+ * coordinate of the box to get a new coordinate from [-1,1]. */
+void wlr_matrix_project_box(float mat[static 9], const struct wlr_box *box,
+ enum wl_output_transform transform, const float projection[static 9]);
+
+/**
+ * Writes a 2D orthographic projection matrix to mat of (width, height) with a
+ * specified wl_output_transform.
+ *
+ * Equivalent to glOrtho(0, width, 0, height, 1, -1) with the transform applied.
+ */
+void matrix_projection(float mat[static 9], int width, int height,
+ enum wl_output_transform transform);
+
+/**
+ * Compute the inverse of a matrix.
+ *
+ * The matrix needs to be inversible.
+ */
+void matrix_invert(float out[static 9], float m[static 9]);
+
+#endif
diff --git a/scenefx/include/util/time.h b/scenefx/include/util/time.h
new file mode 100644
index 0000000..dd164bf
--- /dev/null
+++ b/scenefx/include/util/time.h
@@ -0,0 +1,35 @@
+#ifndef UTIL_TIME_H
+#define UTIL_TIME_H
+
+#include <stdint.h>
+#include <time.h>
+
+static const long NSEC_PER_SEC = 1000000000;
+
+/**
+ * Get the current time, in milliseconds.
+ */
+int64_t get_current_time_msec(void);
+
+/**
+ * Convert a timespec to milliseconds.
+ */
+int64_t timespec_to_msec(const struct timespec *a);
+
+/**
+ * Convert a timespec to nanoseconds.
+ */
+int64_t timespec_to_nsec(const struct timespec *a);
+
+/**
+ * Convert nanoseconds to a timespec.
+ */
+void timespec_from_nsec(struct timespec *r, int64_t nsec);
+
+/**
+ * Subtracts timespec `b` from timespec `a`, and stores the difference in `r`.
+ */
+void timespec_sub(struct timespec *r, const struct timespec *a,
+ const struct timespec *b);
+
+#endif
diff --git a/scenefx/meson.build b/scenefx/meson.build
new file mode 100644
index 0000000..4198ce0
--- /dev/null
+++ b/scenefx/meson.build
@@ -0,0 +1,164 @@
+project(
+ 'scenefx',
+ 'c',
+ version: '0.4.1',
+ license: 'MIT',
+ meson_version: '>=1.3',
+ default_options: [
+ 'c_std=' + (meson.version().version_compare('>=1.4.0') ? 'c23,c11' : 'c11'),
+ 'warning_level=2',
+ 'werror=true',
+ 'wrap_mode=nodownload',
+ ],
+)
+
+version = meson.project_version().split('-')[0]
+version_major = version.split('.')[0]
+version_minor = version.split('.')[1]
+versioned_name = '@0@-@1@.@2@'.format(meson.project_name(), version_major, version_minor)
+
+little_endian = target_machine.endian() == 'little'
+big_endian = target_machine.endian() == 'big'
+
+add_project_arguments([
+ '-D_POSIX_C_SOURCE=200809L',
+ '-DWLR_USE_UNSTABLE',
+ '-DWLR_PRIVATE=',
+ '-DWLR_LITTLE_ENDIAN=@0@'.format(little_endian.to_int()),
+ '-DWLR_BIG_ENDIAN=@0@'.format(big_endian.to_int()),
+], language: 'c')
+
+cc = meson.get_compiler('c')
+
+add_project_arguments(cc.get_supported_arguments([
+ '-Wundef',
+ '-Wlogical-op',
+ '-Wmissing-include-dirs',
+ '-Wold-style-definition',
+ '-Wpointer-arith',
+ '-Winit-self',
+ '-Wstrict-prototypes',
+ '-Wimplicit-fallthrough=2',
+ '-Wendif-labels',
+ '-Wstrict-aliasing=2',
+ '-Woverflow',
+ '-Wmissing-prototypes',
+ '-Walloca',
+
+ '-Wno-missing-braces',
+ '-Wno-missing-field-initializers',
+ '-Wno-unused-parameter',
+]), language: 'c')
+
+fs = import('fs')
+
+# Strip relative path prefixes from the code if possible, otherwise hide them.
+relative_dir = fs.relative_to(meson.current_source_dir(), meson.global_build_root()) + '/'
+if cc.has_argument('-fmacro-prefix-map=/prefix/to/hide=')
+ add_project_arguments(
+ '-fmacro-prefix-map=@0@='.format(relative_dir),
+ language: 'c',
+ )
+else
+ add_project_arguments(
+ '-D_WLR_REL_SRC_DIR="@0@"'.format(relative_dir),
+ language: 'c',
+ )
+endif
+
+wayland_kwargs = {
+ 'version': '>=1.23.1',
+ 'fallback': 'wayland',
+ 'default_options': [
+ 'tests=false',
+ 'documentation=false',
+ ],
+}
+wayland_server = dependency('wayland-server',
+ kwargs: wayland_kwargs,
+)
+
+wlroots_options = [ 'examples=false' ]
+wlroots_version = ['>=0.19.0', '<0.20.0']
+wlroots = dependency('wlroots-0.19',
+ version: wlroots_version,
+ default_options: wlroots_options,
+ required: false,
+)
+if not wlroots.found()
+ wlroots_proj = subproject(
+ 'wlroots',
+ default_options: wlroots_options,
+ version: wlroots_version,
+ )
+ wlroots = wlroots_proj.get_variable('wlroots')
+endif
+
+
+drm = dependency('libdrm',
+ version: '>=2.4.122',
+ fallback: 'libdrm',
+ default_options: [
+ 'auto_features=disabled',
+ 'tests=false',
+ ],
+)
+xkbcommon = dependency('xkbcommon')
+pixman = dependency('pixman-1',
+ version: '>=0.43.0',
+ fallback: 'pixman',
+ default_options: ['werror=false'],
+)
+math = cc.find_library('m')
+rt = cc.find_library('rt')
+
+scenefx_files = []
+scenefx_deps = [
+ wlroots,
+ wayland_server,
+ drm,
+ xkbcommon,
+ pixman,
+ math,
+ rt,
+]
+
+subdir('protocol')
+subdir('render')
+
+subdir('types')
+subdir('util')
+
+subdir('include')
+
+scenefx_inc = include_directories('include')
+
+lib_scenefx = library(
+ versioned_name, scenefx_files,
+ dependencies: scenefx_deps,
+ include_directories: [ scenefx_inc ],
+ install: true,
+)
+
+
+scenefx = declare_dependency(
+ link_with: lib_scenefx,
+ dependencies: scenefx_deps,
+ include_directories: scenefx_inc,
+)
+
+meson.override_dependency(versioned_name, scenefx)
+
+if get_option('examples')
+ subdir('examples')
+ subdir('tinywl')
+endif
+
+pkgconfig = import('pkgconfig')
+pkgconfig.generate(
+ lib_scenefx,
+ name: versioned_name,
+ version: meson.project_version(),
+ description: 'Wlroots effects library',
+ subdirs: versioned_name,
+)
diff --git a/scenefx/meson_options.txt b/scenefx/meson_options.txt
new file mode 100644
index 0000000..8b527ea
--- /dev/null
+++ b/scenefx/meson_options.txt
@@ -0,0 +1,2 @@
+option('examples', type: 'boolean', value: true, description: 'Build example applications')
+option('renderers', type: 'array', choices: ['auto', 'gles2'], value: ['auto'], description: 'Select built-in renderers')
diff --git a/scenefx/protocol/meson.build b/scenefx/protocol/meson.build
new file mode 100644
index 0000000..f65e2f6
--- /dev/null
+++ b/scenefx/protocol/meson.build
@@ -0,0 +1,52 @@
+wayland_protos = dependency('wayland-protocols',
+ version: '>=1.41',
+ fallback: 'wayland-protocols',
+ default_options: ['tests=false'],
+)
+wl_protocol_dir = wayland_protos.get_variable('pkgdatadir')
+
+wayland_scanner_dep = dependency('wayland-scanner',
+ kwargs: wayland_kwargs,
+ native: true,
+)
+wayland_scanner = find_program(
+ wayland_scanner_dep.get_variable('wayland_scanner'),
+ native: true,
+)
+
+protocols = {
+ 'xdg-shell': wl_protocol_dir / 'stable/xdg-shell/xdg-shell.xml',
+}
+
+protocols_code = {}
+protocols_server_header = {}
+protocols_client_header = {}
+foreach name, path : protocols
+ code = custom_target(
+ name.underscorify() + '_c',
+ input: path,
+ output: '@[email protected]',
+ command: [wayland_scanner, 'private-code', '@INPUT@', '@OUTPUT@'],
+ )
+ scenefx_files += code
+
+ server_header = custom_target(
+ name.underscorify() + '_server_h',
+ input: path,
+ output: '@[email protected]',
+ command: [wayland_scanner, 'server-header', '@INPUT@', '@OUTPUT@'],
+ )
+ scenefx_files += server_header
+
+ client_header = custom_target(
+ name.underscorify() + '_client_h',
+ input: path,
+ output: '@[email protected]',
+ command: [wayland_scanner, 'client-header', '@INPUT@', '@OUTPUT@'],
+ build_by_default: false,
+ )
+
+ protocols_code += { name: code }
+ protocols_server_header += { name: server_header }
+ protocols_client_header += { name: client_header }
+endforeach
diff --git a/scenefx/render/egl.c b/scenefx/render/egl.c
new file mode 100644
index 0000000..75cef15
--- /dev/null
+++ b/scenefx/render/egl.c
@@ -0,0 +1,1132 @@
+#include <assert.h>
+#include <drm_fourcc.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <gbm.h>
+#include <wlr/render/egl.h>
+#include <wlr/util/log.h>
+#include <wlr/util/region.h>
+#include <xf86drm.h>
+#include "render/egl.h"
+#include "util/env.h"
+
+static enum wlr_log_importance egl_log_importance_to_wlr(EGLint type) {
+ switch (type) {
+ case EGL_DEBUG_MSG_CRITICAL_KHR: return WLR_ERROR;
+ case EGL_DEBUG_MSG_ERROR_KHR: return WLR_ERROR;
+ case EGL_DEBUG_MSG_WARN_KHR: return WLR_ERROR;
+ case EGL_DEBUG_MSG_INFO_KHR: return WLR_INFO;
+ default: return WLR_INFO;
+ }
+}
+
+static const char *egl_error_str(EGLint error) {
+ switch (error) {
+ case EGL_SUCCESS:
+ return "EGL_SUCCESS";
+ case EGL_NOT_INITIALIZED:
+ return "EGL_NOT_INITIALIZED";
+ case EGL_BAD_ACCESS:
+ return "EGL_BAD_ACCESS";
+ case EGL_BAD_ALLOC:
+ return "EGL_BAD_ALLOC";
+ case EGL_BAD_ATTRIBUTE:
+ return "EGL_BAD_ATTRIBUTE";
+ case EGL_BAD_CONTEXT:
+ return "EGL_BAD_CONTEXT";
+ case EGL_BAD_CONFIG:
+ return "EGL_BAD_CONFIG";
+ case EGL_BAD_CURRENT_SURFACE:
+ return "EGL_BAD_CURRENT_SURFACE";
+ case EGL_BAD_DISPLAY:
+ return "EGL_BAD_DISPLAY";
+ case EGL_BAD_DEVICE_EXT:
+ return "EGL_BAD_DEVICE_EXT";
+ case EGL_BAD_SURFACE:
+ return "EGL_BAD_SURFACE";
+ case EGL_BAD_MATCH:
+ return "EGL_BAD_MATCH";
+ case EGL_BAD_PARAMETER:
+ return "EGL_BAD_PARAMETER";
+ case EGL_BAD_NATIVE_PIXMAP:
+ return "EGL_BAD_NATIVE_PIXMAP";
+ case EGL_BAD_NATIVE_WINDOW:
+ return "EGL_BAD_NATIVE_WINDOW";
+ case EGL_CONTEXT_LOST:
+ return "EGL_CONTEXT_LOST";
+ }
+ return "unknown error";
+}
+
+static void egl_log(EGLenum error, const char *command, EGLint msg_type,
+ EGLLabelKHR thread, EGLLabelKHR obj, const char *msg) {
+ _wlr_log(egl_log_importance_to_wlr(msg_type),
+ "[EGL] command: %s, error: %s (0x%x), message: \"%s\"",
+ command, egl_error_str(error), error, msg);
+}
+
+static bool check_egl_ext(const char *exts, const char *ext) {
+ size_t extlen = strlen(ext);
+ const char *end = exts + strlen(exts);
+
+ while (exts < end) {
+ if (*exts == ' ') {
+ exts++;
+ continue;
+ }
+ size_t n = strcspn(exts, " ");
+ if (n == extlen && strncmp(ext, exts, n) == 0) {
+ return true;
+ }
+ exts += n;
+ }
+ return false;
+}
+
+static void load_egl_proc(void *proc_ptr, const char *name) {
+ void *proc = (void *)eglGetProcAddress(name);
+ if (proc == NULL) {
+ wlr_log(WLR_ERROR, "eglGetProcAddress(%s) failed", name);
+ abort();
+ }
+ *(void **)proc_ptr = proc;
+}
+
+static int get_egl_dmabuf_formats(struct wlr_egl *egl, EGLint **formats);
+static int get_egl_dmabuf_modifiers(struct wlr_egl *egl, EGLint format,
+ uint64_t **modifiers, EGLBoolean **external_only);
+
+static void log_modifier(uint64_t modifier, bool external_only) {
+ char *mod_name = drmGetFormatModifierName(modifier);
+ wlr_log(WLR_DEBUG, " %s (0x%016"PRIX64"): ✓ texture %s render",
+ mod_name ? mod_name : "<unknown>", modifier, external_only ? "✗" : "✓");
+ free(mod_name);
+}
+
+static void init_dmabuf_formats(struct wlr_egl *egl) {
+ bool no_modifiers = env_parse_bool("WLR_EGL_NO_MODIFIERS");
+ if (no_modifiers) {
+ wlr_log(WLR_INFO, "WLR_EGL_NO_MODIFIERS set, disabling modifiers for EGL");
+ }
+
+ EGLint *formats;
+ int formats_len = get_egl_dmabuf_formats(egl, &formats);
+ if (formats_len < 0) {
+ return;
+ }
+
+ wlr_log(WLR_DEBUG, "Supported DMA-BUF formats:");
+
+ bool has_modifiers = false;
+ for (int i = 0; i < formats_len; i++) {
+ EGLint fmt = formats[i];
+
+ uint64_t *modifiers = NULL;
+ EGLBoolean *external_only = NULL;
+ int modifiers_len = 0;
+ if (!no_modifiers) {
+ modifiers_len = get_egl_dmabuf_modifiers(egl, fmt, &modifiers, &external_only);
+ }
+ if (modifiers_len < 0) {
+ continue;
+ }
+
+ has_modifiers = has_modifiers || modifiers_len > 0;
+
+ bool all_external_only = true;
+ for (int j = 0; j < modifiers_len; j++) {
+ wlr_drm_format_set_add(&egl->dmabuf_texture_formats, fmt,
+ modifiers[j]);
+ if (!external_only[j]) {
+ wlr_drm_format_set_add(&egl->dmabuf_render_formats, fmt,
+ modifiers[j]);
+ all_external_only = false;
+ }
+ }
+
+ // EGL always supports implicit modifiers. If at least one modifier supports rendering,
+ // assume the implicit modifier supports rendering too.
+ wlr_drm_format_set_add(&egl->dmabuf_texture_formats, fmt,
+ DRM_FORMAT_MOD_INVALID);
+ if (modifiers_len == 0 || !all_external_only) {
+ wlr_drm_format_set_add(&egl->dmabuf_render_formats, fmt,
+ DRM_FORMAT_MOD_INVALID);
+ }
+
+ if (modifiers_len == 0) {
+ // Asume the linear layout is supported if the driver doesn't
+ // explicitly say otherwise
+ wlr_drm_format_set_add(&egl->dmabuf_texture_formats, fmt,
+ DRM_FORMAT_MOD_LINEAR);
+ wlr_drm_format_set_add(&egl->dmabuf_render_formats, fmt,
+ DRM_FORMAT_MOD_LINEAR);
+ }
+
+ if (wlr_log_get_verbosity() >= WLR_DEBUG) {
+ char *fmt_name = drmGetFormatName(fmt);
+ wlr_log(WLR_DEBUG, " %s (0x%08"PRIX32")",
+ fmt_name ? fmt_name : "<unknown>", fmt);
+ free(fmt_name);
+
+ log_modifier(DRM_FORMAT_MOD_INVALID, false);
+ if (modifiers_len == 0) {
+ log_modifier(DRM_FORMAT_MOD_LINEAR, false);
+ }
+ for (int j = 0; j < modifiers_len; j++) {
+ log_modifier(modifiers[j], external_only[j]);
+ }
+ }
+
+ free(modifiers);
+ free(external_only);
+ }
+ free(formats);
+
+ egl->has_modifiers = has_modifiers;
+ if (!no_modifiers) {
+ wlr_log(WLR_DEBUG, "EGL DMA-BUF format modifiers %s",
+ has_modifiers ? "supported" : "unsupported");
+ }
+}
+
+static struct wlr_egl *egl_create(void) {
+ const char *client_exts_str = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);
+ if (client_exts_str == NULL) {
+ if (eglGetError() == EGL_BAD_DISPLAY) {
+ wlr_log(WLR_ERROR, "EGL_EXT_client_extensions not supported");
+ } else {
+ wlr_log(WLR_ERROR, "Failed to query EGL client extensions");
+ }
+ return NULL;
+ }
+
+ wlr_log(WLR_INFO, "Supported EGL client extensions: %s", client_exts_str);
+
+ if (!check_egl_ext(client_exts_str, "EGL_EXT_platform_base")) {
+ wlr_log(WLR_ERROR, "EGL_EXT_platform_base not supported");
+ return NULL;
+ }
+
+ struct wlr_egl *egl = calloc(1, sizeof(*egl));
+ if (egl == NULL) {
+ wlr_log_errno(WLR_ERROR, "Allocation failed");
+ return NULL;
+ }
+
+ load_egl_proc(&egl->procs.eglGetPlatformDisplayEXT,
+ "eglGetPlatformDisplayEXT");
+
+ egl->exts.KHR_platform_gbm = check_egl_ext(client_exts_str,
+ "EGL_KHR_platform_gbm");
+ egl->exts.EXT_platform_device = check_egl_ext(client_exts_str,
+ "EGL_EXT_platform_device");
+ egl->exts.KHR_display_reference = check_egl_ext(client_exts_str,
+ "EGL_KHR_display_reference");
+
+ if (check_egl_ext(client_exts_str, "EGL_EXT_device_base") || check_egl_ext(client_exts_str, "EGL_EXT_device_enumeration")) {
+ load_egl_proc(&egl->procs.eglQueryDevicesEXT, "eglQueryDevicesEXT");
+ }
+
+ if (check_egl_ext(client_exts_str, "EGL_EXT_device_base") || check_egl_ext(client_exts_str, "EGL_EXT_device_query")) {
+ egl->exts.EXT_device_query = true;
+ load_egl_proc(&egl->procs.eglQueryDeviceStringEXT,
+ "eglQueryDeviceStringEXT");
+ load_egl_proc(&egl->procs.eglQueryDisplayAttribEXT,
+ "eglQueryDisplayAttribEXT");
+ }
+
+ if (check_egl_ext(client_exts_str, "EGL_KHR_debug")) {
+ load_egl_proc(&egl->procs.eglDebugMessageControlKHR,
+ "eglDebugMessageControlKHR");
+
+ static const EGLAttrib debug_attribs[] = {
+ EGL_DEBUG_MSG_CRITICAL_KHR, EGL_TRUE,
+ EGL_DEBUG_MSG_ERROR_KHR, EGL_TRUE,
+ EGL_DEBUG_MSG_WARN_KHR, EGL_TRUE,
+ EGL_DEBUG_MSG_INFO_KHR, EGL_TRUE,
+ EGL_NONE,
+ };
+ egl->procs.eglDebugMessageControlKHR(egl_log, debug_attribs);
+ }
+
+ if (eglBindAPI(EGL_OPENGL_ES_API) == EGL_FALSE) {
+ wlr_log(WLR_ERROR, "Failed to bind to the OpenGL ES API");
+ free(egl);
+ return NULL;
+ }
+
+ return egl;
+}
+
+static bool egl_init_display(struct wlr_egl *egl, EGLDisplay display) {
+ egl->display = display;
+
+ EGLint major, minor;
+ if (eglInitialize(egl->display, &major, &minor) == EGL_FALSE) {
+ wlr_log(WLR_ERROR, "Failed to initialize EGL");
+ return false;
+ }
+
+ const char *display_exts_str = eglQueryString(egl->display, EGL_EXTENSIONS);
+ if (display_exts_str == NULL) {
+ wlr_log(WLR_ERROR, "Failed to query EGL display extensions");
+ return false;
+ }
+
+ if (check_egl_ext(display_exts_str, "EGL_KHR_image_base")) {
+ egl->exts.KHR_image_base = true;
+ load_egl_proc(&egl->procs.eglCreateImageKHR, "eglCreateImageKHR");
+ load_egl_proc(&egl->procs.eglDestroyImageKHR, "eglDestroyImageKHR");
+ }
+
+ egl->exts.EXT_image_dma_buf_import =
+ check_egl_ext(display_exts_str, "EGL_EXT_image_dma_buf_import");
+ if (check_egl_ext(display_exts_str,
+ "EGL_EXT_image_dma_buf_import_modifiers")) {
+ egl->exts.EXT_image_dma_buf_import_modifiers = true;
+ load_egl_proc(&egl->procs.eglQueryDmaBufFormatsEXT,
+ "eglQueryDmaBufFormatsEXT");
+ load_egl_proc(&egl->procs.eglQueryDmaBufModifiersEXT,
+ "eglQueryDmaBufModifiersEXT");
+ }
+
+ egl->exts.EXT_create_context_robustness =
+ check_egl_ext(display_exts_str, "EGL_EXT_create_context_robustness");
+
+ const char *device_exts_str = NULL, *driver_name = NULL;
+ if (egl->exts.EXT_device_query) {
+ EGLAttrib device_attrib;
+ if (!egl->procs.eglQueryDisplayAttribEXT(egl->display,
+ EGL_DEVICE_EXT, &device_attrib)) {
+ wlr_log(WLR_ERROR, "eglQueryDisplayAttribEXT(EGL_DEVICE_EXT) failed");
+ return false;
+ }
+ egl->device = (EGLDeviceEXT)device_attrib;
+
+ device_exts_str =
+ egl->procs.eglQueryDeviceStringEXT(egl->device, EGL_EXTENSIONS);
+ if (device_exts_str == NULL) {
+ wlr_log(WLR_ERROR, "eglQueryDeviceStringEXT(EGL_EXTENSIONS) failed");
+ return false;
+ }
+
+#ifdef EGL_DRIVER_NAME_EXT
+ if (check_egl_ext(device_exts_str, "EGL_EXT_device_persistent_id")) {
+ driver_name = egl->procs.eglQueryDeviceStringEXT(egl->device,
+ EGL_DRIVER_NAME_EXT);
+ }
+#endif
+
+ egl->exts.EXT_device_drm =
+ check_egl_ext(device_exts_str, "EGL_EXT_device_drm");
+ egl->exts.EXT_device_drm_render_node =
+ check_egl_ext(device_exts_str, "EGL_EXT_device_drm_render_node");
+
+ // The only way a non-DRM device is selected is when the user
+ // explicitly picks software rendering
+ if (check_egl_ext(device_exts_str, "EGL_MESA_device_software") &&
+ egl->exts.EXT_device_drm) {
+ if (env_parse_bool("WLR_RENDERER_ALLOW_SOFTWARE")) {
+ wlr_log(WLR_INFO, "Using software rendering");
+ } else {
+ wlr_log(WLR_ERROR, "Software rendering detected, please use "
+ "the WLR_RENDERER_ALLOW_SOFTWARE environment variable "
+ "to proceed");
+ return false;
+ }
+ }
+ }
+
+ if (!check_egl_ext(display_exts_str, "EGL_KHR_no_config_context") &&
+ !check_egl_ext(display_exts_str, "EGL_MESA_configless_context")) {
+ wlr_log(WLR_ERROR, "EGL_KHR_no_config_context or "
+ "EGL_MESA_configless_context not supported");
+ return false;
+ }
+
+ if (!check_egl_ext(display_exts_str, "EGL_KHR_surfaceless_context")) {
+ wlr_log(WLR_ERROR, "EGL_KHR_surfaceless_context not supported");
+ return false;
+ }
+
+ if (check_egl_ext(display_exts_str, "EGL_KHR_fence_sync") &&
+ check_egl_ext(display_exts_str, "EGL_ANDROID_native_fence_sync")) {
+ load_egl_proc(&egl->procs.eglCreateSyncKHR, "eglCreateSyncKHR");
+ load_egl_proc(&egl->procs.eglDestroySyncKHR, "eglDestroySyncKHR");
+ load_egl_proc(&egl->procs.eglDupNativeFenceFDANDROID,
+ "eglDupNativeFenceFDANDROID");
+ }
+
+ if (check_egl_ext(display_exts_str, "EGL_KHR_wait_sync")) {
+ load_egl_proc(&egl->procs.eglWaitSyncKHR, "eglWaitSyncKHR");
+ }
+
+ egl->exts.IMG_context_priority =
+ check_egl_ext(display_exts_str, "EGL_IMG_context_priority");
+
+ wlr_log(WLR_INFO, "Using EGL %d.%d", (int)major, (int)minor);
+ wlr_log(WLR_INFO, "Supported EGL display extensions: %s", display_exts_str);
+ if (device_exts_str != NULL) {
+ wlr_log(WLR_INFO, "Supported EGL device extensions: %s", device_exts_str);
+ }
+ wlr_log(WLR_INFO, "EGL vendor: %s", eglQueryString(egl->display, EGL_VENDOR));
+ if (driver_name != NULL) {
+ wlr_log(WLR_INFO, "EGL driver name: %s", driver_name);
+ }
+
+ init_dmabuf_formats(egl);
+
+ return true;
+}
+
+static bool egl_init(struct wlr_egl *egl, EGLenum platform,
+ void *remote_display) {
+ EGLint display_attribs[3] = {0};
+ size_t display_attribs_len = 0;
+
+ if (egl->exts.KHR_display_reference) {
+ display_attribs[display_attribs_len++] = EGL_TRACK_REFERENCES_KHR;
+ display_attribs[display_attribs_len++] = EGL_TRUE;
+ }
+
+ display_attribs[display_attribs_len++] = EGL_NONE;
+ assert(display_attribs_len <= sizeof(display_attribs) / sizeof(display_attribs[0]));
+
+ EGLDisplay display = egl->procs.eglGetPlatformDisplayEXT(platform,
+ remote_display, display_attribs);
+ if (display == EGL_NO_DISPLAY) {
+ wlr_log(WLR_ERROR, "Failed to create EGL display");
+ return false;
+ }
+
+ if (!egl_init_display(egl, display)) {
+ if (egl->exts.KHR_display_reference) {
+ eglTerminate(display);
+ }
+ return false;
+ }
+
+ size_t atti = 0;
+ EGLint attribs[7];
+
+ // Try with gles3 first to check if the graphics drivers support it.
+ // If it fails, it will fallback to gles2
+ attribs[atti++] = EGL_CONTEXT_CLIENT_VERSION;
+ attribs[atti++] = 3;
+
+ // Request a high priority context if possible
+ // TODO: only do this if we're running as the DRM master
+ bool request_high_priority = egl->exts.IMG_context_priority;
+
+ // Try to reschedule all of our rendering to be completed first. If it
+ // fails, it will fallback to the default priority (MEDIUM).
+ if (request_high_priority) {
+ attribs[atti++] = EGL_CONTEXT_PRIORITY_LEVEL_IMG;
+ attribs[atti++] = EGL_CONTEXT_PRIORITY_HIGH_IMG;
+ }
+
+ if (egl->exts.EXT_create_context_robustness) {
+ attribs[atti++] = EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_EXT;
+ attribs[atti++] = EGL_LOSE_CONTEXT_ON_RESET_EXT;
+ }
+
+ attribs[atti++] = EGL_NONE;
+ assert(atti <= sizeof(attribs)/sizeof(attribs[0]));
+
+ // Attempt to create a context with gles3
+ egl->context = eglCreateContext(egl->display, EGL_NO_CONFIG_KHR,
+ EGL_NO_CONTEXT, attribs);
+ if (egl->context != EGL_NO_CONTEXT) {
+ wlr_log(WLR_DEBUG, "Created EGL context using OpenGL ES 3.0");
+ } else {
+ wlr_log(WLR_INFO, "Failed to create EGL context using OpenGL ES 3.0");
+
+ // Retry with gles2
+ attribs[1] = 2;
+ egl->context = eglCreateContext(egl->display, EGL_NO_CONFIG_KHR,
+ EGL_NO_CONTEXT, attribs);
+ if (egl->context != EGL_NO_CONTEXT) {
+ wlr_log(WLR_DEBUG, "Created EGL context using OpenGL ES 2.0");
+ } else {
+ wlr_log(WLR_ERROR, "Failed to create EGL context using OpenGL ES 2.0");
+ return false;
+ }
+ }
+
+ if (request_high_priority) {
+ EGLint priority = EGL_CONTEXT_PRIORITY_MEDIUM_IMG;
+ eglQueryContext(egl->display, egl->context,
+ EGL_CONTEXT_PRIORITY_LEVEL_IMG, &priority);
+ if (priority != EGL_CONTEXT_PRIORITY_HIGH_IMG) {
+ wlr_log(WLR_INFO, "Failed to obtain a high priority context");
+ } else {
+ wlr_log(WLR_DEBUG, "Obtained high priority context");
+ }
+ }
+
+ return true;
+}
+
+static bool device_has_name(const drmDevice *device, const char *name);
+
+static EGLDeviceEXT get_egl_device_from_drm_fd(struct wlr_egl *egl,
+ int drm_fd) {
+ if (egl->procs.eglQueryDevicesEXT == NULL) {
+ wlr_log(WLR_DEBUG, "EGL_EXT_device_enumeration not supported");
+ return EGL_NO_DEVICE_EXT;
+ } else if (!egl->exts.EXT_device_query) {
+ wlr_log(WLR_DEBUG, "EGL_EXT_device_query not supported");
+ return EGL_NO_DEVICE_EXT;
+ }
+
+ EGLint nb_devices = 0;
+ if (!egl->procs.eglQueryDevicesEXT(0, NULL, &nb_devices)) {
+ wlr_log(WLR_ERROR, "Failed to query EGL devices");
+ return EGL_NO_DEVICE_EXT;
+ }
+
+ EGLDeviceEXT *devices = calloc(nb_devices, sizeof(*devices));
+ if (devices == NULL) {
+ wlr_log_errno(WLR_ERROR, "Failed to allocate EGL device list");
+ return EGL_NO_DEVICE_EXT;
+ }
+
+ if (!egl->procs.eglQueryDevicesEXT(nb_devices, devices, &nb_devices)) {
+ wlr_log(WLR_ERROR, "Failed to query EGL devices");
+ free(devices);
+ return EGL_NO_DEVICE_EXT;
+ }
+
+ drmDevice *selected_drm_device = NULL;
+ if (drm_fd >= 0) {
+ int ret = drmGetDevice(drm_fd, &selected_drm_device);
+ if (ret < 0) {
+ wlr_log(WLR_ERROR, "Failed to get DRM device: %s", strerror(-ret));
+ free(devices);
+ return EGL_NO_DEVICE_EXT;
+ }
+ }
+
+ EGLDeviceEXT egl_device = NULL;
+ for (int i = 0; i < nb_devices; i++) {
+ const char *device_exts_str = egl->procs.eglQueryDeviceStringEXT(devices[i], EGL_EXTENSIONS);
+ if (device_exts_str == NULL) {
+ wlr_log(WLR_ERROR, "eglQueryDeviceStringEXT(EGL_EXTENSIONS) failed");
+ continue;
+ }
+
+ const char *egl_device_name = NULL;
+ if (check_egl_ext(device_exts_str, "EGL_EXT_device_drm")) {
+ egl_device_name = egl->procs.eglQueryDeviceStringEXT(devices[i], EGL_DRM_DEVICE_FILE_EXT);
+ if (egl_device_name == NULL) {
+ wlr_log(WLR_ERROR, "eglQueryDeviceStringEXT(EGL_DRM_DEVICE_FILE_EXT) failed");
+ continue;
+ }
+ }
+
+ bool is_software = check_egl_ext(device_exts_str, "EGL_MESA_device_software");
+
+ bool found;
+ if (selected_drm_device != NULL) {
+ found = egl_device_name != NULL && device_has_name(selected_drm_device, egl_device_name);
+ } else {
+ found = is_software;
+ }
+ if (found) {
+ if (egl_device_name != NULL) {
+ wlr_log(WLR_DEBUG, "Using EGL device %s", egl_device_name);
+ }
+ egl_device = devices[i];
+ break;
+ }
+ }
+
+ drmFreeDevice(&selected_drm_device);
+ free(devices);
+
+ return egl_device;
+}
+
+static int open_render_node(int drm_fd) {
+ char *render_name = drmGetRenderDeviceNameFromFd(drm_fd);
+ if (render_name == NULL) {
+ // This can happen on split render/display platforms, fallback to
+ // primary node
+ render_name = drmGetPrimaryDeviceNameFromFd(drm_fd);
+ if (render_name == NULL) {
+ wlr_log_errno(WLR_ERROR, "drmGetPrimaryDeviceNameFromFd failed");
+ return -1;
+ }
+ wlr_log(WLR_DEBUG, "DRM device '%s' has no render node, "
+ "falling back to primary node", render_name);
+ }
+
+ int render_fd = open(render_name, O_RDWR | O_CLOEXEC);
+ if (render_fd < 0) {
+ wlr_log_errno(WLR_ERROR, "Failed to open DRM node '%s'", render_name);
+ }
+ free(render_name);
+ return render_fd;
+}
+
+struct wlr_egl *wlr_egl_create_with_drm_fd(int drm_fd) {
+ struct wlr_egl *egl = egl_create();
+ if (egl == NULL) {
+ wlr_log(WLR_ERROR, "Failed to create EGL context");
+ return NULL;
+ }
+
+ if (egl->exts.EXT_platform_device) {
+ /*
+ * Search for the EGL device matching the DRM fd using the
+ * EXT_device_enumeration extension.
+ */
+ EGLDeviceEXT egl_device = get_egl_device_from_drm_fd(egl, drm_fd);
+ if (egl_device != EGL_NO_DEVICE_EXT) {
+ if (egl_init(egl, EGL_PLATFORM_DEVICE_EXT, egl_device)) {
+ wlr_log(WLR_DEBUG, "Using EGL_PLATFORM_DEVICE_EXT");
+ return egl;
+ }
+ goto error;
+ }
+ /* Falls back on GBM in case the device was not found */
+ } else {
+ wlr_log(WLR_DEBUG, "EXT_platform_device not supported");
+ }
+
+ if (egl->exts.KHR_platform_gbm && drm_fd >= 0) {
+ int gbm_fd = open_render_node(drm_fd);
+ if (gbm_fd < 0) {
+ wlr_log(WLR_ERROR, "Failed to open DRM render node");
+ goto error;
+ }
+
+ egl->gbm_device = gbm_create_device(gbm_fd);
+ if (!egl->gbm_device) {
+ close(gbm_fd);
+ wlr_log(WLR_ERROR, "Failed to create GBM device");
+ goto error;
+ }
+
+ if (egl_init(egl, EGL_PLATFORM_GBM_KHR, egl->gbm_device)) {
+ wlr_log(WLR_DEBUG, "Using EGL_PLATFORM_GBM_KHR");
+ return egl;
+ }
+
+ gbm_device_destroy(egl->gbm_device);
+ close(gbm_fd);
+ } else {
+ wlr_log(WLR_DEBUG, "KHR_platform_gbm not supported");
+ }
+
+error:
+ wlr_log(WLR_ERROR, "Failed to initialize EGL context");
+ free(egl);
+ eglReleaseThread();
+ return NULL;
+}
+
+struct wlr_egl *wlr_egl_create_with_context(EGLDisplay display,
+ EGLContext context) {
+ EGLint client_type;
+ if (!eglQueryContext(display, context, EGL_CONTEXT_CLIENT_TYPE, &client_type) ||
+ client_type != EGL_OPENGL_ES_API) {
+ wlr_log(WLR_ERROR, "Unsupported EGL context client type (need OpenGL ES)");
+ return NULL;
+ }
+
+ EGLint client_version;
+ if (!eglQueryContext(display, context, EGL_CONTEXT_CLIENT_VERSION, &client_version) ||
+ client_version < 2) {
+ wlr_log(WLR_ERROR, "Unsupported EGL context client version (need OpenGL ES >= 2)");
+ return NULL;
+ }
+
+ struct wlr_egl *egl = egl_create();
+ if (egl == NULL) {
+ return NULL;
+ }
+
+ if (!egl_init_display(egl, display)) {
+ free(egl);
+ return NULL;
+ }
+
+ egl->context = context;
+
+ return egl;
+}
+
+void wlr_egl_destroy(struct wlr_egl *egl) {
+ if (egl == NULL) {
+ return;
+ }
+
+ wlr_drm_format_set_finish(&egl->dmabuf_render_formats);
+ wlr_drm_format_set_finish(&egl->dmabuf_texture_formats);
+
+ eglMakeCurrent(egl->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+ eglDestroyContext(egl->display, egl->context);
+
+ if (egl->exts.KHR_display_reference) {
+ eglTerminate(egl->display);
+ }
+
+ eglReleaseThread();
+
+ if (egl->gbm_device) {
+ int gbm_fd = gbm_device_get_fd(egl->gbm_device);
+ gbm_device_destroy(egl->gbm_device);
+ close(gbm_fd);
+ }
+
+ free(egl);
+}
+
+EGLDisplay wlr_egl_get_display(struct wlr_egl *egl) {
+ return egl->display;
+}
+
+EGLContext wlr_egl_get_context(struct wlr_egl *egl) {
+ return egl->context;
+}
+
+bool wlr_egl_destroy_image(struct wlr_egl *egl, EGLImage image) {
+ if (!egl->exts.KHR_image_base) {
+ return false;
+ }
+ if (!image) {
+ return true;
+ }
+ return egl->procs.eglDestroyImageKHR(egl->display, image);
+}
+
+bool wlr_egl_make_current(struct wlr_egl *egl,
+ struct wlr_egl_context *save_context) {
+ if (save_context != NULL) {
+ save_context->display = eglGetCurrentDisplay();
+ save_context->context = eglGetCurrentContext();
+ save_context->draw_surface = eglGetCurrentSurface(EGL_DRAW);
+ save_context->read_surface = eglGetCurrentSurface(EGL_READ);
+ }
+ if (!eglMakeCurrent(egl->display, EGL_NO_SURFACE, EGL_NO_SURFACE,
+ egl->context)) {
+ wlr_log(WLR_ERROR, "eglMakeCurrent failed");
+ return false;
+ }
+ return true;
+}
+
+bool wlr_egl_unset_current(struct wlr_egl *egl) {
+ if (!eglMakeCurrent(egl->display, EGL_NO_SURFACE, EGL_NO_SURFACE,
+ EGL_NO_CONTEXT)) {
+ wlr_log(WLR_ERROR, "eglMakeCurrent failed");
+ return false;
+ }
+ return true;
+}
+
+bool wlr_egl_restore_context(struct wlr_egl_context *context) {
+ // If the saved context is a null-context, we must use the current
+ // display instead of the saved display because eglMakeCurrent() can't
+ // handle EGL_NO_DISPLAY.
+ EGLDisplay display = context->display == EGL_NO_DISPLAY ?
+ eglGetCurrentDisplay() : context->display;
+
+ // If the current display is also EGL_NO_DISPLAY, we assume that there
+ // is currently no context set and no action needs to be taken to unset
+ // the context.
+ if (display == EGL_NO_DISPLAY) {
+ return true;
+ }
+
+ return eglMakeCurrent(display, context->draw_surface,
+ context->read_surface, context->context);
+}
+
+EGLImageKHR wlr_egl_create_image_from_dmabuf(struct wlr_egl *egl,
+ struct wlr_dmabuf_attributes *attributes, bool *external_only) {
+ if (!egl->exts.KHR_image_base || !egl->exts.EXT_image_dma_buf_import) {
+ wlr_log(WLR_ERROR, "dmabuf import extension not present");
+ return NULL;
+ }
+
+ if (attributes->modifier != DRM_FORMAT_MOD_INVALID &&
+ attributes->modifier != DRM_FORMAT_MOD_LINEAR &&
+ !egl->has_modifiers) {
+ wlr_log(WLR_ERROR, "EGL implementation doesn't support modifiers");
+ return NULL;
+ }
+
+ unsigned int atti = 0;
+ EGLint attribs[50];
+ attribs[atti++] = EGL_WIDTH;
+ attribs[atti++] = attributes->width;
+ attribs[atti++] = EGL_HEIGHT;
+ attribs[atti++] = attributes->height;
+ attribs[atti++] = EGL_LINUX_DRM_FOURCC_EXT;
+ attribs[atti++] = attributes->format;
+
+ struct {
+ EGLint fd;
+ EGLint offset;
+ EGLint pitch;
+ EGLint mod_lo;
+ EGLint mod_hi;
+ } attr_names[WLR_DMABUF_MAX_PLANES] = {
+ {
+ EGL_DMA_BUF_PLANE0_FD_EXT,
+ EGL_DMA_BUF_PLANE0_OFFSET_EXT,
+ EGL_DMA_BUF_PLANE0_PITCH_EXT,
+ EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT,
+ EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT
+ }, {
+ EGL_DMA_BUF_PLANE1_FD_EXT,
+ EGL_DMA_BUF_PLANE1_OFFSET_EXT,
+ EGL_DMA_BUF_PLANE1_PITCH_EXT,
+ EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT,
+ EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT
+ }, {
+ EGL_DMA_BUF_PLANE2_FD_EXT,
+ EGL_DMA_BUF_PLANE2_OFFSET_EXT,
+ EGL_DMA_BUF_PLANE2_PITCH_EXT,
+ EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT,
+ EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT
+ }, {
+ EGL_DMA_BUF_PLANE3_FD_EXT,
+ EGL_DMA_BUF_PLANE3_OFFSET_EXT,
+ EGL_DMA_BUF_PLANE3_PITCH_EXT,
+ EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT,
+ EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT
+ }
+ };
+
+ for (int i = 0; i < attributes->n_planes; i++) {
+ attribs[atti++] = attr_names[i].fd;
+ attribs[atti++] = attributes->fd[i];
+ attribs[atti++] = attr_names[i].offset;
+ attribs[atti++] = attributes->offset[i];
+ attribs[atti++] = attr_names[i].pitch;
+ attribs[atti++] = attributes->stride[i];
+ if (egl->has_modifiers &&
+ attributes->modifier != DRM_FORMAT_MOD_INVALID) {
+ attribs[atti++] = attr_names[i].mod_lo;
+ attribs[atti++] = attributes->modifier & 0xFFFFFFFF;
+ attribs[atti++] = attr_names[i].mod_hi;
+ attribs[atti++] = attributes->modifier >> 32;
+ }
+ }
+
+ // Our clients don't expect our usage to trash the buffer contents
+ attribs[atti++] = EGL_IMAGE_PRESERVED_KHR;
+ attribs[atti++] = EGL_TRUE;
+
+ attribs[atti++] = EGL_NONE;
+ assert(atti <= sizeof(attribs)/sizeof(attribs[0]));
+
+ EGLImageKHR image = egl->procs.eglCreateImageKHR(egl->display, EGL_NO_CONTEXT,
+ EGL_LINUX_DMA_BUF_EXT, NULL, attribs);
+ if (image == EGL_NO_IMAGE_KHR) {
+ wlr_log(WLR_ERROR, "eglCreateImageKHR failed");
+ return EGL_NO_IMAGE_KHR;
+ }
+
+ *external_only = !wlr_drm_format_set_has(&egl->dmabuf_render_formats,
+ attributes->format, attributes->modifier);
+ return image;
+}
+
+static int get_egl_dmabuf_formats(struct wlr_egl *egl, EGLint **formats) {
+ if (!egl->exts.EXT_image_dma_buf_import) {
+ wlr_log(WLR_DEBUG, "DMA-BUF import extension not present");
+ return -1;
+ }
+
+ // when we only have the image_dmabuf_import extension we can't query
+ // which formats are supported. These two are on almost always
+ // supported; it's the intended way to just try to create buffers.
+ // Just a guess but better than not supporting dmabufs at all,
+ // given that the modifiers extension isn't supported everywhere.
+ if (!egl->exts.EXT_image_dma_buf_import_modifiers) {
+ static const EGLint fallback_formats[] = {
+ DRM_FORMAT_ARGB8888,
+ DRM_FORMAT_XRGB8888,
+ };
+ int num = sizeof(fallback_formats) / sizeof(fallback_formats[0]);
+
+ *formats = calloc(num, sizeof(**formats));
+ if (!*formats) {
+ wlr_log_errno(WLR_ERROR, "Allocation failed");
+ return -1;
+ }
+
+ memcpy(*formats, fallback_formats, num * sizeof(**formats));
+ return num;
+ }
+
+ EGLint num;
+ if (!egl->procs.eglQueryDmaBufFormatsEXT(egl->display, 0, NULL, &num)) {
+ wlr_log(WLR_ERROR, "Failed to query number of dmabuf formats");
+ return -1;
+ }
+
+ *formats = calloc(num, sizeof(**formats));
+ if (*formats == NULL) {
+ wlr_log(WLR_ERROR, "Allocation failed: %s", strerror(errno));
+ return -1;
+ }
+
+ if (!egl->procs.eglQueryDmaBufFormatsEXT(egl->display, num, *formats, &num)) {
+ wlr_log(WLR_ERROR, "Failed to query dmabuf format");
+ free(*formats);
+ return -1;
+ }
+ return num;
+}
+
+static int get_egl_dmabuf_modifiers(struct wlr_egl *egl, EGLint format,
+ uint64_t **modifiers, EGLBoolean **external_only) {
+ *modifiers = NULL;
+ *external_only = NULL;
+
+ if (!egl->exts.EXT_image_dma_buf_import) {
+ wlr_log(WLR_DEBUG, "DMA-BUF extension not present");
+ return -1;
+ }
+ if (!egl->exts.EXT_image_dma_buf_import_modifiers) {
+ return 0;
+ }
+
+ EGLint num;
+ if (!egl->procs.eglQueryDmaBufModifiersEXT(egl->display, format, 0,
+ NULL, NULL, &num)) {
+ wlr_log(WLR_ERROR, "Failed to query dmabuf number of modifiers");
+ return -1;
+ }
+ if (num == 0) {
+ return 0;
+ }
+
+ *modifiers = calloc(num, sizeof(**modifiers));
+ if (*modifiers == NULL) {
+ wlr_log_errno(WLR_ERROR, "Allocation failed");
+ return -1;
+ }
+ *external_only = calloc(num, sizeof(**external_only));
+ if (*external_only == NULL) {
+ wlr_log_errno(WLR_ERROR, "Allocation failed");
+ free(*modifiers);
+ *modifiers = NULL;
+ return -1;
+ }
+
+ if (!egl->procs.eglQueryDmaBufModifiersEXT(egl->display, format, num,
+ *modifiers, *external_only, &num)) {
+ wlr_log(WLR_ERROR, "Failed to query dmabuf modifiers");
+ free(*modifiers);
+ free(*external_only);
+ return -1;
+ }
+ return num;
+}
+
+const struct wlr_drm_format_set *wlr_egl_get_dmabuf_texture_formats(
+ struct wlr_egl *egl) {
+ return &egl->dmabuf_texture_formats;
+}
+
+const struct wlr_drm_format_set *wlr_egl_get_dmabuf_render_formats(
+ struct wlr_egl *egl) {
+ return &egl->dmabuf_render_formats;
+}
+
+static bool device_has_name(const drmDevice *device, const char *name) {
+ for (size_t i = 0; i < DRM_NODE_MAX; i++) {
+ if (!(device->available_nodes & (1 << i))) {
+ continue;
+ }
+ if (strcmp(device->nodes[i], name) == 0) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static char *get_render_name(const char *name) {
+ uint32_t flags = 0;
+ int devices_len = drmGetDevices2(flags, NULL, 0);
+ if (devices_len < 0) {
+ wlr_log(WLR_ERROR, "drmGetDevices2 failed: %s", strerror(-devices_len));
+ return NULL;
+ }
+ drmDevice **devices = calloc(devices_len, sizeof(*devices));
+ if (devices == NULL) {
+ wlr_log_errno(WLR_ERROR, "Allocation failed");
+ return NULL;
+ }
+ devices_len = drmGetDevices2(flags, devices, devices_len);
+ if (devices_len < 0) {
+ free(devices);
+ wlr_log(WLR_ERROR, "drmGetDevices2 failed: %s", strerror(-devices_len));
+ return NULL;
+ }
+
+ const drmDevice *match = NULL;
+ for (int i = 0; i < devices_len; i++) {
+ if (device_has_name(devices[i], name)) {
+ match = devices[i];
+ break;
+ }
+ }
+
+ char *render_name = NULL;
+ if (match == NULL) {
+ wlr_log(WLR_ERROR, "Cannot find DRM device %s", name);
+ } else if (!(match->available_nodes & (1 << DRM_NODE_RENDER))) {
+ // Likely a split display/render setup. Pick the primary node and hope
+ // Mesa will open the right render node under-the-hood.
+ wlr_log(WLR_DEBUG, "DRM device %s has no render node, "
+ "falling back to primary node", name);
+ assert(match->available_nodes & (1 << DRM_NODE_PRIMARY));
+ render_name = strdup(match->nodes[DRM_NODE_PRIMARY]);
+ } else {
+ render_name = strdup(match->nodes[DRM_NODE_RENDER]);
+ }
+
+ for (int i = 0; i < devices_len; i++) {
+ drmFreeDevice(&devices[i]);
+ }
+ free(devices);
+
+ return render_name;
+}
+
+static int dup_egl_device_drm_fd(struct wlr_egl *egl) {
+ if (egl->device == EGL_NO_DEVICE_EXT || (!egl->exts.EXT_device_drm &&
+ !egl->exts.EXT_device_drm_render_node)) {
+ return -1;
+ }
+
+ char *render_name = NULL;
+#ifdef EGL_EXT_device_drm_render_node
+ if (egl->exts.EXT_device_drm_render_node) {
+ const char *name = egl->procs.eglQueryDeviceStringEXT(egl->device,
+ EGL_DRM_RENDER_NODE_FILE_EXT);
+ if (name == NULL) {
+ wlr_log(WLR_DEBUG, "EGL device has no render node");
+ return -1;
+ }
+ render_name = strdup(name);
+ }
+#endif
+
+ if (render_name == NULL) {
+ const char *primary_name = egl->procs.eglQueryDeviceStringEXT(egl->device,
+ EGL_DRM_DEVICE_FILE_EXT);
+ if (primary_name == NULL) {
+ wlr_log(WLR_ERROR,
+ "eglQueryDeviceStringEXT(EGL_DRM_DEVICE_FILE_EXT) failed");
+ return -1;
+ }
+
+ render_name = get_render_name(primary_name);
+ if (render_name == NULL) {
+ wlr_log(WLR_ERROR, "Can't find render node name for device %s",
+ primary_name);
+ return -1;
+ }
+ }
+
+ int render_fd = open(render_name, O_RDWR | O_NONBLOCK | O_CLOEXEC);
+ if (render_fd < 0) {
+ wlr_log_errno(WLR_ERROR, "Failed to open DRM render node %s",
+ render_name);
+ free(render_name);
+ return -1;
+ }
+ free(render_name);
+
+ return render_fd;
+}
+
+int wlr_egl_dup_drm_fd(struct wlr_egl *egl) {
+ int fd = dup_egl_device_drm_fd(egl);
+ if (fd >= 0) {
+ return fd;
+ }
+
+ // Fallback to GBM's FD if we can't use EGLDevice
+ if (egl->gbm_device == NULL) {
+ return -1;
+ }
+
+ fd = fcntl(gbm_device_get_fd(egl->gbm_device), F_DUPFD_CLOEXEC, 0);
+ if (fd < 0) {
+ wlr_log_errno(WLR_ERROR, "Failed to dup GBM FD");
+ }
+ return fd;
+}
+
+EGLSyncKHR wlr_egl_create_sync(struct wlr_egl *egl, int fence_fd) {
+ if (!egl->procs.eglCreateSyncKHR) {
+ return EGL_NO_SYNC_KHR;
+ }
+
+ EGLint attribs[3] = { EGL_NONE };
+ int dup_fd = -1;
+ if (fence_fd >= 0) {
+ dup_fd = fcntl(fence_fd, F_DUPFD_CLOEXEC, 0);
+ if (dup_fd < 0) {
+ wlr_log_errno(WLR_ERROR, "dup failed");
+ return EGL_NO_SYNC_KHR;
+ }
+
+ attribs[0] = EGL_SYNC_NATIVE_FENCE_FD_ANDROID;
+ attribs[1] = dup_fd;
+ attribs[2] = EGL_NONE;
+ }
+
+ EGLSyncKHR sync = egl->procs.eglCreateSyncKHR(egl->display,
+ EGL_SYNC_NATIVE_FENCE_ANDROID, attribs);
+ if (sync == EGL_NO_SYNC_KHR) {
+ wlr_log(WLR_ERROR, "eglCreateSyncKHR failed");
+ if (dup_fd >= 0) {
+ close(dup_fd);
+ }
+ }
+ return sync;
+}
+
+void wlr_egl_destroy_sync(struct wlr_egl *egl, EGLSyncKHR sync) {
+ if (sync == EGL_NO_SYNC_KHR) {
+ return;
+ }
+ assert(egl->procs.eglDestroySyncKHR);
+ if (egl->procs.eglDestroySyncKHR(egl->display, sync) != EGL_TRUE) {
+ wlr_log(WLR_ERROR, "eglDestroySyncKHR failed");
+ }
+}
+
+int wlr_egl_dup_fence_fd(struct wlr_egl *egl, EGLSyncKHR sync) {
+ if (!egl->procs.eglDupNativeFenceFDANDROID) {
+ return -1;
+ }
+
+ int fd = egl->procs.eglDupNativeFenceFDANDROID(egl->display, sync);
+ if (fd == EGL_NO_NATIVE_FENCE_FD_ANDROID) {
+ wlr_log(WLR_ERROR, "eglDupNativeFenceFDANDROID failed");
+ return -1;
+ }
+
+ return fd;
+}
+
+bool wlr_egl_wait_sync(struct wlr_egl *egl, EGLSyncKHR sync) {
+ if (egl->procs.eglWaitSyncKHR(egl->display, sync, 0) != EGL_TRUE) {
+ wlr_log(WLR_ERROR, "eglWaitSyncKHR failed");
+ return false;
+ }
+ return true;
+}
diff --git a/scenefx/render/fx_renderer/fx_effect_framebuffers.c b/scenefx/render/fx_renderer/fx_effect_framebuffers.c
new file mode 100644
index 0000000..b0caa7c
--- /dev/null
+++ b/scenefx/render/fx_renderer/fx_effect_framebuffers.c
@@ -0,0 +1,57 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <wlr/types/wlr_output.h>
+#include <wlr/util/addon.h>
+#include <wlr/util/log.h>
+
+#include "scenefx/render/fx_renderer/fx_effect_framebuffers.h"
+
+static void addon_handle_destroy(struct wlr_addon *addon) {
+ struct fx_effect_framebuffers *fbos = wl_container_of(addon, fbos, addon);
+ wlr_addon_finish(&fbos->addon);
+ free(fbos);
+}
+
+static const struct wlr_addon_interface fbos_addon_impl = {
+ .name = "fx_effect_framebuffers",
+ .destroy = addon_handle_destroy,
+};
+
+static bool fx_effect_framebuffers_assign(struct wlr_output *output,
+ struct fx_effect_framebuffers *fbos) {
+ wlr_addon_init(&fbos->addon, &output->addons, output, &fbos_addon_impl);
+ return true;
+}
+
+struct fx_effect_framebuffers *fx_effect_framebuffers_try_get(struct wlr_output *output) {
+ struct fx_effect_framebuffers *fbos = NULL;
+ if (!output) {
+ return NULL;
+ }
+
+ struct wlr_addon *addon = wlr_addon_find(&output->addons, output,
+ &fbos_addon_impl);
+ if (!addon) {
+ goto create_new;
+ }
+
+ if (!(fbos = wl_container_of(addon, fbos, addon))) {
+ goto create_new;
+ }
+ return fbos;
+
+create_new:;
+ fbos = calloc(1, sizeof(*fbos));
+ if (!fbos) {
+ wlr_log(WLR_ERROR, "Could not allocate a fx_effect_framebuffers");
+ return NULL;
+ }
+
+ if (!fx_effect_framebuffers_assign(output, fbos)) {
+ wlr_log(WLR_ERROR, "Could not assign fx_effect_framebuffers to output: '%s'",
+ output->name);
+ free(fbos);
+ return NULL;
+ }
+ return fbos;
+}
diff --git a/scenefx/render/fx_renderer/fx_framebuffer.c b/scenefx/render/fx_renderer/fx_framebuffer.c
new file mode 100644
index 0000000..c8835f4
--- /dev/null
+++ b/scenefx/render/fx_renderer/fx_framebuffer.c
@@ -0,0 +1,186 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <wlr/interfaces/wlr_buffer.h>
+#include <wlr/render/allocator.h>
+#include <wlr/render/interface.h>
+#include <wlr/render/swapchain.h>
+#include <wlr/util/log.h>
+
+#include "render/egl.h"
+#include "render/fx_renderer/fx_renderer.h"
+
+static void handle_buffer_destroy(struct wlr_addon *addon) {
+ struct fx_framebuffer *buffer =
+ wl_container_of(addon, buffer, addon);
+ fx_framebuffer_destroy(buffer);
+}
+
+static const struct wlr_addon_interface buffer_addon_impl = {
+ .name = "fx_framebuffer",
+ .destroy = handle_buffer_destroy,
+};
+
+GLuint fx_framebuffer_get_fbo(struct fx_framebuffer *buffer) {
+ if (buffer->external_only) {
+ wlr_log(WLR_ERROR, "DMA-BUF format is external-only");
+ return 0;
+ }
+
+ if (buffer->fbo) {
+ return buffer->fbo;
+ }
+
+ push_fx_debug(buffer->renderer);
+
+ if (!buffer->rbo) {
+ glGenRenderbuffers(1, &buffer->rbo);
+ glBindRenderbuffer(GL_RENDERBUFFER, buffer->rbo);
+ buffer->renderer->procs.glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER,
+ buffer->image);
+ glBindRenderbuffer(GL_RENDERBUFFER, 0);
+ }
+
+ glGenFramebuffers(1, &buffer->fbo);
+ glBindFramebuffer(GL_FRAMEBUFFER, buffer->fbo);
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ GL_RENDERBUFFER, buffer->rbo);
+ GLenum fb_status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
+
+ // Init stencil buffer
+ glGenRenderbuffers(1, &buffer->sb);
+ glBindRenderbuffer(GL_RENDERBUFFER, buffer->sb);
+ glRenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8,
+ buffer->buffer->width, buffer->buffer->height);
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
+ GL_RENDERBUFFER, buffer->sb);
+ fb_status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
+
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+
+ if (fb_status != GL_FRAMEBUFFER_COMPLETE) {
+ wlr_log(WLR_ERROR, "Failed to create FBO");
+ glDeleteFramebuffers(1, &buffer->fbo);
+ buffer->fbo = 0;
+ }
+
+ pop_fx_debug(buffer->renderer);
+
+ return buffer->fbo;
+}
+
+void fx_framebuffer_get_or_create_custom(struct fx_renderer *renderer,
+ struct wlr_output *output, struct wlr_swapchain *swapchain,
+ struct fx_framebuffer **fx_framebuffer, bool *failed) {
+ if (*failed) {
+ return;
+ }
+
+ struct wlr_allocator *allocator = output->allocator;
+ if (!swapchain) {
+ if (!output->swapchain) {
+ wlr_log(WLR_ERROR, "Failed to allocate buffer, no swapchain");
+ *failed = true;
+ return;
+ }
+ swapchain = output->swapchain;
+ }
+ int width = output->width;
+ int height = output->height;
+ struct wlr_buffer *wlr_buffer = NULL;
+
+ if (*fx_framebuffer == NULL) {
+ wlr_buffer = wlr_allocator_create_buffer(allocator, width, height,
+ &swapchain->format);
+ if (wlr_buffer == NULL) {
+ wlr_log(WLR_ERROR, "Failed to allocate buffer");
+ *failed = true;
+ return;
+ }
+ } else {
+ if ((wlr_buffer = (*fx_framebuffer)->buffer) &&
+ wlr_buffer->width == width &&
+ wlr_buffer->height == height) {
+ return;
+ }
+ // Create a new wlr_buffer if it's null or if the output size has
+ // changed
+ fx_framebuffer_destroy(*fx_framebuffer);
+ wlr_buffer_drop(wlr_buffer);
+ wlr_buffer = wlr_allocator_create_buffer(allocator,
+ width, height, &swapchain->format);
+ }
+ *fx_framebuffer = fx_framebuffer_get_or_create(renderer, wlr_buffer);
+ fx_framebuffer_get_fbo(*fx_framebuffer);
+}
+
+struct fx_framebuffer *fx_framebuffer_get_or_create(struct fx_renderer *renderer,
+ struct wlr_buffer *wlr_buffer) {
+ struct wlr_addon *addon =
+ wlr_addon_find(&wlr_buffer->addons, renderer, &buffer_addon_impl);
+ if (addon) {
+ struct fx_framebuffer *buffer = wl_container_of(addon, buffer, addon);
+ return buffer;
+ }
+
+ struct fx_framebuffer *buffer = calloc(1, sizeof(*buffer));
+ if (buffer == NULL) {
+ wlr_log_errno(WLR_ERROR, "Allocation failed");
+ return NULL;
+ }
+ buffer->buffer = wlr_buffer;
+ buffer->renderer = renderer;
+
+ struct wlr_dmabuf_attributes dmabuf = {0};
+ if (!wlr_buffer_get_dmabuf(wlr_buffer, &dmabuf)) {
+ goto error_buffer;
+ }
+
+ buffer->image = wlr_egl_create_image_from_dmabuf(renderer->egl,
+ &dmabuf, &buffer->external_only);
+ if (buffer->image == EGL_NO_IMAGE_KHR) {
+ goto error_buffer;
+ }
+
+ wlr_addon_init(&buffer->addon, &wlr_buffer->addons, renderer,
+ &buffer_addon_impl);
+
+ wl_list_insert(&renderer->buffers, &buffer->link);
+
+ wlr_log(WLR_DEBUG, "Created GL FBO for buffer %dx%d",
+ wlr_buffer->width, wlr_buffer->height);
+
+ return buffer;
+
+error_buffer:
+ free(buffer);
+ return NULL;
+}
+
+void fx_framebuffer_bind(struct fx_framebuffer *fx_buffer) {
+ glBindFramebuffer(GL_FRAMEBUFFER, fx_buffer->fbo);
+}
+
+void fx_framebuffer_destroy(struct fx_framebuffer *fx_buffer) {
+ // Release the framebuffer
+ wl_list_remove(&fx_buffer->link);
+ wlr_addon_finish(&fx_buffer->addon);
+
+ struct wlr_egl_context prev_ctx;
+ wlr_egl_make_current(fx_buffer->renderer->egl, &prev_ctx);
+
+ glDeleteFramebuffers(1, &fx_buffer->fbo);
+ fx_buffer->fbo = -1;
+ glDeleteRenderbuffers(1, &fx_buffer->rbo);
+ fx_buffer->rbo = -1;
+ glDeleteTextures(1, &fx_buffer->tex);
+ fx_buffer->tex = -1;
+ glDeleteRenderbuffers(1, &fx_buffer->sb);
+ fx_buffer->sb = -1;
+
+ wlr_egl_destroy_image(fx_buffer->renderer->egl, fx_buffer->image);
+
+ wlr_egl_restore_context(&prev_ctx);
+
+ free(fx_buffer);
+}
+
diff --git a/scenefx/render/fx_renderer/fx_pass.c b/scenefx/render/fx_renderer/fx_pass.c
new file mode 100644
index 0000000..92df07d
--- /dev/null
+++ b/scenefx/render/fx_renderer/fx_pass.c
@@ -0,0 +1,1184 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <pixman.h>
+#include <time.h>
+#include <unistd.h>
+#include <wlr/render/allocator.h>
+#include <wlr/render/drm_syncobj.h>
+#include <wlr/util/transform.h>
+#include <wlr/util/log.h>
+#include <wlr/util/region.h>
+
+#include "render/egl.h"
+#include "render/fx_renderer/fx_renderer.h"
+#include "render/fx_renderer/shaders.h"
+#include "render/pass.h"
+#include "scenefx/render/fx_renderer/fx_renderer.h"
+#include "scenefx/render/fx_renderer/fx_effect_framebuffers.h"
+#include "scenefx/types/fx/corner_location.h"
+#include "scenefx/types/fx/blur_data.h"
+#include "util/matrix.h"
+
+#define MAX_QUADS 86 // 4kb
+
+struct fx_render_texture_options fx_render_texture_options_default(
+ const struct wlr_render_texture_options *base) {
+ struct fx_render_texture_options options = {
+ .corner_radius = 0,
+ .corners = CORNER_LOCATION_NONE,
+ .discard_transparent = false,
+ .clip_box = NULL,
+ };
+ memcpy(&options.base, base, sizeof(*base));
+ return options;
+}
+
+struct fx_render_rect_options fx_render_rect_options_default(
+ const struct wlr_render_rect_options *base) {
+ struct fx_render_rect_options options = {
+ .base = *base,
+ .clipped_region = {
+ .area = { .0, .0, .0, .0 },
+ .corner_radius = 0,
+ .corners = CORNER_LOCATION_NONE,
+ },
+ };
+ return options;
+}
+
+// Gets a non-transformed wlr_box
+static struct wlr_box get_monitor_box(struct wlr_output *output) {
+ return (struct wlr_box) { 0, 0, output->width, output->height };
+}
+
+///
+/// Base Wlroots pass functions
+///
+
+static const struct wlr_render_pass_impl render_pass_impl;
+
+static struct fx_gles_render_pass *get_render_pass(struct wlr_render_pass *wlr_pass) {
+ assert(wlr_pass->impl == &render_pass_impl);
+ struct fx_gles_render_pass *pass = wl_container_of(wlr_pass, pass, base);
+ return pass;
+}
+
+static bool render_pass_submit(struct wlr_render_pass *wlr_pass) {
+ struct fx_gles_render_pass *pass = get_render_pass(wlr_pass);
+ struct fx_renderer *renderer = pass->buffer->renderer;
+ struct fx_render_timer *timer = pass->timer;
+ bool ok = false;
+
+ push_fx_debug(renderer);
+
+ if (timer) {
+ // clear disjoint flag
+ GLint64 disjoint;
+ renderer->procs.glGetInteger64vEXT(GL_GPU_DISJOINT_EXT, &disjoint);
+ // set up the query
+ renderer->procs.glQueryCounterEXT(timer->id, GL_TIMESTAMP_EXT);
+ // get end-of-CPU-work time in GL time domain
+ renderer->procs.glGetInteger64vEXT(GL_TIMESTAMP_EXT, &timer->gl_cpu_end);
+ // get end-of-CPU-work time in CPU time domain
+ clock_gettime(CLOCK_MONOTONIC, &timer->cpu_end);
+ }
+
+ if (pass->signal_timeline != NULL) {
+ EGLSyncKHR sync = wlr_egl_create_sync(renderer->egl, -1);
+ if (sync == EGL_NO_SYNC_KHR) {
+ goto out;
+ }
+
+ int sync_file_fd = wlr_egl_dup_fence_fd(renderer->egl, sync);
+ wlr_egl_destroy_sync(renderer->egl, sync);
+ if (sync_file_fd < 0) {
+ goto out;
+ }
+
+ ok = wlr_drm_syncobj_timeline_import_sync_file(pass->signal_timeline, pass->signal_point, sync_file_fd);
+ close(sync_file_fd);
+ if (!ok) {
+ goto out;
+ }
+ } else {
+ glFlush();
+ }
+
+ ok = true;
+
+out:
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+
+ pop_fx_debug(renderer);
+ wlr_egl_restore_context(&pass->prev_ctx);
+
+ wlr_drm_syncobj_timeline_unref(pass->signal_timeline);
+ wlr_buffer_unlock(pass->buffer->buffer);
+ struct fx_effect_framebuffers *fbos = fx_effect_framebuffers_try_get(pass->output);
+ if (fbos) {
+ pixman_region32_fini(&fbos->blur_padding_region);
+ }
+ free(pass);
+
+ return ok;
+}
+
+static void render_pass_add_texture(struct wlr_render_pass *wlr_pass,
+ const struct wlr_render_texture_options *options) {
+ struct fx_gles_render_pass *pass = get_render_pass(wlr_pass);
+ const struct fx_render_texture_options fx_options =
+ fx_render_texture_options_default(options);
+ // Re-use fx function but with default options
+ fx_render_pass_add_texture(pass, &fx_options);
+}
+
+static void render_pass_add_rect(struct wlr_render_pass *wlr_pass,
+ const struct wlr_render_rect_options *options) {
+ struct fx_gles_render_pass *pass = get_render_pass(wlr_pass);
+ const struct fx_render_rect_options fx_options =
+ fx_render_rect_options_default(options);
+ // Re-use fx function but with default options
+ fx_render_pass_add_rect(pass, &fx_options);
+}
+
+static const struct wlr_render_pass_impl render_pass_impl = {
+ .submit = render_pass_submit,
+ .add_texture = render_pass_add_texture,
+ .add_rect = render_pass_add_rect,
+};
+
+///
+/// FX pass functions
+///
+
+// TODO: REMOVE STENCILING
+
+// Initialize the stenciling work
+static void stencil_mask_init(void) {
+ glClearStencil(0);
+ glClear(GL_STENCIL_BUFFER_BIT);
+ glEnable(GL_STENCIL_TEST);
+
+ glStencilFunc(GL_ALWAYS, 1, 0xFF);
+ glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
+ // Disable writing to color buffer
+ glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
+}
+
+// Close the mask
+static void stencil_mask_close(bool draw_inside_mask) {
+ // Reenable writing to color buffer
+ glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+ if (draw_inside_mask) {
+ glStencilFunc(GL_EQUAL, 1, 0xFF);
+ glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
+ return;
+ }
+ glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
+ glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
+}
+
+// Finish stenciling and clear the buffer
+static void stencil_mask_fini(void) {
+ glClearStencil(0);
+ glClear(GL_STENCIL_BUFFER_BIT);
+ glDisable(GL_STENCIL_TEST);
+}
+
+static void render(const struct wlr_box *box, const pixman_region32_t *clip, GLint attrib) {
+ pixman_region32_t region;
+ pixman_region32_init_rect(®ion, box->x, box->y, box->width, box->height);
+
+ if (clip) {
+ pixman_region32_intersect(®ion, ®ion, clip);
+ }
+
+ int rects_len;
+ const pixman_box32_t *rects = pixman_region32_rectangles(®ion, &rects_len);
+ if (rects_len == 0) {
+ pixman_region32_fini(®ion);
+ return;
+ }
+
+ glEnableVertexAttribArray(attrib);
+
+ for (int i = 0; i < rects_len;) {
+ int batch = rects_len - i < MAX_QUADS ? rects_len - i : MAX_QUADS;
+ int batch_end = batch + i;
+
+ size_t vert_index = 0;
+ GLfloat verts[MAX_QUADS * 6 * 2];
+ for (; i < batch_end; i++) {
+ const pixman_box32_t *rect = &rects[i];
+
+ verts[vert_index++] = (GLfloat)(rect->x1 - box->x) / box->width;
+ verts[vert_index++] = (GLfloat)(rect->y1 - box->y) / box->height;
+ verts[vert_index++] = (GLfloat)(rect->x2 - box->x) / box->width;
+ verts[vert_index++] = (GLfloat)(rect->y1 - box->y) / box->height;
+ verts[vert_index++] = (GLfloat)(rect->x1 - box->x) / box->width;
+ verts[vert_index++] = (GLfloat)(rect->y2 - box->y) / box->height;
+ verts[vert_index++] = (GLfloat)(rect->x2 - box->x) / box->width;
+ verts[vert_index++] = (GLfloat)(rect->y1 - box->y) / box->height;
+ verts[vert_index++] = (GLfloat)(rect->x2 - box->x) / box->width;
+ verts[vert_index++] = (GLfloat)(rect->y2 - box->y) / box->height;
+ verts[vert_index++] = (GLfloat)(rect->x1 - box->x) / box->width;
+ verts[vert_index++] = (GLfloat)(rect->y2 - box->y) / box->height;
+ }
+
+ glVertexAttribPointer(attrib, 2, GL_FLOAT, GL_FALSE, 0, verts);
+ glDrawArrays(GL_TRIANGLES, 0, batch * 6);
+ }
+
+ glDisableVertexAttribArray(attrib);
+
+ pixman_region32_fini(®ion);
+}
+
+static void set_proj_matrix(GLint loc, float proj[9], const struct wlr_box *box) {
+ float gl_matrix[9];
+ wlr_matrix_identity(gl_matrix);
+ wlr_matrix_translate(gl_matrix, box->x, box->y);
+ wlr_matrix_scale(gl_matrix, box->width, box->height);
+ wlr_matrix_multiply(gl_matrix, proj, gl_matrix);
+ glUniformMatrix3fv(loc, 1, GL_FALSE, gl_matrix);
+}
+
+static void set_tex_matrix(GLint loc, enum wl_output_transform trans,
+ const struct wlr_fbox *box) {
+ float tex_matrix[9];
+ wlr_matrix_identity(tex_matrix);
+ wlr_matrix_translate(tex_matrix, box->x, box->y);
+ wlr_matrix_scale(tex_matrix, box->width, box->height);
+ wlr_matrix_translate(tex_matrix, .5, .5);
+
+ // since textures have a different origin point we have to transform
+ // differently if we are rotating
+ if (trans & WL_OUTPUT_TRANSFORM_90) {
+ wlr_matrix_transform(tex_matrix, wlr_output_transform_invert(trans));
+ } else {
+ wlr_matrix_transform(tex_matrix, trans);
+ }
+ wlr_matrix_translate(tex_matrix, -.5, -.5);
+
+ glUniformMatrix3fv(loc, 1, GL_FALSE, tex_matrix);
+}
+
+static void setup_blending(enum wlr_render_blend_mode mode) {
+ switch (mode) {
+ case WLR_RENDER_BLEND_MODE_PREMULTIPLIED:
+ glEnable(GL_BLEND);
+ break;
+ case WLR_RENDER_BLEND_MODE_NONE:
+ glDisable(GL_BLEND);
+ break;
+ }
+}
+
+void fx_render_pass_add_texture(struct fx_gles_render_pass *pass,
+ const struct fx_render_texture_options *fx_options) {
+ const struct wlr_render_texture_options *options = &fx_options->base;
+ struct fx_renderer *renderer = pass->buffer->renderer;
+ struct fx_texture *texture = fx_get_texture(options->texture);
+
+ struct tex_shader *shader = NULL;
+
+ switch (texture->target) {
+ case GL_TEXTURE_2D:
+ if (texture->has_alpha) {
+ shader = &renderer->shaders.tex_rgba;
+ } else {
+ shader = &renderer->shaders.tex_rgbx;
+ }
+ break;
+ case GL_TEXTURE_EXTERNAL_OES:
+ // EGL_EXT_image_dma_buf_import_modifiers requires
+ // GL_OES_EGL_image_external
+ assert(renderer->exts.OES_egl_image_external);
+ shader = &renderer->shaders.tex_ext;
+ break;
+ default:
+ abort();
+ }
+
+ struct wlr_box dst_box;
+ struct wlr_fbox src_fbox;
+ wlr_render_texture_options_get_src_box(options, &src_fbox);
+ wlr_render_texture_options_get_dst_box(options, &dst_box);
+ float alpha = wlr_render_texture_options_get_alpha(options);
+
+ const struct wlr_box *clip_box = &dst_box;
+ if (!wlr_box_empty(fx_options->clip_box)) {
+ clip_box = fx_options->clip_box;
+ }
+
+ src_fbox.x /= options->texture->width;
+ src_fbox.y /= options->texture->height;
+ src_fbox.width /= options->texture->width;
+ src_fbox.height /= options->texture->height;
+
+ push_fx_debug(renderer);
+
+ if (options->wait_timeline != NULL) {
+ int sync_file_fd =
+ wlr_drm_syncobj_timeline_export_sync_file(options->wait_timeline, options->wait_point);
+ if (sync_file_fd < 0) {
+ return;
+ }
+
+ EGLSyncKHR sync = wlr_egl_create_sync(renderer->egl, sync_file_fd);
+ close(sync_file_fd);
+ if (sync == EGL_NO_SYNC_KHR) {
+ return;
+ }
+
+ bool ok = wlr_egl_wait_sync(renderer->egl, sync);
+ wlr_egl_destroy_sync(renderer->egl, sync);
+ if (!ok) {
+ return;
+ }
+ }
+
+ bool has_alpha = texture->has_alpha
+ || alpha < 1.0
+ || fx_options->corner_radius > 0
+ || fx_options->discard_transparent;
+ setup_blending(!has_alpha ? WLR_RENDER_BLEND_MODE_NONE : options->blend_mode);
+
+ glUseProgram(shader->program);
+
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(texture->target, texture->tex);
+
+ switch (options->filter_mode) {
+ case WLR_SCALE_FILTER_BILINEAR:
+ glTexParameteri(texture->target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(texture->target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ break;
+ case WLR_SCALE_FILTER_NEAREST:
+ glTexParameteri(texture->target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(texture->target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ break;
+ }
+
+ enum corner_location corners = fx_options->corners;
+
+ glUniform1i(shader->tex, 0);
+ glUniform1f(shader->alpha, alpha);
+ glUniform2f(shader->size, clip_box->width, clip_box->height);
+ glUniform2f(shader->position, clip_box->x, clip_box->y);
+ glUniform1f(shader->discard_transparent, fx_options->discard_transparent);
+ glUniform1f(shader->radius_top_left, (CORNER_LOCATION_TOP_LEFT & corners) == CORNER_LOCATION_TOP_LEFT ?
+ fx_options->corner_radius : 0);
+ glUniform1f(shader->radius_top_right, (CORNER_LOCATION_TOP_RIGHT & corners) == CORNER_LOCATION_TOP_RIGHT ?
+ fx_options->corner_radius : 0);
+ glUniform1f(shader->radius_bottom_left, (CORNER_LOCATION_BOTTOM_LEFT & corners) == CORNER_LOCATION_BOTTOM_LEFT ?
+ fx_options->corner_radius : 0);
+ glUniform1f(shader->radius_bottom_right, (CORNER_LOCATION_BOTTOM_RIGHT & corners) == CORNER_LOCATION_BOTTOM_RIGHT ?
+ fx_options->corner_radius : 0);
+
+ set_proj_matrix(shader->proj, pass->projection_matrix, &dst_box);
+ set_tex_matrix(shader->tex_proj, options->transform, &src_fbox);
+
+ render(&dst_box, options->clip, shader->pos_attrib);
+
+ glBindTexture(texture->target, 0);
+ pop_fx_debug(renderer);
+}
+
+void fx_render_pass_add_rect(struct fx_gles_render_pass *pass,
+ const struct fx_render_rect_options *fx_options) {
+ const struct wlr_render_rect_options *options = &fx_options->base;
+
+ struct fx_renderer *renderer = pass->buffer->renderer;
+
+ const struct wlr_render_color *color = &options->color;
+ struct wlr_box box;
+ wlr_render_rect_options_get_box(options, pass->buffer->buffer, &box);
+
+ pixman_region32_t clip_region;
+ if (options->clip) {
+ pixman_region32_init(&clip_region);
+ pixman_region32_copy(&clip_region, options->clip);
+ } else {
+ pixman_region32_init_rect(&clip_region, box.x, box.y, box.width, box.height);
+ }
+ const struct wlr_box clipped_region_box = fx_options->clipped_region.area;
+ enum corner_location clipped_region_corners = fx_options->clipped_region.corners;
+ int clipped_region_corner_radius = clipped_region_corners != CORNER_LOCATION_NONE ?
+ fx_options->clipped_region.corner_radius : 0;
+ if (!wlr_box_empty(&clipped_region_box)) {
+ pixman_region32_t user_clip_region;
+ pixman_region32_init_rect(
+ &user_clip_region,
+ clipped_region_box.x + clipped_region_corner_radius * 0.3,
+ clipped_region_box.y + clipped_region_corner_radius * 0.3,
+ fmax(clipped_region_box.width - clipped_region_corner_radius * 0.6, 0),
+ fmax(clipped_region_box.height - clipped_region_corner_radius * 0.6, 0)
+ );
+ pixman_region32_subtract(&clip_region, &clip_region, &user_clip_region);
+ pixman_region32_fini(&user_clip_region);
+
+ push_fx_debug(renderer);
+ setup_blending(options->blend_mode);
+ } else {
+ push_fx_debug(renderer);
+ setup_blending(color->a == 1.0 ? WLR_RENDER_BLEND_MODE_NONE : options->blend_mode);
+ }
+
+ struct quad_shader shader = renderer->shaders.quad;
+ glUseProgram(shader.program);
+ set_proj_matrix(shader.proj, pass->projection_matrix, &box);
+ glUniform4f(shader.color, color->r, color->g, color->b, color->a);
+ glUniform2f(shader.clip_size, clipped_region_box.width, clipped_region_box.height);
+ glUniform2f(shader.clip_position, clipped_region_box.x, clipped_region_box.y);
+ glUniform1f(shader.clip_radius_top_left, (CORNER_LOCATION_TOP_LEFT & clipped_region_corners) == CORNER_LOCATION_TOP_LEFT ?
+ clipped_region_corner_radius : 0);
+ glUniform1f(shader.clip_radius_top_right, (CORNER_LOCATION_TOP_RIGHT & clipped_region_corners) == CORNER_LOCATION_TOP_RIGHT ?
+ clipped_region_corner_radius : 0);
+ glUniform1f(shader.clip_radius_bottom_left, (CORNER_LOCATION_BOTTOM_LEFT & clipped_region_corners) == CORNER_LOCATION_BOTTOM_LEFT ?
+ clipped_region_corner_radius : 0);
+ glUniform1f(shader.clip_radius_bottom_right, (CORNER_LOCATION_BOTTOM_RIGHT & clipped_region_corners) == CORNER_LOCATION_BOTTOM_RIGHT ?
+ clipped_region_corner_radius : 0);
+
+ render(&box, &clip_region, renderer->shaders.quad.pos_attrib);
+ pixman_region32_fini(&clip_region);
+
+ pop_fx_debug(renderer);
+}
+
+void fx_render_pass_add_rect_grad(struct fx_gles_render_pass *pass,
+ const struct fx_render_rect_grad_options *fx_options) {
+ const struct wlr_render_rect_options *options = &fx_options->base;
+
+ struct fx_renderer *renderer = pass->buffer->renderer;
+
+ if (renderer->shaders.quad_grad.max_len <= fx_options->gradient.count) {
+ EGLint client_version;
+ eglQueryContext(renderer->egl->display, renderer->egl->context,
+ EGL_CONTEXT_CLIENT_VERSION, &client_version);
+
+ glDeleteProgram(renderer->shaders.quad_grad.program);
+ if (!link_quad_grad_program(&renderer->shaders.quad_grad, (GLint) client_version, fx_options->gradient.count + 1)) {
+ wlr_log(WLR_ERROR, "Could not link quad shader after updating max_len to %d. Aborting renderer", fx_options->gradient.count + 1);
+ abort();
+ }
+ }
+
+ struct wlr_box box;
+ wlr_render_rect_options_get_box(options, pass->buffer->buffer, &box);
+
+ push_fx_debug(renderer);
+ setup_blending(options->blend_mode);
+
+ glUseProgram(renderer->shaders.quad_grad.program);
+
+ set_proj_matrix(renderer->shaders.quad_grad.proj, pass->projection_matrix, &box);
+ glUniform4fv(renderer->shaders.quad_grad.colors, fx_options->gradient.count, (GLfloat*)fx_options->gradient.colors);
+ glUniform1i(renderer->shaders.quad_grad.count, fx_options->gradient.count);
+ glUniform2f(renderer->shaders.quad_grad.size, fx_options->gradient.range.width, fx_options->gradient.range.height);
+ glUniform1f(renderer->shaders.quad_grad.degree, fx_options->gradient.degree);
+ glUniform1f(renderer->shaders.quad_grad.linear, fx_options->gradient.linear);
+ glUniform1f(renderer->shaders.quad_grad.blend, fx_options->gradient.blend);
+ glUniform2f(renderer->shaders.quad_grad.grad_box, fx_options->gradient.range.x, fx_options->gradient.range.y);
+ glUniform2f(renderer->shaders.quad_grad.origin, fx_options->gradient.origin[0], fx_options->gradient.origin[1]);
+
+ render(&box, options->clip, renderer->shaders.quad_grad.pos_attrib);
+
+ pop_fx_debug(renderer);
+}
+
+void fx_render_pass_add_rounded_rect(struct fx_gles_render_pass *pass,
+ const struct fx_render_rounded_rect_options *fx_options) {
+ const struct wlr_render_rect_options *options = &fx_options->base;
+
+ struct wlr_box box;
+ wlr_render_rect_options_get_box(options, pass->buffer->buffer, &box);
+ assert(box.width > 0 && box.height > 0);
+
+ struct fx_renderer *renderer = pass->buffer->renderer;
+
+ const struct wlr_render_color *color = &options->color;
+
+ pixman_region32_t clip_region;
+ if (options->clip) {
+ pixman_region32_init(&clip_region);
+ pixman_region32_copy(&clip_region, options->clip);
+ } else {
+ pixman_region32_init_rect(&clip_region, box.x, box.y, box.width, box.height);
+ }
+ const struct wlr_box clipped_region_box = fx_options->clipped_region.area;
+ enum corner_location clipped_region_corners = fx_options->clipped_region.corners;
+ int clipped_region_corner_radius = clipped_region_corners != CORNER_LOCATION_NONE ?
+ fx_options->clipped_region.corner_radius : 0;
+ if (!wlr_box_empty(&clipped_region_box)) {
+ pixman_region32_t user_clip_region;
+ pixman_region32_init_rect(
+ &user_clip_region,
+ clipped_region_box.x + clipped_region_corner_radius * 0.3,
+ clipped_region_box.y + clipped_region_corner_radius * 0.3,
+ fmax(clipped_region_box.width - clipped_region_corner_radius * 0.6, 0),
+ fmax(clipped_region_box.height - clipped_region_corner_radius * 0.6, 0)
+ );
+ pixman_region32_subtract(&clip_region, &clip_region, &user_clip_region);
+ pixman_region32_fini(&user_clip_region);
+ }
+
+ push_fx_debug(renderer);
+ setup_blending(WLR_RENDER_BLEND_MODE_PREMULTIPLIED);
+
+ struct quad_round_shader shader = renderer->shaders.quad_round;
+
+ glUseProgram(shader.program);
+
+ set_proj_matrix(shader.proj, pass->projection_matrix, &box);
+ glUniform4f(shader.color, color->r, color->g, color->b, color->a);
+
+ glUniform2f(shader.size, box.width, box.height);
+ glUniform2f(shader.position, box.x, box.y);
+ glUniform2f(shader.clip_size, clipped_region_box.width, clipped_region_box.height);
+ glUniform2f(shader.clip_position, clipped_region_box.x, clipped_region_box.y);
+ glUniform1f(shader.clip_radius_top_left, (CORNER_LOCATION_TOP_LEFT & clipped_region_corners) == CORNER_LOCATION_TOP_LEFT ?
+ clipped_region_corner_radius : 0);
+ glUniform1f(shader.clip_radius_top_right, (CORNER_LOCATION_TOP_RIGHT & clipped_region_corners) == CORNER_LOCATION_TOP_RIGHT ?
+ clipped_region_corner_radius : 0);
+ glUniform1f(shader.clip_radius_bottom_left, (CORNER_LOCATION_BOTTOM_LEFT & clipped_region_corners) == CORNER_LOCATION_BOTTOM_LEFT ?
+ clipped_region_corner_radius : 0);
+ glUniform1f(shader.clip_radius_bottom_right, (CORNER_LOCATION_BOTTOM_RIGHT & clipped_region_corners) == CORNER_LOCATION_BOTTOM_RIGHT ?
+ clipped_region_corner_radius : 0);
+
+ enum corner_location corners = fx_options->corners;
+ glUniform1f(shader.radius_top_left, (CORNER_LOCATION_TOP_LEFT & corners) == CORNER_LOCATION_TOP_LEFT ?
+ fx_options->corner_radius : 0);
+ glUniform1f(shader.radius_top_right, (CORNER_LOCATION_TOP_RIGHT & corners) == CORNER_LOCATION_TOP_RIGHT ?
+ fx_options->corner_radius : 0);
+ glUniform1f(shader.radius_bottom_left, (CORNER_LOCATION_BOTTOM_LEFT & corners) == CORNER_LOCATION_BOTTOM_LEFT ?
+ fx_options->corner_radius : 0);
+ glUniform1f(shader.radius_bottom_right, (CORNER_LOCATION_BOTTOM_RIGHT & corners) == CORNER_LOCATION_BOTTOM_RIGHT ?
+ fx_options->corner_radius : 0);
+ glUniform1f(shader.fade_inset, fx_options->fade_inset);
+
+ render(&box, &clip_region, renderer->shaders.quad_round.pos_attrib);
+ pixman_region32_fini(&clip_region);
+
+ pop_fx_debug(renderer);
+}
+
+void fx_render_pass_add_rounded_rect_grad(struct fx_gles_render_pass *pass,
+ const struct fx_render_rounded_rect_grad_options *fx_options) {
+ const struct wlr_render_rect_options *options = &fx_options->base;
+
+ struct fx_renderer *renderer = pass->buffer->renderer;
+
+ if (renderer->shaders.quad_grad_round.max_len <= fx_options->gradient.count) {
+ EGLint client_version;
+ eglQueryContext(renderer->egl->display, renderer->egl->context,
+ EGL_CONTEXT_CLIENT_VERSION, &client_version);
+
+ glDeleteProgram(renderer->shaders.quad_grad_round.program);
+ if (!link_quad_grad_round_program(&renderer->shaders.quad_grad_round, (GLint) client_version, fx_options->gradient.count + 1)) {
+ wlr_log(WLR_ERROR, "Could not link quad shader after updating max_len to %d. Aborting renderer", fx_options->gradient.count + 1);
+ abort();
+ }
+ }
+
+ struct wlr_box box;
+ wlr_render_rect_options_get_box(options, pass->buffer->buffer, &box);
+
+ push_fx_debug(renderer);
+ setup_blending(WLR_RENDER_BLEND_MODE_PREMULTIPLIED);
+
+ struct quad_grad_round_shader shader = renderer->shaders.quad_grad_round;
+ glUseProgram(shader.program);
+
+ set_proj_matrix(shader.proj, pass->projection_matrix, &box);
+
+ glUniform2f(shader.size, box.width, box.height);
+ glUniform2f(shader.position, box.x, box.y);
+
+ glUniform4fv(shader.colors, fx_options->gradient.count, (GLfloat*)fx_options->gradient.colors);
+ glUniform1i(shader.count, fx_options->gradient.count);
+ glUniform2f(shader.grad_size, fx_options->gradient.range.width, fx_options->gradient.range.height);
+ glUniform1f(shader.degree, fx_options->gradient.degree);
+ glUniform1f(shader.linear, fx_options->gradient.linear);
+ glUniform1f(shader.blend, fx_options->gradient.blend);
+ glUniform2f(shader.grad_box, fx_options->gradient.range.x, fx_options->gradient.range.y);
+ glUniform2f(shader.origin, fx_options->gradient.origin[0], fx_options->gradient.origin[1]);
+
+ enum corner_location corners = fx_options->corners;
+ glUniform1f(shader.radius_top_left, (CORNER_LOCATION_TOP_LEFT & corners) == CORNER_LOCATION_TOP_LEFT ?
+ fx_options->corner_radius : 0);
+ glUniform1f(shader.radius_top_right, (CORNER_LOCATION_TOP_RIGHT & corners) == CORNER_LOCATION_TOP_RIGHT ?
+ fx_options->corner_radius : 0);
+ glUniform1f(shader.radius_bottom_left, (CORNER_LOCATION_BOTTOM_LEFT & corners) == CORNER_LOCATION_BOTTOM_LEFT ?
+ fx_options->corner_radius : 0);
+ glUniform1f(shader.radius_bottom_right, (CORNER_LOCATION_BOTTOM_RIGHT & corners) == CORNER_LOCATION_BOTTOM_RIGHT ?
+ fx_options->corner_radius : 0);
+
+ render(&box, options->clip, shader.pos_attrib);
+
+ pop_fx_debug(renderer);
+}
+
+void fx_render_pass_add_box_shadow(struct fx_gles_render_pass *pass,
+ const struct fx_render_box_shadow_options *options) {
+ struct fx_renderer *renderer = pass->buffer->renderer;
+
+ struct wlr_box box = options->box;
+ assert(box.width > 0 && box.height > 0);
+
+ pixman_region32_t clip_region;
+ if (options->clip) {
+ pixman_region32_init(&clip_region);
+ pixman_region32_copy(&clip_region, options->clip);
+ } else {
+ pixman_region32_init_rect(&clip_region, box.x, box.y, box.width, box.height);
+ }
+ const struct wlr_box clipped_region_box = options->clipped_region.area;
+ enum corner_location clipped_region_corners = options->clipped_region.corners;
+ int clipped_region_corner_radius = clipped_region_corners != CORNER_LOCATION_NONE ?
+ options->clipped_region.corner_radius : 0;
+ if (!wlr_box_empty(&clipped_region_box)) {
+ pixman_region32_t user_clip_region;
+ pixman_region32_init_rect(
+ &user_clip_region,
+ clipped_region_box.x + clipped_region_corner_radius * 0.3,
+ clipped_region_box.y + clipped_region_corner_radius * 0.3,
+ fmax(clipped_region_box.width - clipped_region_corner_radius * 0.6, 0),
+ fmax(clipped_region_box.height - clipped_region_corner_radius * 0.6, 0)
+ );
+ pixman_region32_subtract(&clip_region, &clip_region, &user_clip_region);
+ pixman_region32_fini(&user_clip_region);
+ }
+
+ push_fx_debug(renderer);
+ // blending will practically always be needed (unless we have a madman
+ // who uses opaque shadows with zero sigma), so just enable it
+ setup_blending(WLR_RENDER_BLEND_MODE_PREMULTIPLIED);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+ glUseProgram(renderer->shaders.box_shadow.program);
+
+ const struct wlr_render_color *color = &options->color;
+ set_proj_matrix(renderer->shaders.box_shadow.proj, pass->projection_matrix, &box);
+ glUniform4f(renderer->shaders.box_shadow.color, color->r, color->g, color->b, color->a);
+ glUniform1f(renderer->shaders.box_shadow.blur_sigma, options->blur_sigma);
+ glUniform2f(renderer->shaders.box_shadow.size, box.width, box.height);
+ glUniform2f(renderer->shaders.box_shadow.position, box.x, box.y);
+ glUniform1f(renderer->shaders.box_shadow.corner_radius, options->corner_radius);
+
+ glUniform1f(renderer->shaders.box_shadow.clip_radius_top_left,
+ (CORNER_LOCATION_TOP_LEFT & clipped_region_corners) == CORNER_LOCATION_TOP_LEFT ?
+ clipped_region_corner_radius : 0);
+ glUniform1f(renderer->shaders.box_shadow.clip_radius_top_right,
+ (CORNER_LOCATION_TOP_RIGHT & clipped_region_corners) == CORNER_LOCATION_TOP_RIGHT ?
+ clipped_region_corner_radius : 0);
+ glUniform1f(renderer->shaders.box_shadow.clip_radius_bottom_left,
+ (CORNER_LOCATION_BOTTOM_LEFT & clipped_region_corners) == CORNER_LOCATION_BOTTOM_LEFT ?
+ clipped_region_corner_radius : 0);
+ glUniform1f(renderer->shaders.box_shadow.clip_radius_bottom_right,
+ (CORNER_LOCATION_BOTTOM_RIGHT & clipped_region_corners) == CORNER_LOCATION_BOTTOM_RIGHT ?
+ clipped_region_corner_radius : 0);
+
+ glUniform2f(renderer->shaders.box_shadow.clip_position, clipped_region_box.x, clipped_region_box.y);
+ glUniform2f(renderer->shaders.box_shadow.clip_size, clipped_region_box.width, clipped_region_box.height);
+
+ render(&box, &clip_region, renderer->shaders.box_shadow.pos_attrib);
+ pixman_region32_fini(&clip_region);
+
+ glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
+
+ pop_fx_debug(renderer);
+}
+
+// Renders the blur for each damaged rect and swaps the buffer
+static void render_blur_segments(struct fx_gles_render_pass *pass,
+ struct fx_render_blur_pass_options *fx_options, struct blur_shader* shader) {
+ struct fx_render_texture_options *tex_options = &fx_options->tex_options;
+ struct wlr_render_texture_options *options = &tex_options->base;
+ struct fx_renderer *renderer = pass->buffer->renderer;
+ struct blur_data *blur_data = fx_options->blur_data;
+
+ // Swap fbo
+ if (fx_options->current_buffer == pass->fx_effect_framebuffers->effects_buffer) {
+ fx_framebuffer_bind(pass->fx_effect_framebuffers->effects_buffer_swapped);
+ } else {
+ fx_framebuffer_bind(pass->fx_effect_framebuffers->effects_buffer);
+ }
+
+ options->texture = fx_texture_from_buffer(&renderer->wlr_renderer,
+ fx_options->current_buffer->buffer);
+ struct fx_texture *texture = fx_get_texture(options->texture);
+
+ /*
+ * Render
+ */
+
+ struct wlr_box dst_box;
+ struct wlr_fbox src_fbox;
+ wlr_render_texture_options_get_src_box(options, &src_fbox);
+ wlr_render_texture_options_get_dst_box(options, &dst_box);
+ src_fbox.x /= options->texture->width;
+ src_fbox.y /= options->texture->height;
+ src_fbox.width /= options->texture->width;
+ src_fbox.height /= options->texture->height;
+
+ glDisable(GL_BLEND);
+ glDisable(GL_STENCIL_TEST);
+
+ push_fx_debug(renderer);
+
+ glUseProgram(shader->program);
+
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(texture->target, texture->tex);
+
+ switch (options->filter_mode) {
+ case WLR_SCALE_FILTER_BILINEAR:
+ glTexParameteri(texture->target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(texture->target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ break;
+ case WLR_SCALE_FILTER_NEAREST:
+ abort();
+ }
+
+ glUniform1i(shader->tex, 0);
+ glUniform1f(shader->radius, blur_data->radius);
+
+ if (shader == &renderer->shaders.blur1) {
+ glUniform2f(shader->halfpixel,
+ 0.5f / (options->texture->width / 2.0f),
+ 0.5f / (options->texture->height / 2.0f));
+ } else {
+ glUniform2f(shader->halfpixel,
+ 0.5f / (options->texture->width * 2.0f),
+ 0.5f / (options->texture->height * 2.0f));
+ }
+
+ set_proj_matrix(shader->proj, pass->projection_matrix, &dst_box);
+ set_tex_matrix(shader->tex_proj, options->transform, &src_fbox);
+
+ render(&dst_box, options->clip, shader->pos_attrib);
+
+ glBindTexture(texture->target, 0);
+ pop_fx_debug(renderer);
+
+ wlr_texture_destroy(options->texture);
+
+ // Swap buffer. We don't want to draw to the same buffer
+ if (fx_options->current_buffer != pass->fx_effect_framebuffers->effects_buffer) {
+ fx_options->current_buffer = pass->fx_effect_framebuffers->effects_buffer;
+ } else {
+ fx_options->current_buffer = pass->fx_effect_framebuffers->effects_buffer_swapped;
+ }
+}
+
+static void render_blur_effects(struct fx_gles_render_pass *pass,
+ struct fx_render_blur_pass_options *fx_options) {
+ struct fx_render_texture_options *tex_options = &fx_options->tex_options;
+ struct wlr_render_texture_options *options = &tex_options->base;
+ struct fx_renderer *renderer = pass->buffer->renderer;
+ struct blur_data *blur_data = fx_options->blur_data;
+ struct fx_texture *texture = fx_get_texture(options->texture);
+
+ struct blur_effects_shader shader = renderer->shaders.blur_effects;
+
+ struct wlr_box dst_box;
+ struct wlr_fbox src_fbox;
+ wlr_render_texture_options_get_src_box(options, &src_fbox);
+ wlr_render_texture_options_get_dst_box(options, &dst_box);
+
+ src_fbox.x /= options->texture->width;
+ src_fbox.y /= options->texture->height;
+ src_fbox.width /= options->texture->width;
+ src_fbox.height /= options->texture->height;
+
+ glDisable(GL_BLEND);
+ glDisable(GL_STENCIL_TEST);
+
+ push_fx_debug(renderer);
+
+ glUseProgram(shader.program);
+
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(texture->target, texture->tex);
+
+ switch (options->filter_mode) {
+ case WLR_SCALE_FILTER_BILINEAR:
+ glTexParameteri(texture->target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(texture->target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ break;
+ case WLR_SCALE_FILTER_NEAREST:
+ abort();
+ }
+
+ glUniform1i(shader.tex, 0);
+ glUniform1f(shader.noise, blur_data->noise);
+ glUniform1f(shader.brightness, blur_data->brightness);
+ glUniform1f(shader.contrast, blur_data->contrast);
+ glUniform1f(shader.saturation, blur_data->saturation);
+
+ set_proj_matrix(shader.proj, pass->projection_matrix, &dst_box);
+ set_tex_matrix(shader.tex_proj, options->transform, &src_fbox);
+
+ render(&dst_box, options->clip, shader.pos_attrib);
+
+ glBindTexture(texture->target, 0);
+ pop_fx_debug(renderer);
+
+ wlr_texture_destroy(options->texture);
+}
+
+// Blurs the main_buffer content and returns the blurred framebuffer
+static struct fx_framebuffer *get_main_buffer_blur(struct fx_gles_render_pass *pass,
+ struct fx_render_blur_pass_options *fx_options) {
+ struct fx_renderer *renderer = pass->buffer->renderer;
+ struct blur_data *blur_data = fx_options->blur_data;
+ struct wlr_box monitor_box = get_monitor_box(pass->output);
+
+ pixman_region32_t damage;
+ pixman_region32_init(&damage);
+ pixman_region32_copy(&damage, fx_options->tex_options.base.clip);
+ wlr_region_transform(&damage, &damage, fx_options->tex_options.base.transform,
+ monitor_box.width, monitor_box.height);
+
+ wlr_region_expand(&damage, &damage, blur_data_calc_size(fx_options->blur_data));
+
+ // damage region will be scaled, make a temp
+ pixman_region32_t scaled_damage;
+ pixman_region32_init(&scaled_damage);
+
+ fx_options->tex_options.base.src_box = (struct wlr_fbox) {
+ monitor_box.x,
+ monitor_box.y,
+ monitor_box.width,
+ monitor_box.height,
+ };
+ fx_options->tex_options.base.dst_box = monitor_box;
+ // Clip the blur to the damage
+ fx_options->tex_options.base.clip = &scaled_damage;
+ // Artifacts with NEAREST filter
+ fx_options->tex_options.base.filter_mode = WLR_SCALE_FILTER_BILINEAR;
+
+ // Downscale
+ for (int i = 0; i < blur_data->num_passes; ++i) {
+ wlr_region_scale(&scaled_damage, &damage, 1.0f / (1 << (i + 1)));
+ render_blur_segments(pass, fx_options, &renderer->shaders.blur1);
+ }
+
+ // Upscale
+ for (int i = blur_data->num_passes - 1; i >= 0; --i) {
+ // when upsampling we make the region twice as big
+ wlr_region_scale(&scaled_damage, &damage, 1.0f / (1 << i));
+ render_blur_segments(pass, fx_options, &renderer->shaders.blur2);
+ }
+
+ pixman_region32_fini(&scaled_damage);
+
+ // Render additional blur effects like saturation, noise, contrast, etc...
+ if (blur_data_should_parameters_blur_effects(blur_data)
+ && pixman_region32_not_empty(&damage)) {
+ if (fx_options->current_buffer == pass->fx_effect_framebuffers->effects_buffer) {
+ fx_framebuffer_bind(pass->fx_effect_framebuffers->effects_buffer_swapped);
+ } else {
+ fx_framebuffer_bind(pass->fx_effect_framebuffers->effects_buffer);
+ }
+ fx_options->tex_options.base.clip = &damage;
+ fx_options->tex_options.base.texture = fx_texture_from_buffer(
+ &renderer->wlr_renderer, fx_options->current_buffer->buffer);
+ render_blur_effects(pass, fx_options);
+ if (fx_options->current_buffer != pass->fx_effect_framebuffers->effects_buffer) {
+ fx_options->current_buffer = pass->fx_effect_framebuffers->effects_buffer;
+ } else {
+ fx_options->current_buffer = pass->fx_effect_framebuffers->effects_buffer_swapped;
+ }
+ }
+
+ pixman_region32_fini(&damage);
+
+ // Bind back to the default buffer
+ fx_framebuffer_bind(pass->buffer);
+
+ return fx_options->current_buffer;
+}
+
+void fx_render_pass_add_blur(struct fx_gles_render_pass *pass,
+ struct fx_render_blur_pass_options *fx_options) {
+ if (pass->buffer->renderer->basic_renderer) {
+ wlr_log(WLR_ERROR, "Please use 'fx_renderer_begin_buffer_pass' instead of "
+ "'wlr_renderer_begin_buffer_pass' to use advanced effects");
+ return;
+ }
+ struct fx_renderer *renderer = pass->buffer->renderer;
+ struct fx_render_texture_options *tex_options = &fx_options->tex_options;
+ const struct wlr_render_texture_options *options = &tex_options->base;
+
+ pixman_region32_t translucent_region;
+ pixman_region32_init(&translucent_region);
+
+ struct wlr_box dst_box;
+ wlr_render_texture_options_get_dst_box(options, &dst_box);
+
+ // Gets the translucent region
+ pixman_box32_t surface_box = { 0, 0, dst_box.width, dst_box.height };
+ pixman_region32_copy(&translucent_region, fx_options->opaque_region);
+ pixman_region32_inverse(&translucent_region, &translucent_region, &surface_box);
+ if (!pixman_region32_not_empty(&translucent_region)) {
+ goto damage_finish;
+ }
+
+ struct fx_framebuffer *buffer = pass->fx_effect_framebuffers->optimized_blur_buffer;
+ if (!buffer || !fx_options->use_optimized_blur) {
+ if (!buffer) {
+ wlr_log(WLR_ERROR, "Warning: Failed to use optimized blur");
+ }
+ pixman_region32_translate(&translucent_region, dst_box.x, dst_box.y);
+ pixman_region32_intersect(&translucent_region, &translucent_region, options->clip);
+
+ // Render the blur into its own buffer
+ struct fx_render_blur_pass_options blur_options = *fx_options;
+ blur_options.tex_options.base.clip = &translucent_region;
+ blur_options.current_buffer = pass->buffer;
+ buffer = get_main_buffer_blur(pass, &blur_options);
+ }
+ struct wlr_texture *wlr_texture =
+ fx_texture_from_buffer(&renderer->wlr_renderer, buffer->buffer);
+ struct fx_texture *blur_texture = fx_get_texture(wlr_texture);
+ blur_texture->has_alpha = true;
+
+ // Get a stencil of the window ignoring transparent regions
+ if (fx_options->ignore_transparent && fx_options->tex_options.base.texture) {
+ stencil_mask_init();
+
+ struct fx_render_texture_options tex_options = fx_options->tex_options;
+ tex_options.discard_transparent = true;
+ fx_render_pass_add_texture(pass, &tex_options);
+
+ stencil_mask_close(true);
+ }
+
+ // Draw the blurred texture
+ tex_options->base.dst_box = get_monitor_box(pass->output);
+ tex_options->base.src_box = (struct wlr_fbox) {
+ .x = 0,
+ .y = 0,
+ .width = buffer->buffer->width,
+ .height = buffer->buffer->height,
+ };
+ tex_options->base.texture = &blur_texture->wlr_texture;
+ fx_render_pass_add_texture(pass, tex_options);
+
+ wlr_texture_destroy(&blur_texture->wlr_texture);
+
+ // Finish stenciling
+ if (fx_options->ignore_transparent && fx_options->tex_options.base.texture) {
+ stencil_mask_fini();
+ }
+
+damage_finish:
+ pixman_region32_fini(&translucent_region);
+}
+
+bool fx_render_pass_add_optimized_blur(struct fx_gles_render_pass *pass,
+ struct fx_render_blur_pass_options *fx_options) {
+ if (pass->buffer->renderer->basic_renderer) {
+ wlr_log(WLR_ERROR, "Please use 'fx_renderer_begin_buffer_pass' instead of "
+ "'wlr_renderer_begin_buffer_pass' to use advanced effects");
+ return false;
+ }
+ struct fx_renderer *renderer = pass->buffer->renderer;
+ struct wlr_box dst_box = fx_options->tex_options.base.dst_box;
+
+ pixman_region32_t clip;
+ pixman_region32_init_rect(&clip,
+ dst_box.x, dst_box.y, dst_box.width, dst_box.height);
+
+ // Render the blur into its own buffer
+ struct fx_render_blur_pass_options blur_options = *fx_options;
+ blur_options.current_buffer = pass->buffer;
+ blur_options.tex_options.base.clip = &clip;
+ struct fx_framebuffer *buffer = get_main_buffer_blur(pass, &blur_options);
+
+ // Update the optimized blur buffer if invalid
+ bool failed = false;
+ fx_framebuffer_get_or_create_custom(renderer, pass->output, NULL,
+ &pass->fx_effect_framebuffers->optimized_blur_buffer, &failed);
+ if (failed) {
+ goto finish;
+ }
+
+ // Render the newly blurred content into the blur_buffer
+ fx_renderer_read_to_buffer(pass, &clip,
+ pass->fx_effect_framebuffers->optimized_blur_buffer, buffer);
+
+finish:
+ pixman_region32_fini(&clip);
+ return !failed;
+}
+
+// TODO: Use blitting for glesv3
+void fx_renderer_read_to_buffer(struct fx_gles_render_pass *pass,
+ pixman_region32_t *_region, struct fx_framebuffer *dst_buffer,
+ struct fx_framebuffer *src_buffer) {
+ if (!_region || !pixman_region32_not_empty(_region)) {
+ return;
+ }
+
+ pixman_region32_t region;
+ pixman_region32_init(®ion);
+ pixman_region32_copy(®ion, _region);
+
+ struct wlr_texture *src_tex =
+ fx_texture_from_buffer(&pass->buffer->renderer->wlr_renderer, src_buffer->buffer);
+ if (src_tex == NULL) {
+ pixman_region32_fini(®ion);
+ return;
+ }
+
+ // Draw onto the dst_buffer
+ fx_framebuffer_bind(dst_buffer);
+ wlr_render_pass_add_texture(&pass->base, &(struct wlr_render_texture_options) {
+ .texture = src_tex,
+ .clip = ®ion,
+ .transform = WL_OUTPUT_TRANSFORM_NORMAL,
+ .blend_mode = WLR_RENDER_BLEND_MODE_NONE,
+ .dst_box = (struct wlr_box){
+ .width = dst_buffer->buffer->width,
+ .height = dst_buffer->buffer->height,
+ },
+ .src_box = (struct wlr_fbox){
+ .x = 0,
+ .y = 0,
+ .width = src_buffer->buffer->width,
+ .height = src_buffer->buffer->height,
+ },
+ });
+ wlr_texture_destroy(src_tex);
+
+ // Bind back to the main WLR buffer
+ fx_framebuffer_bind(pass->buffer);
+
+ pixman_region32_fini(®ion);
+}
+
+
+static const char *reset_status_str(GLenum status) {
+ switch (status) {
+ case GL_GUILTY_CONTEXT_RESET_KHR:
+ return "guilty";
+ case GL_INNOCENT_CONTEXT_RESET_KHR:
+ return "innocent";
+ case GL_UNKNOWN_CONTEXT_RESET_KHR:
+ return "unknown";
+ default:
+ return "<invalid>";
+ }
+}
+
+static struct fx_gles_render_pass *begin_buffer_pass(struct fx_framebuffer *buffer,
+ struct wlr_egl_context *prev_ctx, struct fx_render_timer *timer,
+ struct wlr_drm_syncobj_timeline *signal_timeline, uint64_t signal_point) {
+ struct fx_renderer *renderer = buffer->renderer;
+ struct wlr_buffer *wlr_buffer = buffer->buffer;
+
+ if (renderer->procs.glGetGraphicsResetStatusKHR) {
+ GLenum status = renderer->procs.glGetGraphicsResetStatusKHR();
+ if (status != GL_NO_ERROR) {
+ wlr_log(WLR_ERROR, "GPU reset (%s)", reset_status_str(status));
+ wl_signal_emit_mutable(&renderer->wlr_renderer.events.lost, NULL);
+ return NULL;
+ }
+ }
+
+ GLint fbo = fx_framebuffer_get_fbo(buffer);
+ if (!fbo) {
+ return NULL;
+ }
+
+ struct fx_gles_render_pass *pass = calloc(1, sizeof(*pass));
+ if (pass == NULL) {
+ return NULL;
+ }
+
+ wlr_render_pass_init(&pass->base, &render_pass_impl);
+ wlr_buffer_lock(wlr_buffer);
+ pass->buffer = buffer;
+ pass->timer = timer;
+ pass->prev_ctx = *prev_ctx;
+ if (signal_timeline != NULL) {
+ pass->signal_timeline = wlr_drm_syncobj_timeline_ref(signal_timeline);
+ pass->signal_point = signal_point;
+ }
+
+ matrix_projection(pass->projection_matrix, wlr_buffer->width, wlr_buffer->height,
+ WL_OUTPUT_TRANSFORM_FLIPPED_180);
+
+ push_fx_debug(renderer);
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+
+ glViewport(0, 0, wlr_buffer->width, wlr_buffer->height);
+ glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
+ glDisable(GL_SCISSOR_TEST);
+ pop_fx_debug(renderer);
+
+ return pass;
+}
+
+struct fx_gles_render_pass *fx_renderer_begin_buffer_pass(
+ struct wlr_renderer *wlr_renderer, struct wlr_buffer *wlr_buffer,
+ struct wlr_output *output, const struct fx_buffer_pass_options *fx_options) {
+ const struct wlr_buffer_pass_options *options = fx_options->base;
+ struct fx_renderer *renderer = fx_get_renderer(wlr_renderer);
+
+ renderer->basic_renderer = (output == NULL);
+
+ struct wlr_egl_context prev_ctx = {0};
+ if (!wlr_egl_make_current(renderer->egl, &prev_ctx)) {
+ return NULL;
+ }
+
+ struct fx_render_timer *timer = NULL;
+ if (options->timer) {
+ timer = fx_get_render_timer(options->timer);
+ clock_gettime(CLOCK_MONOTONIC, &timer->cpu_start);
+ }
+
+ struct fx_framebuffer *buffer = fx_framebuffer_get_or_create(renderer, wlr_buffer);
+ if (!buffer) {
+ return NULL;
+ }
+
+ // For per output framebuffers
+ struct fx_effect_framebuffers *fbos = NULL;
+ // Update the buffers if needed
+ if (!renderer->basic_renderer) {
+ bool failed = false;
+
+ fbos = fx_effect_framebuffers_try_get(output);
+ failed |= fbos == NULL;
+ if (fbos) {
+ pixman_region32_init(&fbos->blur_padding_region);
+
+ fx_framebuffer_get_or_create_custom(renderer, output, fx_options->swapchain,
+ &fbos->blur_saved_pixels_buffer, &failed);
+ fx_framebuffer_get_or_create_custom(renderer, output, fx_options->swapchain,
+ &fbos->effects_buffer, &failed);
+ fx_framebuffer_get_or_create_custom(renderer, output, fx_options->swapchain,
+ &fbos->effects_buffer_swapped, &failed);
+ }
+
+ if (failed) {
+ // Revert back to not running advanced effects
+ renderer->basic_renderer = true;
+ }
+ }
+
+ struct fx_gles_render_pass *pass = begin_buffer_pass(buffer,
+ &prev_ctx, timer, options->signal_timeline, options->signal_point);
+ if (!pass) {
+ return NULL;
+ }
+ pass->fx_effect_framebuffers = fbos;
+ pass->output = output;
+ return pass;
+}
diff --git a/scenefx/render/fx_renderer/fx_renderer.c b/scenefx/render/fx_renderer/fx_renderer.c
new file mode 100644
index 0000000..ff42020
--- /dev/null
+++ b/scenefx/render/fx_renderer/fx_renderer.c
@@ -0,0 +1,546 @@
+/*
+ The original wlr_renderer was heavily referenced in making this project
+ https://gitlab.freedesktop.org/wlroots/wlroots/-/tree/master/render/gles2
+*/
+
+#include <assert.h>
+#include <drm_fourcc.h>
+#include <GLES2/gl2.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <wlr/backend.h>
+#include <wlr/render/allocator.h>
+#include <wlr/render/egl.h>
+#include <wlr/render/interface.h>
+#include <wlr/util/box.h>
+#include <wlr/util/log.h>
+#include <xf86drm.h>
+
+#include "render/egl.h"
+#include "render/fx_renderer/shaders.h"
+#include "render/fx_renderer/fx_renderer.h"
+#include "render/fx_renderer/util.h"
+#include "scenefx/render/fx_renderer/fx_renderer.h"
+#include "scenefx/render/pass.h"
+#include "util/time.h"
+
+static const struct wlr_renderer_impl renderer_impl;
+static const struct wlr_render_timer_impl render_timer_impl;
+
+bool wlr_renderer_is_fx(struct wlr_renderer *wlr_renderer) {
+ return wlr_renderer->impl == &renderer_impl;
+}
+
+struct fx_renderer *fx_get_renderer(
+ struct wlr_renderer *wlr_renderer) {
+ assert(wlr_renderer_is_fx(wlr_renderer));
+ struct fx_renderer *renderer = wl_container_of(wlr_renderer, renderer, wlr_renderer);
+ return renderer;
+}
+
+bool wlr_render_timer_is_fx(struct wlr_render_timer *timer) {
+ return timer->impl == &render_timer_impl;
+}
+
+struct fx_render_timer *fx_get_render_timer(struct wlr_render_timer *wlr_timer) {
+ assert(wlr_render_timer_is_fx(wlr_timer));
+ struct fx_render_timer *timer = wl_container_of(wlr_timer, timer, base);
+ return timer;
+}
+
+static const struct wlr_drm_format_set *fx_get_texture_formats(
+ struct wlr_renderer *wlr_renderer, uint32_t buffer_caps) {
+ struct fx_renderer *renderer = fx_get_renderer(wlr_renderer);
+ if (buffer_caps & WLR_BUFFER_CAP_DMABUF) {
+ return wlr_egl_get_dmabuf_texture_formats(renderer->egl);
+ } else if (buffer_caps & WLR_BUFFER_CAP_DATA_PTR) {
+ return &renderer->shm_texture_formats;
+ } else {
+ return NULL;
+ }
+}
+
+static const struct wlr_drm_format_set *fx_get_render_formats(
+ struct wlr_renderer *wlr_renderer) {
+ struct fx_renderer *renderer = fx_get_renderer(wlr_renderer);
+ return wlr_egl_get_dmabuf_render_formats(renderer->egl);
+}
+
+static int fx_get_drm_fd(struct wlr_renderer *wlr_renderer) {
+ struct fx_renderer *renderer =
+ fx_get_renderer(wlr_renderer);
+
+ if (renderer->drm_fd < 0) {
+ renderer->drm_fd = wlr_egl_dup_drm_fd(renderer->egl);
+ }
+
+ return renderer->drm_fd;
+}
+
+static void fx_renderer_destroy(struct wlr_renderer *wlr_renderer) {
+ struct fx_renderer *renderer = fx_get_renderer(wlr_renderer);
+
+ wlr_egl_make_current(renderer->egl, NULL);
+
+ struct fx_texture *tex, *tex_tmp;
+ wl_list_for_each_safe(tex, tex_tmp, &renderer->textures, link) {
+ fx_texture_destroy(tex);
+ }
+
+ struct fx_framebuffer *buffer, *buffer_tmp;
+ wl_list_for_each_safe(buffer, buffer_tmp, &renderer->buffers, link) {
+ fx_framebuffer_destroy(buffer);
+ }
+
+ push_fx_debug(renderer);
+ glDeleteProgram(renderer->shaders.quad.program);
+ glDeleteProgram(renderer->shaders.tex_rgba.program);
+ glDeleteProgram(renderer->shaders.tex_rgbx.program);
+ glDeleteProgram(renderer->shaders.tex_ext.program);
+ pop_fx_debug(renderer);
+
+ if (renderer->exts.KHR_debug) {
+ glDisable(GL_DEBUG_OUTPUT_KHR);
+ renderer->procs.glDebugMessageCallbackKHR(NULL, NULL);
+ }
+
+ wlr_egl_unset_current(renderer->egl);
+ wlr_egl_destroy(renderer->egl);
+
+ wlr_drm_format_set_finish(&renderer->shm_texture_formats);
+
+ if (renderer->drm_fd >= 0) {
+ close(renderer->drm_fd);
+ }
+
+ free(renderer);
+}
+
+static struct wlr_render_pass *begin_buffer_pass(struct wlr_renderer *wlr_renderer,
+ struct wlr_buffer *wlr_buffer, const struct wlr_buffer_pass_options *options) {
+ struct fx_gles_render_pass *pass =
+ fx_renderer_begin_buffer_pass(wlr_renderer, wlr_buffer, NULL, &(struct fx_buffer_pass_options) {
+ .base = options,
+ .swapchain = NULL,
+ });
+ if (!pass) {
+ return NULL;
+ }
+ return &pass->base;
+}
+
+GLuint fx_renderer_get_buffer_fbo(struct wlr_renderer *wlr_renderer,
+ struct wlr_buffer *wlr_buffer) {
+ struct fx_renderer *renderer = fx_get_renderer(wlr_renderer);
+ GLuint fbo = 0;
+
+ struct wlr_egl_context prev_ctx = {0};
+ if (!wlr_egl_make_current(renderer->egl, &prev_ctx)) {
+ return 0;
+ }
+
+ struct fx_framebuffer *buffer = fx_framebuffer_get_or_create(renderer, wlr_buffer);
+ if (buffer) {
+ fbo = fx_framebuffer_get_fbo(buffer);
+ }
+
+ wlr_egl_restore_context(&prev_ctx);
+ return fbo;
+}
+
+
+static struct wlr_render_timer *fx_render_timer_create(struct wlr_renderer *wlr_renderer) {
+ struct fx_renderer *renderer = fx_get_renderer(wlr_renderer);
+ if (!renderer->exts.EXT_disjoint_timer_query) {
+ wlr_log(WLR_ERROR, "can't create timer, EXT_disjoint_timer_query not available");
+ return NULL;
+ }
+
+ struct fx_render_timer *timer = calloc(1, sizeof(*timer));
+ if (!timer) {
+ return NULL;
+ }
+ timer->base.impl = &render_timer_impl;
+ timer->renderer = renderer;
+
+ struct wlr_egl_context prev_ctx;
+ wlr_egl_make_current(renderer->egl, &prev_ctx);
+ renderer->procs.glGenQueriesEXT(1, &timer->id);
+ wlr_egl_restore_context(&prev_ctx);
+
+ return &timer->base;
+}
+
+static int fx_get_render_time(struct wlr_render_timer *wlr_timer) {
+ struct fx_render_timer *timer = fx_get_render_timer(wlr_timer);
+ struct fx_renderer *renderer = timer->renderer;
+
+ struct wlr_egl_context prev_ctx;
+ wlr_egl_make_current(renderer->egl, &prev_ctx);
+
+ GLint64 disjoint;
+ renderer->procs.glGetInteger64vEXT(GL_GPU_DISJOINT_EXT, &disjoint);
+ if (disjoint) {
+ wlr_log(WLR_ERROR, "a disjoint operation occurred and the render timer is invalid");
+ wlr_egl_restore_context(&prev_ctx);
+ return -1;
+ }
+
+ GLint available;
+ renderer->procs.glGetQueryObjectivEXT(timer->id,
+ GL_QUERY_RESULT_AVAILABLE_EXT, &available);
+ if (!available) {
+ wlr_log(WLR_ERROR, "timer was read too early, gpu isn't done!");
+ wlr_egl_restore_context(&prev_ctx);
+ return -1;
+ }
+
+ GLuint64 gl_render_end;
+ renderer->procs.glGetQueryObjectui64vEXT(timer->id, GL_QUERY_RESULT_EXT,
+ &gl_render_end);
+
+ int64_t cpu_nsec_total = timespec_to_nsec(&timer->cpu_end) - timespec_to_nsec(&timer->cpu_start);
+
+ wlr_egl_restore_context(&prev_ctx);
+ return gl_render_end - timer->gl_cpu_end + cpu_nsec_total;
+}
+
+static void fx_render_timer_destroy(struct wlr_render_timer *wlr_timer) {
+ struct fx_render_timer *timer = wl_container_of(wlr_timer, timer, base);
+ struct fx_renderer *renderer = timer->renderer;
+
+ struct wlr_egl_context prev_ctx;
+ wlr_egl_make_current(renderer->egl, &prev_ctx);
+ renderer->procs.glDeleteQueriesEXT(1, &timer->id);
+ wlr_egl_restore_context(&prev_ctx);
+ free(timer);
+}
+
+static const struct wlr_renderer_impl renderer_impl = {
+ .destroy = fx_renderer_destroy,
+ .get_texture_formats = fx_get_texture_formats,
+ .get_render_formats = fx_get_render_formats,
+ .get_drm_fd = fx_get_drm_fd,
+ .texture_from_buffer = fx_texture_from_buffer,
+ .begin_buffer_pass = begin_buffer_pass,
+ .render_timer_create = fx_render_timer_create,
+};
+
+static const struct wlr_render_timer_impl render_timer_impl = {
+ .get_duration_ns = fx_get_render_time,
+ .destroy = fx_render_timer_destroy,
+};
+
+void push_fx_debug_(struct fx_renderer *renderer,
+ const char *file, const char *func) {
+ if (!renderer->procs.glPushDebugGroupKHR) {
+ return;
+ }
+
+ int len = snprintf(NULL, 0, "%s:%s", file, func) + 1;
+ char str[len];
+ snprintf(str, len, "%s:%s", file, func);
+ renderer->procs.glPushDebugGroupKHR(GL_DEBUG_SOURCE_APPLICATION_KHR, 1, -1, str);
+}
+
+void pop_fx_debug(struct fx_renderer *renderer) {
+ if (renderer->procs.glPopDebugGroupKHR) {
+ renderer->procs.glPopDebugGroupKHR();
+ }
+}
+
+static enum wlr_log_importance fx_log_importance_to_wlr(GLenum type) {
+ switch (type) {
+ case GL_DEBUG_TYPE_ERROR_KHR: return WLR_ERROR;
+ case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_KHR: return WLR_DEBUG;
+ case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_KHR: return WLR_ERROR;
+ case GL_DEBUG_TYPE_PORTABILITY_KHR: return WLR_DEBUG;
+ case GL_DEBUG_TYPE_PERFORMANCE_KHR: return WLR_DEBUG;
+ case GL_DEBUG_TYPE_OTHER_KHR: return WLR_DEBUG;
+ case GL_DEBUG_TYPE_MARKER_KHR: return WLR_DEBUG;
+ case GL_DEBUG_TYPE_PUSH_GROUP_KHR: return WLR_DEBUG;
+ case GL_DEBUG_TYPE_POP_GROUP_KHR: return WLR_DEBUG;
+ default: return WLR_DEBUG;
+ }
+}
+
+static void fx_log(GLenum src, GLenum type, GLuint id, GLenum severity,
+ GLsizei len, const GLchar *msg, const void *user) {
+ _wlr_log(fx_log_importance_to_wlr(type), "[GLES2] %s", msg);
+}
+
+static struct wlr_renderer *renderer_autocreate(struct wlr_backend *backend, int drm_fd) {
+ bool own_drm_fd = false;
+ if (!open_preferred_drm_fd(backend, &drm_fd, &own_drm_fd)) {
+ wlr_log(WLR_ERROR, "Cannot create GLES2 renderer: no DRM FD available");
+ return NULL;
+ }
+
+ struct wlr_egl *egl = wlr_egl_create_with_drm_fd(drm_fd);
+ if (egl == NULL) {
+ wlr_log(WLR_ERROR, "Could not initialize EGL");
+ return NULL;
+ }
+
+ struct wlr_renderer *renderer = fx_renderer_create_egl(egl);
+ if (!renderer) {
+ wlr_log(WLR_ERROR, "Failed to create the FX renderer");
+ wlr_egl_destroy(egl);
+ return NULL;
+ }
+
+ if (own_drm_fd && drm_fd >= 0) {
+ close(drm_fd);
+ }
+
+ return renderer;
+}
+
+struct wlr_renderer *fx_renderer_create_with_drm_fd(int drm_fd) {
+ assert(drm_fd >= 0);
+
+ return renderer_autocreate(NULL, drm_fd);
+}
+
+struct wlr_renderer *fx_renderer_create(struct wlr_backend *backend) {
+ return renderer_autocreate(backend, -1);
+}
+
+static bool link_shaders(struct fx_renderer *renderer) {
+ EGLint client_version;
+ eglQueryContext(renderer->egl->display, renderer->egl->context,
+ EGL_CONTEXT_CLIENT_VERSION, &client_version);
+
+ // quad fragment shader
+ if (!link_quad_program(&renderer->shaders.quad, (GLint) client_version)) {
+ wlr_log(WLR_ERROR, "Could not link quad shader");
+ goto error;
+ }
+
+ // quad fragment shader with gradients
+ if (!link_quad_grad_program(&renderer->shaders.quad_grad, (GLint) client_version, 16)) {
+ wlr_log(WLR_ERROR, "Could not link quad grad shader");
+ goto error;
+ }
+
+ if (!link_quad_grad_round_program(&renderer->shaders.quad_grad_round, (GLint) client_version, 16)) {
+ wlr_log(WLR_ERROR, "Could not link quad grad round shader");
+ goto error;
+ }
+
+ if (!link_quad_round_program(&renderer->shaders.quad_round, (GLint) client_version)) {
+ wlr_log(WLR_ERROR, "Could not link quad round shader");
+ goto error;
+ }
+
+ // fragment shaders
+ if (!link_tex_program(&renderer->shaders.tex_rgba, (GLint) client_version, SHADER_SOURCE_TEXTURE_RGBA)) {
+ wlr_log(WLR_ERROR, "Could not link tex_RGBA shader");
+ goto error;
+ }
+ if (!link_tex_program(&renderer->shaders.tex_rgbx, (GLint) client_version, SHADER_SOURCE_TEXTURE_RGBX)) {
+ wlr_log(WLR_ERROR, "Could not link tex_RGBX shader");
+ goto error;
+ }
+ if (!link_tex_program(&renderer->shaders.tex_ext, (GLint) client_version, SHADER_SOURCE_TEXTURE_EXTERNAL)) {
+ wlr_log(WLR_ERROR, "Could not link tex_EXTERNAL shader");
+ goto error;
+ }
+
+ // box shadow shader
+ if (!link_box_shadow_program(&renderer->shaders.box_shadow, (GLint) client_version)) {
+ wlr_log(WLR_ERROR, "Could not link box shadow shader");
+ goto error;
+ }
+
+ // Blur shaders
+ if (!link_blur1_program(&renderer->shaders.blur1, (GLint) client_version)) {
+ wlr_log(WLR_ERROR, "Could not link blur1 shader");
+ goto error;
+ }
+ if (!link_blur2_program(&renderer->shaders.blur2, (GLint) client_version)) {
+ wlr_log(WLR_ERROR, "Could not link blur2 shader");
+ goto error;
+ }
+ if (!link_blur_effects_program(&renderer->shaders.blur_effects, (GLint) client_version)) {
+ wlr_log(WLR_ERROR, "Could not link blur_effects shader");
+ goto error;
+ }
+
+ return true;
+
+error:
+ glDeleteProgram(renderer->shaders.quad.program);
+ glDeleteProgram(renderer->shaders.quad_round.program);
+ glDeleteProgram(renderer->shaders.quad_grad.program);
+ glDeleteProgram(renderer->shaders.quad_grad_round.program);
+ glDeleteProgram(renderer->shaders.tex_rgba.program);
+ glDeleteProgram(renderer->shaders.tex_rgbx.program);
+ glDeleteProgram(renderer->shaders.tex_ext.program);
+ glDeleteProgram(renderer->shaders.box_shadow.program);
+ glDeleteProgram(renderer->shaders.blur1.program);
+ glDeleteProgram(renderer->shaders.blur2.program);
+ glDeleteProgram(renderer->shaders.blur_effects.program);
+
+ return false;
+}
+
+struct wlr_renderer *fx_renderer_create_egl(struct wlr_egl *egl) {
+ if (!wlr_egl_make_current(egl, NULL)) {
+ return NULL;
+ }
+
+ const char *exts_str = (const char *)glGetString(GL_EXTENSIONS);
+ if (exts_str == NULL) {
+ wlr_log(WLR_ERROR, "Failed to get GL_EXTENSIONS");
+ return NULL;
+ }
+
+ struct fx_renderer *renderer = calloc(1, sizeof(struct fx_renderer));
+ if (renderer == NULL) {
+ return NULL;
+ }
+ wlr_renderer_init(&renderer->wlr_renderer, &renderer_impl, WLR_BUFFER_CAP_DMABUF);
+ renderer->wlr_renderer.features.output_color_transform = false;
+
+ wl_list_init(&renderer->buffers);
+ wl_list_init(&renderer->textures);
+
+ renderer->egl = egl;
+ renderer->exts_str = exts_str;
+ renderer->drm_fd = -1;
+
+ wlr_log(WLR_INFO, "Creating scenefx FX renderer");
+ wlr_log(WLR_INFO, "Using %s", glGetString(GL_VERSION));
+ wlr_log(WLR_INFO, "GL vendor: %s", glGetString(GL_VENDOR));
+ wlr_log(WLR_INFO, "GL renderer: %s", glGetString(GL_RENDERER));
+ wlr_log(WLR_INFO, "Supported FX extensions: %s", exts_str);
+
+ if (!renderer->egl->exts.EXT_image_dma_buf_import) {
+ wlr_log(WLR_ERROR, "EGL_EXT_image_dma_buf_import not supported");
+ free(renderer);
+ return NULL;
+ }
+ if (!check_gl_ext(exts_str, "GL_EXT_texture_format_BGRA8888")) {
+ wlr_log(WLR_ERROR, "BGRA8888 format not supported by GLES2");
+ free(renderer);
+ return NULL;
+ }
+ if (!check_gl_ext(exts_str, "GL_EXT_unpack_subimage")) {
+ wlr_log(WLR_ERROR, "GL_EXT_unpack_subimage not supported");
+ free(renderer);
+ return NULL;
+ }
+
+ renderer->exts.EXT_read_format_bgra =
+ check_gl_ext(exts_str, "GL_EXT_read_format_bgra");
+
+ renderer->exts.EXT_texture_type_2_10_10_10_REV =
+ check_gl_ext(exts_str, "GL_EXT_texture_type_2_10_10_10_REV");
+
+ renderer->exts.OES_texture_half_float_linear =
+ check_gl_ext(exts_str, "GL_OES_texture_half_float_linear");
+
+ renderer->exts.EXT_texture_norm16 =
+ check_gl_ext(exts_str, "GL_EXT_texture_norm16");
+
+ if (check_gl_ext(exts_str, "GL_KHR_debug")) {
+ renderer->exts.KHR_debug = true;
+ load_gl_proc(&renderer->procs.glDebugMessageCallbackKHR,
+ "glDebugMessageCallbackKHR");
+ load_gl_proc(&renderer->procs.glDebugMessageControlKHR,
+ "glDebugMessageControlKHR");
+ }
+
+ // TODO: the rest of the gl checks
+ if (check_gl_ext(exts_str, "GL_OES_EGL_image_external")) {
+ renderer->exts.OES_egl_image_external = true;
+ load_gl_proc(&renderer->procs.glEGLImageTargetTexture2DOES,
+ "glEGLImageTargetTexture2DOES");
+ }
+
+ if (check_gl_ext(exts_str, "GL_OES_EGL_image")) {
+ renderer->exts.OES_egl_image = true;
+ load_gl_proc(&renderer->procs.glEGLImageTargetRenderbufferStorageOES,
+ "glEGLImageTargetRenderbufferStorageOES");
+ }
+
+ if (check_gl_ext(exts_str, "GL_KHR_robustness")) {
+ GLint notif_strategy = 0;
+ glGetIntegerv(GL_RESET_NOTIFICATION_STRATEGY_KHR, ¬if_strategy);
+ switch (notif_strategy) {
+ case GL_LOSE_CONTEXT_ON_RESET_KHR:
+ wlr_log(WLR_DEBUG, "GPU reset notifications are enabled");
+ load_gl_proc(&renderer->procs.glGetGraphicsResetStatusKHR,
+ "glGetGraphicsResetStatusKHR");
+ break;
+ case GL_NO_RESET_NOTIFICATION_KHR:
+ wlr_log(WLR_DEBUG, "GPU reset notifications are disabled");
+ break;
+ }
+ }
+
+ if (check_gl_ext(exts_str, "GL_EXT_disjoint_timer_query")) {
+ renderer->exts.EXT_disjoint_timer_query = true;
+ load_gl_proc(&renderer->procs.glGenQueriesEXT, "glGenQueriesEXT");
+ load_gl_proc(&renderer->procs.glDeleteQueriesEXT, "glDeleteQueriesEXT");
+ load_gl_proc(&renderer->procs.glQueryCounterEXT, "glQueryCounterEXT");
+ load_gl_proc(&renderer->procs.glGetQueryObjectivEXT, "glGetQueryObjectivEXT");
+ load_gl_proc(&renderer->procs.glGetQueryObjectui64vEXT, "glGetQueryObjectui64vEXT");
+ if (eglGetProcAddress("glGetInteger64vEXT")) {
+ load_gl_proc(&renderer->procs.glGetInteger64vEXT, "glGetInteger64vEXT");
+ } else {
+ load_gl_proc(&renderer->procs.glGetInteger64vEXT, "glGetInteger64v");
+ }
+ }
+
+ if (renderer->exts.KHR_debug) {
+ glEnable(GL_DEBUG_OUTPUT_KHR);
+ glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_KHR);
+ renderer->procs.glDebugMessageCallbackKHR(fx_log, NULL);
+
+ // Silence unwanted message types
+ renderer->procs.glDebugMessageControlKHR(GL_DONT_CARE,
+ GL_DEBUG_TYPE_POP_GROUP_KHR, GL_DONT_CARE, 0, NULL, GL_FALSE);
+ renderer->procs.glDebugMessageControlKHR(GL_DONT_CARE,
+ GL_DEBUG_TYPE_PUSH_GROUP_KHR, GL_DONT_CARE, 0, NULL, GL_FALSE);
+ }
+
+ push_fx_debug(renderer);
+
+ // Link all shaders
+ if (!link_shaders(renderer)) {
+ goto error;
+ }
+
+ pop_fx_debug(renderer);
+
+ wlr_log(WLR_INFO, "FX RENDERER: Shaders Initialized Successfully");
+
+ wlr_egl_unset_current(renderer->egl);
+
+ get_fx_shm_formats(renderer, &renderer->shm_texture_formats);
+
+ int drm_fd = wlr_renderer_get_drm_fd(&renderer->wlr_renderer);
+ uint64_t cap_syncobj_timeline;
+ if (drm_fd >= 0 && drmGetCap(drm_fd, DRM_CAP_SYNCOBJ_TIMELINE, &cap_syncobj_timeline) == 0) {
+ renderer->wlr_renderer.features.timeline = egl->procs.eglDupNativeFenceFDANDROID &&
+ egl->procs.eglWaitSyncKHR && cap_syncobj_timeline != 0;
+ }
+
+ return &renderer->wlr_renderer;
+
+error:
+ pop_fx_debug(renderer);
+
+ if (renderer->exts.KHR_debug) {
+ glDisable(GL_DEBUG_OUTPUT_KHR);
+ renderer->procs.glDebugMessageCallbackKHR(NULL, NULL);
+ }
+
+ wlr_egl_unset_current(renderer->egl);
+
+ free(renderer);
+ return NULL;
+}
diff --git a/scenefx/render/fx_renderer/fx_texture.c b/scenefx/render/fx_renderer/fx_texture.c
new file mode 100644
index 0000000..f69f735
--- /dev/null
+++ b/scenefx/render/fx_renderer/fx_texture.c
@@ -0,0 +1,435 @@
+#include <assert.h>
+#include <stdlib.h>
+#include <wlr/render/interface.h>
+#include <wlr/types/wlr_buffer.h>
+#include <wlr/util/log.h>
+#include <drm_fourcc.h>
+
+#include "scenefx/render/fx_renderer/fx_renderer.h"
+#include "render/fx_renderer/fx_renderer.h"
+#include "render/pixel_format.h"
+#include "render/egl.h"
+
+static const struct wlr_texture_impl texture_impl;
+
+bool wlr_texture_is_fx(struct wlr_texture *wlr_texture) {
+ return wlr_texture->impl == &texture_impl;
+}
+
+struct fx_texture *fx_get_texture(struct wlr_texture *wlr_texture) {
+ assert(wlr_texture_is_fx(wlr_texture));
+ struct fx_texture *texture = wl_container_of(wlr_texture, texture, wlr_texture);
+ return texture;
+}
+
+static bool fx_texture_update_from_buffer(struct wlr_texture *wlr_texture,
+ struct wlr_buffer *buffer, const pixman_region32_t *damage) {
+ struct fx_texture *texture = fx_get_texture(wlr_texture);
+
+ if (texture->drm_format == DRM_FORMAT_INVALID) {
+ return false;
+ }
+
+ void *data;
+ uint32_t format;
+ size_t stride;
+ if (!wlr_buffer_begin_data_ptr_access(buffer,
+ WLR_BUFFER_DATA_PTR_ACCESS_READ, &data, &format, &stride)) {
+ return false;
+ }
+
+ if (format != texture->drm_format) {
+ wlr_buffer_end_data_ptr_access(buffer);
+ return false;
+ }
+
+ const struct fx_pixel_format *fmt =
+ get_fx_format_from_drm(texture->drm_format);
+ assert(fmt);
+
+ const struct wlr_pixel_format_info *drm_fmt =
+ drm_get_pixel_format_info(texture->drm_format);
+ assert(drm_fmt);
+ if (pixel_format_info_pixels_per_block(drm_fmt) != 1) {
+ wlr_buffer_end_data_ptr_access(buffer);
+ wlr_log(WLR_ERROR, "Cannot update texture: block formats are not supported");
+ return false;
+ }
+
+ if (!pixel_format_info_check_stride(drm_fmt, stride, buffer->width)) {
+ wlr_buffer_end_data_ptr_access(buffer);
+ return false;
+ }
+
+ struct wlr_egl_context prev_ctx;
+ wlr_egl_make_current(texture->fx_renderer->egl, &prev_ctx);
+
+ push_fx_debug(texture->fx_renderer);
+
+ glBindTexture(GL_TEXTURE_2D, texture->tex);
+
+ int rects_len = 0;
+ pixman_box32_t *rects = pixman_region32_rectangles(damage, &rects_len);
+
+ for (int i = 0; i < rects_len; i++) {
+ pixman_box32_t rect = rects[i];
+
+ glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, stride / drm_fmt->bytes_per_block);
+ glPixelStorei(GL_UNPACK_SKIP_PIXELS_EXT, rect.x1);
+ glPixelStorei(GL_UNPACK_SKIP_ROWS_EXT, rect.y1);
+
+ int width = rect.x2 - rect.x1;
+ int height = rect.y2 - rect.y1;
+ glTexSubImage2D(GL_TEXTURE_2D, 0, rect.x1, rect.y1, width, height,
+ fmt->gl_format, fmt->gl_type, data);
+ }
+
+ glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0);
+ glPixelStorei(GL_UNPACK_SKIP_PIXELS_EXT, 0);
+ glPixelStorei(GL_UNPACK_SKIP_ROWS_EXT, 0);
+
+ glBindTexture(GL_TEXTURE_2D, 0);
+
+ pop_fx_debug(texture->fx_renderer);
+
+ wlr_egl_restore_context(&prev_ctx);
+
+ wlr_buffer_end_data_ptr_access(buffer);
+
+ return true;
+}
+
+void fx_texture_destroy(struct fx_texture *texture) {
+ wl_list_remove(&texture->link);
+ if (texture->buffer != NULL) {
+ wlr_buffer_unlock(texture->buffer->buffer);
+ } else {
+ struct wlr_egl_context prev_ctx;
+ wlr_egl_make_current(texture->fx_renderer->egl, &prev_ctx);
+
+ push_fx_debug(texture->fx_renderer);
+
+ glDeleteTextures(1, &texture->tex);
+ glDeleteFramebuffers(1, &texture->fbo);
+
+ pop_fx_debug(texture->fx_renderer);
+
+ wlr_egl_restore_context(&prev_ctx);
+ }
+
+ free(texture);
+}
+
+static bool fx_texture_bind(struct fx_texture *texture) {
+ if (texture->fbo) {
+ glBindFramebuffer(GL_FRAMEBUFFER, texture->fbo);
+ } else if (texture->buffer) {
+ if (texture->buffer->external_only) {
+ return false;
+ }
+
+ GLuint fbo = fx_framebuffer_get_fbo(texture->buffer);
+ if (!fbo) {
+ return false;
+ }
+
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+} else {
+ glGenFramebuffers(1, &texture->fbo);
+ glBindFramebuffer(GL_FRAMEBUFFER, texture->fbo);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ texture->target, texture->tex, 0);
+
+ GLenum fb_status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
+ if (fb_status != GL_FRAMEBUFFER_COMPLETE) {
+ wlr_log(WLR_ERROR, "Failed to create FBO");
+ glDeleteFramebuffers(1, &texture->fbo);
+ texture->fbo = 0;
+
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static bool fx_texture_read_pixels(struct wlr_texture *wlr_texture,
+ const struct wlr_texture_read_pixels_options *options) {
+ struct fx_texture *texture = fx_get_texture(wlr_texture);
+
+ struct wlr_box src;
+ wlr_texture_read_pixels_options_get_src_box(options, wlr_texture, &src);
+
+ const struct fx_pixel_format *fmt =
+ get_fx_format_from_drm(options->format);
+ if (fmt == NULL || !is_fx_pixel_format_supported(texture->fx_renderer, fmt)) {
+ wlr_log(WLR_ERROR, "Cannot read pixels: unsupported pixel format 0x%"PRIX32, options->format);
+ return false;
+ }
+
+ if (fmt->gl_format == GL_BGRA_EXT && !texture->fx_renderer->exts.EXT_read_format_bgra) {
+ wlr_log(WLR_ERROR,
+ "Cannot read pixels: missing GL_EXT_read_format_bgra extension");
+ return false;
+ }
+
+ const struct wlr_pixel_format_info *drm_fmt =
+ drm_get_pixel_format_info(fmt->drm_format);
+ assert(drm_fmt);
+ if (pixel_format_info_pixels_per_block(drm_fmt) != 1) {
+ wlr_log(WLR_ERROR, "Cannot read pixels: block formats are not supported");
+ return false;
+ }
+
+ push_fx_debug(texture->fx_renderer);
+ struct wlr_egl_context prev_ctx;
+ if (!wlr_egl_make_current(texture->fx_renderer->egl, &prev_ctx)) {
+ return false;
+ }
+
+ if (!fx_texture_bind(texture)) {
+ return false;
+ }
+
+ // Make sure any pending drawing is finished before we try to read it
+ glFinish();
+
+ glGetError(); // Clear the error flag
+
+ unsigned char *p = wlr_texture_read_pixel_options_get_data(options);
+
+ glPixelStorei(GL_PACK_ALIGNMENT, 1);
+ uint32_t pack_stride = pixel_format_info_min_stride(drm_fmt, src.width);
+ if (pack_stride == options->stride && options->dst_x == 0) {
+ // Under these particular conditions, we can read the pixels with only
+ // one glReadPixels call
+
+ glReadPixels(src.x, src.y, src.width, src.height, fmt->gl_format, fmt->gl_type, p);
+ } else {
+ // Unfortunately GLES2 doesn't support GL_PACK_ROW_LENGTH, so we have to read
+ // the lines out row by row
+ for (int32_t i = 0; i < src.height; ++i) {
+ uint32_t y = src.y + i;
+ glReadPixels(src.x, y, src.width, 1, fmt->gl_format,
+ fmt->gl_type, p + i * options->stride);
+ }
+ }
+
+ wlr_egl_restore_context(&prev_ctx);
+ pop_fx_debug(texture->fx_renderer);
+
+ return glGetError() == GL_NO_ERROR;
+}
+
+static uint32_t fx_texture_preferred_read_format(struct wlr_texture *wlr_texture) {
+ struct fx_texture *texture = fx_get_texture(wlr_texture);
+
+ push_fx_debug(texture->fx_renderer);
+
+ uint32_t fmt = DRM_FORMAT_INVALID;
+
+ struct wlr_egl_context prev_ctx;
+ if (!wlr_egl_make_current(texture->fx_renderer->egl, &prev_ctx)) {
+ return fmt;
+ }
+
+ if (!fx_texture_bind(texture)) {
+ goto out;
+ }
+
+ GLint gl_format = -1, gl_type = -1, alpha_size = -1;
+ glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT, &gl_format);
+ glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE, &gl_type);
+ glGetIntegerv(GL_ALPHA_BITS, &alpha_size);
+
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ pop_fx_debug(texture->fx_renderer);
+
+ const struct fx_pixel_format *pix_fmt =
+ get_fx_format_from_gl(gl_format, gl_type, alpha_size > 0);
+ if (pix_fmt != NULL) {
+ fmt = pix_fmt->drm_format;
+ goto out;
+ }
+
+ if (texture->fx_renderer->exts.EXT_read_format_bgra) {
+ fmt = DRM_FORMAT_XRGB8888;
+ goto out;
+ }
+
+out:
+ wlr_egl_restore_context(&prev_ctx);
+ return fmt;
+}
+
+static void texture_handle_buffer_destroy(struct wlr_texture *wlr_texture) {
+ fx_texture_destroy(fx_get_texture(wlr_texture));
+}
+
+static const struct wlr_texture_impl texture_impl = {
+ .update_from_buffer = fx_texture_update_from_buffer,
+ .read_pixels = fx_texture_read_pixels,
+ .preferred_read_format = fx_texture_preferred_read_format,
+ .destroy = texture_handle_buffer_destroy,
+};
+
+static struct fx_texture *fx_texture_create(
+ struct fx_renderer *renderer, uint32_t width, uint32_t height) {
+ struct fx_texture *texture = calloc(1, sizeof(struct fx_texture));
+ if (texture == NULL) {
+ wlr_log_errno(WLR_ERROR, "Allocation failed");
+ return NULL;
+ }
+ wlr_texture_init(&texture->wlr_texture, &renderer->wlr_renderer,
+ &texture_impl, width, height);
+ texture->fx_renderer = renderer;
+ wl_list_insert(&renderer->textures, &texture->link);
+ return texture;
+}
+
+static struct wlr_texture *fx_texture_from_pixels(
+ struct wlr_renderer *wlr_renderer,
+ uint32_t drm_format, uint32_t stride, uint32_t width,
+ uint32_t height, const void *data) {
+ struct fx_renderer *renderer = fx_get_renderer(wlr_renderer);
+
+ const struct fx_pixel_format *fmt = get_fx_format_from_drm(drm_format);
+ if (fmt == NULL) {
+ wlr_log(WLR_ERROR, "Unsupported pixel format 0x%"PRIX32, drm_format);
+ return NULL;
+ }
+
+ const struct wlr_pixel_format_info *drm_fmt =
+ drm_get_pixel_format_info(drm_format);
+ assert(drm_fmt);
+ if (pixel_format_info_pixels_per_block(drm_fmt) != 1) {
+ wlr_log(WLR_ERROR, "Cannot upload texture: block formats are not supported");
+ return NULL;
+ }
+
+ if (!pixel_format_info_check_stride(drm_fmt, stride, width)) {
+ return NULL;
+ }
+
+ struct fx_texture *texture =
+ fx_texture_create(renderer, width, height);
+ if (texture == NULL) {
+ return NULL;
+ }
+ texture->target = GL_TEXTURE_2D;
+ texture->has_alpha = pixel_format_has_alpha(fmt->drm_format);
+ texture->drm_format = fmt->drm_format;
+
+ GLint internal_format = fmt->gl_internalformat;
+ if (!internal_format) {
+ internal_format = fmt->gl_format;
+ }
+
+ struct wlr_egl_context prev_ctx;
+ wlr_egl_make_current(renderer->egl, &prev_ctx);
+
+ push_fx_debug(renderer);
+
+ glGenTextures(1, &texture->tex);
+ glBindTexture(GL_TEXTURE_2D, texture->tex);
+
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, stride / drm_fmt->bytes_per_block);
+ glTexImage2D(GL_TEXTURE_2D, 0, internal_format, width, height, 0,
+ fmt->gl_format, fmt->gl_type, data);
+ glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0);
+
+ glBindTexture(GL_TEXTURE_2D, 0);
+
+ pop_fx_debug(renderer);
+
+ wlr_egl_restore_context(&prev_ctx);
+
+ return &texture->wlr_texture;
+}
+
+static struct wlr_texture *fx_texture_from_dmabuf(
+ struct wlr_renderer *wlr_renderer, struct wlr_buffer *wlr_buffer,
+ struct wlr_dmabuf_attributes *attribs) {
+ struct fx_renderer *renderer = fx_get_renderer(wlr_renderer);
+
+ if (!renderer->procs.glEGLImageTargetTexture2DOES) {
+ return NULL;
+ }
+
+ struct fx_framebuffer *buffer = fx_framebuffer_get_or_create(renderer, wlr_buffer);
+ if (!buffer) {
+ return NULL;
+ }
+
+ struct fx_texture *texture =
+ fx_texture_create(renderer, attribs->width, attribs->height);
+ if (texture == NULL) {
+ return NULL;
+ }
+ texture->target = buffer->external_only ? GL_TEXTURE_EXTERNAL_OES : GL_TEXTURE_2D;
+ texture->buffer = buffer;
+ texture->drm_format = DRM_FORMAT_INVALID; // texture can't be written anyways
+ texture->has_alpha = pixel_format_has_alpha(attribs->format);
+
+ struct wlr_egl_context prev_ctx;
+ wlr_egl_make_current(renderer->egl, &prev_ctx);
+ push_fx_debug(texture->fx_renderer);
+
+ bool invalid;
+ if (!buffer->tex) {
+ glGenTextures(1, &buffer->tex);
+ invalid = true;
+ } else {
+ // External changes are immediately made visible by the GL implementation
+ invalid = !buffer->external_only;
+ }
+
+ if (invalid) {
+ glBindTexture(texture->target, buffer->tex);
+ glTexParameteri(texture->target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(texture->target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ renderer->procs.glEGLImageTargetTexture2DOES(texture->target, buffer->image);
+ glBindTexture(texture->target, 0);
+ }
+
+ pop_fx_debug(texture->fx_renderer);
+ wlr_egl_restore_context(&prev_ctx);
+
+ texture->tex = buffer->tex;
+ wlr_buffer_lock(texture->buffer->buffer);
+ return &texture->wlr_texture;
+}
+
+struct wlr_texture *fx_texture_from_buffer(struct wlr_renderer *wlr_renderer,
+ struct wlr_buffer *buffer) {
+ struct fx_renderer *renderer = fx_get_renderer(wlr_renderer);
+
+ void *data;
+ uint32_t format;
+ size_t stride;
+ struct wlr_dmabuf_attributes dmabuf;
+ if (wlr_buffer_get_dmabuf(buffer, &dmabuf)) {
+ return fx_texture_from_dmabuf(&renderer->wlr_renderer, buffer, &dmabuf);
+ } else if (wlr_buffer_begin_data_ptr_access(buffer,
+ WLR_BUFFER_DATA_PTR_ACCESS_READ, &data, &format, &stride)) {
+ struct wlr_texture *tex = fx_texture_from_pixels(wlr_renderer,
+ format, stride, buffer->width, buffer->height, data);
+ wlr_buffer_end_data_ptr_access(buffer);
+ return tex;
+ } else {
+ return NULL;
+ }
+}
+
+void fx_texture_get_attribs(struct wlr_texture *wlr_texture,
+ struct fx_texture_attribs *attribs) {
+ struct fx_texture *texture = fx_get_texture(wlr_texture);
+ *attribs = (struct fx_texture_attribs){
+ .target = texture->target,
+ .tex = texture->tex,
+ .has_alpha = texture->has_alpha,
+ };
+}
diff --git a/scenefx/render/fx_renderer/gles2/meson.build b/scenefx/render/fx_renderer/gles2/meson.build
new file mode 100644
index 0000000..70cde2c
--- /dev/null
+++ b/scenefx/render/fx_renderer/gles2/meson.build
@@ -0,0 +1,2 @@
+subdir('shaders')
+
diff --git a/scenefx/render/fx_renderer/gles2/shaders/blur1.frag b/scenefx/render/fx_renderer/gles2/shaders/blur1.frag
new file mode 100644
index 0000000..bb27600
--- /dev/null
+++ b/scenefx/render/fx_renderer/gles2/shaders/blur1.frag
@@ -0,0 +1,19 @@
+precision mediump float;
+
+varying mediump vec2 v_texcoord;
+uniform sampler2D tex;
+
+uniform float radius;
+uniform vec2 halfpixel;
+
+void main() {
+ vec2 uv = v_texcoord * 2.0;
+
+ vec4 sum = texture2D(tex, uv) * 4.0;
+ sum += texture2D(tex, uv - halfpixel.xy * radius);
+ sum += texture2D(tex, uv + halfpixel.xy * radius);
+ sum += texture2D(tex, uv + vec2(halfpixel.x, -halfpixel.y) * radius);
+ sum += texture2D(tex, uv - vec2(halfpixel.x, -halfpixel.y) * radius);
+
+ gl_FragColor = sum / 8.0;
+}
diff --git a/scenefx/render/fx_renderer/gles2/shaders/blur2.frag b/scenefx/render/fx_renderer/gles2/shaders/blur2.frag
new file mode 100644
index 0000000..2c947f2
--- /dev/null
+++ b/scenefx/render/fx_renderer/gles2/shaders/blur2.frag
@@ -0,0 +1,23 @@
+precision mediump float;
+
+varying mediump vec2 v_texcoord;
+uniform sampler2D tex;
+
+uniform float radius;
+uniform vec2 halfpixel;
+
+void main() {
+ vec2 uv = v_texcoord / 2.0;
+
+ vec4 sum = texture2D(tex, uv + vec2(-halfpixel.x * 2.0, 0.0) * radius);
+
+ sum += texture2D(tex, uv + vec2(-halfpixel.x, halfpixel.y) * radius) * 2.0;
+ sum += texture2D(tex, uv + vec2(0.0, halfpixel.y * 2.0) * radius);
+ sum += texture2D(tex, uv + vec2(halfpixel.x, halfpixel.y) * radius) * 2.0;
+ sum += texture2D(tex, uv + vec2(halfpixel.x * 2.0, 0.0) * radius);
+ sum += texture2D(tex, uv + vec2(halfpixel.x, -halfpixel.y) * radius) * 2.0;
+ sum += texture2D(tex, uv + vec2(0.0, -halfpixel.y * 2.0) * radius);
+ sum += texture2D(tex, uv + vec2(-halfpixel.x, -halfpixel.y) * radius) * 2.0;
+
+ gl_FragColor = sum / 12.0;
+}
diff --git a/scenefx/render/fx_renderer/gles2/shaders/blur_effects.frag b/scenefx/render/fx_renderer/gles2/shaders/blur_effects.frag
new file mode 100644
index 0000000..6f7411f
--- /dev/null
+++ b/scenefx/render/fx_renderer/gles2/shaders/blur_effects.frag
@@ -0,0 +1,58 @@
+#ifdef GL_FRAGMENT_PRECISION_HIGH
+precision highp float;
+#else
+precision mediump float;
+#endif
+
+varying vec2 v_texcoord;
+uniform sampler2D tex;
+
+uniform float brightness;
+uniform float contrast;
+uniform float saturation;
+uniform float noise;
+
+mat4 brightnessMatrix() {
+ float b = brightness - 1.0;
+ return mat4(1, 0, 0, 0,
+ 0, 1, 0, 0,
+ 0, 0, 1, 0,
+ b, b, b, 1);
+}
+
+mat4 contrastMatrix() {
+ float t = (1.0 - contrast) / 2.0;
+ return mat4(contrast, 0, 0, 0,
+ 0, contrast, 0, 0,
+ 0, 0, contrast, 0,
+ t, t, t, 1);
+}
+
+mat4 saturationMatrix() {
+ vec3 luminance = vec3(0.3086, 0.6094, 0.0820) * (1.0 - saturation);
+ vec3 red = vec3(luminance.x);
+ red.x += saturation;
+ vec3 green = vec3(luminance.y);
+ green.y += saturation;
+ vec3 blue = vec3(luminance.z);
+ blue.z += saturation;
+ return mat4(red, 0,
+ green, 0,
+ blue, 0,
+ 0, 0, 0, 1);
+}
+
+float noiseAmount(vec2 p) {
+ vec3 p3 = fract(vec3(p.xyx) * 1689.1984);
+ p3 += dot(p3, p3.yzx + 33.33);
+ float hash = fract((p3.x + p3.y) * p3.z);
+ return (mod(hash, 1.0) - 0.5) * noise;
+};
+
+void main() {
+ vec4 color = texture2D(tex, v_texcoord);
+ // Do *not* transpose the combined matrix when multiplying
+ color = brightnessMatrix() * contrastMatrix() * saturationMatrix() * color;
+ color.xyz += noiseAmount(v_texcoord);
+ gl_FragColor = color;
+}
diff --git a/scenefx/render/fx_renderer/gles2/shaders/box_shadow.frag b/scenefx/render/fx_renderer/gles2/shaders/box_shadow.frag
new file mode 100644
index 0000000..6a40109
--- /dev/null
+++ b/scenefx/render/fx_renderer/gles2/shaders/box_shadow.frag
@@ -0,0 +1,96 @@
+// Writeup: https://madebyevan.com/shaders/fast-rounded-rectangle-shadows/
+
+#ifdef GL_FRAGMENT_PRECISION_HIGH
+precision highp float;
+#else
+precision mediump float;
+#endif
+
+varying vec4 v_color;
+varying vec2 v_texcoord;
+
+uniform vec2 position;
+uniform vec2 size;
+uniform float blur_sigma;
+uniform float corner_radius;
+uniform vec2 clip_position;
+uniform vec2 clip_size;
+uniform float clip_radius_top_left;
+uniform float clip_radius_top_right;
+uniform float clip_radius_bottom_left;
+uniform float clip_radius_bottom_right;
+
+float gaussian(float x, float sigma) {
+ const float pi = 3.141592653589793;
+ return exp(-(x * x) / (2.0 * sigma * sigma)) / (sqrt(2.0 * pi) * sigma);
+}
+
+// approximates the error function, needed for the gaussian integral
+vec2 erf(vec2 x) {
+ vec2 s = sign(x), a = abs(x);
+ x = 1.0 + (0.278393 + (0.230389 + 0.078108 * (a * a)) * a) * a;
+ x *= x;
+ return s - s / (x * x);
+}
+
+// return the blurred mask along the x dimension
+float roundedBoxShadowX(float x, float y, float sigma, float corner, vec2 halfSize) {
+ float delta = min(halfSize.y - corner - abs(y), 0.0);
+ float curved = halfSize.x - corner + sqrt(max(0.0, corner * corner - delta * delta));
+ vec2 integral = 0.5 + 0.5 * erf((x + vec2(-curved, curved)) * (sqrt(0.5) / sigma));
+ return integral.y - integral.x;
+}
+
+// return the mask for the shadow of a box from lower to upper
+float roundedBoxShadow(vec2 lower, vec2 upper, vec2 point, float sigma, float corner_radius) {
+ // Center everything to make the math easier
+ vec2 center = (lower + upper) * 0.5;
+ vec2 halfSize = (upper - lower) * 0.5;
+ point -= center;
+
+ // The signal is only non-zero in a limited range, so don't waste samples
+ float low = point.y - halfSize.y;
+ float high = point.y + halfSize.y;
+ float start = clamp(-3.0 * sigma, low, high);
+ float end = clamp(3.0 * sigma, low, high);
+
+ // Accumulate samples (we can get away with surprisingly few samples)
+ float step = (end - start) / 4.0;
+ float y = start + step * 0.5;
+ float value = 0.0;
+ for (int i = 0; i < 4; i++) {
+ value += roundedBoxShadowX(point.x, point.y - y, sigma, corner_radius, halfSize) * gaussian(y, sigma) * step;
+ y += step;
+ }
+
+ return value;
+}
+
+// per-pixel "random" number between 0 and 1
+float random() {
+ return fract(sin(dot(vec2(12.9898, 78.233), gl_FragCoord.xy)) * 43758.5453);
+}
+
+float corner_alpha(vec2 size, vec2 position, float round_tl, float round_tr, float round_bl, float round_br);
+
+void main() {
+ float shadow_alpha = v_color.a * roundedBoxShadow(
+ position + blur_sigma,
+ position + size - blur_sigma,
+ gl_FragCoord.xy, blur_sigma * 0.5,
+ corner_radius);
+ // dither the alpha to break up color bands
+ shadow_alpha += (random() - 0.5) / 128.0;
+
+ // Clipping
+ float clip_corner_alpha = corner_alpha(
+ clip_size - 1.5,
+ clip_position + 0.75,
+ clip_radius_top_left,
+ clip_radius_top_right,
+ clip_radius_bottom_left,
+ clip_radius_bottom_right
+ );
+
+ gl_FragColor = vec4(v_color.rgb, shadow_alpha) * clip_corner_alpha;
+}
diff --git a/scenefx/render/fx_renderer/gles2/shaders/common.vert b/scenefx/render/fx_renderer/gles2/shaders/common.vert
new file mode 100644
index 0000000..9e7b073
--- /dev/null
+++ b/scenefx/render/fx_renderer/gles2/shaders/common.vert
@@ -0,0 +1,13 @@
+uniform mat3 proj;
+uniform vec4 color;
+uniform mat3 tex_proj;
+attribute vec2 pos;
+varying vec4 v_color;
+varying vec2 v_texcoord;
+
+void main() {
+ vec3 pos3 = vec3(pos, 1.0);
+ gl_Position = vec4(pos3 * proj, 1.0);
+ v_color = color;
+ v_texcoord = (pos3 * tex_proj).xy;
+}
diff --git a/scenefx/render/fx_renderer/gles2/shaders/corner_alpha.frag b/scenefx/render/fx_renderer/gles2/shaders/corner_alpha.frag
new file mode 100644
index 0000000..e560f6c
--- /dev/null
+++ b/scenefx/render/fx_renderer/gles2/shaders/corner_alpha.frag
@@ -0,0 +1,35 @@
+float get_dist(vec2 q, float radius) {
+ return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - radius;
+}
+
+float corner_alpha(vec2 size, vec2 position, float radius_tl, float radius_tr, float radius_bl, float radius_br) {
+ vec2 relative_pos = (gl_FragCoord.xy - position);
+
+ vec2 top_left = abs(relative_pos - size) - size + radius_tl;
+ vec2 top_right = abs(relative_pos - vec2(0, size.y)) - size + radius_tr;
+ vec2 bottom_left = abs(relative_pos - vec2(size.x, 0)) - size + radius_bl;
+ vec2 bottom_right = abs(relative_pos) - size + radius_br;
+
+ float dist = max(
+ max(get_dist(top_left, radius_tl), get_dist(top_right, radius_tr)),
+ max(get_dist(bottom_left, radius_bl), get_dist(bottom_right, radius_br))
+ );
+
+ return smoothstep(0.0, 1.0, dist);
+}
+
+float corner_dist(vec2 size, vec2 position, float radius_tl, float radius_tr, float radius_bl, float radius_br) {
+ vec2 relative_pos = (gl_FragCoord.xy - position);
+
+ vec2 top_left = abs(relative_pos - size) - size + radius_tl;
+ vec2 top_right = abs(relative_pos - vec2(0, size.y)) - size + radius_tr;
+ vec2 bottom_left = abs(relative_pos - vec2(size.x, 0)) - size + radius_bl;
+ vec2 bottom_right = abs(relative_pos) - size + radius_br;
+
+ float dist = max(
+ max(get_dist(top_left, radius_tl), get_dist(top_right, radius_tr)),
+ max(get_dist(bottom_left, radius_bl), get_dist(bottom_right, radius_br))
+ );
+
+ return dist;
+}
diff --git a/scenefx/render/fx_renderer/gles2/shaders/embed.sh b/scenefx/render/fx_renderer/gles2/shaders/embed.sh
new file mode 100644
index 0000000..47f0789
--- /dev/null
+++ b/scenefx/render/fx_renderer/gles2/shaders/embed.sh
@@ -0,0 +1,11 @@
+#!/bin/sh -eu
+
+var=${1:-data}
+hex="$(od -A n -t x1 -v)"
+
+echo "static const char $var[] = {"
+for byte in $hex; do
+ echo " 0x$byte,"
+done
+echo " 0x00,"
+echo "};"
diff --git a/scenefx/render/fx_renderer/gles2/shaders/gradient.frag b/scenefx/render/fx_renderer/gles2/shaders/gradient.frag
new file mode 100644
index 0000000..407e273
--- /dev/null
+++ b/scenefx/render/fx_renderer/gles2/shaders/gradient.frag
@@ -0,0 +1,41 @@
+vec4 gradient(vec4 colors[LEN], int count, vec2 size, vec2 grad_box, vec2 origin, float degree, bool linear, bool blend) {
+ float step;
+
+ vec2 normal = (gl_FragCoord.xy - grad_box)/size;
+ vec2 uv = normal - origin;
+
+ float rad = radians(degree);
+
+ if (linear) {
+ uv *= vec2(1.0)/vec2(abs(cos(rad)) + abs(sin(rad)));
+
+ vec2 rotated = vec2(uv.x * cos(rad) - uv.y * sin(rad) + origin.x,
+ uv.x * sin(rad) + uv.y * cos(rad) + origin.y);
+
+ step = rotated.x;
+ } else {
+ vec2 uv = normal - origin;
+ uv = vec2(uv.x * cos(rad) - uv.y * sin(rad),
+ uv.x * sin(rad) + uv.y * cos(rad));
+
+ uv = vec2(-atan(uv.y, uv.x)/3.14159265 * 0.5 + 0.5, 0.0);
+ step = uv.x;
+ }
+
+ if (!blend) {
+ float smooth_fac = 1.0/float(count);
+ int ind = int(step/smooth_fac);
+
+ return colors[ind];
+ }
+
+ float smooth_fac = 1.0/float(count - 1);
+ int ind = int(step/smooth_fac);
+ float at = float(ind)*smooth_fac;
+
+ vec4 color = colors[ind];
+ if(ind > 0) color = mix(colors[ind - 1], color, smoothstep(at - smooth_fac, at, step));
+ if(ind <= count - 1) color = mix(color, colors[ind + 1], smoothstep(at, at + smooth_fac, step));
+
+ return color;
+}
diff --git a/scenefx/render/fx_renderer/gles2/shaders/meson.build b/scenefx/render/fx_renderer/gles2/shaders/meson.build
new file mode 100644
index 0000000..260e07d
--- /dev/null
+++ b/scenefx/render/fx_renderer/gles2/shaders/meson.build
@@ -0,0 +1,30 @@
+embed = find_program('./embed.sh', native: true)
+
+shaders = [
+ 'common.vert',
+ 'gradient.frag',
+ 'corner_alpha.frag',
+ 'quad.frag',
+ 'quad_grad.frag',
+ 'quad_round.frag',
+ 'quad_grad_round.frag',
+ 'tex.frag',
+ 'box_shadow.frag',
+ 'blur1.frag',
+ 'blur2.frag',
+ 'blur_effects.frag',
+]
+
+foreach name : shaders
+ output = name.underscorify() + '_gles2_src.h'
+ var = name.underscorify() + '_gles2_src'
+ scenefx_files += custom_target(
+ output,
+ command: [embed, var],
+ input: name,
+ output: output,
+ feed: true,
+ capture: true,
+ )
+endforeach
+
diff --git a/scenefx/render/fx_renderer/gles2/shaders/quad.frag b/scenefx/render/fx_renderer/gles2/shaders/quad.frag
new file mode 100644
index 0000000..3144f93
--- /dev/null
+++ b/scenefx/render/fx_renderer/gles2/shaders/quad.frag
@@ -0,0 +1,31 @@
+#ifdef GL_FRAGMENT_PRECISION_HIGH
+precision highp float;
+#else
+precision mediump float;
+#endif
+
+varying vec4 v_color;
+varying vec2 v_texcoord;
+
+uniform vec2 clip_size;
+uniform vec2 clip_position;
+uniform float clip_radius_top_left;
+uniform float clip_radius_top_right;
+uniform float clip_radius_bottom_left;
+uniform float clip_radius_bottom_right;
+
+float corner_alpha(vec2 size, vec2 position, float radius_tl, float radius_tr, float radius_bl, float radius_br);
+
+void main() {
+ // Clipping
+ float clip_corner_alpha = corner_alpha(
+ clip_size - 1.0,
+ clip_position + 0.5,
+ clip_radius_top_left,
+ clip_radius_top_right,
+ clip_radius_bottom_left,
+ clip_radius_bottom_right
+ );
+
+ gl_FragColor = v_color * clip_corner_alpha;
+}
diff --git a/scenefx/render/fx_renderer/gles2/shaders/quad_grad.frag b/scenefx/render/fx_renderer/gles2/shaders/quad_grad.frag
new file mode 100644
index 0000000..fed138c
--- /dev/null
+++ b/scenefx/render/fx_renderer/gles2/shaders/quad_grad.frag
@@ -0,0 +1,25 @@
+#define LEN %d
+
+#ifdef GL_FRAGMENT_PRECISION_HIGH
+precision highp float;
+#else
+precision mediump float;
+#endif
+
+varying vec4 v_color;
+varying vec2 v_texcoord;
+
+uniform vec4 colors[LEN];
+uniform vec2 size;
+uniform float degree;
+uniform vec2 grad_box;
+uniform vec2 origin;
+uniform bool linear;
+uniform bool blend;
+uniform int count;
+
+vec4 gradient(vec4 colors[LEN], int count, vec2 size, vec2 grad_box, vec2 origin, float degree, bool linear, bool blend);
+
+void main(){
+ gl_FragColor = gradient(colors, count, size, grad_box, origin, degree, linear, blend);
+}
diff --git a/scenefx/render/fx_renderer/gles2/shaders/quad_grad_round.frag b/scenefx/render/fx_renderer/gles2/shaders/quad_grad_round.frag
new file mode 100644
index 0000000..bbf7400
--- /dev/null
+++ b/scenefx/render/fx_renderer/gles2/shaders/quad_grad_round.frag
@@ -0,0 +1,46 @@
+#define LEN %d
+
+#ifdef GL_FRAGMENT_PRECISION_HIGH
+precision highp float;
+#else
+precision mediump float;
+#endif
+
+varying vec4 v_color;
+varying vec2 v_texcoord;
+
+uniform vec2 size;
+uniform vec2 position;
+
+uniform float radius_top_left;
+uniform float radius_top_right;
+uniform float radius_bottom_left;
+uniform float radius_bottom_right;
+
+uniform vec4 colors[LEN];
+uniform vec2 grad_size;
+uniform float degree;
+uniform vec2 grad_box;
+uniform vec2 origin;
+uniform bool linear;
+uniform bool blend;
+uniform int count;
+
+vec4 gradient(vec4 colors[LEN], int count, vec2 size, vec2 grad_box, vec2 origin, float degree, bool linear, bool blend);
+
+float corner_alpha(vec2 size, vec2 position, float round_tl, float round_tr, float round_bl, float round_br);
+
+// TODO:
+void main() {
+ float quad_corner_alpha = corner_alpha(
+ size - 1.0,
+ position + 0.5,
+ radius_top_left,
+ radius_top_right,
+ radius_bottom_left,
+ radius_bottom_right
+ );
+ float rect_alpha = v_color.a * quad_corner_alpha;
+
+ gl_FragColor = mix(vec4(0), gradient(colors, count, size, grad_box, origin, degree, linear, blend), rect_alpha);
+}
diff --git a/scenefx/render/fx_renderer/gles2/shaders/quad_round.frag b/scenefx/render/fx_renderer/gles2/shaders/quad_round.frag
new file mode 100644
index 0000000..7efced1
--- /dev/null
+++ b/scenefx/render/fx_renderer/gles2/shaders/quad_round.frag
@@ -0,0 +1,64 @@
+#ifdef GL_FRAGMENT_PRECISION_HIGH
+precision highp float;
+#else
+precision mediump float;
+#endif
+
+varying vec4 v_color;
+varying vec2 v_texcoord;
+
+uniform vec2 size;
+uniform vec2 position;
+uniform float radius_top_left;
+uniform float radius_top_right;
+uniform float radius_bottom_left;
+uniform float radius_bottom_right;
+uniform float fade_inset;
+
+uniform vec2 clip_size;
+uniform vec2 clip_position;
+uniform float clip_radius_top_left;
+uniform float clip_radius_top_right;
+uniform float clip_radius_bottom_left;
+uniform float clip_radius_bottom_right;
+
+float corner_alpha(vec2 size, vec2 position, float round_tl, float round_tr, float round_bl, float round_br);
+float corner_dist(vec2 size, vec2 position, float round_tl, float round_tr, float round_bl, float round_br);
+
+void main() {
+ float quad_corner_alpha = corner_alpha(
+ size - 1.0,
+ position + 0.5,
+ radius_top_left,
+ radius_top_right,
+ radius_bottom_left,
+ radius_bottom_right
+ );
+
+ // Clipping
+ float clip_corner_alpha = corner_alpha(
+ clip_size - 1.0,
+ clip_position + 0.5,
+ clip_radius_top_left,
+ clip_radius_top_right,
+ clip_radius_bottom_left,
+ clip_radius_bottom_right
+ );
+
+ vec4 final_color = v_color;
+ if (fade_inset > 0.0) {
+ float d = corner_dist(
+ size - 1.0,
+ position + 0.5,
+ radius_top_left,
+ radius_top_right,
+ radius_bottom_left,
+ radius_bottom_right
+ );
+ float inside_dist = -d;
+ float fade_factor = clamp(inside_dist / fade_inset, 0.0, 1.0);
+ final_color *= fade_factor;
+ }
+
+ gl_FragColor = mix(final_color, vec4(0.0), quad_corner_alpha) * clip_corner_alpha;
+}
diff --git a/scenefx/render/fx_renderer/gles2/shaders/tex.frag b/scenefx/render/fx_renderer/gles2/shaders/tex.frag
new file mode 100644
index 0000000..08ad3a1
--- /dev/null
+++ b/scenefx/render/fx_renderer/gles2/shaders/tex.frag
@@ -0,0 +1,65 @@
+#define SOURCE %d
+
+#define SOURCE_TEXTURE_RGBA 1
+#define SOURCE_TEXTURE_RGBX 2
+#define SOURCE_TEXTURE_EXTERNAL 3
+
+#if !defined(SOURCE)
+#error "Missing shader preamble"
+#endif
+
+#if SOURCE == SOURCE_TEXTURE_EXTERNAL
+#extension GL_OES_EGL_image_external : require
+#endif
+
+#ifdef GL_FRAGMENT_PRECISION_HIGH
+precision highp float;
+#else
+precision mediump float;
+#endif
+
+varying vec2 v_texcoord;
+
+#if SOURCE == SOURCE_TEXTURE_EXTERNAL
+uniform samplerExternalOES tex;
+#elif SOURCE == SOURCE_TEXTURE_RGBA || SOURCE == SOURCE_TEXTURE_RGBX
+uniform sampler2D tex;
+#endif
+
+uniform float alpha;
+
+uniform vec2 size;
+uniform vec2 position;
+uniform float radius_top_left;
+uniform float radius_top_right;
+uniform float radius_bottom_left;
+uniform float radius_bottom_right;
+
+uniform bool discard_transparent;
+
+vec4 sample_texture() {
+#if SOURCE == SOURCE_TEXTURE_RGBA || SOURCE == SOURCE_TEXTURE_EXTERNAL
+ return texture2D(tex, v_texcoord);
+#elif SOURCE == SOURCE_TEXTURE_RGBX
+ return vec4(texture2D(tex, v_texcoord).rgb, 1.0);
+#endif
+}
+
+float corner_alpha(vec2 size, vec2 position, float round_tl, float round_tr, float round_bl, float round_br);
+
+void main() {
+ float corner_alpha = corner_alpha(
+ size - 0.5,
+ position + 0.25,
+ radius_top_left,
+ radius_top_right,
+ radius_bottom_left,
+ radius_bottom_right
+ );
+ gl_FragColor = mix(sample_texture() * alpha, vec4(0.0), corner_alpha);
+
+ if (discard_transparent && gl_FragColor.a == 0.0) {
+ discard;
+ return;
+ }
+}
diff --git a/scenefx/render/fx_renderer/gles3/meson.build b/scenefx/render/fx_renderer/gles3/meson.build
new file mode 100644
index 0000000..40da7c3
--- /dev/null
+++ b/scenefx/render/fx_renderer/gles3/meson.build
@@ -0,0 +1 @@
+subdir('shaders')
\ No newline at end of file
diff --git a/scenefx/render/fx_renderer/gles3/shaders/blur1.frag b/scenefx/render/fx_renderer/gles3/shaders/blur1.frag
new file mode 100644
index 0000000..55504a2
--- /dev/null
+++ b/scenefx/render/fx_renderer/gles3/shaders/blur1.frag
@@ -0,0 +1,23 @@
+#version 300 es
+
+precision mediump float;
+
+in mediump vec2 v_texcoord;
+uniform sampler2D tex;
+
+uniform float radius;
+uniform vec2 halfpixel;
+
+out vec4 fragColor;
+
+void main() {
+ vec2 uv = v_texcoord * 2.0;
+
+ vec4 sum = texture2D(tex, uv) * 4.0;
+ sum += texture2D(tex, uv - halfpixel.xy * radius);
+ sum += texture2D(tex, uv + halfpixel.xy * radius);
+ sum += texture2D(tex, uv + vec2(halfpixel.x, -halfpixel.y) * radius);
+ sum += texture2D(tex, uv - vec2(halfpixel.x, -halfpixel.y) * radius);
+
+ fragColor = sum / 8.0;
+}
diff --git a/scenefx/render/fx_renderer/gles3/shaders/blur2.frag b/scenefx/render/fx_renderer/gles3/shaders/blur2.frag
new file mode 100644
index 0000000..6fc90b6
--- /dev/null
+++ b/scenefx/render/fx_renderer/gles3/shaders/blur2.frag
@@ -0,0 +1,27 @@
+#version 300 es
+
+precision mediump float;
+
+in mediump vec2 v_texcoord;
+uniform sampler2D tex;
+
+uniform float radius;
+uniform vec2 halfpixel;
+
+out vec4 fragColor;
+
+void main() {
+ vec2 uv = v_texcoord / 2.0;
+
+ vec4 sum = texture2D(tex, uv + vec2(-halfpixel.x * 2.0, 0.0) * radius);
+
+ sum += texture2D(tex, uv + vec2(-halfpixel.x, halfpixel.y) * radius) * 2.0;
+ sum += texture2D(tex, uv + vec2(0.0, halfpixel.y * 2.0) * radius);
+ sum += texture2D(tex, uv + vec2(halfpixel.x, halfpixel.y) * radius) * 2.0;
+ sum += texture2D(tex, uv + vec2(halfpixel.x * 2.0, 0.0) * radius);
+ sum += texture2D(tex, uv + vec2(halfpixel.x, -halfpixel.y) * radius) * 2.0;
+ sum += texture2D(tex, uv + vec2(0.0, -halfpixel.y * 2.0) * radius);
+ sum += texture2D(tex, uv + vec2(-halfpixel.x, -halfpixel.y) * radius) * 2.0;
+
+ fragColor = sum / 12.0;
+}
diff --git a/scenefx/render/fx_renderer/gles3/shaders/blur_effects.frag b/scenefx/render/fx_renderer/gles3/shaders/blur_effects.frag
new file mode 100644
index 0000000..901f2a8
--- /dev/null
+++ b/scenefx/render/fx_renderer/gles3/shaders/blur_effects.frag
@@ -0,0 +1,58 @@
+#version 300 es
+
+precision highp float;
+
+in vec2 v_texcoord;
+uniform sampler2D tex;
+
+uniform float brightness;
+uniform float contrast;
+uniform float saturation;
+uniform float noise;
+
+out vec4 fragColor;
+
+mat4 brightnessMatrix() {
+ float b = brightness - 1.0;
+ return mat4(1, 0, 0, 0,
+ 0, 1, 0, 0,
+ 0, 0, 1, 0,
+ b, b, b, 1);
+}
+
+mat4 contrastMatrix() {
+ float t = (1.0 - contrast) / 2.0;
+ return mat4(contrast, 0, 0, 0,
+ 0, contrast, 0, 0,
+ 0, 0, contrast, 0,
+ t, t, t, 1);
+}
+
+mat4 saturationMatrix() {
+ vec3 luminance = vec3(0.3086, 0.6094, 0.0820) * (1.0 - saturation);
+ vec3 red = vec3(luminance.x);
+ red.x += saturation;
+ vec3 green = vec3(luminance.y);
+ green.y += saturation;
+ vec3 blue = vec3(luminance.z);
+ blue.z += saturation;
+ return mat4(red, 0,
+ green, 0,
+ blue, 0,
+ 0, 0, 0, 1);
+}
+
+float noiseAmount(vec2 p) {
+ vec3 p3 = fract(vec3(p.xyx) * 1689.1984);
+ p3 += dot(p3, p3.yzx + 33.33);
+ float hash = fract((p3.x + p3.y) * p3.z);
+ return (mod(hash, 1.0) - 0.5) * noise;
+};
+
+void main() {
+ vec4 color = texture2D(tex, v_texcoord);
+ // Do *not* transpose the combined matrix when multiplying
+ color = brightnessMatrix() * contrastMatrix() * saturationMatrix() * color;
+ color.xyz += noiseAmount(v_texcoord);
+ fragColor = color;
+}
diff --git a/scenefx/render/fx_renderer/gles3/shaders/box_shadow.frag b/scenefx/render/fx_renderer/gles3/shaders/box_shadow.frag
new file mode 100644
index 0000000..2f6e961
--- /dev/null
+++ b/scenefx/render/fx_renderer/gles3/shaders/box_shadow.frag
@@ -0,0 +1,96 @@
+// Writeup: https://madebyevan.com/shaders/fast-rounded-rectangle-shadows/
+
+#version 300 es
+
+precision highp float;
+
+in vec4 v_color;
+in vec2 v_texcoord;
+
+uniform vec2 position;
+uniform vec2 size;
+uniform float blur_sigma;
+uniform float corner_radius;
+uniform vec2 clip_position;
+uniform vec2 clip_size;
+uniform float clip_radius_top_left;
+uniform float clip_radius_top_right;
+uniform float clip_radius_bottom_left;
+uniform float clip_radius_bottom_right;
+
+out vec4 fragColor;
+
+float gaussian(float x, float sigma) {
+ const float pi = 3.141592653589793;
+ return exp(-(x * x) / (2.0 * sigma * sigma)) / (sqrt(2.0 * pi) * sigma);
+}
+
+// approximates the error function, needed for the gaussian integral
+vec2 erf(vec2 x) {
+ vec2 s = sign(x), a = abs(x);
+ x = 1.0 + (0.278393 + (0.230389 + 0.078108 * (a * a)) * a) * a;
+ x *= x;
+ return s - s / (x * x);
+}
+
+// return the blurred mask along the x dimension
+float roundedBoxShadowX(float x, float y, float sigma, float corner, vec2 halfSize) {
+ float delta = min(halfSize.y - corner - abs(y), 0.0);
+ float curved = halfSize.x - corner + sqrt(max(0.0, corner * corner - delta * delta));
+ vec2 integral = 0.5 + 0.5 * erf((x + vec2(-curved, curved)) * (sqrt(0.5) / sigma));
+ return integral.y - integral.x;
+}
+
+// return the mask for the shadow of a box from lower to upper
+float roundedBoxShadow(vec2 lower, vec2 upper, vec2 point, float sigma, float corner_radius) {
+ // Center everything to make the math easier
+ vec2 center = (lower + upper) * 0.5;
+ vec2 halfSize = (upper - lower) * 0.5;
+ point -= center;
+
+ // The signal is only non-zero in a limited range, so don't waste samples
+ float low = point.y - halfSize.y;
+ float high = point.y + halfSize.y;
+ float start = clamp(-3.0 * sigma, low, high);
+ float end = clamp(3.0 * sigma, low, high);
+
+ // Accumulate samples (we can get away with surprisingly few samples)
+ float step = (end - start) / 4.0;
+ float y = start + step * 0.5;
+ float value = 0.0;
+ for (int i = 0; i < 4; i++) {
+ value += roundedBoxShadowX(point.x, point.y - y, sigma, corner_radius, halfSize) * gaussian(y, sigma) * step;
+ y += step;
+ }
+
+ return value;
+}
+
+// per-pixel "random" number between 0 and 1
+float random() {
+ return fract(sin(dot(vec2(12.9898, 78.233), gl_FragCoord.xy)) * 43758.5453);
+}
+
+float corner_alpha(vec2 size, vec2 position, float round_tl, float round_tr, float round_bl, float round_br);
+
+void main() {
+ float shadow_alpha = v_color.a * roundedBoxShadow(
+ position + blur_sigma,
+ position + size - blur_sigma,
+ gl_FragCoord.xy, blur_sigma * 0.5,
+ corner_radius);
+ // dither the alpha to break up color bands
+ shadow_alpha += (random() - 0.5) / 128.0;
+
+ // Clipping
+ float clip_corner_alpha = corner_alpha(
+ clip_size - 1.5,
+ clip_position + 0.75,
+ clip_radius_top_left,
+ clip_radius_top_right,
+ clip_radius_bottom_left,
+ clip_radius_bottom_right
+ );
+
+ fragColor = vec4(v_color.rgb, shadow_alpha) * clip_corner_alpha;
+}
diff --git a/scenefx/render/fx_renderer/gles3/shaders/common.vert b/scenefx/render/fx_renderer/gles3/shaders/common.vert
new file mode 100644
index 0000000..b420686
--- /dev/null
+++ b/scenefx/render/fx_renderer/gles3/shaders/common.vert
@@ -0,0 +1,15 @@
+#version 300 es
+
+uniform mat3 proj;
+uniform vec4 color;
+uniform mat3 tex_proj;
+in vec2 pos;
+out vec4 v_color;
+out vec2 v_texcoord;
+
+void main() {
+ vec3 pos3 = vec3(pos, 1.0);
+ gl_Position = vec4(pos3 * proj, 1.0);
+ v_color = color;
+ v_texcoord = (pos3 * tex_proj).xy;
+}
diff --git a/scenefx/render/fx_renderer/gles3/shaders/corner_alpha.frag b/scenefx/render/fx_renderer/gles3/shaders/corner_alpha.frag
new file mode 100644
index 0000000..e560f6c
--- /dev/null
+++ b/scenefx/render/fx_renderer/gles3/shaders/corner_alpha.frag
@@ -0,0 +1,35 @@
+float get_dist(vec2 q, float radius) {
+ return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - radius;
+}
+
+float corner_alpha(vec2 size, vec2 position, float radius_tl, float radius_tr, float radius_bl, float radius_br) {
+ vec2 relative_pos = (gl_FragCoord.xy - position);
+
+ vec2 top_left = abs(relative_pos - size) - size + radius_tl;
+ vec2 top_right = abs(relative_pos - vec2(0, size.y)) - size + radius_tr;
+ vec2 bottom_left = abs(relative_pos - vec2(size.x, 0)) - size + radius_bl;
+ vec2 bottom_right = abs(relative_pos) - size + radius_br;
+
+ float dist = max(
+ max(get_dist(top_left, radius_tl), get_dist(top_right, radius_tr)),
+ max(get_dist(bottom_left, radius_bl), get_dist(bottom_right, radius_br))
+ );
+
+ return smoothstep(0.0, 1.0, dist);
+}
+
+float corner_dist(vec2 size, vec2 position, float radius_tl, float radius_tr, float radius_bl, float radius_br) {
+ vec2 relative_pos = (gl_FragCoord.xy - position);
+
+ vec2 top_left = abs(relative_pos - size) - size + radius_tl;
+ vec2 top_right = abs(relative_pos - vec2(0, size.y)) - size + radius_tr;
+ vec2 bottom_left = abs(relative_pos - vec2(size.x, 0)) - size + radius_bl;
+ vec2 bottom_right = abs(relative_pos) - size + radius_br;
+
+ float dist = max(
+ max(get_dist(top_left, radius_tl), get_dist(top_right, radius_tr)),
+ max(get_dist(bottom_left, radius_bl), get_dist(bottom_right, radius_br))
+ );
+
+ return dist;
+}
diff --git a/scenefx/render/fx_renderer/gles3/shaders/embed.sh b/scenefx/render/fx_renderer/gles3/shaders/embed.sh
new file mode 100644
index 0000000..47f0789
--- /dev/null
+++ b/scenefx/render/fx_renderer/gles3/shaders/embed.sh
@@ -0,0 +1,11 @@
+#!/bin/sh -eu
+
+var=${1:-data}
+hex="$(od -A n -t x1 -v)"
+
+echo "static const char $var[] = {"
+for byte in $hex; do
+ echo " 0x$byte,"
+done
+echo " 0x00,"
+echo "};"
diff --git a/scenefx/render/fx_renderer/gles3/shaders/gradient.frag b/scenefx/render/fx_renderer/gles3/shaders/gradient.frag
new file mode 100644
index 0000000..f5522ac
--- /dev/null
+++ b/scenefx/render/fx_renderer/gles3/shaders/gradient.frag
@@ -0,0 +1,41 @@
+vec4 gradient(vec4 colors[LEN], int count, vec2 size, vec2 grad_box, vec2 origin, float degree, bool linear, bool blend) {
+ float step;
+
+ vec2 normal = (gl_FragCoord.xy - grad_box)/size;
+ vec2 uv = normal - origin;
+
+ float rad = radians(degree);
+
+ if (linear) {
+ uv *= vec2(1.0)/vec2(abs(cos(rad)) + abs(sin(rad)));
+
+ vec2 rotated = vec2(uv.x * cos(rad) - uv.y * sin(rad) + origin.x,
+ uv.x * sin(rad) + uv.y * cos(rad) + origin.y);
+
+ step = rotated.x;
+ } else {
+ vec2 uv = normal - origin;
+ uv = vec2(uv.x * cos(rad) - uv.y * sin(rad),
+ uv.x * sin(rad) + uv.y * cos(rad));
+
+ uv = vec2(-atan(uv.y, uv.x)/3.14159265 * 0.5 + 0.5, 0.0);
+ step = uv.x;
+ }
+
+ if (!blend) {
+ float smooth_fac = 1.0/float(count);
+ int ind = int(step/smooth_fac);
+
+ return colors[ind];
+ }
+
+ float smooth_fac = 1.0/float(count - 1);
+ int ind = int(step/smooth_fac);
+ float at = float(ind)*smooth_fac;
+
+ vec4 color = colors[ind];
+ if(ind > 0) color = mix(colors[ind - 1], color, smoothstep(at - smooth_fac, at, step));
+ if(ind <= count - 1) color = mix(color, colors[ind + 1], smoothstep(at, at + smooth_fac, step));
+
+ return color;
+}
diff --git a/scenefx/render/fx_renderer/gles3/shaders/meson.build b/scenefx/render/fx_renderer/gles3/shaders/meson.build
new file mode 100644
index 0000000..e7cd6fd
--- /dev/null
+++ b/scenefx/render/fx_renderer/gles3/shaders/meson.build
@@ -0,0 +1,30 @@
+embed = find_program('./embed.sh', native: true)
+
+shaders = [
+ 'common.vert',
+ 'gradient.frag',
+ 'corner_alpha.frag',
+ 'quad.frag',
+ 'quad_grad.frag',
+ 'quad_round.frag',
+ 'quad_grad_round.frag',
+ 'tex.frag',
+ 'box_shadow.frag',
+ 'blur1.frag',
+ 'blur2.frag',
+ 'blur_effects.frag',
+]
+
+foreach name : shaders
+ output = name.underscorify() + '_gles3_src.h'
+ var = name.underscorify() + '_gles3_src'
+ scenefx_files += custom_target(
+ output,
+ command: [embed, var],
+ input: name,
+ output: output,
+ feed: true,
+ capture: true,
+ )
+endforeach
+
diff --git a/scenefx/render/fx_renderer/gles3/shaders/quad.frag b/scenefx/render/fx_renderer/gles3/shaders/quad.frag
new file mode 100644
index 0000000..63eeac4
--- /dev/null
+++ b/scenefx/render/fx_renderer/gles3/shaders/quad.frag
@@ -0,0 +1,31 @@
+#version 300 es
+
+precision highp float;
+
+in vec4 v_color;
+in vec2 v_texcoord;
+
+uniform vec2 clip_size;
+uniform vec2 clip_position;
+uniform float clip_radius_top_left;
+uniform float clip_radius_top_right;
+uniform float clip_radius_bottom_left;
+uniform float clip_radius_bottom_right;
+
+out vec4 fragColor;
+
+float corner_alpha(vec2 size, vec2 position, float radius_tl, float radius_tr, float radius_bl, float radius_br);
+
+void main() {
+ // Clipping
+ float clip_corner_alpha = corner_alpha(
+ clip_size - 1.0,
+ clip_position + 0.5,
+ clip_radius_top_left,
+ clip_radius_top_right,
+ clip_radius_bottom_left,
+ clip_radius_bottom_right
+ );
+
+ fragColor = v_color * clip_corner_alpha;
+}
diff --git a/scenefx/render/fx_renderer/gles3/shaders/quad_grad.frag b/scenefx/render/fx_renderer/gles3/shaders/quad_grad.frag
new file mode 100644
index 0000000..25bd978
--- /dev/null
+++ b/scenefx/render/fx_renderer/gles3/shaders/quad_grad.frag
@@ -0,0 +1,25 @@
+#version 300 es
+
+#define LEN %d
+
+precision highp float;
+
+in vec4 v_color;
+in vec2 v_texcoord;
+
+uniform vec4 colors[LEN];
+uniform vec2 size;
+uniform float degree;
+uniform vec2 grad_box;
+uniform vec2 origin;
+uniform bool linear;
+uniform bool blend;
+uniform int count;
+
+out vec4 fragColor;
+
+vec4 gradient(vec4 colors[LEN], int count, vec2 size, vec2 grad_box, vec2 origin, float degree, bool linear, bool blend);
+
+void main(){
+ fragColor = gradient(colors, count, size, grad_box, origin, degree, linear, blend);
+}
diff --git a/scenefx/render/fx_renderer/gles3/shaders/quad_grad_round.frag b/scenefx/render/fx_renderer/gles3/shaders/quad_grad_round.frag
new file mode 100644
index 0000000..0882cc8
--- /dev/null
+++ b/scenefx/render/fx_renderer/gles3/shaders/quad_grad_round.frag
@@ -0,0 +1,46 @@
+#version 300 es
+
+#define LEN %d
+
+precision highp float;
+
+in vec4 v_color;
+in vec2 v_texcoord;
+
+uniform vec2 size;
+uniform vec2 position;
+
+uniform float radius_top_left;
+uniform float radius_top_right;
+uniform float radius_bottom_left;
+uniform float radius_bottom_right;
+
+uniform vec4 colors[LEN];
+uniform vec2 grad_size;
+uniform float degree;
+uniform vec2 grad_box;
+uniform vec2 origin;
+uniform bool linear;
+uniform bool blend;
+uniform int count;
+
+out vec4 fragColor;
+
+vec4 gradient(vec4 colors[LEN], int count, vec2 size, vec2 grad_box, vec2 origin, float degree, bool linear, bool blend);
+
+float corner_alpha(vec2 size, vec2 position, float round_tl, float round_tr, float round_bl, float round_br);
+
+// TODO:
+void main() {
+ float quad_corner_alpha = corner_alpha(
+ size - 1.0,
+ position + 0.5,
+ radius_top_left,
+ radius_top_right,
+ radius_bottom_left,
+ radius_bottom_right
+ );
+ float rect_alpha = v_color.a * quad_corner_alpha;
+
+ fragColor = mix(vec4(0), gradient(colors, count, size, grad_box, origin, degree, linear, blend), rect_alpha);
+}
diff --git a/scenefx/render/fx_renderer/gles3/shaders/quad_round.frag b/scenefx/render/fx_renderer/gles3/shaders/quad_round.frag
new file mode 100644
index 0000000..aabf7d3
--- /dev/null
+++ b/scenefx/render/fx_renderer/gles3/shaders/quad_round.frag
@@ -0,0 +1,64 @@
+#version 300 es
+
+precision highp float;
+
+in vec4 v_color;
+in vec2 v_texcoord;
+
+uniform vec2 size;
+uniform vec2 position;
+uniform float radius_top_left;
+uniform float radius_top_right;
+uniform float radius_bottom_left;
+uniform float radius_bottom_right;
+uniform float fade_inset;
+
+uniform vec2 clip_size;
+uniform vec2 clip_position;
+uniform float clip_radius_top_left;
+uniform float clip_radius_top_right;
+uniform float clip_radius_bottom_left;
+uniform float clip_radius_bottom_right;
+
+out vec4 fragColor;
+
+float corner_alpha(vec2 size, vec2 position, float round_tl, float round_tr, float round_bl, float round_br);
+float corner_dist(vec2 size, vec2 position, float round_tl, float round_tr, float round_bl, float round_br);
+
+void main() {
+ float quad_corner_alpha = corner_alpha(
+ size - 1.0,
+ position + 0.5,
+ radius_top_left,
+ radius_top_right,
+ radius_bottom_left,
+ radius_bottom_right
+ );
+
+ // Clipping
+ float clip_corner_alpha = corner_alpha(
+ clip_size - 1.0,
+ clip_position + 0.5,
+ clip_radius_top_left,
+ clip_radius_top_right,
+ clip_radius_bottom_left,
+ clip_radius_bottom_right
+ );
+
+ vec4 final_color = v_color;
+ if (fade_inset > 0.0) {
+ float d = corner_dist(
+ size - 1.0,
+ position + 0.5,
+ radius_top_left,
+ radius_top_right,
+ radius_bottom_left,
+ radius_bottom_right
+ );
+ float inside_dist = -d;
+ float fade_factor = clamp(inside_dist / fade_inset, 0.0, 1.0);
+ final_color *= fade_factor;
+ }
+
+ fragColor = mix(final_color, vec4(0.0), quad_corner_alpha) * clip_corner_alpha;
+}
diff --git a/scenefx/render/fx_renderer/gles3/shaders/tex.frag b/scenefx/render/fx_renderer/gles3/shaders/tex.frag
new file mode 100644
index 0000000..f8f7edf
--- /dev/null
+++ b/scenefx/render/fx_renderer/gles3/shaders/tex.frag
@@ -0,0 +1,65 @@
+#version 300 es
+
+#define SOURCE %d
+
+#define SOURCE_TEXTURE_RGBA 1
+#define SOURCE_TEXTURE_RGBX 2
+#define SOURCE_TEXTURE_EXTERNAL 3
+
+#if !defined(SOURCE)
+#error "Missing shader preamble"
+#endif
+
+#if SOURCE == SOURCE_TEXTURE_EXTERNAL
+#extension GL_OES_EGL_image_external : require
+#endif
+
+precision highp float;
+
+in vec2 v_texcoord;
+
+#if SOURCE == SOURCE_TEXTURE_EXTERNAL
+uniform samplerExternalOES tex;
+#elif SOURCE == SOURCE_TEXTURE_RGBA || SOURCE == SOURCE_TEXTURE_RGBX
+uniform sampler2D tex;
+#endif
+
+uniform float alpha;
+
+uniform vec2 size;
+uniform vec2 position;
+uniform float radius_top_left;
+uniform float radius_top_right;
+uniform float radius_bottom_left;
+uniform float radius_bottom_right;
+
+uniform bool discard_transparent;
+
+out vec4 fragColor;
+
+vec4 sample_texture() {
+#if SOURCE == SOURCE_TEXTURE_RGBA || SOURCE == SOURCE_TEXTURE_EXTERNAL
+ return texture2D(tex, v_texcoord);
+#elif SOURCE == SOURCE_TEXTURE_RGBX
+ return vec4(texture2D(tex, v_texcoord).rgb, 1.0);
+#endif
+}
+
+float corner_alpha(vec2 size, vec2 position, float round_tl, float round_tr, float round_bl, float round_br);
+
+void main() {
+ float corner_alpha = corner_alpha(
+ size - 0.5,
+ position + 0.25,
+ radius_top_left,
+ radius_top_right,
+ radius_bottom_left,
+ radius_bottom_right
+ );
+ fragColor = mix(sample_texture() * alpha, vec4(0.0), corner_alpha);
+
+ if (discard_transparent && fragColor.a == 0.0) {
+ discard;
+ return;
+ }
+}
diff --git a/scenefx/render/fx_renderer/meson.build b/scenefx/render/fx_renderer/meson.build
new file mode 100644
index 0000000..1d584bd
--- /dev/null
+++ b/scenefx/render/fx_renderer/meson.build
@@ -0,0 +1,46 @@
+renderers = get_option('renderers')
+if 'auto' in renderers and get_option('auto_features').enabled()
+ renderers = ['gles2']
+elif 'auto' in renderers and get_option('auto_features').disabled()
+ renderers = []
+endif
+
+scenefx_files += files(
+ 'util.c',
+ 'shaders.c',
+ 'pixel_format.c',
+ 'fx_pass.c',
+ 'fx_framebuffer.c',
+ 'fx_effect_framebuffers.c',
+ 'fx_texture.c',
+ 'fx_renderer.c',
+)
+
+if 'gles2' in renderers or 'auto' in renderers
+ egl = dependency('egl', required: 'gles2' in renderers)
+ if egl.found()
+ eglext_version = cc.get_define(
+ 'EGL_EGLEXT_VERSION',
+ dependencies: egl,
+ prefix: '#include <EGL/eglext.h>',
+ ).to_int()
+ if eglext_version < 20210604
+ egl = dependency(
+ '',
+ required: 'gles2' in renderers,
+ not_found_message: 'EGL headers too old',
+ )
+ endif
+ endif
+ gbm = dependency('gbm', required: 'gles2' in renderers)
+ glesv2 = dependency('glesv2', required: 'gles2' in renderers)
+
+ glslang = find_program('glslang', 'glslangValidator', native: true, required: false, disabler: true)
+
+ if egl.found() and gbm.found() and glesv2.found()
+ scenefx_deps += [egl, gbm, glesv2]
+ endif
+ subdir('gles3')
+ subdir('gles2')
+endif
+
diff --git a/scenefx/render/fx_renderer/pixel_format.c b/scenefx/render/fx_renderer/pixel_format.c
new file mode 100644
index 0000000..9fff2ea
--- /dev/null
+++ b/scenefx/render/fx_renderer/pixel_format.c
@@ -0,0 +1,163 @@
+#include <drm_fourcc.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+
+#include "render/fx_renderer/fx_renderer.h"
+#include "render/pixel_format.h"
+
+/*
+ * The DRM formats are little endian while the GL formats are big endian,
+ * so DRM_FORMAT_ARGB8888 is actually compatible with GL_BGRA_EXT.
+ */
+static const struct fx_pixel_format formats[] = {
+ {
+ .drm_format = DRM_FORMAT_ARGB8888,
+ .gl_format = GL_BGRA_EXT,
+ .gl_type = GL_UNSIGNED_BYTE,
+ },
+ {
+ .drm_format = DRM_FORMAT_XRGB8888,
+ .gl_format = GL_BGRA_EXT,
+ .gl_type = GL_UNSIGNED_BYTE,
+ },
+ {
+ .drm_format = DRM_FORMAT_XBGR8888,
+ .gl_format = GL_RGBA,
+ .gl_type = GL_UNSIGNED_BYTE,
+ },
+ {
+ .drm_format = DRM_FORMAT_ABGR8888,
+ .gl_format = GL_RGBA,
+ .gl_type = GL_UNSIGNED_BYTE,
+ },
+ {
+ .drm_format = DRM_FORMAT_BGR888,
+ .gl_format = GL_RGB,
+ .gl_type = GL_UNSIGNED_BYTE,
+ },
+#if WLR_LITTLE_ENDIAN
+ {
+ .drm_format = DRM_FORMAT_RGBX4444,
+ .gl_format = GL_RGBA,
+ .gl_type = GL_UNSIGNED_SHORT_4_4_4_4,
+ },
+ {
+ .drm_format = DRM_FORMAT_RGBA4444,
+ .gl_format = GL_RGBA,
+ .gl_type = GL_UNSIGNED_SHORT_4_4_4_4,
+ },
+ {
+ .drm_format = DRM_FORMAT_RGBX5551,
+ .gl_format = GL_RGBA,
+ .gl_type = GL_UNSIGNED_SHORT_5_5_5_1,
+ },
+ {
+ .drm_format = DRM_FORMAT_RGBA5551,
+ .gl_format = GL_RGBA,
+ .gl_type = GL_UNSIGNED_SHORT_5_5_5_1,
+ },
+ {
+ .drm_format = DRM_FORMAT_RGB565,
+ .gl_format = GL_RGB,
+ .gl_type = GL_UNSIGNED_SHORT_5_6_5,
+ },
+ {
+ .drm_format = DRM_FORMAT_XBGR2101010,
+ .gl_format = GL_RGBA,
+ .gl_type = GL_UNSIGNED_INT_2_10_10_10_REV_EXT,
+ },
+ {
+ .drm_format = DRM_FORMAT_ABGR2101010,
+ .gl_format = GL_RGBA,
+ .gl_type = GL_UNSIGNED_INT_2_10_10_10_REV_EXT,
+ },
+ {
+ .drm_format = DRM_FORMAT_XBGR16161616F,
+ .gl_format = GL_RGBA,
+ .gl_type = GL_HALF_FLOAT_OES,
+ },
+ {
+ .drm_format = DRM_FORMAT_ABGR16161616F,
+ .gl_format = GL_RGBA,
+ .gl_type = GL_HALF_FLOAT_OES,
+ },
+ {
+ .drm_format = DRM_FORMAT_XBGR16161616,
+ .gl_internalformat = GL_RGBA16_EXT,
+ .gl_format = GL_RGBA,
+ .gl_type = GL_UNSIGNED_SHORT,
+ },
+ {
+ .drm_format = DRM_FORMAT_ABGR16161616,
+ .gl_internalformat = GL_RGBA16_EXT,
+ .gl_format = GL_RGBA,
+ .gl_type = GL_UNSIGNED_SHORT,
+ },
+#endif
+};
+
+// TODO: more pixel formats
+
+/*
+ * Return true if supported for texturing, even if other operations like
+ * reading aren't supported.
+ */
+bool is_fx_pixel_format_supported(const struct fx_renderer *renderer,
+ const struct fx_pixel_format *format) {
+ if (format->gl_type == GL_UNSIGNED_INT_2_10_10_10_REV_EXT
+ && !renderer->exts.EXT_texture_type_2_10_10_10_REV) {
+ return false;
+ }
+ if (format->gl_type == GL_HALF_FLOAT_OES
+ && !renderer->exts.OES_texture_half_float_linear) {
+ return false;
+ }
+ if (format->gl_type == GL_UNSIGNED_SHORT
+ && !renderer->exts.EXT_texture_norm16) {
+ return false;
+ }
+ /*
+ * Note that we don't need to check for GL_EXT_texture_format_BGRA8888
+ * here, since we've already checked if we have it at renderer creation
+ * time and bailed out if not. We do the check there because Wayland
+ * requires all compositors to support SHM buffers in that format.
+ */
+ return true;
+}
+
+const struct fx_pixel_format *get_fx_format_from_drm(uint32_t fmt) {
+ for (size_t i = 0; i < sizeof(formats) / sizeof(*formats); ++i) {
+ if (formats[i].drm_format == fmt) {
+ return &formats[i];
+ }
+ }
+ return NULL;
+}
+
+const struct fx_pixel_format *get_fx_format_from_gl(
+ GLint gl_format, GLint gl_type, bool alpha) {
+ for (size_t i = 0; i < sizeof(formats) / sizeof(*formats); ++i) {
+ if (formats[i].gl_format != gl_format ||
+ formats[i].gl_type != gl_type) {
+ continue;
+ }
+
+ if (pixel_format_has_alpha(formats[i].drm_format) != alpha) {
+ continue;
+ }
+
+ return &formats[i];
+ }
+ return NULL;
+}
+
+void get_fx_shm_formats(const struct fx_renderer *renderer,
+ struct wlr_drm_format_set *out) {
+ for (size_t i = 0; i < sizeof(formats) / sizeof(formats[0]); i++) {
+ if (!is_fx_pixel_format_supported(renderer, &formats[i])) {
+ continue;
+ }
+ wlr_drm_format_set_add(out, formats[i].drm_format, DRM_FORMAT_MOD_INVALID);
+ wlr_drm_format_set_add(out, formats[i].drm_format, DRM_FORMAT_MOD_LINEAR);
+ }
+}
diff --git a/scenefx/render/fx_renderer/shaders.c b/scenefx/render/fx_renderer/shaders.c
new file mode 100644
index 0000000..b740c5f
--- /dev/null
+++ b/scenefx/render/fx_renderer/shaders.c
@@ -0,0 +1,388 @@
+#include <EGL/egl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <wlr/util/log.h>
+
+#include "render/fx_renderer/shaders.h"
+
+// shaders
+#include "GLES2/gl2.h"
+// gles3
+#include "common_vert_gles3_src.h"
+#include "gradient_frag_gles3_src.h"
+#include "corner_alpha_frag_gles3_src.h"
+#include "quad_frag_gles3_src.h"
+#include "quad_grad_frag_gles3_src.h"
+#include "quad_round_frag_gles3_src.h"
+#include "quad_grad_round_frag_gles3_src.h"
+#include "tex_frag_gles3_src.h"
+#include "box_shadow_frag_gles3_src.h"
+#include "blur1_frag_gles3_src.h"
+#include "blur2_frag_gles3_src.h"
+#include "blur_effects_frag_gles3_src.h"
+// gles2
+#include "common_vert_gles2_src.h"
+#include "gradient_frag_gles2_src.h"
+#include "corner_alpha_frag_gles2_src.h"
+#include "quad_frag_gles2_src.h"
+#include "quad_grad_frag_gles2_src.h"
+#include "quad_round_frag_gles2_src.h"
+#include "quad_grad_round_frag_gles2_src.h"
+#include "tex_frag_gles2_src.h"
+#include "box_shadow_frag_gles2_src.h"
+#include "blur1_frag_gles2_src.h"
+#include "blur2_frag_gles2_src.h"
+#include "blur_effects_frag_gles2_src.h"
+
+GLuint compile_shader(GLuint type, const GLchar *src) {
+ GLuint shader = glCreateShader(type);
+ glShaderSource(shader, 1, &src, NULL);
+ glCompileShader(shader);
+
+ GLint ok;
+ glGetShaderiv(shader, GL_COMPILE_STATUS, &ok);
+ if (ok == GL_FALSE) {
+ wlr_log(WLR_ERROR, "Failed to compile shader");
+ glDeleteShader(shader);
+ shader = 0;
+ }
+
+ return shader;
+}
+
+GLuint link_program(const GLchar *frag_src, GLint client_version) {
+ const GLchar *vert_src = client_version > 2 ? common_vert_gles3_src : common_vert_gles2_src;
+ GLuint vert = compile_shader(GL_VERTEX_SHADER, vert_src);
+ if (!vert) {
+ goto error;
+ }
+
+ GLuint frag = compile_shader(GL_FRAGMENT_SHADER, frag_src);
+ if (!frag) {
+ glDeleteShader(vert);
+ goto error;
+ }
+
+ GLuint prog = glCreateProgram();
+ glAttachShader(prog, vert);
+ glAttachShader(prog, frag);
+ glLinkProgram(prog);
+
+ glDetachShader(prog, vert);
+ glDetachShader(prog, frag);
+ glDeleteShader(vert);
+ glDeleteShader(frag);
+
+ GLint ok;
+ glGetProgramiv(prog, GL_LINK_STATUS, &ok);
+ if (ok == GL_FALSE) {
+ wlr_log(WLR_ERROR, "Failed to link shader");
+ glDeleteProgram(prog);
+ goto error;
+ }
+
+ return prog;
+
+error:
+ return 0;
+}
+
+
+bool check_gl_ext(const char *exts, const char *ext) {
+ size_t extlen = strlen(ext);
+ const char *end = exts + strlen(exts);
+
+ while (exts < end) {
+ if (exts[0] == ' ') {
+ exts++;
+ continue;
+ }
+ size_t n = strcspn(exts, " ");
+ if (n == extlen && strncmp(ext, exts, n) == 0) {
+ return true;
+ }
+ exts += n;
+ }
+ return false;
+}
+
+void load_gl_proc(void *proc_ptr, const char *name) {
+ void *proc = (void *)eglGetProcAddress(name);
+ if (proc == NULL) {
+ wlr_log(WLR_ERROR, "FX RENDERER: eglGetProcAddress(%s) failed", name);
+ abort();
+ }
+ *(void **)proc_ptr = proc;
+}
+
+// Shaders
+
+bool link_quad_program(struct quad_shader *shader, GLint client_version) {
+ GLchar quad_src[4096];
+ if (client_version > 2) {
+ snprintf(quad_src, sizeof(quad_src), "%s\n%s", quad_frag_gles3_src,
+ corner_alpha_frag_gles3_src);
+ } else {
+ snprintf(quad_src, sizeof(quad_src), "%s\n%s", quad_frag_gles2_src,
+ corner_alpha_frag_gles2_src);
+ }
+
+ GLuint prog;
+ shader->program = prog = link_program(quad_src, client_version);
+ if (!shader->program) {
+ return false;
+ }
+
+ shader->proj = glGetUniformLocation(prog, "proj");
+ shader->color = glGetUniformLocation(prog, "color");
+ shader->pos_attrib = glGetAttribLocation(prog, "pos");
+ shader->clip_size = glGetUniformLocation(prog, "clip_size");
+ shader->clip_position = glGetUniformLocation(prog, "clip_position");
+ shader->clip_radius_top_left = glGetUniformLocation(prog, "clip_radius_top_left");
+ shader->clip_radius_top_right = glGetUniformLocation(prog, "clip_radius_top_right");
+ shader->clip_radius_bottom_left = glGetUniformLocation(prog, "clip_radius_bottom_left");
+ shader->clip_radius_bottom_right = glGetUniformLocation(prog, "clip_radius_bottom_right");
+
+ return true;
+}
+
+bool link_quad_grad_program(struct quad_grad_shader *shader, GLint client_version, int max_len) {
+ GLchar quad_src_part[2048];
+ GLchar quad_src[4096];
+ if (client_version > 2) {
+ snprintf(quad_src_part, sizeof(quad_src_part),
+ quad_grad_frag_gles3_src, max_len);
+ snprintf(quad_src, sizeof(quad_src),
+ "%s\n%s", quad_src_part, gradient_frag_gles3_src);
+ } else {
+ snprintf(quad_src_part, sizeof(quad_src_part),
+ quad_grad_frag_gles2_src, max_len);
+ snprintf(quad_src, sizeof(quad_src),
+ "%s\n%s", quad_src_part, gradient_frag_gles2_src);
+ }
+
+ GLuint prog;
+ shader->program = prog = link_program(quad_src, client_version);
+ if (!shader->program) {
+ return false;
+ }
+
+ shader->proj = glGetUniformLocation(prog, "proj");
+ shader->pos_attrib = glGetAttribLocation(prog, "pos");
+ shader->size = glGetUniformLocation(prog, "size");
+ shader->colors = glGetUniformLocation(prog, "colors");
+ shader->degree = glGetUniformLocation(prog, "degree");
+ shader->grad_box = glGetUniformLocation(prog, "grad_box");
+ shader->linear = glGetUniformLocation(prog, "linear");
+ shader->origin = glGetUniformLocation(prog, "origin");
+ shader->count = glGetUniformLocation(prog, "count");
+ shader->blend = glGetUniformLocation(prog, "blend");
+
+ shader->max_len = max_len;
+
+ return true;
+}
+
+bool link_quad_round_program(struct quad_round_shader *shader, GLint client_version) {
+ GLchar quad_src[8192];
+ if (client_version > 2) {
+ snprintf(quad_src, sizeof(quad_src), "%s\n%s", quad_round_frag_gles3_src,
+ corner_alpha_frag_gles3_src);
+ } else {
+ snprintf(quad_src, sizeof(quad_src), "%s\n%s", quad_round_frag_gles2_src,
+ corner_alpha_frag_gles2_src);
+ }
+
+ GLuint prog;
+ shader->program = prog = link_program(quad_src, client_version);
+ if (!shader->program) {
+ return false;
+ }
+
+ shader->proj = glGetUniformLocation(prog, "proj");
+ shader->color = glGetUniformLocation(prog, "color");
+ shader->pos_attrib = glGetAttribLocation(prog, "pos");
+ shader->size = glGetUniformLocation(prog, "size");
+ shader->position = glGetUniformLocation(prog, "position");
+ shader->radius_top_left = glGetUniformLocation(prog, "radius_top_left");
+ shader->radius_top_right = glGetUniformLocation(prog, "radius_top_right");
+ shader->radius_bottom_left = glGetUniformLocation(prog, "radius_bottom_left");
+ shader->radius_bottom_right = glGetUniformLocation(prog, "radius_bottom_right");
+
+ shader->clip_size = glGetUniformLocation(prog, "clip_size");
+ shader->clip_position = glGetUniformLocation(prog, "clip_position");
+ shader->clip_radius_top_left = glGetUniformLocation(prog, "clip_radius_top_left");
+ shader->clip_radius_top_right = glGetUniformLocation(prog, "clip_radius_top_right");
+ shader->clip_radius_bottom_left = glGetUniformLocation(prog, "clip_radius_bottom_left");
+ shader->clip_radius_bottom_right = glGetUniformLocation(prog, "clip_radius_bottom_right");
+ shader->fade_inset = glGetUniformLocation(prog, "fade_inset");
+
+ return true;
+}
+
+bool link_quad_grad_round_program(struct quad_grad_round_shader *shader, GLint client_version, int max_len) {
+ GLchar quad_src_part[2048];
+ GLchar quad_src[8192];
+ if (client_version > 2) {
+ snprintf(quad_src_part, sizeof(quad_src_part),
+ quad_grad_round_frag_gles3_src, max_len);
+ snprintf(quad_src, sizeof(quad_src),
+ "%s\n%s\n%s", quad_src_part, gradient_frag_gles3_src, corner_alpha_frag_gles3_src);
+ } else {
+ snprintf(quad_src_part, sizeof(quad_src_part),
+ quad_grad_round_frag_gles2_src, max_len);
+ snprintf(quad_src, sizeof(quad_src),
+ "%s\n%s\n%s", quad_src_part, gradient_frag_gles2_src, corner_alpha_frag_gles2_src);
+ }
+
+ GLuint prog;
+ shader->program = prog = link_program(quad_src, client_version);
+ if (!shader->program) {
+ return false;
+ }
+
+ shader->proj = glGetUniformLocation(prog, "proj");
+ shader->color = glGetUniformLocation(prog, "color");
+ shader->pos_attrib = glGetAttribLocation(prog, "pos");
+ shader->size = glGetUniformLocation(prog, "size");
+ shader->position = glGetUniformLocation(prog, "position");
+ shader->radius_top_left = glGetUniformLocation(prog, "radius_top_left");
+ shader->radius_top_right = glGetUniformLocation(prog, "radius_top_right");
+ shader->radius_bottom_left = glGetUniformLocation(prog, "radius_bottom_left");
+ shader->radius_bottom_right = glGetUniformLocation(prog, "radius_bottom_right");
+
+ shader->grad_size = glGetUniformLocation(prog, "grad_size");
+ shader->colors = glGetUniformLocation(prog, "colors");
+ shader->degree = glGetUniformLocation(prog, "degree");
+ shader->grad_box = glGetUniformLocation(prog, "grad_box");
+ shader->linear = glGetUniformLocation(prog, "linear");
+ shader->origin = glGetUniformLocation(prog, "origin");
+ shader->count = glGetUniformLocation(prog, "count");
+ shader->blend = glGetUniformLocation(prog, "blend");
+
+ shader->max_len = max_len;
+
+ return true;
+}
+
+bool link_tex_program(struct tex_shader *shader, GLint client_version, enum fx_tex_shader_source source) {
+ GLchar frag_src_part[2048];
+ GLchar frag_src[4096];
+ if (client_version > 2) {
+ snprintf(frag_src_part, sizeof(frag_src_part),
+ tex_frag_gles3_src, source);
+ snprintf(frag_src, sizeof(frag_src),
+ "%s\n%s\n", frag_src_part, corner_alpha_frag_gles3_src);
+ } else {
+ snprintf(frag_src_part, sizeof(frag_src_part),
+ tex_frag_gles2_src, source);
+ snprintf(frag_src, sizeof(frag_src),
+ "%s\n%s\n", frag_src_part, corner_alpha_frag_gles2_src);
+ }
+
+ GLuint prog;
+ shader->program = prog = link_program(frag_src, client_version);
+ if (!shader->program) {
+ return false;
+ }
+
+ shader->proj = glGetUniformLocation(prog, "proj");
+ shader->tex = glGetUniformLocation(prog, "tex");
+ shader->alpha = glGetUniformLocation(prog, "alpha");
+ shader->pos_attrib = glGetAttribLocation(prog, "pos");
+ shader->tex_proj = glGetUniformLocation(prog, "tex_proj");
+ shader->size = glGetUniformLocation(prog, "size");
+ shader->position = glGetUniformLocation(prog, "position");
+ shader->radius_top_left = glGetUniformLocation(prog, "radius_top_left");
+ shader->radius_top_right = glGetUniformLocation(prog, "radius_top_right");
+ shader->radius_bottom_left = glGetUniformLocation(prog, "radius_bottom_left");
+ shader->radius_bottom_right = glGetUniformLocation(prog, "radius_bottom_right");
+ shader->discard_transparent = glGetUniformLocation(prog, "discard_transparent");
+
+ return true;
+}
+
+bool link_box_shadow_program(struct box_shadow_shader *shader, GLint client_version) {
+ GLchar shadow_src[8192];
+ if (client_version > 2) {
+ snprintf(shadow_src, sizeof(shadow_src), "%s\n%s", box_shadow_frag_gles3_src,
+ corner_alpha_frag_gles3_src);
+ } else {
+ snprintf(shadow_src, sizeof(shadow_src), "%s\n%s", box_shadow_frag_gles2_src,
+ corner_alpha_frag_gles2_src);
+ }
+
+ GLuint prog;
+ shader->program = prog = link_program(shadow_src, client_version);
+ if (!shader->program) {
+ return false;
+ }
+ shader->proj = glGetUniformLocation(prog, "proj");
+ shader->color = glGetUniformLocation(prog, "color");
+ shader->pos_attrib = glGetAttribLocation(prog, "pos");
+ shader->position = glGetUniformLocation(prog, "position");
+ shader->size = glGetUniformLocation(prog, "size");
+ shader->blur_sigma = glGetUniformLocation(prog, "blur_sigma");
+ shader->corner_radius = glGetUniformLocation(prog, "corner_radius");
+ shader->clip_position = glGetUniformLocation(prog, "clip_position");
+ shader->clip_size = glGetUniformLocation(prog, "clip_size");
+ shader->clip_radius_top_left = glGetUniformLocation(prog, "clip_radius_top_left");
+ shader->clip_radius_top_right = glGetUniformLocation(prog, "clip_radius_top_right");
+ shader->clip_radius_bottom_left = glGetUniformLocation(prog, "clip_radius_bottom_left");
+ shader->clip_radius_bottom_right = glGetUniformLocation(prog, "clip_radius_bottom_right");
+
+ return true;
+}
+
+bool link_blur1_program(struct blur_shader *shader, GLint client_version) {
+ GLuint prog;
+ shader->program = prog = client_version > 2 ? link_program(blur1_frag_gles3_src, client_version)
+ : link_program(blur1_frag_gles2_src, client_version);
+ if (!shader->program) {
+ return false;
+ }
+ shader->proj = glGetUniformLocation(prog, "proj");
+ shader->tex = glGetUniformLocation(prog, "tex");
+ shader->pos_attrib = glGetAttribLocation(prog, "pos");
+ shader->tex_proj = glGetUniformLocation(prog, "tex_proj");
+ shader->radius = glGetUniformLocation(prog, "radius");
+ shader->halfpixel = glGetUniformLocation(prog, "halfpixel");
+
+ return true;
+}
+
+bool link_blur2_program(struct blur_shader *shader, GLint client_version) {
+ GLuint prog;
+ shader->program = prog = client_version > 2 ? link_program(blur2_frag_gles3_src, client_version)
+ : link_program(blur2_frag_gles2_src, client_version);
+ if (!shader->program) {
+ return false;
+ }
+ shader->proj = glGetUniformLocation(prog, "proj");
+ shader->tex = glGetUniformLocation(prog, "tex");
+ shader->pos_attrib = glGetAttribLocation(prog, "pos");
+ shader->tex_proj = glGetUniformLocation(prog, "tex_proj");
+ shader->radius = glGetUniformLocation(prog, "radius");
+ shader->halfpixel = glGetUniformLocation(prog, "halfpixel");
+
+ return true;
+}
+
+bool link_blur_effects_program(struct blur_effects_shader *shader, GLint client_version) {
+ GLuint prog;
+ shader->program = prog = client_version > 2 ? link_program(blur_effects_frag_gles3_src, client_version)
+ : link_program(blur_effects_frag_gles2_src, client_version);
+ if (!shader->program) {
+ return false;
+ }
+ shader->proj = glGetUniformLocation(prog, "proj");
+ shader->tex = glGetUniformLocation(prog, "tex");
+ shader->pos_attrib = glGetAttribLocation(prog, "pos");
+ shader->tex_proj = glGetUniformLocation(prog, "tex_proj");
+ shader->noise = glGetUniformLocation(prog, "noise");
+ shader->brightness = glGetUniformLocation(prog, "brightness");
+ shader->contrast = glGetUniformLocation(prog, "contrast");
+ shader->saturation = glGetUniformLocation(prog, "saturation");
+
+ return true;
+}
diff --git a/scenefx/render/fx_renderer/util.c b/scenefx/render/fx_renderer/util.c
new file mode 100644
index 0000000..dd044e0
--- /dev/null
+++ b/scenefx/render/fx_renderer/util.c
@@ -0,0 +1,110 @@
+#include <fcntl.h>
+#include <unistd.h>
+#include <wlr/util/log.h>
+#include <wlr/types/wlr_buffer.h>
+#include <xf86drm.h>
+
+#include "render/fx_renderer/util.h"
+#include "util/env.h"
+
+static int open_drm_render_node(void) {
+ uint32_t flags = 0;
+ int devices_len = drmGetDevices2(flags, NULL, 0);
+ if (devices_len < 0) {
+ wlr_log(WLR_ERROR, "drmGetDevices2 failed: %s", strerror(-devices_len));
+ return -1;
+ }
+ drmDevice **devices = calloc(devices_len, sizeof(*devices));
+ if (devices == NULL) {
+ wlr_log_errno(WLR_ERROR, "Allocation failed");
+ return -1;
+ }
+ devices_len = drmGetDevices2(flags, devices, devices_len);
+ if (devices_len < 0) {
+ free(devices);
+ wlr_log(WLR_ERROR, "drmGetDevices2 failed: %s", strerror(-devices_len));
+ return -1;
+ }
+
+ int fd = -1;
+ for (int i = 0; i < devices_len; i++) {
+ drmDevice *dev = devices[i];
+ if (dev->available_nodes & (1 << DRM_NODE_RENDER)) {
+ const char *name = dev->nodes[DRM_NODE_RENDER];
+ wlr_log(WLR_DEBUG, "Opening DRM render node '%s'", name);
+ fd = open(name, O_RDWR | O_CLOEXEC);
+ if (fd < 0) {
+ wlr_log_errno(WLR_ERROR, "Failed to open '%s'", name);
+ goto out;
+ }
+ break;
+ }
+ }
+ if (fd < 0) {
+ wlr_log(WLR_ERROR, "Failed to find any DRM render node");
+ }
+
+out:
+ for (int i = 0; i < devices_len; i++) {
+ drmFreeDevice(&devices[i]);
+ }
+ free(devices);
+
+ return fd;
+}
+
+bool open_preferred_drm_fd(struct wlr_backend *backend, int *drm_fd_ptr,
+ bool *own_drm_fd) {
+ if (*drm_fd_ptr >= 0) {
+ return true;
+ }
+
+ if (env_parse_bool("WLR_RENDERER_FORCE_SOFTWARE")) {
+ *drm_fd_ptr = -1;
+ *own_drm_fd = false;
+ return true;
+ }
+
+ // Allow the user to override the render node
+ const char *render_name = getenv("WLR_RENDER_DRM_DEVICE");
+ if (render_name != NULL) {
+ wlr_log(WLR_INFO,
+ "Opening DRM render node '%s' from WLR_RENDER_DRM_DEVICE",
+ render_name);
+ int drm_fd = open(render_name, O_RDWR | O_CLOEXEC);
+ if (drm_fd < 0) {
+ wlr_log_errno(WLR_ERROR, "Failed to open '%s'", render_name);
+ return false;
+ }
+ if (drmGetNodeTypeFromFd(drm_fd) != DRM_NODE_RENDER) {
+ wlr_log(WLR_ERROR, "'%s' is not a DRM render node", render_name);
+ close(drm_fd);
+ return false;
+ }
+ *drm_fd_ptr = drm_fd;
+ *own_drm_fd = true;
+ return true;
+ }
+
+ // Prefer the backend's DRM node, if any
+ int backend_drm_fd = wlr_backend_get_drm_fd(backend);
+ if (backend_drm_fd >= 0) {
+ *drm_fd_ptr = backend_drm_fd;
+ *own_drm_fd = false;
+ return true;
+ }
+
+ // If the backend hasn't picked a DRM FD, but accepts DMA-BUFs, pick an
+ // arbitrary render node
+ if (backend->buffer_caps & WLR_BUFFER_CAP_DMABUF) {
+ int drm_fd = open_drm_render_node();
+ if (drm_fd < 0) {
+ return false;
+ }
+ *drm_fd_ptr = drm_fd;
+ *own_drm_fd = true;
+ return true;
+ }
+
+ return false;
+}
diff --git a/scenefx/render/meson.build b/scenefx/render/meson.build
new file mode 100644
index 0000000..c5dd423
--- /dev/null
+++ b/scenefx/render/meson.build
@@ -0,0 +1,6 @@
+scenefx_files += files(
+ 'pixel_format.c',
+ 'egl.c',
+)
+
+subdir('fx_renderer')
diff --git a/scenefx/render/pixel_format.c b/scenefx/render/pixel_format.c
new file mode 100644
index 0000000..7befbf0
--- /dev/null
+++ b/scenefx/render/pixel_format.c
@@ -0,0 +1,277 @@
+#include <assert.h>
+#include <drm_fourcc.h>
+#include <wlr/util/log.h>
+#include "render/pixel_format.h"
+
+static const struct wlr_pixel_format_info pixel_format_info[] = {
+ {
+ .drm_format = DRM_FORMAT_XRGB8888,
+ .bytes_per_block = 4,
+ },
+ {
+ .drm_format = DRM_FORMAT_ARGB8888,
+ .opaque_substitute = DRM_FORMAT_XRGB8888,
+ .bytes_per_block = 4,
+ },
+ {
+ .drm_format = DRM_FORMAT_XBGR8888,
+ .bytes_per_block = 4,
+ },
+ {
+ .drm_format = DRM_FORMAT_ABGR8888,
+ .opaque_substitute = DRM_FORMAT_XBGR8888,
+ .bytes_per_block = 4,
+ },
+ {
+ .drm_format = DRM_FORMAT_RGBX8888,
+ .bytes_per_block = 4,
+ },
+ {
+ .drm_format = DRM_FORMAT_RGBA8888,
+ .opaque_substitute = DRM_FORMAT_RGBX8888,
+ .bytes_per_block = 4,
+ },
+ {
+ .drm_format = DRM_FORMAT_BGRX8888,
+ .bytes_per_block = 4,
+ },
+ {
+ .drm_format = DRM_FORMAT_BGRA8888,
+ .opaque_substitute = DRM_FORMAT_BGRX8888,
+ .bytes_per_block = 4,
+ },
+ {
+ .drm_format = DRM_FORMAT_R8,
+ .bytes_per_block = 1,
+ },
+ {
+ .drm_format = DRM_FORMAT_GR88,
+ .bytes_per_block = 2,
+ },
+ {
+ .drm_format = DRM_FORMAT_RGB888,
+ .bytes_per_block = 3,
+ },
+ {
+ .drm_format = DRM_FORMAT_BGR888,
+ .bytes_per_block = 3,
+ },
+ {
+ .drm_format = DRM_FORMAT_RGBX4444,
+ .bytes_per_block = 2,
+ },
+ {
+ .drm_format = DRM_FORMAT_RGBA4444,
+ .opaque_substitute = DRM_FORMAT_RGBX4444,
+ .bytes_per_block = 2,
+ },
+ {
+ .drm_format = DRM_FORMAT_BGRX4444,
+ .bytes_per_block = 2,
+ },
+ {
+ .drm_format = DRM_FORMAT_BGRA4444,
+ .opaque_substitute = DRM_FORMAT_BGRX4444,
+ .bytes_per_block = 2,
+ },
+ {
+ .drm_format = DRM_FORMAT_RGBX5551,
+ .bytes_per_block = 2,
+ },
+ {
+ .drm_format = DRM_FORMAT_RGBA5551,
+ .opaque_substitute = DRM_FORMAT_RGBX5551,
+ .bytes_per_block = 2,
+ },
+ {
+ .drm_format = DRM_FORMAT_BGRX5551,
+ .bytes_per_block = 2,
+ },
+ {
+ .drm_format = DRM_FORMAT_BGRA5551,
+ .opaque_substitute = DRM_FORMAT_BGRX5551,
+ .bytes_per_block = 2,
+ },
+ {
+ .drm_format = DRM_FORMAT_XRGB1555,
+ .bytes_per_block = 2,
+ },
+ {
+ .drm_format = DRM_FORMAT_ARGB1555,
+ .opaque_substitute = DRM_FORMAT_XRGB1555,
+ .bytes_per_block = 2,
+ },
+ {
+ .drm_format = DRM_FORMAT_RGB565,
+ .bytes_per_block = 2,
+ },
+ {
+ .drm_format = DRM_FORMAT_BGR565,
+ .bytes_per_block = 2,
+ },
+ {
+ .drm_format = DRM_FORMAT_XRGB2101010,
+ .bytes_per_block = 4,
+ },
+ {
+ .drm_format = DRM_FORMAT_ARGB2101010,
+ .opaque_substitute = DRM_FORMAT_XRGB2101010,
+ .bytes_per_block = 4,
+ },
+ {
+ .drm_format = DRM_FORMAT_XBGR2101010,
+ .bytes_per_block = 4,
+ },
+ {
+ .drm_format = DRM_FORMAT_ABGR2101010,
+ .opaque_substitute = DRM_FORMAT_XBGR2101010,
+ .bytes_per_block = 4,
+ },
+ {
+ .drm_format = DRM_FORMAT_XBGR16161616F,
+ .bytes_per_block = 8,
+ },
+ {
+ .drm_format = DRM_FORMAT_ABGR16161616F,
+ .opaque_substitute = DRM_FORMAT_XBGR16161616F,
+ .bytes_per_block = 8,
+ },
+ {
+ .drm_format = DRM_FORMAT_XBGR16161616,
+ .bytes_per_block = 8,
+ },
+ {
+ .drm_format = DRM_FORMAT_ABGR16161616,
+ .opaque_substitute = DRM_FORMAT_XBGR16161616,
+ .bytes_per_block = 8,
+ },
+ {
+ .drm_format = DRM_FORMAT_YVYU,
+ .bytes_per_block = 4,
+ .block_width = 2,
+ .block_height = 1,
+ },
+ {
+ .drm_format = DRM_FORMAT_VYUY,
+ .bytes_per_block = 4,
+ .block_width = 2,
+ .block_height = 1,
+ },
+};
+
+static const uint32_t opaque_pixel_formats[] = {
+ DRM_FORMAT_XRGB8888,
+ DRM_FORMAT_XBGR8888,
+ DRM_FORMAT_RGBX8888,
+ DRM_FORMAT_BGRX8888,
+ DRM_FORMAT_R8,
+ DRM_FORMAT_GR88,
+ DRM_FORMAT_RGB888,
+ DRM_FORMAT_BGR888,
+ DRM_FORMAT_RGBX4444,
+ DRM_FORMAT_BGRX4444,
+ DRM_FORMAT_RGBX5551,
+ DRM_FORMAT_BGRX5551,
+ DRM_FORMAT_XRGB1555,
+ DRM_FORMAT_RGB565,
+ DRM_FORMAT_BGR565,
+ DRM_FORMAT_XRGB2101010,
+ DRM_FORMAT_XBGR2101010,
+ DRM_FORMAT_XBGR16161616F,
+ DRM_FORMAT_XBGR16161616,
+ DRM_FORMAT_YVYU,
+ DRM_FORMAT_VYUY,
+ DRM_FORMAT_NV12,
+ DRM_FORMAT_P010,
+};
+
+static const size_t pixel_format_info_size =
+ sizeof(pixel_format_info) / sizeof(pixel_format_info[0]);
+
+static const size_t opaque_pixel_formats_size =
+ sizeof(opaque_pixel_formats) / sizeof(opaque_pixel_formats[0]);
+
+const struct wlr_pixel_format_info *drm_get_pixel_format_info(uint32_t fmt) {
+ for (size_t i = 0; i < pixel_format_info_size; ++i) {
+ if (pixel_format_info[i].drm_format == fmt) {
+ return &pixel_format_info[i];
+ }
+ }
+
+ return NULL;
+}
+
+uint32_t convert_wl_shm_format_to_drm(enum wl_shm_format fmt) {
+ switch (fmt) {
+ case WL_SHM_FORMAT_XRGB8888:
+ return DRM_FORMAT_XRGB8888;
+ case WL_SHM_FORMAT_ARGB8888:
+ return DRM_FORMAT_ARGB8888;
+ default:
+ return (uint32_t)fmt;
+ }
+}
+
+enum wl_shm_format convert_drm_format_to_wl_shm(uint32_t fmt) {
+ switch (fmt) {
+ case DRM_FORMAT_XRGB8888:
+ return WL_SHM_FORMAT_XRGB8888;
+ case DRM_FORMAT_ARGB8888:
+ return WL_SHM_FORMAT_ARGB8888;
+ default:
+ return (enum wl_shm_format)fmt;
+ }
+}
+
+uint32_t pixel_format_info_pixels_per_block(const struct wlr_pixel_format_info *info) {
+ uint32_t pixels = info->block_width * info->block_height;
+ return pixels > 0 ? pixels : 1;
+}
+
+static int32_t div_round_up(int32_t dividend, int32_t divisor) {
+ int32_t quotient = dividend / divisor;
+ if (dividend % divisor != 0) {
+ quotient++;
+ }
+ return quotient;
+}
+
+int32_t pixel_format_info_min_stride(const struct wlr_pixel_format_info *fmt, int32_t width) {
+ int32_t pixels_per_block = (int32_t)pixel_format_info_pixels_per_block(fmt);
+ int32_t bytes_per_block = (int32_t)fmt->bytes_per_block;
+ if (width > INT32_MAX / bytes_per_block) {
+ wlr_log(WLR_DEBUG, "Invalid width %d (overflow)", width);
+ return 0;
+ }
+ return div_round_up(width * bytes_per_block, pixels_per_block);
+}
+
+bool pixel_format_info_check_stride(const struct wlr_pixel_format_info *fmt,
+ int32_t stride, int32_t width) {
+ int32_t bytes_per_block = (int32_t)fmt->bytes_per_block;
+ if (stride % bytes_per_block != 0) {
+ wlr_log(WLR_DEBUG, "Invalid stride %d (incompatible with %d "
+ "bytes-per-block)", stride, bytes_per_block);
+ return false;
+ }
+
+ int32_t min_stride = pixel_format_info_min_stride(fmt, width);
+ if (min_stride <= 0) {
+ return false;
+ } else if (stride < min_stride) {
+ wlr_log(WLR_DEBUG, "Invalid stride %d (too small for %d "
+ "bytes-per-block and width %d)", stride, bytes_per_block, width);
+ return false;
+ }
+
+ return true;
+}
+
+bool pixel_format_has_alpha(uint32_t fmt) {
+ for (size_t i = 0; i < opaque_pixel_formats_size; i++) {
+ if (fmt == opaque_pixel_formats[i]) {
+ return false;
+ }
+ }
+ return true;
+}
diff --git a/scenefx/tinywl/.gitignore b/scenefx/tinywl/.gitignore
new file mode 100644
index 0000000..5de73fa
--- /dev/null
+++ b/scenefx/tinywl/.gitignore
@@ -0,0 +1,3 @@
+tinywl
+tinywl.o
+*-protocol.h
diff --git a/scenefx/tinywl/LICENSE b/scenefx/tinywl/LICENSE
new file mode 100644
index 0000000..0153f30
--- /dev/null
+++ b/scenefx/tinywl/LICENSE
@@ -0,0 +1,125 @@
+This work is licensed under CC0, which effectively puts it in the public domain.
+
+---
+
+Creative Commons Legal Code
+
+CC0 1.0 Universal
+
+ CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
+ LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
+ ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
+ INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
+ REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
+ PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
+ THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
+ HEREUNDER.
+
+Statement of Purpose
+
+The laws of most jurisdictions throughout the world automatically confer
+exclusive Copyright and Related Rights (defined below) upon the creator
+and subsequent owner(s) (each and all, an "owner") of an original work of
+authorship and/or a database (each, a "Work").
+
+Certain owners wish to permanently relinquish those rights to a Work for
+the purpose of contributing to a commons of creative, cultural and
+scientific works ("Commons") that the public can reliably and without fear
+of later claims of infringement build upon, modify, incorporate in other
+works, reuse and redistribute as freely as possible in any form whatsoever
+and for any purposes, including without limitation commercial purposes.
+These owners may contribute to the Commons to promote the ideal of a free
+culture and the further production of creative, cultural and scientific
+works, or to gain reputation or greater distribution for their Work in
+part through the use and efforts of others.
+
+For these and/or other purposes and motivations, and without any
+expectation of additional consideration or compensation, the person
+associating CC0 with a Work (the "Affirmer"), to the extent that he or she
+is an owner of Copyright and Related Rights in the Work, voluntarily
+elects to apply CC0 to the Work and publicly distribute the Work under its
+terms, with knowledge of his or her Copyright and Related Rights in the
+Work and the meaning and intended legal effect of CC0 on those rights.
+
+1. Copyright and Related Rights. A Work made available under CC0 may be
+protected by copyright and related or neighboring rights ("Copyright and
+Related Rights"). Copyright and Related Rights include, but are not
+limited to, the following:
+
+ i. the right to reproduce, adapt, distribute, perform, display,
+ communicate, and translate a Work;
+ ii. moral rights retained by the original author(s) and/or performer(s);
+iii. publicity and privacy rights pertaining to a person's image or
+ likeness depicted in a Work;
+ iv. rights protecting against unfair competition in regards to a Work,
+ subject to the limitations in paragraph 4(a), below;
+ v. rights protecting the extraction, dissemination, use and reuse of data
+ in a Work;
+ vi. database rights (such as those arising under Directive 96/9/EC of the
+ European Parliament and of the Council of 11 March 1996 on the legal
+ protection of databases, and under any national implementation
+ thereof, including any amended or successor version of such
+ directive); and
+vii. other similar, equivalent or corresponding rights throughout the
+ world based on applicable law or treaty, and any national
+ implementations thereof.
+
+2. Waiver. To the greatest extent permitted by, but not in contravention
+of, applicable law, Affirmer hereby overtly, fully, permanently,
+irrevocably and unconditionally waives, abandons, and surrenders all of
+Affirmer's Copyright and Related Rights and associated claims and causes
+of action, whether now known or unknown (including existing as well as
+future claims and causes of action), in the Work (i) in all territories
+worldwide, (ii) for the maximum duration provided by applicable law or
+treaty (including future time extensions), (iii) in any current or future
+medium and for any number of copies, and (iv) for any purpose whatsoever,
+including without limitation commercial, advertising or promotional
+purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
+member of the public at large and to the detriment of Affirmer's heirs and
+successors, fully intending that such Waiver shall not be subject to
+revocation, rescission, cancellation, termination, or any other legal or
+equitable action to disrupt the quiet enjoyment of the Work by the public
+as contemplated by Affirmer's express Statement of Purpose.
+
+3. Public License Fallback. Should any part of the Waiver for any reason
+be judged legally invalid or ineffective under applicable law, then the
+Waiver shall be preserved to the maximum extent permitted taking into
+account Affirmer's express Statement of Purpose. In addition, to the
+extent the Waiver is so judged Affirmer hereby grants to each affected
+person a royalty-free, non transferable, non sublicensable, non exclusive,
+irrevocable and unconditional license to exercise Affirmer's Copyright and
+Related Rights in the Work (i) in all territories worldwide, (ii) for the
+maximum duration provided by applicable law or treaty (including future
+time extensions), (iii) in any current or future medium and for any number
+of copies, and (iv) for any purpose whatsoever, including without
+limitation commercial, advertising or promotional purposes (the
+"License"). The License shall be deemed effective as of the date CC0 was
+applied by Affirmer to the Work. Should any part of the License for any
+reason be judged legally invalid or ineffective under applicable law, such
+partial invalidity or ineffectiveness shall not invalidate the remainder
+of the License, and in such case Affirmer hereby affirms that he or she
+will not (i) exercise any of his or her remaining Copyright and Related
+Rights in the Work or (ii) assert any associated claims and causes of
+action with respect to the Work, in either case contrary to Affirmer's
+express Statement of Purpose.
+
+4. Limitations and Disclaimers.
+
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
+ surrendered, licensed or otherwise affected by this document.
+ b. Affirmer offers the Work as-is and makes no representations or
+ warranties of any kind concerning the Work, express, implied,
+ statutory or otherwise, including without limitation warranties of
+ title, merchantability, fitness for a particular purpose, non
+ infringement, or the absence of latent or other defects, accuracy, or
+ the present or absence of errors, whether or not discoverable, all to
+ the greatest extent permissible under applicable law.
+ c. Affirmer disclaims responsibility for clearing rights of other persons
+ that may apply to the Work or any use thereof, including without
+ limitation any person's Copyright and Related Rights in the Work.
+ Further, Affirmer disclaims responsibility for obtaining any necessary
+ consents, permissions or other rights required for any use of the
+ Work.
+ d. Affirmer understands and acknowledges that Creative Commons is not a
+ party to this document and has no duty or obligation with respect to
+ this CC0 or use of the Work.
diff --git a/scenefx/tinywl/Makefile b/scenefx/tinywl/Makefile
new file mode 100644
index 0000000..fb5f315
--- /dev/null
+++ b/scenefx/tinywl/Makefile
@@ -0,0 +1,27 @@
+PKG_CONFIG?=pkg-config
+WAYLAND_PROTOCOLS!=$(PKG_CONFIG) --variable=pkgdatadir wayland-protocols
+WAYLAND_SCANNER!=$(PKG_CONFIG) --variable=wayland_scanner wayland-scanner
+
+PKGS="scenefx-0.2" "wlroots-0.19" wayland-server xkbcommon
+CFLAGS_PKG_CONFIG!=$(PKG_CONFIG) --cflags $(PKGS)
+CFLAGS+=$(CFLAGS_PKG_CONFIG)
+LIBS!=$(PKG_CONFIG) --libs $(PKGS)
+
+all: tinywl
+
+# wayland-scanner is a tool which generates C headers and rigging for Wayland
+# protocols, which are specified in XML. wlroots requires you to rig these up
+# to your build system yourself and provide them in the include path.
+xdg-shell-protocol.h:
+ $(WAYLAND_SCANNER) server-header \
+ $(WAYLAND_PROTOCOLS)/stable/xdg-shell/xdg-shell.xml $@
+
+tinywl.o: tinywl.c xdg-shell-protocol.h
+ $(CC) -c $< -g -Werror $(CFLAGS) -I. -DWLR_USE_UNSTABLE -o $@
+tinywl: tinywl.o
+ $(CC) $^ $> -g -Werror $(CFLAGS) $(LDFLAGS) $(LIBS) -o $@
+
+clean:
+ rm -f tinywl tinywl.o xdg-shell-protocol.h
+
+.PHONY: all clean
diff --git a/scenefx/tinywl/README.md b/scenefx/tinywl/README.md
new file mode 100644
index 0000000..7fc83b9
--- /dev/null
+++ b/scenefx/tinywl/README.md
@@ -0,0 +1,45 @@
+# TinyWL
+
+This is the "minimum viable product" Wayland compositor based on wlroots. It
+aims to implement a Wayland compositor in the fewest lines of code possible,
+while still supporting a reasonable set of features. Reading this code is the
+best starting point for anyone looking to build their own Wayland compositor
+based on wlroots.
+
+## Building TinyWL
+
+TinyWL is disconnected from the main wlroots build system, in order to make it
+easier to understand the build requirements for your own Wayland compositors.
+Simply install the dependencies:
+
+- wlroots
+- wayland-protocols
+
+And run `make`.
+
+## Running TinyWL
+
+You can run TinyWL with `./tinywl`. In an existing Wayland or X11 session,
+tinywl will open a Wayland or X11 window respectively to act as a virtual
+display. You can then open Wayland windows by setting `WAYLAND_DISPLAY` to the
+value shown in the logs. You can also run `./tinywl` from a TTY.
+
+In either case, you will likely want to specify `-s [cmd]` to run a command at
+startup, such as a terminal emulator. This will be necessary to start any new
+programs from within the compositor, as TinyWL does not support any custom
+keybindings. TinyWL supports the following keybindings:
+
+- `Alt+Escape`: Terminate the compositor
+- `Alt+F1`: Cycle between windows
+
+## Limitations
+
+Notable omissions from TinyWL:
+
+- HiDPI support
+- Any kind of configuration, e.g. output layout
+- Any protocol other than xdg-shell (e.g. layer-shell, for
+ panels/taskbars/etc; or Xwayland, for proxied X11 windows)
+- Optional protocols, e.g. screen capture, primary selection, virtual
+ keyboard, etc. Most of these are plug-and-play with wlroots, but they're
+ omitted for brevity.
diff --git a/scenefx/tinywl/meson.build b/scenefx/tinywl/meson.build
new file mode 100644
index 0000000..4c3a1d0
--- /dev/null
+++ b/scenefx/tinywl/meson.build
@@ -0,0 +1,5 @@
+executable(
+ 'tinywl',
+ ['tinywl.c', protocols_client_header['xdg-shell']],
+ dependencies: scenefx,
+)
diff --git a/scenefx/tinywl/tinywl.c b/scenefx/tinywl/tinywl.c
new file mode 100644
index 0000000..c6e2a5d
--- /dev/null
+++ b/scenefx/tinywl/tinywl.c
@@ -0,0 +1,1281 @@
+#include <assert.h>
+#include <getopt.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <time.h>
+#include <scenefx/render/fx_renderer/fx_renderer.h>
+#include <scenefx/types/fx/clipped_region.h>
+#include <scenefx/types/fx/corner_location.h>
+#include <scenefx/types/wlr_scene.h>
+#include <unistd.h>
+#include <wayland-server-core.h>
+#include <wayland-util.h>
+#include <wlr/backend.h>
+#include <wlr/render/allocator.h>
+#include <wlr/render/wlr_renderer.h>
+#include <wlr/types/wlr_cursor.h>
+#include <wlr/types/wlr_compositor.h>
+#include <wlr/types/wlr_data_device.h>
+#include <wlr/types/wlr_input_device.h>
+#include <wlr/types/wlr_keyboard.h>
+#include <wlr/types/wlr_output.h>
+#include <wlr/types/wlr_output_layout.h>
+#include <wlr/types/wlr_pointer.h>
+#include <wlr/types/wlr_seat.h>
+#include <wlr/types/wlr_subcompositor.h>
+#include <wlr/types/wlr_xcursor_manager.h>
+#include <wlr/types/wlr_xdg_shell.h>
+#include <wlr/util/log.h>
+#include <xkbcommon/xkbcommon.h>
+
+#define BORDER_THICKNESS 3
+
+/* For brevity's sake, struct members are annotated where they are used. */
+enum tinywl_cursor_mode {
+ TINYWL_CURSOR_PASSTHROUGH,
+ TINYWL_CURSOR_MOVE,
+ TINYWL_CURSOR_RESIZE,
+};
+
+struct tinywl_server {
+ struct wl_display *wl_display;
+ struct wlr_backend *backend;
+ struct wlr_renderer *renderer;
+ struct wlr_allocator *allocator;
+ struct wlr_scene *scene;
+ struct wlr_scene_output_layout *scene_layout;
+ struct {
+ struct wlr_scene_tree *bottom_layer;
+ /** Used for optimized blur. Everything exclusively below gets blurred */
+ struct wlr_scene_optimized_blur *blur_layer;
+ struct wlr_scene_tree *toplevel_layer;
+ } layers;
+
+ struct wlr_xdg_shell *xdg_shell;
+ struct wl_listener new_xdg_toplevel;
+ struct wl_listener new_xdg_popup;
+ struct wl_list toplevels;
+
+ struct wlr_cursor *cursor;
+ struct wlr_xcursor_manager *cursor_mgr;
+ struct wl_listener cursor_motion;
+ struct wl_listener cursor_motion_absolute;
+ struct wl_listener cursor_button;
+ struct wl_listener cursor_axis;
+ struct wl_listener cursor_frame;
+
+ struct wlr_seat *seat;
+ struct wl_listener new_input;
+ struct wl_listener request_cursor;
+ struct wl_listener request_set_selection;
+ struct wl_list keyboards;
+ enum tinywl_cursor_mode cursor_mode;
+ struct tinywl_toplevel *grabbed_toplevel;
+ double grab_x, grab_y;
+ struct wlr_box grab_geobox;
+ uint32_t resize_edges;
+
+ struct wlr_output_layout *output_layout;
+ struct wl_list outputs;
+ struct wl_listener new_output;
+};
+
+struct tinywl_output {
+ struct wl_list link;
+ struct tinywl_server *server;
+ struct wlr_output *wlr_output;
+ struct wl_listener frame;
+ struct wl_listener request_state;
+ struct wl_listener destroy;
+};
+
+struct tinywl_toplevel {
+ struct wl_list link;
+ struct tinywl_server *server;
+ struct wlr_xdg_toplevel *xdg_toplevel;
+ struct wlr_scene_tree *xdg_scene_tree;
+ struct wlr_scene_tree *scene_tree;
+ struct wl_listener map;
+ struct wl_listener unmap;
+ struct wl_listener commit;
+ struct wl_listener destroy;
+ struct wl_listener request_move;
+ struct wl_listener request_resize;
+ struct wl_listener request_maximize;
+ struct wl_listener request_fullscreen;
+
+ float opacity;
+ int corner_radius;
+ struct wlr_scene_shadow *shadow;
+ struct wlr_scene_rect *border;
+};
+
+struct tinywl_popup {
+ struct wlr_xdg_popup *xdg_popup;
+ struct wl_listener commit;
+ struct wl_listener destroy;
+};
+
+struct tinywl_keyboard {
+ struct wl_list link;
+ struct tinywl_server *server;
+ struct wlr_keyboard *wlr_keyboard;
+
+ struct wl_listener modifiers;
+ struct wl_listener key;
+ struct wl_listener destroy;
+};
+
+static void focus_toplevel(struct tinywl_toplevel *toplevel) {
+ /* Note: this function only deals with keyboard focus. */
+ if (toplevel == NULL) {
+ return;
+ }
+ struct tinywl_server *server = toplevel->server;
+ struct wlr_seat *seat = server->seat;
+ struct wlr_surface *prev_surface = seat->keyboard_state.focused_surface;
+ struct wlr_surface *surface = toplevel->xdg_toplevel->base->surface;
+ if (prev_surface == surface) {
+ /* Don't re-focus an already focused surface. */
+ return;
+ }
+ if (prev_surface) {
+ /*
+ * Deactivate the previously focused surface. This lets the client know
+ * it no longer has focus and the client will repaint accordingly, e.g.
+ * stop displaying a caret.
+ */
+ struct wlr_xdg_toplevel *prev_toplevel =
+ wlr_xdg_toplevel_try_from_wlr_surface(prev_surface);
+ if (prev_toplevel != NULL) {
+ wlr_xdg_toplevel_set_activated(prev_toplevel, false);
+ }
+ }
+ struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(seat);
+ /* Move the toplevel to the front */
+ wlr_scene_node_raise_to_top(&toplevel->scene_tree->node);
+ wl_list_remove(&toplevel->link);
+ wl_list_insert(&server->toplevels, &toplevel->link);
+ /* Activate the new surface */
+ wlr_xdg_toplevel_set_activated(toplevel->xdg_toplevel, true);
+ /*
+ * Tell the seat to have the keyboard enter this surface. wlroots will keep
+ * track of this and automatically send key events to the appropriate
+ * clients without additional work on your part.
+ */
+ if (keyboard != NULL) {
+ wlr_seat_keyboard_notify_enter(seat, surface,
+ keyboard->keycodes, keyboard->num_keycodes, &keyboard->modifiers);
+ }
+}
+
+static void keyboard_handle_modifiers(
+ struct wl_listener *listener, void *data) {
+ /* This event is raised when a modifier key, such as shift or alt, is
+ * pressed. We simply communicate this to the client. */
+ struct tinywl_keyboard *keyboard =
+ wl_container_of(listener, keyboard, modifiers);
+ /*
+ * A seat can only have one keyboard, but this is a limitation of the
+ * Wayland protocol - not wlroots. We assign all connected keyboards to the
+ * same seat. You can swap out the underlying wlr_keyboard like this and
+ * wlr_seat handles this transparently.
+ */
+ wlr_seat_set_keyboard(keyboard->server->seat, keyboard->wlr_keyboard);
+ /* Send modifiers to the client. */
+ wlr_seat_keyboard_notify_modifiers(keyboard->server->seat,
+ &keyboard->wlr_keyboard->modifiers);
+}
+
+static bool handle_keybinding(struct tinywl_server *server, xkb_keysym_t sym) {
+ /*
+ * Here we handle compositor keybindings. This is when the compositor is
+ * processing keys, rather than passing them on to the client for its own
+ * processing.
+ *
+ * This function assumes Alt is held down.
+ */
+ switch (sym) {
+ case XKB_KEY_Escape:
+ wl_display_terminate(server->wl_display);
+ break;
+ case XKB_KEY_F1:
+ /* Cycle to the next toplevel */
+ if (wl_list_length(&server->toplevels) < 2) {
+ break;
+ }
+ struct tinywl_toplevel *next_toplevel =
+ wl_container_of(server->toplevels.prev, next_toplevel, link);
+ focus_toplevel(next_toplevel);
+ break;
+ default:
+ return false;
+ }
+ return true;
+}
+
+static void keyboard_handle_key(
+ struct wl_listener *listener, void *data) {
+ /* This event is raised when a key is pressed or released. */
+ struct tinywl_keyboard *keyboard =
+ wl_container_of(listener, keyboard, key);
+ struct tinywl_server *server = keyboard->server;
+ struct wlr_keyboard_key_event *event = data;
+ struct wlr_seat *seat = server->seat;
+
+ /* Translate libinput keycode -> xkbcommon */
+ uint32_t keycode = event->keycode + 8;
+ /* Get a list of keysyms based on the keymap for this keyboard */
+ const xkb_keysym_t *syms;
+ int nsyms = xkb_state_key_get_syms(
+ keyboard->wlr_keyboard->xkb_state, keycode, &syms);
+
+ bool handled = false;
+ uint32_t modifiers = wlr_keyboard_get_modifiers(keyboard->wlr_keyboard);
+ if ((modifiers & WLR_MODIFIER_ALT) &&
+ event->state == WL_KEYBOARD_KEY_STATE_PRESSED) {
+ /* If alt is held down and this button was _pressed_, we attempt to
+ * process it as a compositor keybinding. */
+ for (int i = 0; i < nsyms; i++) {
+ handled = handle_keybinding(server, syms[i]);
+ }
+ }
+
+ if (!handled) {
+ /* Otherwise, we pass it along to the client. */
+ wlr_seat_set_keyboard(seat, keyboard->wlr_keyboard);
+ wlr_seat_keyboard_notify_key(seat, event->time_msec,
+ event->keycode, event->state);
+ }
+}
+
+static void keyboard_handle_destroy(struct wl_listener *listener, void *data) {
+ /* This event is raised by the keyboard base wlr_input_device to signal
+ * the destruction of the wlr_keyboard. It will no longer receive events
+ * and should be destroyed.
+ */
+ struct tinywl_keyboard *keyboard =
+ wl_container_of(listener, keyboard, destroy);
+ wl_list_remove(&keyboard->modifiers.link);
+ wl_list_remove(&keyboard->key.link);
+ wl_list_remove(&keyboard->destroy.link);
+ wl_list_remove(&keyboard->link);
+ free(keyboard);
+}
+
+static void server_new_keyboard(struct tinywl_server *server,
+ struct wlr_input_device *device) {
+ struct wlr_keyboard *wlr_keyboard = wlr_keyboard_from_input_device(device);
+
+ struct tinywl_keyboard *keyboard = calloc(1, sizeof(*keyboard));
+ keyboard->server = server;
+ keyboard->wlr_keyboard = wlr_keyboard;
+
+ /* We need to prepare an XKB keymap and assign it to the keyboard. This
+ * assumes the defaults (e.g. layout = "us"). */
+ struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
+ struct xkb_keymap *keymap = xkb_keymap_new_from_names(context, NULL,
+ XKB_KEYMAP_COMPILE_NO_FLAGS);
+
+ wlr_keyboard_set_keymap(wlr_keyboard, keymap);
+ xkb_keymap_unref(keymap);
+ xkb_context_unref(context);
+ wlr_keyboard_set_repeat_info(wlr_keyboard, 25, 600);
+
+ /* Here we set up listeners for keyboard events. */
+ keyboard->modifiers.notify = keyboard_handle_modifiers;
+ wl_signal_add(&wlr_keyboard->events.modifiers, &keyboard->modifiers);
+ keyboard->key.notify = keyboard_handle_key;
+ wl_signal_add(&wlr_keyboard->events.key, &keyboard->key);
+ keyboard->destroy.notify = keyboard_handle_destroy;
+ wl_signal_add(&device->events.destroy, &keyboard->destroy);
+
+ wlr_seat_set_keyboard(server->seat, keyboard->wlr_keyboard);
+
+ /* And add the keyboard to our list of keyboards */
+ wl_list_insert(&server->keyboards, &keyboard->link);
+}
+
+static void server_new_pointer(struct tinywl_server *server,
+ struct wlr_input_device *device) {
+ /* We don't do anything special with pointers. All of our pointer handling
+ * is proxied through wlr_cursor. On another compositor, you might take this
+ * opportunity to do libinput configuration on the device to set
+ * acceleration, etc. */
+ wlr_cursor_attach_input_device(server->cursor, device);
+}
+
+static void server_new_input(struct wl_listener *listener, void *data) {
+ /* This event is raised by the backend when a new input device becomes
+ * available. */
+ struct tinywl_server *server =
+ wl_container_of(listener, server, new_input);
+ struct wlr_input_device *device = data;
+ switch (device->type) {
+ case WLR_INPUT_DEVICE_KEYBOARD:
+ server_new_keyboard(server, device);
+ break;
+ case WLR_INPUT_DEVICE_POINTER:
+ server_new_pointer(server, device);
+ break;
+ default:
+ break;
+ }
+ /* We need to let the wlr_seat know what our capabilities are, which is
+ * communiciated to the client. In TinyWL we always have a cursor, even if
+ * there are no pointer devices, so we always include that capability. */
+ uint32_t caps = WL_SEAT_CAPABILITY_POINTER;
+ if (!wl_list_empty(&server->keyboards)) {
+ caps |= WL_SEAT_CAPABILITY_KEYBOARD;
+ }
+ wlr_seat_set_capabilities(server->seat, caps);
+}
+
+static void seat_request_cursor(struct wl_listener *listener, void *data) {
+ struct tinywl_server *server = wl_container_of(
+ listener, server, request_cursor);
+ /* This event is raised by the seat when a client provides a cursor image */
+ struct wlr_seat_pointer_request_set_cursor_event *event = data;
+ struct wlr_seat_client *focused_client =
+ server->seat->pointer_state.focused_client;
+ /* This can be sent by any client, so we check to make sure this one is
+ * actually has pointer focus first. */
+ if (focused_client == event->seat_client) {
+ /* Once we've vetted the client, we can tell the cursor to use the
+ * provided surface as the cursor image. It will set the hardware cursor
+ * on the output that it's currently on and continue to do so as the
+ * cursor moves between outputs. */
+ wlr_cursor_set_surface(server->cursor, event->surface,
+ event->hotspot_x, event->hotspot_y);
+ }
+}
+
+static void seat_request_set_selection(struct wl_listener *listener, void *data) {
+ /* This event is raised by the seat when a client wants to set the selection,
+ * usually when the user copies something. wlroots allows compositors to
+ * ignore such requests if they so choose, but in tinywl we always honor
+ */
+ struct tinywl_server *server = wl_container_of(
+ listener, server, request_set_selection);
+ struct wlr_seat_request_set_selection_event *event = data;
+ wlr_seat_set_selection(server->seat, event->source, event->serial);
+}
+
+static struct tinywl_toplevel *desktop_toplevel_at(
+ struct tinywl_server *server, double lx, double ly,
+ struct wlr_surface **surface, double *sx, double *sy) {
+ /* This returns the topmost node in the scene at the given layout coords.
+ * We only care about surface nodes as we are specifically looking for a
+ * surface in the surface tree of a tinywl_toplevel. */
+ struct wlr_scene_node *node = wlr_scene_node_at(
+ &server->scene->tree.node, lx, ly, sx, sy);
+ if (node == NULL || node->type != WLR_SCENE_NODE_BUFFER) {
+ return NULL;
+ }
+ struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_from_node(node);
+ struct wlr_scene_surface *scene_surface =
+ wlr_scene_surface_try_from_buffer(scene_buffer);
+ if (!scene_surface) {
+ return NULL;
+ }
+
+ *surface = scene_surface->surface;
+ /* Find the node corresponding to the tinywl_toplevel at the root of this
+ * surface tree, it is the only one for which we set the data field. */
+ struct wlr_scene_tree *tree = node->parent;
+ while (tree != NULL && tree->node.data == NULL) {
+ tree = tree->node.parent;
+ }
+ return tree->node.data;
+}
+
+static void reset_cursor_mode(struct tinywl_server *server) {
+ /* Reset the cursor mode to passthrough. */
+ server->cursor_mode = TINYWL_CURSOR_PASSTHROUGH;
+ server->grabbed_toplevel = NULL;
+}
+
+static void process_cursor_move(struct tinywl_server *server) {
+ /* Move the grabbed toplevel to the new position. */
+ struct tinywl_toplevel *toplevel = server->grabbed_toplevel;
+ wlr_scene_node_set_position(&toplevel->scene_tree->node,
+ server->cursor->x - server->grab_x,
+ server->cursor->y - server->grab_y);
+}
+
+static void process_cursor_resize(struct tinywl_server *server) {
+ /*
+ * Resizing the grabbed toplevel can be a little bit complicated, because we
+ * could be resizing from any corner or edge. This not only resizes the
+ * toplevel on one or two axes, but can also move the toplevel if you resize
+ * from the top or left edges (or top-left corner).
+ *
+ * Note that some shortcuts are taken here. In a more fleshed-out
+ * compositor, you'd wait for the client to prepare a buffer at the new
+ * size, then commit any movement that was prepared.
+ */
+ struct tinywl_toplevel *toplevel = server->grabbed_toplevel;
+ double border_x = server->cursor->x - server->grab_x;
+ double border_y = server->cursor->y - server->grab_y;
+ int new_left = server->grab_geobox.x;
+ int new_right = server->grab_geobox.x + server->grab_geobox.width;
+ int new_top = server->grab_geobox.y;
+ int new_bottom = server->grab_geobox.y + server->grab_geobox.height;
+
+ if (server->resize_edges & WLR_EDGE_TOP) {
+ new_top = border_y;
+ if (new_top >= new_bottom) {
+ new_top = new_bottom - 1;
+ }
+ } else if (server->resize_edges & WLR_EDGE_BOTTOM) {
+ new_bottom = border_y;
+ if (new_bottom <= new_top) {
+ new_bottom = new_top + 1;
+ }
+ }
+ if (server->resize_edges & WLR_EDGE_LEFT) {
+ new_left = border_x;
+ if (new_left >= new_right) {
+ new_left = new_right - 1;
+ }
+ } else if (server->resize_edges & WLR_EDGE_RIGHT) {
+ new_right = border_x;
+ if (new_right <= new_left) {
+ new_right = new_left + 1;
+ }
+ }
+
+ struct wlr_box *geo_box = &toplevel->xdg_toplevel->base->geometry;
+ wlr_scene_node_set_position(&toplevel->scene_tree->node,
+ new_left - geo_box->x, new_top - geo_box->y);
+
+ int new_width = new_right - new_left;
+ int new_height = new_bottom - new_top;
+ wlr_xdg_toplevel_set_size(toplevel->xdg_toplevel, new_width, new_height);
+
+ // Clip to geometry
+ struct wlr_box clip = {
+ .width = new_width,
+ .height = new_height,
+ .x = geo_box->x,
+ .y = geo_box->y,
+ };
+ wlr_scene_subsurface_tree_set_clip(&toplevel->scene_tree->node, &clip);
+}
+
+static void process_cursor_motion(struct tinywl_server *server, uint32_t time) {
+ /* If the mode is non-passthrough, delegate to those functions. */
+ if (server->cursor_mode == TINYWL_CURSOR_MOVE) {
+ process_cursor_move(server);
+ return;
+ } else if (server->cursor_mode == TINYWL_CURSOR_RESIZE) {
+ process_cursor_resize(server);
+ return;
+ }
+
+ /* Otherwise, find the toplevel under the pointer and send the event along. */
+ double sx, sy;
+ struct wlr_seat *seat = server->seat;
+ struct wlr_surface *surface = NULL;
+ struct tinywl_toplevel *toplevel = desktop_toplevel_at(server,
+ server->cursor->x, server->cursor->y, &surface, &sx, &sy);
+ if (!toplevel) {
+ /* If there's no toplevel under the cursor, set the cursor image to a
+ * default. This is what makes the cursor image appear when you move it
+ * around the screen, not over any toplevels. */
+ wlr_cursor_set_xcursor(server->cursor, server->cursor_mgr, "default");
+ }
+ if (surface) {
+ /*
+ * Send pointer enter and motion events.
+ *
+ * The enter event gives the surface "pointer focus", which is distinct
+ * from keyboard focus. You get pointer focus by moving the pointer over
+ * a window.
+ *
+ * Note that wlroots will avoid sending duplicate enter/motion events if
+ * the surface has already has pointer focus or if the client is already
+ * aware of the coordinates passed.
+ */
+ wlr_seat_pointer_notify_enter(seat, surface, sx, sy);
+ wlr_seat_pointer_notify_motion(seat, time, sx, sy);
+ } else {
+ /* Clear pointer focus so future button events and such are not sent to
+ * the last client to have the cursor over it. */
+ wlr_seat_pointer_clear_focus(seat);
+ }
+}
+
+static void server_cursor_motion(struct wl_listener *listener, void *data) {
+ /* This event is forwarded by the cursor when a pointer emits a _relative_
+ * pointer motion event (i.e. a delta) */
+ struct tinywl_server *server =
+ wl_container_of(listener, server, cursor_motion);
+ struct wlr_pointer_motion_event *event = data;
+ /* The cursor doesn't move unless we tell it to. The cursor automatically
+ * handles constraining the motion to the output layout, as well as any
+ * special configuration applied for the specific input device which
+ * generated the event. You can pass NULL for the device if you want to move
+ * the cursor around without any input. */
+ wlr_cursor_move(server->cursor, &event->pointer->base,
+ event->delta_x, event->delta_y);
+ process_cursor_motion(server, event->time_msec);
+}
+
+static void server_cursor_motion_absolute(
+ struct wl_listener *listener, void *data) {
+ /* This event is forwarded by the cursor when a pointer emits an _absolute_
+ * motion event, from 0..1 on each axis. This happens, for example, when
+ * wlroots is running under a Wayland window rather than KMS+DRM, and you
+ * move the mouse over the window. You could enter the window from any edge,
+ * so we have to warp the mouse there. There is also some hardware which
+ * emits these events. */
+ struct tinywl_server *server =
+ wl_container_of(listener, server, cursor_motion_absolute);
+ struct wlr_pointer_motion_absolute_event *event = data;
+ wlr_cursor_warp_absolute(server->cursor, &event->pointer->base, event->x,
+ event->y);
+ process_cursor_motion(server, event->time_msec);
+}
+
+static void server_cursor_button(struct wl_listener *listener, void *data) {
+ /* This event is forwarded by the cursor when a pointer emits a button
+ * event. */
+ struct tinywl_server *server =
+ wl_container_of(listener, server, cursor_button);
+ struct wlr_pointer_button_event *event = data;
+ /* Notify the client with pointer focus that a button press has occurred */
+ wlr_seat_pointer_notify_button(server->seat,
+ event->time_msec, event->button, event->state);
+ if (event->state == WL_POINTER_BUTTON_STATE_RELEASED) {
+ /* If you released any buttons, we exit interactive move/resize mode. */
+ reset_cursor_mode(server);
+ } else {
+ /* Focus that client if the button was _pressed_ */
+ double sx, sy;
+ struct wlr_surface *surface = NULL;
+ struct tinywl_toplevel *toplevel = desktop_toplevel_at(server,
+ server->cursor->x, server->cursor->y, &surface, &sx, &sy);
+ focus_toplevel(toplevel);
+ }
+}
+
+static void server_cursor_axis(struct wl_listener *listener, void *data) {
+ /* This event is forwarded by the cursor when a pointer emits an axis event,
+ * for example when you move the scroll wheel. */
+ struct tinywl_server *server =
+ wl_container_of(listener, server, cursor_axis);
+ struct wlr_pointer_axis_event *event = data;
+ /* Notify the client with pointer focus of the axis event. */
+ wlr_seat_pointer_notify_axis(server->seat,
+ event->time_msec, event->orientation, event->delta,
+ event->delta_discrete, event->source, event->relative_direction);
+}
+
+static void server_cursor_frame(struct wl_listener *listener, void *data) {
+ /* This event is forwarded by the cursor when a pointer emits an frame
+ * event. Frame events are sent after regular pointer events to group
+ * multiple events together. For instance, two axis events may happen at the
+ * same time, in which case a frame event won't be sent in between. */
+ struct tinywl_server *server =
+ wl_container_of(listener, server, cursor_frame);
+ /* Notify the client with pointer focus of the frame event. */
+ wlr_seat_pointer_notify_frame(server->seat);
+}
+
+static void xdg_toplevel_commit(struct wl_listener *listener, void *data) {
+ /* Called when a new surface state is committed. */
+ struct tinywl_toplevel *toplevel = wl_container_of(listener, toplevel, commit);
+
+ if (toplevel->xdg_toplevel->base->initial_commit) {
+ /* When an xdg_surface performs an initial commit, the compositor must
+ * reply with a configure so the client can map the surface. tinywl
+ * configures the xdg_toplevel with 0,0 size to let the client pick the
+ * dimensions itself. */
+ wlr_xdg_toplevel_set_size(toplevel->xdg_toplevel, 0, 0);
+ return;
+ }
+
+ struct wlr_box *geometry = &toplevel->xdg_toplevel->base->geometry;
+ wlr_scene_subsurface_tree_set_clip(&toplevel->xdg_scene_tree->node, geometry);
+
+ int border_width = geometry->width + (BORDER_THICKNESS * 2);
+ int border_height = geometry->height + (BORDER_THICKNESS * 2);
+
+ // technically we dont actually need the hole here since optimized blur would
+ // hide the border + shadow, but we do here to show compositors how to implement it
+
+ wlr_scene_rect_set_size(toplevel->border, border_width, border_height);
+ wlr_scene_rect_set_clipped_region(toplevel->border, (struct clipped_region) {
+ .corner_radius = toplevel->corner_radius,
+ .corners = CORNER_LOCATION_ALL,
+ .area = { BORDER_THICKNESS, BORDER_THICKNESS, geometry->width, geometry->height }
+ });
+
+ int blur_sigma = toplevel->shadow->blur_sigma;
+ wlr_scene_shadow_set_size(toplevel->shadow,
+ border_width + (blur_sigma * 2),
+ border_height + (blur_sigma * 2));
+ wlr_scene_shadow_set_clipped_region(toplevel->shadow, (struct clipped_region) {
+ .corner_radius = toplevel->corner_radius + BORDER_THICKNESS,
+ .corners = CORNER_LOCATION_ALL,
+ .area = { blur_sigma, blur_sigma, border_width, border_height }
+ });
+}
+
+static void output_configure_scene(struct wlr_scene_node *node,
+ struct tinywl_toplevel *toplevel) {
+ if (!node->enabled) {
+ return;
+ }
+
+ struct tinywl_toplevel *_toplevel = node->data;
+ if (_toplevel) {
+ toplevel = _toplevel;
+ }
+
+ if (node->type == WLR_SCENE_NODE_BUFFER) {
+ struct wlr_scene_buffer *buffer = wlr_scene_buffer_from_node(node);
+
+ struct wlr_scene_surface * scene_surface =
+ wlr_scene_surface_try_from_buffer(buffer);
+ if (!scene_surface) {
+ return;
+ }
+
+ struct wlr_xdg_surface *xdg_surface =
+ wlr_xdg_surface_try_from_wlr_surface(scene_surface->surface);
+
+ if (toplevel &&
+ xdg_surface &&
+ xdg_surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL) {
+ wlr_scene_buffer_set_opacity(buffer, toplevel->opacity);
+
+ if (!wlr_subsurface_try_from_wlr_surface(xdg_surface->surface)) {
+ wlr_scene_buffer_set_corner_radius(
+ buffer, toplevel->corner_radius, CORNER_LOCATION_BOTTOM);
+ }
+ }
+ } else if (node->type == WLR_SCENE_NODE_TREE) {
+ struct wlr_scene_tree *tree = wl_container_of(node, tree, node);
+ struct wlr_scene_node *node;
+ wl_list_for_each(node, &tree->children, link) {
+ output_configure_scene(node, toplevel);
+ }
+ }
+}
+
+static void output_frame(struct wl_listener *listener, void *data) {
+ /* This function is called every time an output is ready to display a frame,
+ * generally at the output's refresh rate (e.g. 60Hz). */
+ struct tinywl_output *output = wl_container_of(listener, output, frame);
+ struct wlr_scene *scene = output->server->scene;
+
+ struct wlr_scene_output *scene_output = wlr_scene_get_scene_output(
+ scene, output->wlr_output);
+
+ output_configure_scene(&scene_output->scene->tree.node, NULL);
+
+ /* Render the scene if needed and commit the output */
+ wlr_scene_output_commit(scene_output, NULL);
+
+ struct timespec now;
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ wlr_scene_output_send_frame_done(scene_output, &now);
+}
+
+static void output_request_state(struct wl_listener *listener, void *data) {
+ /* This function is called when the backend requests a new state for
+ * the output. For example, Wayland and X11 backends request a new mode
+ * when the output window is resized. */
+ struct tinywl_output *output = wl_container_of(listener, output, request_state);
+ const struct wlr_output_event_request_state *event = data;
+ wlr_output_commit_state(output->wlr_output, event->state);
+
+ /*
+ * Sets the blur node size to the outputs size. Assumes there's only one
+ * output. More advanced compositors should either implement per output blur
+ * nodes or set it to the size of all outputs.
+ */
+ wlr_scene_optimized_blur_set_size(output->server->layers.blur_layer,
+ output->wlr_output->width, output->wlr_output->height);
+}
+
+static void output_destroy(struct wl_listener *listener, void *data) {
+ struct tinywl_output *output = wl_container_of(listener, output, destroy);
+
+ wl_list_remove(&output->frame.link);
+ wl_list_remove(&output->request_state.link);
+ wl_list_remove(&output->destroy.link);
+ wl_list_remove(&output->link);
+ free(output);
+}
+
+static void server_new_output(struct wl_listener *listener, void *data) {
+ /* This event is raised by the backend when a new output (aka a display or
+ * monitor) becomes available. */
+ struct tinywl_server *server =
+ wl_container_of(listener, server, new_output);
+ struct wlr_output *wlr_output = data;
+
+ /* Configures the output created by the backend to use our allocator
+ * and our renderer. Must be done once, before commiting the output */
+ wlr_output_init_render(wlr_output, server->allocator, server->renderer);
+
+ /* The output may be disabled, switch it on. */
+ struct wlr_output_state state;
+ wlr_output_state_init(&state);
+ wlr_output_state_set_enabled(&state, true);
+
+ /* Some backends don't have modes. DRM+KMS does, and we need to set a mode
+ * before we can use the output. The mode is a tuple of (width, height,
+ * refresh rate), and each monitor supports only a specific set of modes. We
+ * just pick the monitor's preferred mode, a more sophisticated compositor
+ * would let the user configure it. */
+ struct wlr_output_mode *mode = wlr_output_preferred_mode(wlr_output);
+ if (mode != NULL) {
+ wlr_output_state_set_mode(&state, mode);
+ }
+
+ /* Atomically applies the new output state. */
+ wlr_output_commit_state(wlr_output, &state);
+ wlr_output_state_finish(&state);
+
+ /* Allocates and configures our state for this output */
+ struct tinywl_output *output = calloc(1, sizeof(*output));
+ output->wlr_output = wlr_output;
+ output->server = server;
+
+ /* Sets up a listener for the frame event. */
+ output->frame.notify = output_frame;
+ wl_signal_add(&wlr_output->events.frame, &output->frame);
+
+ /* Sets up a listener for the state request event. */
+ output->request_state.notify = output_request_state;
+ wl_signal_add(&wlr_output->events.request_state, &output->request_state);
+
+ /* Sets up a listener for the destroy event. */
+ output->destroy.notify = output_destroy;
+ wl_signal_add(&wlr_output->events.destroy, &output->destroy);
+
+ wl_list_insert(&server->outputs, &output->link);
+
+ /* Adds this to the output layout. The add_auto function arranges outputs
+ * from left-to-right in the order they appear. A more sophisticated
+ * compositor would let the user configure the arrangement of outputs in the
+ * layout.
+ *
+ * The output layout utility automatically adds a wl_output global to the
+ * display, which Wayland clients can see to find out information about the
+ * output (such as DPI, scale factor, manufacturer, etc).
+ */
+ struct wlr_output_layout_output *l_output = wlr_output_layout_add_auto(server->output_layout,
+ wlr_output);
+ struct wlr_scene_output *scene_output = wlr_scene_output_create(server->scene, wlr_output);
+ wlr_scene_output_layout_add_output(server->scene_layout, l_output, scene_output);
+
+ /*
+ * Sets the blur node size to the outputs size. Assumes there's only one
+ * output. More advanced compositors should either implement per output blur
+ * nodes or set it to the size of all outputs.
+ */
+ wlr_scene_optimized_blur_set_size(output->server->layers.blur_layer,
+ output->wlr_output->width, output->wlr_output->height);
+}
+
+static void iter_xdg_scene_buffers(struct wlr_scene_buffer *buffer, int sx,
+ int sy, void *user_data) {
+ struct tinywl_toplevel *toplevel = user_data;
+
+ struct wlr_scene_surface * scene_surface = wlr_scene_surface_try_from_buffer(buffer);
+ if (!scene_surface) {
+ return;
+ }
+
+ struct wlr_xdg_surface *xdg_surface =
+ wlr_xdg_surface_try_from_wlr_surface(scene_surface->surface);
+ if (toplevel &&
+ xdg_surface &&
+ xdg_surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL) {
+ // TODO: Be able to set whole decoration_data instead of calling
+ // each individually?
+ wlr_scene_buffer_set_opacity(buffer, toplevel->opacity);
+
+ if (!wlr_subsurface_try_from_wlr_surface(xdg_surface->surface)) {
+ wlr_scene_buffer_set_corner_radius(buffer, toplevel->corner_radius,
+ CORNER_LOCATION_BOTTOM);
+
+ wlr_scene_buffer_set_backdrop_blur(buffer, true);
+ wlr_scene_buffer_set_backdrop_blur_optimized(buffer, true);
+ wlr_scene_buffer_set_backdrop_blur_ignore_transparent(buffer, true);
+ }
+ }
+}
+
+static void xdg_toplevel_map(struct wl_listener *listener, void *data) {
+ /* Called when the surface is mapped, or ready to display on-screen. */
+ struct tinywl_toplevel *toplevel = wl_container_of(listener, toplevel, map);
+
+ wlr_scene_node_set_enabled(&toplevel->scene_tree->node, true);
+
+ wlr_scene_node_for_each_buffer(&toplevel->scene_tree->node,
+ iter_xdg_scene_buffers, toplevel);
+
+ wl_list_insert(&toplevel->server->toplevels, &toplevel->link);
+
+ focus_toplevel(toplevel);
+}
+
+static void xdg_toplevel_unmap(struct wl_listener *listener, void *data) {
+ /* Called when the surface is unmapped, and should no longer be shown. */
+ struct tinywl_toplevel *toplevel = wl_container_of(listener, toplevel, unmap);
+
+ wlr_scene_node_set_enabled(&toplevel->scene_tree->node, false);
+
+ /* Reset the cursor mode if the grabbed toplevel was unmapped. */
+ if (toplevel == toplevel->server->grabbed_toplevel) {
+ reset_cursor_mode(toplevel->server);
+ }
+
+ wl_list_remove(&toplevel->link);
+}
+
+static void xdg_toplevel_destroy(struct wl_listener *listener, void *data) {
+ /* Called when the xdg_toplevel is destroyed. */
+ struct tinywl_toplevel *toplevel = wl_container_of(listener, toplevel, destroy);
+
+ wl_list_remove(&toplevel->map.link);
+ wl_list_remove(&toplevel->unmap.link);
+ wl_list_remove(&toplevel->commit.link);
+ wl_list_remove(&toplevel->destroy.link);
+ wl_list_remove(&toplevel->request_move.link);
+ wl_list_remove(&toplevel->request_resize.link);
+ wl_list_remove(&toplevel->request_maximize.link);
+ wl_list_remove(&toplevel->request_fullscreen.link);
+
+ wlr_scene_node_destroy(&toplevel->scene_tree->node);
+
+ free(toplevel);
+}
+
+static void begin_interactive(struct tinywl_toplevel *toplevel,
+ enum tinywl_cursor_mode mode, uint32_t edges) {
+ /* This function sets up an interactive move or resize operation, where the
+ * compositor stops propegating pointer events to clients and instead
+ * consumes them itself, to move or resize windows. */
+ struct tinywl_server *server = toplevel->server;
+
+ server->grabbed_toplevel = toplevel;
+ server->cursor_mode = mode;
+
+ if (mode == TINYWL_CURSOR_MOVE) {
+ server->grab_x = server->cursor->x - toplevel->scene_tree->node.x;
+ server->grab_y = server->cursor->y - toplevel->scene_tree->node.y;
+ } else {
+ struct wlr_box *geo_box = &toplevel->xdg_toplevel->base->geometry;
+
+ double border_x = (toplevel->scene_tree->node.x + geo_box->x) +
+ ((edges & WLR_EDGE_RIGHT) ? geo_box->width : 0);
+ double border_y = (toplevel->scene_tree->node.y + geo_box->y) +
+ ((edges & WLR_EDGE_BOTTOM) ? geo_box->height : 0);
+ server->grab_x = server->cursor->x - border_x;
+ server->grab_y = server->cursor->y - border_y;
+
+ server->grab_geobox = *geo_box;
+ server->grab_geobox.x += toplevel->scene_tree->node.x;
+ server->grab_geobox.y += toplevel->scene_tree->node.y;
+
+ server->resize_edges = edges;
+ }
+}
+
+static void xdg_toplevel_request_move(
+ struct wl_listener *listener, void *data) {
+ /* This event is raised when a client would like to begin an interactive
+ * move, typically because the user clicked on their client-side
+ * decorations. Note that a more sophisticated compositor should check the
+ * provided serial against a list of button press serials sent to this
+ * client, to prevent the client from requesting this whenever they want. */
+ struct tinywl_toplevel *toplevel = wl_container_of(listener, toplevel, request_move);
+ begin_interactive(toplevel, TINYWL_CURSOR_MOVE, 0);
+}
+
+static void xdg_toplevel_request_resize(
+ struct wl_listener *listener, void *data) {
+ /* This event is raised when a client would like to begin an interactive
+ * resize, typically because the user clicked on their client-side
+ * decorations. Note that a more sophisticated compositor should check the
+ * provided serial against a list of button press serials sent to this
+ * client, to prevent the client from requesting this whenever they want. */
+ struct wlr_xdg_toplevel_resize_event *event = data;
+ struct tinywl_toplevel *toplevel = wl_container_of(listener, toplevel, request_resize);
+ begin_interactive(toplevel, TINYWL_CURSOR_RESIZE, event->edges);
+}
+
+static void xdg_toplevel_request_maximize(
+ struct wl_listener *listener, void *data) {
+ /* This event is raised when a client would like to maximize itself,
+ * typically because the user clicked on the maximize button on client-side
+ * decorations. tinywl doesn't support maximization, but to conform to
+ * xdg-shell protocol we still must send a configure.
+ * wlr_xdg_surface_schedule_configure() is used to send an empty reply.
+ * However, if the request was sent before an initial commit, we don't do
+ * anything and let the client finish the initial surface setup. */
+ struct tinywl_toplevel *toplevel =
+ wl_container_of(listener, toplevel, request_maximize);
+ if (toplevel->xdg_toplevel->base->initialized) {
+ wlr_xdg_surface_schedule_configure(toplevel->xdg_toplevel->base);
+ }
+}
+
+static void xdg_toplevel_request_fullscreen(
+ struct wl_listener *listener, void *data) {
+ /* Just as with request_maximize, we must send a configure here. */
+ struct tinywl_toplevel *toplevel =
+ wl_container_of(listener, toplevel, request_fullscreen);
+ if (toplevel->xdg_toplevel->base->initialized) {
+ wlr_xdg_surface_schedule_configure(toplevel->xdg_toplevel->base);
+ }
+}
+
+static void server_new_xdg_toplevel(struct wl_listener *listener, void *data) {
+ /* This event is raised when a client creates a new toplevel (application window). */
+ struct tinywl_server *server = wl_container_of(listener, server, new_xdg_toplevel);
+ struct wlr_xdg_toplevel *xdg_toplevel = data;
+
+ /* Allocate a tinywl_toplevel for this surface */
+ struct tinywl_toplevel *toplevel = calloc(1, sizeof(*toplevel));
+ toplevel->server = server;
+ toplevel->xdg_toplevel = xdg_toplevel;
+ toplevel->scene_tree = wlr_scene_tree_create(toplevel->server->layers.toplevel_layer);
+ toplevel->xdg_scene_tree = wlr_scene_xdg_surface_create(
+ toplevel->scene_tree, xdg_toplevel->base);
+ toplevel->scene_tree->node.data = toplevel;
+ xdg_toplevel->base->data = toplevel->scene_tree;
+
+ /* Set the scene_nodes decoration data */
+ toplevel->opacity = 1;
+ toplevel->corner_radius = 20;
+
+ toplevel->border = wlr_scene_rect_create(toplevel->scene_tree, 0, 0,
+ (float[4]){ 1.0f, 0.f, 0.f, 1.0f });
+ wlr_scene_rect_set_corner_radius(toplevel->border,
+ toplevel->corner_radius + BORDER_THICKNESS, CORNER_LOCATION_BOTTOM);
+ wlr_scene_node_set_position(&toplevel->border->node, -BORDER_THICKNESS, -BORDER_THICKNESS);
+
+ float blur_sigma = 20.0f;
+ toplevel->shadow = wlr_scene_shadow_create(toplevel->scene_tree,
+ 0, 0, toplevel->corner_radius, blur_sigma, (float[4]){ 0.f, 1.0f, 0.f, 1.0f });
+ wlr_scene_node_set_position(&toplevel->shadow->node, -BORDER_THICKNESS - blur_sigma,
+ -BORDER_THICKNESS - blur_sigma);
+
+ // Lower the border below the XDG scene tree
+ wlr_scene_node_lower_to_bottom(&toplevel->border->node);
+ // Lower the shadow below the border
+ wlr_scene_node_lower_to_bottom(&toplevel->shadow->node);
+
+ /* Listen to the various events it can emit */
+ toplevel->map.notify = xdg_toplevel_map;
+ wl_signal_add(&xdg_toplevel->base->surface->events.map, &toplevel->map);
+ toplevel->unmap.notify = xdg_toplevel_unmap;
+ wl_signal_add(&xdg_toplevel->base->surface->events.unmap, &toplevel->unmap);
+ toplevel->commit.notify = xdg_toplevel_commit;
+ wl_signal_add(&xdg_toplevel->base->surface->events.commit, &toplevel->commit);
+ toplevel->destroy.notify = xdg_toplevel_destroy;
+ wl_signal_add(&xdg_toplevel->events.destroy, &toplevel->destroy);
+
+ /* cotd */
+ toplevel->request_move.notify = xdg_toplevel_request_move;
+ wl_signal_add(&xdg_toplevel->events.request_move, &toplevel->request_move);
+ toplevel->request_resize.notify = xdg_toplevel_request_resize;
+ wl_signal_add(&xdg_toplevel->events.request_resize, &toplevel->request_resize);
+ toplevel->request_maximize.notify = xdg_toplevel_request_maximize;
+ wl_signal_add(&xdg_toplevel->events.request_maximize, &toplevel->request_maximize);
+ toplevel->request_fullscreen.notify = xdg_toplevel_request_fullscreen;
+ wl_signal_add(&xdg_toplevel->events.request_fullscreen, &toplevel->request_fullscreen);
+}
+
+static void xdg_popup_commit(struct wl_listener *listener, void *data) {
+ /* Called when a new surface state is committed. */
+ struct tinywl_popup *popup = wl_container_of(listener, popup, commit);
+
+ if (popup->xdg_popup->base->initial_commit) {
+ /* When an xdg_surface performs an initial commit, the compositor must
+ * reply with a configure so the client can map the surface.
+ * tinywl sends an empty configure. A more sophisticated compositor
+ * might change an xdg_popup's geometry to ensure it's not positioned
+ * off-screen, for example. */
+ wlr_xdg_surface_schedule_configure(popup->xdg_popup->base);
+ }
+}
+
+static void xdg_popup_destroy(struct wl_listener *listener, void *data) {
+ /* Called when the xdg_popup is destroyed. */
+ struct tinywl_popup *popup = wl_container_of(listener, popup, destroy);
+
+ wl_list_remove(&popup->commit.link);
+ wl_list_remove(&popup->destroy.link);
+
+ free(popup);
+}
+
+static void server_new_xdg_popup(struct wl_listener *listener, void *data) {
+ /* This event is raised when a client creates a new popup. */
+ struct wlr_xdg_popup *xdg_popup = data;
+
+ struct tinywl_popup *popup = calloc(1, sizeof(*popup));
+ popup->xdg_popup = xdg_popup;
+
+ /* We must add xdg popups to the scene graph so they get rendered. The
+ * wlroots scene graph provides a helper for this, but to use it we must
+ * provide the proper parent scene node of the xdg popup. To enable this,
+ * we always set the user data field of xdg_surfaces to the corresponding
+ * scene node. */
+ struct wlr_xdg_surface *parent = wlr_xdg_surface_try_from_wlr_surface(xdg_popup->parent);
+ assert(parent != NULL);
+ struct wlr_scene_tree *parent_tree = parent->data;
+ xdg_popup->base->data = wlr_scene_xdg_surface_create(parent_tree, xdg_popup->base);
+
+ popup->commit.notify = xdg_popup_commit;
+ wl_signal_add(&xdg_popup->base->surface->events.commit, &popup->commit);
+
+ popup->destroy.notify = xdg_popup_destroy;
+ wl_signal_add(&xdg_popup->events.destroy, &popup->destroy);
+}
+
+int main(int argc, char *argv[]) {
+ wlr_log_init(WLR_DEBUG, NULL);
+ char *startup_cmd = NULL;
+
+ int c;
+ while ((c = getopt(argc, argv, "s:h")) != -1) {
+ switch (c) {
+ case 's':
+ startup_cmd = optarg;
+ break;
+ default:
+ printf("Usage: %s [-s startup command]\n", argv[0]);
+ return 0;
+ }
+ }
+ if (optind < argc) {
+ printf("Usage: %s [-s startup command]\n", argv[0]);
+ return 0;
+ }
+
+ struct tinywl_server server = {0};
+ /* The Wayland display is managed by libwayland. It handles accepting
+ * clients from the Unix socket, manging Wayland globals, and so on. */
+ server.wl_display = wl_display_create();
+ /* The backend is a wlroots feature which abstracts the underlying input and
+ * output hardware. The autocreate option will choose the most suitable
+ * backend based on the current environment, such as opening an X11 window
+ * if an X11 server is running. */
+ server.backend = wlr_backend_autocreate(wl_display_get_event_loop(server.wl_display), NULL);
+ if (server.backend == NULL) {
+ wlr_log(WLR_ERROR, "failed to create wlr_backend");
+ return 1;
+ }
+
+ /* Autocreates a renderer, either Pixman, GLES2 or Vulkan for us. The user
+ * can also specify a renderer using the WLR_RENDERER env var.
+ * The renderer is responsible for defining the various pixel formats it
+ * supports for shared memory, this configures that for clients. */
+ server.renderer = fx_renderer_create(server.backend);
+ if (server.renderer == NULL) {
+ wlr_log(WLR_ERROR, "failed to create wlr_renderer");
+ return 1;
+ }
+
+ wlr_renderer_init_wl_display(server.renderer, server.wl_display);
+
+ /* Autocreates an allocator for us.
+ * The allocator is the bridge between the renderer and the backend. It
+ * handles the buffer creation, allowing wlroots to render onto the
+ * screen */
+ server.allocator = wlr_allocator_autocreate(server.backend,
+ server.renderer);
+ if (server.allocator == NULL) {
+ wlr_log(WLR_ERROR, "failed to create wlr_allocator");
+ return 1;
+ }
+
+ /* This creates some hands-off wlroots interfaces. The compositor is
+ * necessary for clients to allocate surfaces, the subcompositor allows to
+ * assign the role of subsurfaces to surfaces and the data device manager
+ * handles the clipboard. Each of these wlroots interfaces has room for you
+ * to dig your fingers in and play with their behavior if you want. Note that
+ * the clients cannot set the selection directly without compositor approval,
+ * see the handling of the request_set_selection event below.*/
+ wlr_compositor_create(server.wl_display, 5, server.renderer);
+ wlr_subcompositor_create(server.wl_display);
+ wlr_data_device_manager_create(server.wl_display);
+
+ /* Creates an output layout, which a wlroots utility for working with an
+ * arrangement of screens in a physical layout. */
+ server.output_layout = wlr_output_layout_create(server.wl_display);
+
+ /* Configure a listener to be notified when new outputs are available on the
+ * backend. */
+ wl_list_init(&server.outputs);
+ server.new_output.notify = server_new_output;
+ wl_signal_add(&server.backend->events.new_output, &server.new_output);
+
+ /* Create a scene graph. This is a wlroots abstraction that handles all
+ * rendering and damage tracking. All the compositor author needs to do
+ * is add things that should be rendered to the scene graph at the proper
+ * positions and then call wlr_scene_output_commit() to render a frame if
+ * necessary.
+ */
+ server.scene = wlr_scene_create();
+ server.scene_layout = wlr_scene_attach_output_layout(server.scene, server.output_layout);
+
+ /* Create all of the basic scene layers */
+ server.layers.bottom_layer = wlr_scene_tree_create(&server.scene->tree);
+ /* Add a bottom rect to demonstrate optimized blur */
+ float bottom_rect_color[4] = { 1, 1, 1, 1 };
+ wlr_scene_rect_create(server.layers.bottom_layer, 200, 200, bottom_rect_color);
+ /* Set the size later */
+ server.layers.blur_layer = wlr_scene_optimized_blur_create(&server.scene->tree, 0, 0);
+ server.layers.toplevel_layer = wlr_scene_tree_create(&server.scene->tree);
+ /* Add a top rect that won't get blurred by optimized */
+ float top_rect_color[4] = { 1, 0, 0, 1 };
+ struct wlr_scene_rect *rect = wlr_scene_rect_create(server.layers.toplevel_layer,
+ 200, 200, top_rect_color);
+ wlr_scene_rect_set_clipped_region(rect, (struct clipped_region) {
+ .corner_radius = 12,
+ .corners = CORNER_LOCATION_TOP,
+ .area = {
+ .x = 50,
+ .y = 50,
+ .width = 100,
+ .height = 100,
+ },
+ });
+ wlr_scene_node_set_position(&rect->node, 200, 200);
+
+ /* Set up xdg-shell version 3. The xdg-shell is a Wayland protocol which is
+ * used for application windows. For more detail on shells, refer to
+ * https://drewdevault.com/2018/07/29/Wayland-shells.html.
+ */
+ wl_list_init(&server.toplevels);
+ server.xdg_shell = wlr_xdg_shell_create(server.wl_display, 3);
+ server.new_xdg_toplevel.notify = server_new_xdg_toplevel;
+ wl_signal_add(&server.xdg_shell->events.new_toplevel, &server.new_xdg_toplevel);
+ server.new_xdg_popup.notify = server_new_xdg_popup;
+ wl_signal_add(&server.xdg_shell->events.new_popup, &server.new_xdg_popup);
+
+ /*
+ * Creates a cursor, which is a wlroots utility for tracking the cursor
+ * image shown on screen.
+ */
+ server.cursor = wlr_cursor_create();
+ wlr_cursor_attach_output_layout(server.cursor, server.output_layout);
+
+ /* Creates an xcursor manager, another wlroots utility which loads up
+ * Xcursor themes to source cursor images from and makes sure that cursor
+ * images are available at all scale factors on the screen (necessary for
+ * HiDPI support). */
+ server.cursor_mgr = wlr_xcursor_manager_create(NULL, 24);
+
+ /*
+ * wlr_cursor *only* displays an image on screen. It does not move around
+ * when the pointer moves. However, we can attach input devices to it, and
+ * it will generate aggregate events for all of them. In these events, we
+ * can choose how we want to process them, forwarding them to clients and
+ * moving the cursor around. More detail on this process is described in
+ * https://drewdevault.com/2018/07/17/Input-handling-in-wlroots.html.
+ *
+ * And more comments are sprinkled throughout the notify functions above.
+ */
+ server.cursor_mode = TINYWL_CURSOR_PASSTHROUGH;
+ server.cursor_motion.notify = server_cursor_motion;
+ wl_signal_add(&server.cursor->events.motion, &server.cursor_motion);
+ server.cursor_motion_absolute.notify = server_cursor_motion_absolute;
+ wl_signal_add(&server.cursor->events.motion_absolute,
+ &server.cursor_motion_absolute);
+ server.cursor_button.notify = server_cursor_button;
+ wl_signal_add(&server.cursor->events.button, &server.cursor_button);
+ server.cursor_axis.notify = server_cursor_axis;
+ wl_signal_add(&server.cursor->events.axis, &server.cursor_axis);
+ server.cursor_frame.notify = server_cursor_frame;
+ wl_signal_add(&server.cursor->events.frame, &server.cursor_frame);
+
+ /*
+ * Configures a seat, which is a single "seat" at which a user sits and
+ * operates the computer. This conceptually includes up to one keyboard,
+ * pointer, touch, and drawing tablet device. We also rig up a listener to
+ * let us know when new input devices are available on the backend.
+ */
+ wl_list_init(&server.keyboards);
+ server.new_input.notify = server_new_input;
+ wl_signal_add(&server.backend->events.new_input, &server.new_input);
+ server.seat = wlr_seat_create(server.wl_display, "seat0");
+ server.request_cursor.notify = seat_request_cursor;
+ wl_signal_add(&server.seat->events.request_set_cursor,
+ &server.request_cursor);
+ server.request_set_selection.notify = seat_request_set_selection;
+ wl_signal_add(&server.seat->events.request_set_selection,
+ &server.request_set_selection);
+
+ /* Add a Unix socket to the Wayland display. */
+ const char *socket = wl_display_add_socket_auto(server.wl_display);
+ if (!socket) {
+ wlr_backend_destroy(server.backend);
+ return 1;
+ }
+
+ /* Start the backend. This will enumerate outputs and inputs, become the DRM
+ * master, etc */
+ if (!wlr_backend_start(server.backend)) {
+ wlr_backend_destroy(server.backend);
+ wl_display_destroy(server.wl_display);
+ return 1;
+ }
+
+ /* Set the WAYLAND_DISPLAY environment variable to our socket and run the
+ * startup command if requested. */
+ setenv("WAYLAND_DISPLAY", socket, true);
+ if (startup_cmd) {
+ if (fork() == 0) {
+ execl("/bin/sh", "/bin/sh", "-c", startup_cmd, (void *)NULL);
+ }
+ }
+ /* Run the Wayland event loop. This does not return until you exit the
+ * compositor. Starting the backend rigged up all of the necessary event
+ * loop configuration to listen to libinput events, DRM events, generate
+ * frame events at the refresh rate, and so on. */
+ wlr_log(WLR_INFO, "Running Wayland compositor on WAYLAND_DISPLAY=%s",
+ socket);
+ wl_display_run(server.wl_display);
+
+
+ /* Once wl_display_run returns, we destroy all clients then shut down the
+ * server. */
+ wl_display_destroy_clients(server.wl_display);
+
+ wl_list_remove(&server.new_xdg_toplevel.link);
+ wl_list_remove(&server.new_xdg_popup.link);
+
+ wl_list_remove(&server.cursor_motion.link);
+ wl_list_remove(&server.cursor_motion_absolute.link);
+ wl_list_remove(&server.cursor_button.link);
+ wl_list_remove(&server.cursor_axis.link);
+ wl_list_remove(&server.cursor_frame.link);
+
+ wl_list_remove(&server.new_input.link);
+ wl_list_remove(&server.request_cursor.link);
+ wl_list_remove(&server.request_set_selection.link);
+
+ wl_list_remove(&server.new_output.link);
+
+ wlr_scene_node_destroy(&server.scene->tree.node);
+ wlr_xcursor_manager_destroy(server.cursor_mgr);
+ wlr_cursor_destroy(server.cursor);
+ wlr_allocator_destroy(server.allocator);
+ wlr_renderer_destroy(server.renderer);
+ wlr_backend_destroy(server.backend);
+ wl_display_destroy(server.wl_display);
+ return 0;
+}
diff --git a/scenefx/types/fx/blur_data.c b/scenefx/types/fx/blur_data.c
new file mode 100644
index 0000000..423c02a
--- /dev/null
+++ b/scenefx/types/fx/blur_data.c
@@ -0,0 +1,27 @@
+#include "scenefx/types/fx/blur_data.h"
+
+struct blur_data blur_data_get_default(void) {
+ return (struct blur_data) {
+ .radius = 5,
+ .num_passes = 3,
+ .noise = 0.02,
+ .brightness = 0.9,
+ .contrast = 0.9,
+ .saturation = 1.1,
+ };
+}
+
+bool is_scene_blur_enabled(struct blur_data *blur_data) {
+ return blur_data->radius > 0 && blur_data->num_passes > 0;
+}
+
+bool blur_data_should_parameters_blur_effects(struct blur_data *blur_data) {
+ return blur_data->brightness != 1.0f
+ || blur_data->saturation != 1.0f
+ || blur_data->contrast != 1.0f
+ || blur_data->noise > 0.0f;
+}
+
+int blur_data_calc_size(struct blur_data *blur_data) {
+ return pow(2, blur_data->num_passes + 1) * blur_data->radius;
+}
diff --git a/scenefx/types/fx/clipped_region.c b/scenefx/types/fx/clipped_region.c
new file mode 100644
index 0000000..b12b0d1
--- /dev/null
+++ b/scenefx/types/fx/clipped_region.c
@@ -0,0 +1,10 @@
+#include "scenefx/types/fx/clipped_region.h"
+#include "scenefx/types/fx/corner_location.h"
+
+struct clipped_region clipped_region_get_default(void) {
+ return (struct clipped_region) {
+ .corner_radius = 0,
+ .corners = CORNER_LOCATION_NONE,
+ .area = (struct wlr_box) {0},
+ };
+}
diff --git a/scenefx/types/fx/corner_location.c b/scenefx/types/fx/corner_location.c
new file mode 100644
index 0000000..e7069f2
--- /dev/null
+++ b/scenefx/types/fx/corner_location.c
@@ -0,0 +1,76 @@
+#include "scenefx/types/fx/corner_location.h"
+
+// Maps the corners to its correct transformed corner
+static const enum corner_location corner_transform[8][CORNER_LOCATION_LAST] = {
+ [WL_OUTPUT_TRANSFORM_NORMAL] = {
+ [CORNER_LOCATION_TOP_LEFT] = CORNER_LOCATION_TOP_LEFT,
+ [CORNER_LOCATION_TOP_RIGHT] = CORNER_LOCATION_TOP_RIGHT,
+ [CORNER_LOCATION_BOTTOM_RIGHT] = CORNER_LOCATION_BOTTOM_RIGHT,
+ [CORNER_LOCATION_BOTTOM_LEFT] = CORNER_LOCATION_BOTTOM_LEFT,
+ },
+ [WL_OUTPUT_TRANSFORM_90] = {
+ [CORNER_LOCATION_TOP_LEFT] = CORNER_LOCATION_BOTTOM_LEFT,
+ [CORNER_LOCATION_TOP_RIGHT] = CORNER_LOCATION_TOP_LEFT,
+ [CORNER_LOCATION_BOTTOM_RIGHT] = CORNER_LOCATION_TOP_RIGHT,
+ [CORNER_LOCATION_BOTTOM_LEFT] = CORNER_LOCATION_BOTTOM_RIGHT,
+ },
+ [WL_OUTPUT_TRANSFORM_180] = {
+ [CORNER_LOCATION_TOP_LEFT] = CORNER_LOCATION_BOTTOM_RIGHT,
+ [CORNER_LOCATION_TOP_RIGHT] = CORNER_LOCATION_BOTTOM_LEFT,
+ [CORNER_LOCATION_BOTTOM_RIGHT] = CORNER_LOCATION_TOP_LEFT,
+ [CORNER_LOCATION_BOTTOM_LEFT] = CORNER_LOCATION_TOP_RIGHT,
+ },
+ [WL_OUTPUT_TRANSFORM_270] = {
+ [CORNER_LOCATION_TOP_LEFT] = CORNER_LOCATION_TOP_RIGHT,
+ [CORNER_LOCATION_TOP_RIGHT] = CORNER_LOCATION_BOTTOM_RIGHT,
+ [CORNER_LOCATION_BOTTOM_RIGHT] = CORNER_LOCATION_BOTTOM_LEFT,
+ [CORNER_LOCATION_BOTTOM_LEFT] = CORNER_LOCATION_TOP_LEFT,
+ },
+ [WL_OUTPUT_TRANSFORM_FLIPPED] = {
+ [CORNER_LOCATION_TOP_LEFT] = CORNER_LOCATION_TOP_RIGHT,
+ [CORNER_LOCATION_TOP_RIGHT] = CORNER_LOCATION_TOP_LEFT,
+ [CORNER_LOCATION_BOTTOM_RIGHT] = CORNER_LOCATION_BOTTOM_LEFT,
+ [CORNER_LOCATION_BOTTOM_LEFT] = CORNER_LOCATION_BOTTOM_RIGHT,
+ },
+ [WL_OUTPUT_TRANSFORM_FLIPPED_90] = {
+ [CORNER_LOCATION_TOP_LEFT] = CORNER_LOCATION_TOP_LEFT,
+ [CORNER_LOCATION_TOP_RIGHT] = CORNER_LOCATION_BOTTOM_LEFT,
+ [CORNER_LOCATION_BOTTOM_RIGHT] = CORNER_LOCATION_BOTTOM_RIGHT,
+ [CORNER_LOCATION_BOTTOM_LEFT] = CORNER_LOCATION_TOP_RIGHT,
+ },
+ [WL_OUTPUT_TRANSFORM_FLIPPED_180] = {
+ [CORNER_LOCATION_TOP_LEFT] = CORNER_LOCATION_BOTTOM_LEFT,
+ [CORNER_LOCATION_TOP_RIGHT] = CORNER_LOCATION_BOTTOM_RIGHT,
+ [CORNER_LOCATION_BOTTOM_RIGHT] = CORNER_LOCATION_TOP_RIGHT,
+ [CORNER_LOCATION_BOTTOM_LEFT] = CORNER_LOCATION_TOP_LEFT,
+ },
+ [WL_OUTPUT_TRANSFORM_FLIPPED_270] = {
+ [CORNER_LOCATION_TOP_LEFT] = CORNER_LOCATION_BOTTOM_RIGHT,
+ [CORNER_LOCATION_TOP_RIGHT] = CORNER_LOCATION_TOP_RIGHT,
+ [CORNER_LOCATION_BOTTOM_RIGHT] = CORNER_LOCATION_TOP_LEFT,
+ [CORNER_LOCATION_BOTTOM_LEFT] = CORNER_LOCATION_BOTTOM_LEFT,
+ },
+};
+
+void corner_location_transform(enum wl_output_transform transform,
+ enum corner_location *corners) {
+ enum corner_location result = CORNER_LOCATION_NONE;
+
+ int bitmask = 1 << (CORNER_LOCATION_COUNT - 1);
+ int mask = 1;
+ while (bitmask) {
+ switch (*corners & mask) {
+ case CORNER_LOCATION_NONE:
+ case CORNER_LOCATION_ALL:
+ break;
+ default:
+ result |= corner_transform[transform][*corners & mask];
+ break;
+ }
+
+ bitmask &= ~mask;
+ mask <<= 1;
+ }
+
+ *corners = result;
+}
diff --git a/scenefx/types/fx/meson.build b/scenefx/types/fx/meson.build
new file mode 100644
index 0000000..749d01d
--- /dev/null
+++ b/scenefx/types/fx/meson.build
@@ -0,0 +1,5 @@
+scenefx_files += files(
+ 'blur_data.c',
+ 'corner_location.c',
+ 'clipped_region.c',
+)
diff --git a/scenefx/types/meson.build b/scenefx/types/meson.build
new file mode 100644
index 0000000..a7e855b
--- /dev/null
+++ b/scenefx/types/meson.build
@@ -0,0 +1,6 @@
+scenefx_files += files(
+ 'scene/wlr_scene.c',
+ 'output/wlr_output.c',
+)
+
+subdir('fx')
diff --git a/scenefx/types/output/wlr_output.c b/scenefx/types/output/wlr_output.c
new file mode 100644
index 0000000..59c9b31
--- /dev/null
+++ b/scenefx/types/output/wlr_output.c
@@ -0,0 +1,23 @@
+#include <stdlib.h>
+
+#include "types/wlr_output.h"
+
+void output_pending_resolution(struct wlr_output *output,
+ const struct wlr_output_state *state, int *width, int *height) {
+ if (state->committed & WLR_OUTPUT_STATE_MODE) {
+ switch (state->mode_type) {
+ case WLR_OUTPUT_STATE_MODE_FIXED:
+ *width = state->mode->width;
+ *height = state->mode->height;
+ return;
+ case WLR_OUTPUT_STATE_MODE_CUSTOM:
+ *width = state->custom_mode.width;
+ *height = state->custom_mode.height;
+ return;
+ }
+ abort();
+ } else {
+ *width = output->width;
+ *height = output->height;
+ }
+}
diff --git a/scenefx/types/scene/wlr_scene.c b/scenefx/types/scene/wlr_scene.c
new file mode 100644
index 0000000..b4c1b63
--- /dev/null
+++ b/scenefx/types/scene/wlr_scene.c
@@ -0,0 +1,3183 @@
+#include <assert.h>
+#include <pixman.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <scenefx/types/wlr_scene.h>
+#include <string.h>
+#include <wlr/backend.h>
+#include <wlr/render/gles2.h>
+#include <wlr/render/drm_syncobj.h>
+#include <wlr/render/swapchain.h>
+#include <wlr/types/wlr_compositor.h>
+#include <wlr/types/wlr_damage_ring.h>
+#include <wlr/types/wlr_gamma_control_v1.h>
+#include <wlr/types/wlr_presentation_time.h>
+#include <wlr/types/wlr_subcompositor.h>
+#include <wlr/types/wlr_xdg_shell.h>
+#include <wlr/util/log.h>
+#include <wlr/util/region.h>
+#include <wlr/util/transform.h>
+
+#include "scenefx/render/fx_renderer/fx_effect_framebuffers.h"
+#include "scenefx/render/fx_renderer/fx_renderer.h"
+#include "scenefx/render/pass.h"
+#include "scenefx/types/fx/clipped_region.h"
+#include "scenefx/types/fx/corner_location.h"
+#include "types/wlr_output.h"
+#include "types/wlr_scene.h"
+#include "util/array.h"
+#include "util/env.h"
+#include "util/time.h"
+#include "wlr/util/box.h"
+
+#include <wlr/config.h>
+
+#if WLR_HAS_XWAYLAND
+#include <wlr/xwayland/xwayland.h>
+#endif
+
+#define DMABUF_FEEDBACK_DEBOUNCE_FRAMES 30
+#define HIGHLIGHT_DAMAGE_FADEOUT_TIME 250
+
+struct wlr_scene_tree *wlr_scene_tree_from_node(struct wlr_scene_node *node) {
+ assert(node->type == WLR_SCENE_NODE_TREE);
+ struct wlr_scene_tree *tree = wl_container_of(node, tree, node);
+ return tree;
+}
+
+struct wlr_scene_rect *wlr_scene_rect_from_node(struct wlr_scene_node *node) {
+ assert(node->type == WLR_SCENE_NODE_RECT);
+ struct wlr_scene_rect *rect = wl_container_of(node, rect, node);
+ return rect;
+}
+
+struct wlr_scene_optimized_blur *wlr_scene_optimized_blur_from_node(
+ struct wlr_scene_node *node) {
+ assert(node->type == WLR_SCENE_NODE_OPTIMIZED_BLUR);
+ struct wlr_scene_optimized_blur *blur_node =
+ wl_container_of(node, blur_node, node);
+ return blur_node;
+}
+
+struct wlr_scene_buffer *wlr_scene_buffer_from_node(
+ struct wlr_scene_node *node) {
+ assert(node->type == WLR_SCENE_NODE_BUFFER);
+ struct wlr_scene_buffer *buffer = wl_container_of(node, buffer, node);
+ return buffer;
+}
+
+struct wlr_scene_shadow *wlr_scene_shadow_from_node(struct wlr_scene_node *node) {
+ assert(node->type == WLR_SCENE_NODE_SHADOW);
+ struct wlr_scene_shadow *shadow = wl_container_of(node, shadow, node);
+ return shadow;
+}
+
+struct wlr_scene *scene_node_get_root(struct wlr_scene_node *node) {
+ struct wlr_scene_tree *tree;
+ if (node->type == WLR_SCENE_NODE_TREE) {
+ tree = wlr_scene_tree_from_node(node);
+ } else {
+ tree = node->parent;
+ }
+
+ while (tree->node.parent != NULL) {
+ tree = tree->node.parent;
+ }
+ struct wlr_scene *scene = wl_container_of(tree, scene, tree);
+ return scene;
+}
+
+static void scene_node_init(struct wlr_scene_node *node,
+ enum wlr_scene_node_type type, struct wlr_scene_tree *parent) {
+ *node = (struct wlr_scene_node){
+ .type = type,
+ .parent = parent,
+ .enabled = true,
+ };
+
+ wl_list_init(&node->link);
+
+ wl_signal_init(&node->events.destroy);
+ pixman_region32_init(&node->visible);
+
+ if (parent != NULL) {
+ wl_list_insert(parent->children.prev, &node->link);
+ }
+
+ wlr_addon_set_init(&node->addons);
+}
+
+struct highlight_region {
+ pixman_region32_t region;
+ struct timespec when;
+ struct wl_list link;
+};
+
+static void scene_buffer_set_buffer(struct wlr_scene_buffer *scene_buffer,
+ struct wlr_buffer *buffer);
+static void scene_buffer_set_texture(struct wlr_scene_buffer *scene_buffer,
+ struct wlr_texture *texture);
+
+void wlr_scene_node_destroy(struct wlr_scene_node *node) {
+ if (node == NULL) {
+ return;
+ }
+
+ // We want to call the destroy listeners before we do anything else
+ // in case the destroy signal would like to remove children before they
+ // are recursively destroyed.
+ wl_signal_emit_mutable(&node->events.destroy, NULL);
+ wlr_addon_set_finish(&node->addons);
+
+ wlr_scene_node_set_enabled(node, false);
+
+ struct wlr_scene *scene = scene_node_get_root(node);
+ if (node->type == WLR_SCENE_NODE_BUFFER) {
+ struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_from_node(node);
+
+ uint64_t active = scene_buffer->active_outputs;
+ if (active) {
+ struct wlr_scene_output *scene_output;
+ wl_list_for_each(scene_output, &scene->outputs, link) {
+ if (active & (1ull << scene_output->index)) {
+ wl_signal_emit_mutable(&scene_buffer->events.output_leave,
+ scene_output);
+ }
+ }
+ }
+
+ scene_buffer_set_buffer(scene_buffer, NULL);
+ scene_buffer_set_texture(scene_buffer, NULL);
+ pixman_region32_fini(&scene_buffer->opaque_region);
+ wlr_drm_syncobj_timeline_unref(scene_buffer->wait_timeline);
+
+ assert(wl_list_empty(&scene_buffer->events.output_leave.listener_list));
+ assert(wl_list_empty(&scene_buffer->events.output_enter.listener_list));
+ assert(wl_list_empty(&scene_buffer->events.outputs_update.listener_list));
+ assert(wl_list_empty(&scene_buffer->events.output_sample.listener_list));
+ assert(wl_list_empty(&scene_buffer->events.frame_done.listener_list));
+ } else if (node->type == WLR_SCENE_NODE_TREE) {
+ struct wlr_scene_tree *scene_tree = wlr_scene_tree_from_node(node);
+
+ if (scene_tree == &scene->tree) {
+ assert(!node->parent);
+ struct wlr_scene_output *scene_output, *scene_output_tmp;
+ wl_list_for_each_safe(scene_output, scene_output_tmp, &scene->outputs, link) {
+ wlr_scene_output_destroy(scene_output);
+ }
+
+ wl_list_remove(&scene->linux_dmabuf_v1_destroy.link);
+ wl_list_remove(&scene->gamma_control_manager_v1_destroy.link);
+ wl_list_remove(&scene->gamma_control_manager_v1_set_gamma.link);
+ } else {
+ assert(node->parent);
+ }
+
+ struct wlr_scene_node *child, *child_tmp;
+ wl_list_for_each_safe(child, child_tmp,
+ &scene_tree->children, link) {
+ wlr_scene_node_destroy(child);
+ }
+ }
+
+ assert(wl_list_empty(&node->events.destroy.listener_list));
+
+ wl_list_remove(&node->link);
+ pixman_region32_fini(&node->visible);
+ free(node);
+}
+
+static void scene_tree_init(struct wlr_scene_tree *tree,
+ struct wlr_scene_tree *parent) {
+ *tree = (struct wlr_scene_tree){0};
+ scene_node_init(&tree->node, WLR_SCENE_NODE_TREE, parent);
+ wl_list_init(&tree->children);
+}
+
+struct wlr_scene *wlr_scene_create(void) {
+ struct wlr_scene *scene = calloc(1, sizeof(*scene));
+ if (scene == NULL) {
+ return NULL;
+ }
+
+ scene_tree_init(&scene->tree, NULL);
+
+ wl_list_init(&scene->outputs);
+ wl_list_init(&scene->linux_dmabuf_v1_destroy.link);
+ wl_list_init(&scene->gamma_control_manager_v1_destroy.link);
+ wl_list_init(&scene->gamma_control_manager_v1_set_gamma.link);
+
+ const char *debug_damage_options[] = {
+ "none",
+ "rerender",
+ "highlight",
+ NULL
+ };
+
+ scene->debug_damage_option = env_parse_switch("WLR_SCENE_DEBUG_DAMAGE", debug_damage_options);
+ scene->direct_scanout = !env_parse_bool("WLR_SCENE_DISABLE_DIRECT_SCANOUT");
+ scene->calculate_visibility = !env_parse_bool("WLR_SCENE_DISABLE_VISIBILITY");
+ scene->highlight_transparent_region = env_parse_bool("WLR_SCENE_HIGHLIGHT_TRANSPARENT_REGION");
+
+ scene->blur_data = blur_data_get_default();
+
+ return scene;
+}
+
+struct wlr_scene_tree *wlr_scene_tree_create(struct wlr_scene_tree *parent) {
+ assert(parent);
+
+ struct wlr_scene_tree *tree = calloc(1, sizeof(*tree));
+ if (tree == NULL) {
+ return NULL;
+ }
+
+ scene_tree_init(tree, parent);
+ return tree;
+}
+
+static void scene_node_get_size(struct wlr_scene_node *node, int *lx, int *ly);
+
+typedef bool (*scene_node_box_iterator_func_t)(struct wlr_scene_node *node,
+ int sx, int sy, void *data);
+
+static bool _scene_nodes_in_box(struct wlr_scene_node *node, struct wlr_box *box,
+ scene_node_box_iterator_func_t iterator, void *user_data, int lx, int ly) {
+ if (!node->enabled) {
+ return false;
+ }
+
+ switch (node->type) {
+ case WLR_SCENE_NODE_TREE:;
+ struct wlr_scene_tree *scene_tree = wlr_scene_tree_from_node(node);
+ struct wlr_scene_node *child;
+ wl_list_for_each_reverse(child, &scene_tree->children, link) {
+ if (_scene_nodes_in_box(child, box, iterator, user_data, lx + child->x, ly + child->y)) {
+ return true;
+ }
+ }
+ break;
+ case WLR_SCENE_NODE_OPTIMIZED_BLUR:;
+ case WLR_SCENE_NODE_RECT:
+ case WLR_SCENE_NODE_SHADOW:
+ case WLR_SCENE_NODE_BUFFER:;
+ struct wlr_box node_box = { .x = lx, .y = ly };
+ scene_node_get_size(node, &node_box.width, &node_box.height);
+
+ if (wlr_box_intersection(&node_box, &node_box, box) &&
+ iterator(node, lx, ly, user_data)) {
+ return true;
+ }
+ break;
+ }
+
+ return false;
+}
+
+static bool scene_nodes_in_box(struct wlr_scene_node *node, struct wlr_box *box,
+ scene_node_box_iterator_func_t iterator, void *user_data) {
+ int x, y;
+ wlr_scene_node_coords(node, &x, &y);
+
+ return _scene_nodes_in_box(node, box, iterator, user_data, x, y);
+}
+
+static void scene_node_opaque_region(struct wlr_scene_node *node, int x, int y,
+ pixman_region32_t *opaque) {
+ int width, height;
+ scene_node_get_size(node, &width, &height);
+
+ if (node->type == WLR_SCENE_NODE_RECT) {
+ struct wlr_scene_rect *scene_rect = wlr_scene_rect_from_node(node);
+ if (scene_rect->corner_radius > 0) {
+ // TODO: this is incorrect
+ return;
+ }
+ if (scene_rect->color[3] != 1) {
+ return;
+ }
+ if (!wlr_box_empty(&scene_rect->clipped_region.area)) {
+ pixman_region32_fini(opaque);
+ pixman_region32_init_rect(opaque, x, y, width, height);
+
+ // Subtract the clipped region from a otherwise fully opaque rect
+ struct wlr_box *clipped = &scene_rect->clipped_region.area;
+ pixman_region32_t clipped_region;
+ pixman_region32_init_rect(&clipped_region, clipped->x + x, clipped->y + y,
+ clipped->width, clipped->height);
+ pixman_region32_subtract(opaque, opaque, &clipped_region);
+ pixman_region32_fini(&clipped_region);
+ return;
+ }
+ } else if (node->type == WLR_SCENE_NODE_SHADOW) {
+ // TODO: test & handle case of blur sigma = 0 and color[3] = 1?
+ return;
+ } else if (node->type == WLR_SCENE_NODE_BUFFER) {
+ struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_from_node(node);
+
+ if (!scene_buffer->buffer) {
+ return;
+ }
+
+ if (scene_buffer->opacity != 1) {
+ return;
+ }
+
+ if (scene_buffer->corner_radius > 0) {
+ // TODO: this is incorrect
+ return;
+ }
+
+ if (!scene_buffer->buffer_is_opaque) {
+ pixman_region32_copy(opaque, &scene_buffer->opaque_region);
+ pixman_region32_intersect_rect(opaque, opaque, 0, 0, width, height);
+ pixman_region32_translate(opaque, x, y);
+ return;
+ }
+ } else if (node->type == WLR_SCENE_NODE_OPTIMIZED_BLUR) {
+ // Always transparent
+ return;
+ }
+
+ pixman_region32_fini(opaque);
+ pixman_region32_init_rect(opaque, x, y, width, height);
+}
+
+struct scene_update_data {
+ pixman_region32_t *visible;
+ pixman_region32_t *update_region;
+ struct wlr_box update_box;
+ struct wl_list *outputs;
+ bool calculate_visibility;
+
+#if WLR_HAS_XWAYLAND
+ struct wlr_xwayland_surface *restack_above;
+#endif
+
+ bool optimized_blur_dirty;
+};
+
+static uint32_t region_area(pixman_region32_t *region) {
+ uint32_t area = 0;
+
+ int nrects;
+ pixman_box32_t *rects = pixman_region32_rectangles(region, &nrects);
+ for (int i = 0; i < nrects; ++i) {
+ area += (rects[i].x2 - rects[i].x1) * (rects[i].y2 - rects[i].y1);
+ }
+
+ return area;
+}
+
+static void scale_region(pixman_region32_t *region, float scale, bool round_up) {
+ wlr_region_scale(region, region, scale);
+
+ if (round_up && floor(scale) != scale) {
+ wlr_region_expand(region, region, 1);
+ }
+}
+
+struct render_data {
+ enum wl_output_transform transform;
+ float scale;
+ struct wlr_box logical;
+ int trans_width, trans_height;
+
+ struct wlr_scene_output *output;
+
+ struct fx_gles_render_pass *render_pass;
+ pixman_region32_t damage;
+
+ bool has_blur;
+};
+
+static void logical_to_buffer_coords(pixman_region32_t *region, const struct render_data *data,
+ bool round_up) {
+ enum wl_output_transform transform = wlr_output_transform_invert(data->transform);
+ scale_region(region, data->scale, round_up);
+ wlr_region_transform(region, region, transform, data->trans_width, data->trans_height);
+}
+
+static void output_to_buffer_coords(pixman_region32_t *damage, struct wlr_output *output) {
+ int width, height;
+ wlr_output_transformed_resolution(output, &width, &height);
+
+ wlr_region_transform(damage, damage,
+ wlr_output_transform_invert(output->transform), width, height);
+}
+
+static int scale_length(int length, int offset, float scale) {
+ return round((offset + length) * scale) - round(offset * scale);
+}
+
+static void scale_box(struct wlr_box *box, float scale) {
+ box->width = scale_length(box->width, box->x, scale);
+ box->height = scale_length(box->height, box->y, scale);
+ box->x = round(box->x * scale);
+ box->y = round(box->y * scale);
+}
+
+static void transform_output_box(struct wlr_box *box, const struct render_data *data) {
+ enum wl_output_transform transform = wlr_output_transform_invert(data->transform);
+ scale_box(box, data->scale);
+ wlr_box_transform(box, box, transform, data->trans_width, data->trans_height);
+}
+
+static void scene_output_damage(struct wlr_scene_output *scene_output,
+ const pixman_region32_t *damage) {
+ struct wlr_output *output = scene_output->output;
+
+ pixman_region32_t clipped;
+ pixman_region32_init(&clipped);
+ pixman_region32_intersect_rect(&clipped, damage, 0, 0, output->width, output->height);
+
+ if (!pixman_region32_empty(&clipped)) {
+ wlr_output_schedule_frame(scene_output->output);
+ wlr_damage_ring_add(&scene_output->damage_ring, &clipped);
+
+ pixman_region32_union(&scene_output->pending_commit_damage,
+ &scene_output->pending_commit_damage, &clipped);
+ }
+
+ pixman_region32_fini(&clipped);
+}
+
+static void scene_output_damage_whole(struct wlr_scene_output *scene_output) {
+ struct wlr_output *output = scene_output->output;
+
+ pixman_region32_t damage;
+ pixman_region32_init_rect(&damage, 0, 0, output->width, output->height);
+ scene_output_damage(scene_output, &damage);
+ pixman_region32_fini(&damage);
+}
+
+static void scene_damage_outputs(struct wlr_scene *scene, pixman_region32_t *damage) {
+ if (pixman_region32_empty(damage)) {
+ return;
+ }
+
+ struct wlr_scene_output *scene_output;
+ wl_list_for_each(scene_output, &scene->outputs, link) {
+ pixman_region32_t output_damage;
+ pixman_region32_init(&output_damage);
+ pixman_region32_copy(&output_damage, damage);
+ pixman_region32_translate(&output_damage,
+ -scene_output->x, -scene_output->y);
+ scale_region(&output_damage, scene_output->output->scale, true);
+ output_to_buffer_coords(&output_damage, scene_output->output);
+ scene_output_damage(scene_output, &output_damage);
+ pixman_region32_fini(&output_damage);
+ }
+}
+
+static void update_node_update_outputs(struct wlr_scene_node *node,
+ struct wl_list *outputs, struct wlr_scene_output *ignore,
+ struct wlr_scene_output *force) {
+ if (node->type != WLR_SCENE_NODE_BUFFER) {
+ return;
+ }
+
+ struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_from_node(node);
+
+ uint32_t largest_overlap = 0;
+ struct wlr_scene_output *old_primary_output = scene_buffer->primary_output;
+ scene_buffer->primary_output = NULL;
+
+ size_t count = 0;
+ uint64_t active_outputs = 0;
+
+ // let's update the outputs in two steps:
+ // - the primary outputs
+ // - the enter/leave signals
+ // This ensures that the enter/leave signals can rely on the primary output
+ // to have a reasonable value. Otherwise, they may get a value that's in
+ // the middle of a calculation.
+ struct wlr_scene_output *scene_output;
+ wl_list_for_each(scene_output, outputs, link) {
+ if (scene_output == ignore) {
+ continue;
+ }
+
+ if (!scene_output->output->enabled) {
+ continue;
+ }
+
+ struct wlr_box output_box = {
+ .x = scene_output->x,
+ .y = scene_output->y,
+ };
+ wlr_output_effective_resolution(scene_output->output,
+ &output_box.width, &output_box.height);
+
+ pixman_region32_t intersection;
+ pixman_region32_init(&intersection);
+ pixman_region32_intersect_rect(&intersection, &node->visible,
+ output_box.x, output_box.y, output_box.width, output_box.height);
+
+ if (!pixman_region32_empty(&intersection)) {
+ uint32_t overlap = region_area(&intersection);
+ if (overlap >= largest_overlap) {
+ largest_overlap = overlap;
+ scene_buffer->primary_output = scene_output;
+ }
+
+ active_outputs |= 1ull << scene_output->index;
+ count++;
+ }
+
+ pixman_region32_fini(&intersection);
+ }
+
+ if (old_primary_output != scene_buffer->primary_output) {
+ scene_buffer->prev_feedback_options =
+ (struct wlr_linux_dmabuf_feedback_v1_init_options){0};
+ }
+
+ uint64_t old_active = scene_buffer->active_outputs;
+ scene_buffer->active_outputs = active_outputs;
+
+ wl_list_for_each(scene_output, outputs, link) {
+ uint64_t mask = 1ull << scene_output->index;
+ bool intersects = active_outputs & mask;
+ bool intersects_before = old_active & mask;
+
+ if (intersects && !intersects_before) {
+ wl_signal_emit_mutable(&scene_buffer->events.output_enter, scene_output);
+ } else if (!intersects && intersects_before) {
+ wl_signal_emit_mutable(&scene_buffer->events.output_leave, scene_output);
+ }
+ }
+
+ // if there are active outputs on this node, we should always have a primary
+ // output
+ assert(!scene_buffer->active_outputs || scene_buffer->primary_output);
+
+ // Skip output update event if nothing was updated
+ if (old_active == active_outputs &&
+ (!force || ((1ull << force->index) & ~active_outputs)) &&
+ old_primary_output == scene_buffer->primary_output) {
+ return;
+ }
+
+ struct wlr_scene_output *outputs_array[64];
+ struct wlr_scene_outputs_update_event event = {
+ .active = outputs_array,
+ .size = count,
+ };
+
+ size_t i = 0;
+ wl_list_for_each(scene_output, outputs, link) {
+ if (~active_outputs & (1ull << scene_output->index)) {
+ continue;
+ }
+
+ assert(i < count);
+ outputs_array[i++] = scene_output;
+ }
+
+ wl_signal_emit_mutable(&scene_buffer->events.outputs_update, &event);
+}
+
+#if WLR_HAS_XWAYLAND
+static struct wlr_xwayland_surface *scene_node_try_get_managed_xwayland_surface(
+ struct wlr_scene_node *node) {
+ if (node->type != WLR_SCENE_NODE_BUFFER) {
+ return NULL;
+ }
+
+ struct wlr_scene_buffer *buffer_node = wlr_scene_buffer_from_node(node);
+ struct wlr_scene_surface *surface_node = wlr_scene_surface_try_from_buffer(buffer_node);
+ if (!surface_node) {
+ return NULL;
+ }
+
+ struct wlr_xwayland_surface *xwayland_surface =
+ wlr_xwayland_surface_try_from_wlr_surface(surface_node->surface);
+ if (!xwayland_surface || xwayland_surface->override_redirect) {
+ return NULL;
+ }
+
+ return xwayland_surface;
+}
+
+static void restack_xwayland_surface(struct wlr_scene_node *node,
+ struct wlr_box *box, struct scene_update_data *data) {
+ struct wlr_xwayland_surface *xwayland_surface =
+ scene_node_try_get_managed_xwayland_surface(node);
+ if (!xwayland_surface) {
+ return;
+ }
+
+ // ensure this node is entirely inside the update region. If not, we can't
+ // restack this node since we're not considering the whole thing.
+ if (wlr_box_contains_box(&data->update_box, box)) {
+ if (data->restack_above) {
+ wlr_xwayland_surface_restack(xwayland_surface, data->restack_above, XCB_STACK_MODE_BELOW);
+ } else {
+ wlr_xwayland_surface_restack(xwayland_surface, NULL, XCB_STACK_MODE_ABOVE);
+ }
+ }
+
+ data->restack_above = xwayland_surface;
+}
+
+static void restack_xwayland_surface_below(struct wlr_scene_node *node) {
+ if (node->type == WLR_SCENE_NODE_TREE) {
+ struct wlr_scene_tree *scene_tree = wlr_scene_tree_from_node(node);
+ struct wlr_scene_node *child;
+ wl_list_for_each(child, &scene_tree->children, link) {
+ restack_xwayland_surface_below(child);
+ }
+ return;
+ }
+
+ struct wlr_xwayland_surface *xwayland_surface =
+ scene_node_try_get_managed_xwayland_surface(node);
+ if (!xwayland_surface) {
+ return;
+ }
+
+ wlr_xwayland_surface_restack(xwayland_surface, NULL, XCB_STACK_MODE_BELOW);
+}
+#endif
+
+static bool scene_node_update_iterator(struct wlr_scene_node *node,
+ int lx, int ly, void *_data) {
+ struct scene_update_data *data = _data;
+
+ if (node->type == WLR_SCENE_NODE_OPTIMIZED_BLUR) {
+ struct wlr_scene_optimized_blur *scene_blur = wlr_scene_optimized_blur_from_node(node);
+ if (scene_blur->dirty) {
+ data->optimized_blur_dirty = true;
+ // Restore the visible region back to default for the nodes below
+ // the optimized blur node
+ pixman_region32_clear(data->visible);
+ pixman_region32_copy(data->visible, data->update_region);
+ }
+ }
+
+ struct wlr_box box = { .x = lx, .y = ly };
+ scene_node_get_size(node, &box.width, &box.height);
+
+ pixman_region32_subtract(&node->visible, &node->visible, data->update_region);
+ pixman_region32_union(&node->visible, &node->visible, data->visible);
+ pixman_region32_intersect_rect(&node->visible, &node->visible,
+ lx, ly, box.width, box.height);
+
+ if (data->calculate_visibility && !data->optimized_blur_dirty) {
+ pixman_region32_t opaque;
+ pixman_region32_init(&opaque);
+ scene_node_opaque_region(node, lx, ly, &opaque);
+ pixman_region32_subtract(data->visible, data->visible, &opaque);
+ pixman_region32_fini(&opaque);
+ }
+
+ update_node_update_outputs(node, data->outputs, NULL, NULL);
+#if WLR_HAS_XWAYLAND
+ restack_xwayland_surface(node, &box, data);
+#endif
+
+ return false;
+}
+
+static void scene_node_visibility(struct wlr_scene_node *node,
+ pixman_region32_t *visible) {
+ if (!node->enabled) {
+ return;
+ }
+
+ if (node->type == WLR_SCENE_NODE_TREE) {
+ struct wlr_scene_tree *scene_tree = wlr_scene_tree_from_node(node);
+ struct wlr_scene_node *child;
+ wl_list_for_each(child, &scene_tree->children, link) {
+ scene_node_visibility(child, visible);
+ }
+ return;
+ }
+
+ pixman_region32_union(visible, visible, &node->visible);
+}
+
+static void scene_node_bounds(struct wlr_scene_node *node,
+ int x, int y, pixman_region32_t *visible) {
+ if (!node->enabled) {
+ return;
+ }
+
+ if (node->type == WLR_SCENE_NODE_TREE) {
+ struct wlr_scene_tree *scene_tree = wlr_scene_tree_from_node(node);
+ struct wlr_scene_node *child;
+ wl_list_for_each(child, &scene_tree->children, link) {
+ scene_node_bounds(child, x + child->x, y + child->y, visible);
+ }
+ return;
+ }
+
+ int width, height;
+ scene_node_get_size(node, &width, &height);
+ pixman_region32_union_rect(visible, visible, x, y, width, height);
+}
+
+static void scene_update_region(struct wlr_scene *scene,
+ pixman_region32_t *update_region) {
+ pixman_region32_t visible;
+ pixman_region32_init(&visible);
+ pixman_region32_copy(&visible, update_region);
+
+ struct pixman_box32 *region_box = pixman_region32_extents(update_region);
+ struct scene_update_data data = {
+ .visible = &visible,
+ .update_region = update_region,
+ .update_box = {
+ .x = region_box->x1,
+ .y = region_box->y1,
+ .width = region_box->x2 - region_box->x1,
+ .height = region_box->y2 - region_box->y1,
+ },
+ .outputs = &scene->outputs,
+ .calculate_visibility = scene->calculate_visibility,
+ .optimized_blur_dirty = false,
+ };
+
+ // update node visibility and output enter/leave events
+ scene_nodes_in_box(&scene->tree.node, &data.update_box, scene_node_update_iterator, &data);
+
+ pixman_region32_fini(&visible);
+}
+
+static void scene_node_update(struct wlr_scene_node *node,
+ pixman_region32_t *damage) {
+ struct wlr_scene *scene = scene_node_get_root(node);
+
+ int x, y;
+ if (!wlr_scene_node_coords(node, &x, &y)) {
+#if WLR_HAS_XWAYLAND
+ restack_xwayland_surface_below(node);
+#endif
+ if (damage) {
+ scene_update_region(scene, damage);
+ scene_damage_outputs(scene, damage);
+ pixman_region32_fini(damage);
+ }
+
+ return;
+ }
+
+ pixman_region32_t visible;
+ if (!damage) {
+ pixman_region32_init(&visible);
+ scene_node_visibility(node, &visible);
+ damage = &visible;
+ }
+
+ pixman_region32_t update_region;
+ pixman_region32_init(&update_region);
+ pixman_region32_copy(&update_region, damage);
+ scene_node_bounds(node, x, y, &update_region);
+
+ scene_update_region(scene, &update_region);
+ pixman_region32_fini(&update_region);
+
+ scene_node_visibility(node, damage);
+ scene_damage_outputs(scene, damage);
+ pixman_region32_fini(damage);
+}
+
+struct wlr_scene_rect *wlr_scene_rect_create(struct wlr_scene_tree *parent,
+ int width, int height, const float color[static 4]) {
+ assert(parent);
+ assert(width >= 0 && height >= 0);
+
+ struct wlr_scene_rect *scene_rect = calloc(1, sizeof(*scene_rect));
+ if (scene_rect == NULL) {
+ return NULL;
+ }
+ scene_node_init(&scene_rect->node, WLR_SCENE_NODE_RECT, parent);
+
+ scene_rect->width = width;
+ scene_rect->height = height;
+ memcpy(scene_rect->color, color, sizeof(scene_rect->color));
+ scene_rect->corner_radius = 0;
+ scene_rect->corners = CORNER_LOCATION_NONE;
+ scene_rect->accepts_input = true;
+ scene_rect->clipped_region = clipped_region_get_default();
+ scene_rect->backdrop_blur = false;
+ scene_rect->backdrop_blur_optimized = false;
+ scene_rect->fade_inset = 0;
+
+ scene_node_update(&scene_rect->node, NULL);
+
+ return scene_rect;
+}
+
+void wlr_scene_rect_set_size(struct wlr_scene_rect *rect, int width, int height) {
+ if (rect->width == width && rect->height == height) {
+ return;
+ }
+
+ assert(width >= 0 && height >= 0);
+
+ rect->width = width;
+ rect->height = height;
+ scene_node_update(&rect->node, NULL);
+}
+
+void wlr_scene_rect_set_color(struct wlr_scene_rect *rect, const float color[static 4]) {
+ if (memcmp(rect->color, color, sizeof(rect->color)) == 0) {
+ return;
+ }
+
+ memcpy(rect->color, color, sizeof(rect->color));
+ scene_node_update(&rect->node, NULL);
+}
+
+void wlr_scene_rect_set_backdrop_blur(struct wlr_scene_rect *rect,
+ bool enabled) {
+ if (rect->backdrop_blur == enabled) {
+ return;
+ }
+ rect->backdrop_blur = enabled;
+ scene_node_update(&rect->node, NULL);
+}
+
+void wlr_scene_rect_set_backdrop_blur_optimized(struct wlr_scene_rect *rect,
+ bool enabled) {
+ if (rect->backdrop_blur_optimized == enabled) {
+ return;
+ }
+ rect->backdrop_blur_optimized = enabled;
+ scene_node_update(&rect->node, NULL);
+}
+
+void wlr_scene_rect_set_fade_inset(struct wlr_scene_rect *rect, int fade_inset) {
+ if (rect->fade_inset == fade_inset) {
+ return;
+ }
+ rect->fade_inset = fade_inset;
+ scene_node_update(&rect->node, NULL);
+}
+
+static void scene_buffer_handle_buffer_release(struct wl_listener *listener,
+ void *data) {
+ struct wlr_scene_buffer *scene_buffer =
+ wl_container_of(listener, scene_buffer, buffer_release);
+
+ scene_buffer->buffer = NULL;
+ wl_list_remove(&scene_buffer->buffer_release.link);
+ wl_list_init(&scene_buffer->buffer_release.link);
+}
+
+static void scene_buffer_set_buffer(struct wlr_scene_buffer *scene_buffer,
+ struct wlr_buffer *buffer) {
+ wl_list_remove(&scene_buffer->buffer_release.link);
+ wl_list_init(&scene_buffer->buffer_release.link);
+ if (scene_buffer->own_buffer) {
+ wlr_buffer_unlock(scene_buffer->buffer);
+ }
+ scene_buffer->buffer = NULL;
+ scene_buffer->own_buffer = false;
+ scene_buffer->buffer_width = scene_buffer->buffer_height = 0;
+ scene_buffer->buffer_is_opaque = false;
+
+ if (!buffer) {
+ return;
+ }
+
+ scene_buffer->own_buffer = true;
+ scene_buffer->buffer = wlr_buffer_lock(buffer);
+ scene_buffer->buffer_width = buffer->width;
+ scene_buffer->buffer_height = buffer->height;
+ scene_buffer->buffer_is_opaque = wlr_buffer_is_opaque(buffer);
+
+ scene_buffer->buffer_release.notify = scene_buffer_handle_buffer_release;
+ wl_signal_add(&buffer->events.release, &scene_buffer->buffer_release);
+}
+
+static void scene_buffer_handle_renderer_destroy(struct wl_listener *listener,
+ void *data) {
+ struct wlr_scene_buffer *scene_buffer = wl_container_of(listener, scene_buffer, renderer_destroy);
+ scene_buffer_set_texture(scene_buffer, NULL);
+}
+
+static void scene_buffer_set_texture(struct wlr_scene_buffer *scene_buffer,
+ struct wlr_texture *texture) {
+ wl_list_remove(&scene_buffer->renderer_destroy.link);
+ wlr_texture_destroy(scene_buffer->texture);
+ scene_buffer->texture = texture;
+
+ if (texture != NULL) {
+ scene_buffer->renderer_destroy.notify = scene_buffer_handle_renderer_destroy;
+ wl_signal_add(&texture->renderer->events.destroy, &scene_buffer->renderer_destroy);
+ } else {
+ wl_list_init(&scene_buffer->renderer_destroy.link);
+ }
+}
+
+static void scene_buffer_set_wait_timeline(struct wlr_scene_buffer *scene_buffer,
+ struct wlr_drm_syncobj_timeline *timeline, uint64_t point) {
+ wlr_drm_syncobj_timeline_unref(scene_buffer->wait_timeline);
+ if (timeline != NULL) {
+ scene_buffer->wait_timeline = wlr_drm_syncobj_timeline_ref(timeline);
+ scene_buffer->wait_point = point;
+ } else {
+ scene_buffer->wait_timeline = NULL;
+ scene_buffer->wait_point = 0;
+ }
+}
+
+void wlr_scene_rect_set_corner_radius(struct wlr_scene_rect *rect, int corner_radius,
+ enum corner_location corners) {
+ if (rect->corner_radius == corner_radius && rect->corners == corners) {
+ return;
+ }
+
+ rect->corner_radius = corner_radius;
+ rect->corners = corners;
+ scene_node_update(&rect->node, NULL);
+}
+
+void wlr_scene_rect_set_clipped_region(struct wlr_scene_rect *rect,
+ struct clipped_region clipped_region) {
+ if (rect->clipped_region.corner_radius == clipped_region.corner_radius &&
+ rect->clipped_region.corners == clipped_region.corners &&
+ wlr_box_equal(&rect->clipped_region.area, &clipped_region.area)) {
+ return;
+ }
+
+ if (clipped_region.corner_radius && clipped_region.corners == CORNER_LOCATION_NONE) {
+ wlr_log(WLR_ERROR, "Applying corner radius without specifying which"
+ " corners to round for rect: %p", rect);
+ }
+ rect->clipped_region = clipped_region;
+ scene_node_update(&rect->node, NULL);
+}
+
+struct wlr_scene_shadow *wlr_scene_shadow_create(struct wlr_scene_tree *parent,
+ int width, int height, int corner_radius, float blur_sigma,
+ const float color [static 4]) {
+ struct wlr_scene_shadow *scene_shadow = calloc(1, sizeof(*scene_shadow));
+ if (scene_shadow == NULL) {
+ return NULL;
+ }
+ assert(parent);
+ scene_node_init(&scene_shadow->node, WLR_SCENE_NODE_SHADOW, parent);
+
+ scene_shadow->width = width;
+ scene_shadow->height = height;
+ scene_shadow->corner_radius = corner_radius;
+ scene_shadow->blur_sigma = blur_sigma;
+ memcpy(scene_shadow->color, color, sizeof(scene_shadow->color));
+ scene_shadow->clipped_region = clipped_region_get_default();
+
+ scene_node_update(&scene_shadow->node, NULL);
+
+ return scene_shadow;
+}
+
+void wlr_scene_shadow_set_size(struct wlr_scene_shadow *shadow, int width, int height) {
+ if (shadow->width == width && shadow->height == height) {
+ return;
+ }
+
+ shadow->width = width;
+ shadow->height = height;
+ scene_node_update(&shadow->node, NULL);
+}
+
+void wlr_scene_shadow_set_corner_radius(struct wlr_scene_shadow *shadow, int corner_radius) {
+ if (shadow->corner_radius == corner_radius) {
+ return;
+ }
+
+ shadow->corner_radius = corner_radius;
+ scene_node_update(&shadow->node, NULL);
+}
+
+void wlr_scene_shadow_set_blur_sigma(struct wlr_scene_shadow *shadow, float blur_sigma) {
+ if (shadow->blur_sigma == blur_sigma) {
+ return;
+ }
+
+ shadow->blur_sigma = blur_sigma;
+ scene_node_update(&shadow->node, NULL);
+}
+
+void wlr_scene_shadow_set_color(struct wlr_scene_shadow *shadow, const float color[static 4]) {
+ if (memcmp(shadow->color, color, sizeof(shadow->color)) == 0) {
+ return;
+ }
+
+ memcpy(shadow->color, color, sizeof(shadow->color));
+ scene_node_update(&shadow->node, NULL);
+}
+
+void wlr_scene_shadow_set_clipped_region(struct wlr_scene_shadow *shadow,
+ struct clipped_region clipped_region) {
+ if (shadow->clipped_region.corner_radius == clipped_region.corner_radius &&
+ shadow->clipped_region.corners == clipped_region.corners &&
+ wlr_box_equal(&shadow->clipped_region.area, &clipped_region.area)) {
+ return;
+ }
+
+ if (clipped_region.corner_radius && clipped_region.corners == CORNER_LOCATION_NONE) {
+ wlr_log(WLR_ERROR, "Applying corner radius without specifying which"
+ " corners to round for shadow: %p", shadow);
+ }
+ shadow->clipped_region = clipped_region;
+ scene_node_update(&shadow->node, NULL);
+}
+
+static void mark_all_optimized_blur_nodes_dirty(struct wlr_scene_node *node) {
+ if (node->type == WLR_SCENE_NODE_OPTIMIZED_BLUR) {
+ struct wlr_scene_optimized_blur *scene_blur = wlr_scene_optimized_blur_from_node(node);
+ wlr_scene_optimized_blur_mark_dirty(scene_blur);
+ } else if (node->type == WLR_SCENE_NODE_TREE) {
+ struct wlr_scene_tree *scene_tree = wlr_scene_tree_from_node(node);
+ struct wlr_scene_node *child;
+ wl_list_for_each(child, &scene_tree->children, link) {
+ mark_all_optimized_blur_nodes_dirty(child);
+ }
+ }
+}
+
+void wlr_scene_set_blur_data(struct wlr_scene *scene, int num_passes,
+ int radius, float noise, float brightness, float contrast, float saturation) {
+ struct blur_data *buff_data = &scene->blur_data;
+ if (buff_data->num_passes == num_passes
+ && buff_data->radius == radius
+ && buff_data->noise == noise
+ && buff_data->brightness == brightness
+ && buff_data->contrast == contrast
+ && buff_data->saturation == saturation) {
+ return;
+ }
+
+ buff_data->num_passes = num_passes;
+ buff_data->radius = radius;
+ buff_data->noise = noise;
+ buff_data->brightness = brightness;
+ buff_data->contrast = contrast;
+ buff_data->saturation = saturation;
+
+ mark_all_optimized_blur_nodes_dirty(&scene->tree.node);
+ scene_node_update(&scene->tree.node, NULL);
+}
+
+void wlr_scene_set_blur_num_passes(struct wlr_scene *scene, int num_passes) {
+ struct blur_data *buff_data = &scene->blur_data;
+ if (buff_data->num_passes == num_passes) {
+ return;
+ }
+ buff_data->num_passes = num_passes;
+ mark_all_optimized_blur_nodes_dirty(&scene->tree.node);
+ scene_node_update(&scene->tree.node, NULL);
+}
+
+void wlr_scene_set_blur_radius(struct wlr_scene *scene, int radius) {
+ struct blur_data *buff_data = &scene->blur_data;
+ if (buff_data->radius == radius) {
+ return;
+ }
+ buff_data->radius = radius;
+ mark_all_optimized_blur_nodes_dirty(&scene->tree.node);
+ scene_node_update(&scene->tree.node, NULL);
+}
+
+void wlr_scene_set_blur_noise(struct wlr_scene *scene, float noise) {
+ struct blur_data *buff_data = &scene->blur_data;
+ if (buff_data->noise == noise) {
+ return;
+ }
+ buff_data->noise = noise;
+ mark_all_optimized_blur_nodes_dirty(&scene->tree.node);
+ scene_node_update(&scene->tree.node, NULL);
+}
+
+void wlr_scene_set_blur_brightness(struct wlr_scene *scene, float brightness) {
+ struct blur_data *buff_data = &scene->blur_data;
+ if (buff_data->brightness == brightness) {
+ return;
+ }
+ buff_data->brightness = brightness;
+ mark_all_optimized_blur_nodes_dirty(&scene->tree.node);
+ scene_node_update(&scene->tree.node, NULL);
+}
+
+void wlr_scene_set_blur_contrast(struct wlr_scene *scene, float contrast) {
+ struct blur_data *buff_data = &scene->blur_data;
+ if (buff_data->contrast == contrast) {
+ return;
+ }
+ buff_data->contrast = contrast;
+ mark_all_optimized_blur_nodes_dirty(&scene->tree.node);
+ scene_node_update(&scene->tree.node, NULL);
+}
+
+void wlr_scene_set_blur_saturation(struct wlr_scene *scene, float saturation) {
+ struct blur_data *buff_data = &scene->blur_data;
+ if (buff_data->saturation == saturation) {
+ return;
+ }
+ buff_data->saturation = saturation;
+ mark_all_optimized_blur_nodes_dirty(&scene->tree.node);
+ scene_node_update(&scene->tree.node, NULL);
+}
+
+struct wlr_scene_optimized_blur *wlr_scene_optimized_blur_create(
+ struct wlr_scene_tree *parent, int width, int height) {
+ struct wlr_scene_optimized_blur *scene_blur = calloc(1, sizeof(*scene_blur));
+ if (scene_blur == NULL) {
+ return NULL;
+ }
+ assert(parent);
+ scene_node_init(&scene_blur->node, WLR_SCENE_NODE_OPTIMIZED_BLUR, parent);
+
+ scene_blur->width = width;
+ scene_blur->height = height;
+ scene_blur->dirty = false;
+
+ scene_node_update(&scene_blur->node, NULL);
+
+ return scene_blur;
+}
+
+void wlr_scene_optimized_blur_set_size(struct wlr_scene_optimized_blur *blur_node,
+ int width, int height) {
+ assert(blur_node);
+ if (blur_node->width == width && blur_node->height == height) {
+ return;
+ }
+
+ blur_node->width = width;
+ blur_node->height = height;
+
+ wlr_scene_optimized_blur_mark_dirty(blur_node);
+}
+
+void wlr_scene_optimized_blur_mark_dirty(struct wlr_scene_optimized_blur *blur_node) {
+ // Skip re-rendering the optimized blur if the blur node is disabled
+ if (blur_node && !blur_node->node.enabled) {
+ return;
+ }
+
+ blur_node->dirty = true;
+
+ scene_node_update(&blur_node->node, NULL);
+}
+
+struct wlr_scene_buffer *wlr_scene_buffer_create(struct wlr_scene_tree *parent,
+ struct wlr_buffer *buffer) {
+ struct wlr_scene_buffer *scene_buffer = calloc(1, sizeof(*scene_buffer));
+ if (scene_buffer == NULL) {
+ return NULL;
+ }
+ assert(parent);
+ scene_node_init(&scene_buffer->node, WLR_SCENE_NODE_BUFFER, parent);
+
+ wl_signal_init(&scene_buffer->events.outputs_update);
+ wl_signal_init(&scene_buffer->events.output_enter);
+ wl_signal_init(&scene_buffer->events.output_leave);
+ wl_signal_init(&scene_buffer->events.output_sample);
+ wl_signal_init(&scene_buffer->events.frame_done);
+
+ pixman_region32_init(&scene_buffer->opaque_region);
+ wl_list_init(&scene_buffer->buffer_release.link);
+ wl_list_init(&scene_buffer->renderer_destroy.link);
+ scene_buffer->opacity = 1;
+
+ scene_buffer->corner_radius = 0;
+ scene_buffer->backdrop_blur = false;
+ scene_buffer->backdrop_blur_optimized = false;
+ scene_buffer->backdrop_blur_ignore_transparent = true;
+ scene_buffer->corners = CORNER_LOCATION_NONE;
+
+ scene_buffer_set_buffer(scene_buffer, buffer);
+ scene_node_update(&scene_buffer->node, NULL);
+
+ return scene_buffer;
+}
+
+void wlr_scene_buffer_set_buffer_with_options(struct wlr_scene_buffer *scene_buffer,
+ struct wlr_buffer *buffer, const struct wlr_scene_buffer_set_buffer_options *options) {
+ const struct wlr_scene_buffer_set_buffer_options default_options = {0};
+ if (options == NULL) {
+ options = &default_options;
+ }
+
+ // specifying a region for a NULL buffer doesn't make sense. We need to know
+ // about the buffer to scale the buffer local coordinates down to scene
+ // coordinates.
+ assert(buffer || !options->damage);
+
+ bool mapped = buffer != NULL;
+ bool prev_mapped = scene_buffer->buffer != NULL || scene_buffer->texture != NULL;
+
+ if (!mapped && !prev_mapped) {
+ // unmapping already unmapped buffer - noop
+ return;
+ }
+
+ // if this node used to not be mapped or its previous displayed
+ // buffer region will be different from what the new buffer would
+ // produce we need to update the node.
+ bool update = mapped != prev_mapped;
+ if (buffer != NULL && scene_buffer->dst_width == 0 && scene_buffer->dst_height == 0) {
+ update = update || scene_buffer->buffer_width != buffer->width ||
+ scene_buffer->buffer_height != buffer->height;
+ }
+
+ // If this is a buffer change, check if it's a single pixel buffer.
+ // Cache that so we can still apply rendering optimisations even when
+ // the original buffer has been freed after texture upload.
+ if (buffer != scene_buffer->buffer) {
+ scene_buffer->is_single_pixel_buffer = false;
+ struct wlr_client_buffer *client_buffer = NULL;
+ if (buffer != NULL) {
+ client_buffer = wlr_client_buffer_get(buffer);
+ }
+ if (client_buffer != NULL && client_buffer->source != NULL) {
+ struct wlr_single_pixel_buffer_v1 *single_pixel_buffer =
+ wlr_single_pixel_buffer_v1_try_from_buffer(client_buffer->source);
+ if (single_pixel_buffer != NULL) {
+ scene_buffer->is_single_pixel_buffer = true;
+ scene_buffer->single_pixel_buffer_color[0] = single_pixel_buffer->r;
+ scene_buffer->single_pixel_buffer_color[1] = single_pixel_buffer->g;
+ scene_buffer->single_pixel_buffer_color[2] = single_pixel_buffer->b;
+ scene_buffer->single_pixel_buffer_color[3] = single_pixel_buffer->a;
+ }
+ }
+ }
+
+ scene_buffer_set_buffer(scene_buffer, buffer);
+ scene_buffer_set_texture(scene_buffer, NULL);
+ scene_buffer_set_wait_timeline(scene_buffer,
+ options->wait_timeline, options->wait_point);
+
+ if (update) {
+ scene_node_update(&scene_buffer->node, NULL);
+ // updating the node will already damage the whole node for us. Return
+ // early to not damage again
+ return;
+ }
+
+ int lx, ly;
+ if (!wlr_scene_node_coords(&scene_buffer->node, &lx, &ly)) {
+ return;
+ }
+
+ pixman_region32_t fallback_damage;
+ pixman_region32_init_rect(&fallback_damage, 0, 0, buffer->width, buffer->height);
+ const pixman_region32_t *damage = options->damage;
+ if (!damage) {
+ damage = &fallback_damage;
+ }
+
+ struct wlr_fbox box = scene_buffer->src_box;
+ if (wlr_fbox_empty(&box)) {
+ box.x = 0;
+ box.y = 0;
+ box.width = buffer->width;
+ box.height = buffer->height;
+ }
+
+ wlr_fbox_transform(&box, &box, scene_buffer->transform,
+ buffer->width, buffer->height);
+
+ float scale_x, scale_y;
+ if (scene_buffer->dst_width || scene_buffer->dst_height) {
+ scale_x = scene_buffer->dst_width / box.width;
+ scale_y = scene_buffer->dst_height / box.height;
+ } else {
+ scale_x = buffer->width / box.width;
+ scale_y = buffer->height / box.height;
+ }
+
+ pixman_region32_t trans_damage;
+ pixman_region32_init(&trans_damage);
+ wlr_region_transform(&trans_damage, damage,
+ scene_buffer->transform, buffer->width, buffer->height);
+ pixman_region32_intersect_rect(&trans_damage, &trans_damage,
+ box.x, box.y, box.width, box.height);
+ pixman_region32_translate(&trans_damage, -box.x, -box.y);
+
+ struct wlr_scene *scene = scene_node_get_root(&scene_buffer->node);
+ struct wlr_scene_output *scene_output;
+ wl_list_for_each(scene_output, &scene->outputs, link) {
+ float output_scale = scene_output->output->scale;
+ float output_scale_x = output_scale * scale_x;
+ float output_scale_y = output_scale * scale_y;
+ pixman_region32_t output_damage;
+ pixman_region32_init(&output_damage);
+ wlr_region_scale_xy(&output_damage, &trans_damage,
+ output_scale_x, output_scale_y);
+
+ // One output pixel will match (buffer_scale_x)x(buffer_scale_y) buffer pixels.
+ // If the buffer is upscaled on the given axis (output_scale_* > 1.0,
+ // buffer_scale_* < 1.0), its contents will bleed into adjacent
+ // (ceil(output_scale_* / 2)) output pixels because of linear filtering.
+ // Additionally, if the buffer is downscaled (output_scale_* < 1.0,
+ // buffer_scale_* > 1.0), and one output pixel matches a non-integer number of
+ // buffer pixels, its contents will bleed into neighboring output pixels.
+ // Handle both cases by computing buffer_scale_{x,y} and checking if they are
+ // integer numbers; ceilf() is used to ensure that the distance is at least 1.
+ float buffer_scale_x = 1.0f / output_scale_x;
+ float buffer_scale_y = 1.0f / output_scale_y;
+ int dist_x = floor(buffer_scale_x) != buffer_scale_x ?
+ (int)ceilf(output_scale_x / 2.0f) : 0;
+ int dist_y = floor(buffer_scale_y) != buffer_scale_y ?
+ (int)ceilf(output_scale_y / 2.0f) : 0;
+ // TODO: expand with per-axis distances
+ wlr_region_expand(&output_damage, &output_damage,
+ dist_x >= dist_y ? dist_x : dist_y);
+
+ pixman_region32_t cull_region;
+ pixman_region32_init(&cull_region);
+ pixman_region32_copy(&cull_region, &scene_buffer->node.visible);
+ scale_region(&cull_region, output_scale, true);
+ pixman_region32_translate(&cull_region, -lx * output_scale, -ly * output_scale);
+ pixman_region32_intersect(&output_damage, &output_damage, &cull_region);
+ pixman_region32_fini(&cull_region);
+
+ pixman_region32_translate(&output_damage,
+ (int)round((lx - scene_output->x) * output_scale),
+ (int)round((ly - scene_output->y) * output_scale));
+ output_to_buffer_coords(&output_damage, scene_output->output);
+ scene_output_damage(scene_output, &output_damage);
+ pixman_region32_fini(&output_damage);
+ }
+
+ pixman_region32_fini(&trans_damage);
+ pixman_region32_fini(&fallback_damage);
+}
+
+void wlr_scene_buffer_set_buffer_with_damage(struct wlr_scene_buffer *scene_buffer,
+ struct wlr_buffer *buffer, const pixman_region32_t *damage) {
+ const struct wlr_scene_buffer_set_buffer_options options = {
+ .damage = damage,
+ };
+ wlr_scene_buffer_set_buffer_with_options(scene_buffer, buffer, &options);
+}
+
+void wlr_scene_buffer_set_buffer(struct wlr_scene_buffer *scene_buffer,
+ struct wlr_buffer *buffer) {
+ wlr_scene_buffer_set_buffer_with_options(scene_buffer, buffer, NULL);
+}
+
+void wlr_scene_buffer_set_opaque_region(struct wlr_scene_buffer *scene_buffer,
+ const pixman_region32_t *region) {
+ if (pixman_region32_equal(&scene_buffer->opaque_region, region)) {
+ return;
+ }
+
+ pixman_region32_copy(&scene_buffer->opaque_region, region);
+
+ int x, y;
+ if (!wlr_scene_node_coords(&scene_buffer->node, &x, &y)) {
+ return;
+ }
+
+ pixman_region32_t update_region;
+ pixman_region32_init(&update_region);
+ scene_node_bounds(&scene_buffer->node, x, y, &update_region);
+ scene_update_region(scene_node_get_root(&scene_buffer->node), &update_region);
+ pixman_region32_fini(&update_region);
+}
+
+void wlr_scene_buffer_set_source_box(struct wlr_scene_buffer *scene_buffer,
+ const struct wlr_fbox *box) {
+ if (wlr_fbox_equal(&scene_buffer->src_box, box)) {
+ return;
+ }
+
+ if (box != NULL) {
+ assert(box->x >= 0 && box->y >= 0 && box->width >= 0 && box->height >= 0);
+ scene_buffer->src_box = *box;
+ } else {
+ scene_buffer->src_box = (struct wlr_fbox){0};
+ }
+
+ scene_node_update(&scene_buffer->node, NULL);
+}
+
+void wlr_scene_buffer_set_dest_size(struct wlr_scene_buffer *scene_buffer,
+ int width, int height) {
+ if (scene_buffer->dst_width == width && scene_buffer->dst_height == height) {
+ return;
+ }
+
+ assert(width >= 0 && height >= 0);
+ scene_buffer->dst_width = width;
+ scene_buffer->dst_height = height;
+ scene_node_update(&scene_buffer->node, NULL);
+}
+
+void wlr_scene_buffer_set_transform(struct wlr_scene_buffer *scene_buffer,
+ enum wl_output_transform transform) {
+ if (scene_buffer->transform == transform) {
+ return;
+ }
+
+ scene_buffer->transform = transform;
+ scene_node_update(&scene_buffer->node, NULL);
+}
+
+void wlr_scene_buffer_send_frame_done(struct wlr_scene_buffer *scene_buffer,
+ struct timespec *now) {
+ if (!pixman_region32_empty(&scene_buffer->node.visible)) {
+ wl_signal_emit_mutable(&scene_buffer->events.frame_done, now);
+ }
+}
+
+void wlr_scene_buffer_set_opacity(struct wlr_scene_buffer *scene_buffer,
+ float opacity) {
+ if (scene_buffer->opacity == opacity) {
+ return;
+ }
+
+ assert(opacity >= 0 && opacity <= 1);
+ scene_buffer->opacity = opacity;
+ scene_node_update(&scene_buffer->node, NULL);
+}
+
+void wlr_scene_buffer_set_filter_mode(struct wlr_scene_buffer *scene_buffer,
+ enum wlr_scale_filter_mode filter_mode) {
+ if (scene_buffer->filter_mode == filter_mode) {
+ return;
+ }
+
+ scene_buffer->filter_mode = filter_mode;
+ scene_node_update(&scene_buffer->node, NULL);
+}
+
+void wlr_scene_buffer_set_corner_radius(struct wlr_scene_buffer *scene_buffer,
+ int radii, enum corner_location corners) {
+ if (scene_buffer->corner_radius == radii
+ && scene_buffer->corners == corners) {
+ return;
+ }
+
+ scene_buffer->corner_radius = radii;
+ scene_buffer->corners = corners;
+ scene_node_update(&scene_buffer->node, NULL);
+}
+
+void wlr_scene_buffer_set_backdrop_blur(struct wlr_scene_buffer *scene_buffer,
+ bool enabled) {
+ if (scene_buffer->backdrop_blur == enabled) {
+ return;
+ }
+ scene_buffer->backdrop_blur = enabled;
+ scene_node_update(&scene_buffer->node, NULL);
+}
+
+void wlr_scene_buffer_set_backdrop_blur_optimized(struct wlr_scene_buffer *scene_buffer,
+ bool enabled) {
+ if (scene_buffer->backdrop_blur_optimized == enabled) {
+ return;
+ }
+ scene_buffer->backdrop_blur_optimized = enabled;
+ scene_node_update(&scene_buffer->node, NULL);
+}
+
+void wlr_scene_buffer_set_backdrop_blur_ignore_transparent(
+ struct wlr_scene_buffer *scene_buffer, bool enabled) {
+ if (scene_buffer->backdrop_blur_ignore_transparent == enabled) {
+ return;
+ }
+
+ scene_buffer->backdrop_blur_ignore_transparent = enabled;
+ scene_node_update(&scene_buffer->node, NULL);
+}
+
+static struct wlr_texture *scene_buffer_get_texture(
+ struct wlr_scene_buffer *scene_buffer, struct wlr_renderer *renderer) {
+ if (scene_buffer->buffer == NULL || scene_buffer->texture != NULL) {
+ return scene_buffer->texture;
+ }
+
+ struct wlr_client_buffer *client_buffer =
+ wlr_client_buffer_get(scene_buffer->buffer);
+ if (client_buffer != NULL) {
+ return client_buffer->texture;
+ }
+
+ struct wlr_texture *texture =
+ wlr_texture_from_buffer(renderer, scene_buffer->buffer);
+ if (texture != NULL && scene_buffer->own_buffer) {
+ scene_buffer->own_buffer = false;
+ wlr_buffer_unlock(scene_buffer->buffer);
+ }
+ scene_buffer_set_texture(scene_buffer, texture);
+ return texture;
+}
+
+static void scene_node_get_size(struct wlr_scene_node *node,
+ int *width, int *height) {
+ *width = 0;
+ *height = 0;
+
+ switch (node->type) {
+ case WLR_SCENE_NODE_TREE:
+ return;
+ case WLR_SCENE_NODE_OPTIMIZED_BLUR:;
+ struct wlr_scene_optimized_blur *scene_blur =
+ wlr_scene_optimized_blur_from_node(node);
+ *width = scene_blur->width;
+ *height = scene_blur->height;
+ break;
+ case WLR_SCENE_NODE_RECT:;
+ struct wlr_scene_rect *scene_rect = wlr_scene_rect_from_node(node);
+ *width = scene_rect->width;
+ *height = scene_rect->height;
+ break;
+ case WLR_SCENE_NODE_SHADOW:;
+ struct wlr_scene_shadow *scene_shadow = wlr_scene_shadow_from_node(node);
+ *width = scene_shadow->width;
+ *height = scene_shadow->height;
+ break;
+ case WLR_SCENE_NODE_BUFFER:;
+ struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_from_node(node);
+ if (scene_buffer->dst_width > 0 && scene_buffer->dst_height > 0) {
+ *width = scene_buffer->dst_width;
+ *height = scene_buffer->dst_height;
+ } else {
+ *width = scene_buffer->buffer_width;
+ *height = scene_buffer->buffer_height;
+ wlr_output_transform_coords(scene_buffer->transform, width, height);
+ }
+ break;
+ }
+}
+
+void wlr_scene_node_set_enabled(struct wlr_scene_node *node, bool enabled) {
+ if (node->enabled == enabled) {
+ return;
+ }
+
+ int x, y;
+ pixman_region32_t visible;
+ pixman_region32_init(&visible);
+ if (wlr_scene_node_coords(node, &x, &y)) {
+ scene_node_visibility(node, &visible);
+ }
+
+ node->enabled = enabled;
+
+ scene_node_update(node, &visible);
+}
+
+void wlr_scene_node_set_position(struct wlr_scene_node *node, int x, int y) {
+ if (node->x == x && node->y == y) {
+ return;
+ }
+
+ node->x = x;
+ node->y = y;
+ scene_node_update(node, NULL);
+}
+
+void wlr_scene_node_place_above(struct wlr_scene_node *node,
+ struct wlr_scene_node *sibling) {
+ assert(node != sibling);
+ assert(node->parent == sibling->parent);
+
+ if (node->link.prev == &sibling->link) {
+ return;
+ }
+
+ wl_list_remove(&node->link);
+ wl_list_insert(&sibling->link, &node->link);
+ scene_node_update(node, NULL);
+}
+
+void wlr_scene_node_place_below(struct wlr_scene_node *node,
+ struct wlr_scene_node *sibling) {
+ assert(node != sibling);
+ assert(node->parent == sibling->parent);
+
+ if (node->link.next == &sibling->link) {
+ return;
+ }
+
+ wl_list_remove(&node->link);
+ wl_list_insert(sibling->link.prev, &node->link);
+ scene_node_update(node, NULL);
+}
+
+void wlr_scene_node_raise_to_top(struct wlr_scene_node *node) {
+ struct wlr_scene_node *current_top = wl_container_of(
+ node->parent->children.prev, current_top, link);
+ if (node == current_top) {
+ return;
+ }
+ wlr_scene_node_place_above(node, current_top);
+}
+
+void wlr_scene_node_lower_to_bottom(struct wlr_scene_node *node) {
+ struct wlr_scene_node *current_bottom = wl_container_of(
+ node->parent->children.next, current_bottom, link);
+ if (node == current_bottom) {
+ return;
+ }
+ wlr_scene_node_place_below(node, current_bottom);
+}
+
+void wlr_scene_node_reparent(struct wlr_scene_node *node,
+ struct wlr_scene_tree *new_parent) {
+ assert(new_parent != NULL);
+
+ if (node->parent == new_parent) {
+ return;
+ }
+
+ /* Ensure that a node cannot become its own ancestor */
+ for (struct wlr_scene_tree *ancestor = new_parent; ancestor != NULL;
+ ancestor = ancestor->node.parent) {
+ assert(&ancestor->node != node);
+ }
+
+ int x, y;
+ pixman_region32_t visible;
+ pixman_region32_init(&visible);
+ if (wlr_scene_node_coords(node, &x, &y)) {
+ scene_node_visibility(node, &visible);
+ }
+
+ wl_list_remove(&node->link);
+ node->parent = new_parent;
+ wl_list_insert(new_parent->children.prev, &node->link);
+ scene_node_update(node, &visible);
+}
+
+bool wlr_scene_node_coords(struct wlr_scene_node *node,
+ int *lx_ptr, int *ly_ptr) {
+ assert(node);
+
+ int lx = 0, ly = 0;
+ bool enabled = true;
+ while (true) {
+ lx += node->x;
+ ly += node->y;
+ enabled = enabled && node->enabled;
+ if (node->parent == NULL) {
+ break;
+ }
+
+ node = &node->parent->node;
+ }
+
+ *lx_ptr = lx;
+ *ly_ptr = ly;
+ return enabled;
+}
+
+static void scene_node_for_each_scene_buffer(struct wlr_scene_node *node,
+ int lx, int ly, wlr_scene_buffer_iterator_func_t user_iterator,
+ void *user_data) {
+ if (!node->enabled) {
+ return;
+ }
+
+ lx += node->x;
+ ly += node->y;
+
+ if (node->type == WLR_SCENE_NODE_BUFFER) {
+ struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_from_node(node);
+ user_iterator(scene_buffer, lx, ly, user_data);
+ } else if (node->type == WLR_SCENE_NODE_TREE) {
+ struct wlr_scene_tree *scene_tree = wlr_scene_tree_from_node(node);
+ struct wlr_scene_node *child;
+ wl_list_for_each(child, &scene_tree->children, link) {
+ scene_node_for_each_scene_buffer(child, lx, ly, user_iterator, user_data);
+ }
+ }
+}
+
+void wlr_scene_node_for_each_buffer(struct wlr_scene_node *node,
+ wlr_scene_buffer_iterator_func_t user_iterator, void *user_data) {
+ scene_node_for_each_scene_buffer(node, 0, 0, user_iterator, user_data);
+}
+
+struct node_at_data {
+ double lx, ly;
+ double rx, ry;
+ struct wlr_scene_node *node;
+};
+
+static bool scene_node_at_iterator(struct wlr_scene_node *node,
+ int lx, int ly, void *data) {
+ struct node_at_data *at_data = data;
+
+ double rx = at_data->lx - lx;
+ double ry = at_data->ly - ly;
+
+ if (node->type == WLR_SCENE_NODE_BUFFER) {
+ struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_from_node(node);
+
+ if (scene_buffer->point_accepts_input &&
+ !scene_buffer->point_accepts_input(scene_buffer, &rx, &ry)) {
+ return false;
+ }
+ } else if (node->type == WLR_SCENE_NODE_RECT) {
+ struct wlr_scene_rect *rect = wlr_scene_rect_from_node(node);
+ if (!rect->accepts_input) {
+ return false;
+ } else if (!wlr_box_empty(&rect->clipped_region.area)
+ && wlr_box_contains_point(&rect->clipped_region.area, rx, ry)) {
+ // Inside clipped region
+ return false;
+ }
+ } else if (node->type == WLR_SCENE_NODE_SHADOW
+ || node->type == WLR_SCENE_NODE_OPTIMIZED_BLUR) {
+ // Disable interaction
+ return false;
+ }
+
+ at_data->rx = rx;
+ at_data->ry = ry;
+ at_data->node = node;
+ return true;
+}
+
+struct wlr_scene_node *wlr_scene_node_at(struct wlr_scene_node *node,
+ double lx, double ly, double *nx, double *ny) {
+ struct wlr_box box = {
+ .x = floor(lx),
+ .y = floor(ly),
+ .width = 1,
+ .height = 1
+ };
+
+ struct node_at_data data = {
+ .lx = lx,
+ .ly = ly
+ };
+
+ if (scene_nodes_in_box(node, &box, scene_node_at_iterator, &data)) {
+ if (nx) {
+ *nx = data.rx;
+ }
+ if (ny) {
+ *ny = data.ry;
+ }
+ return data.node;
+ }
+
+ return NULL;
+}
+
+struct render_list_entry {
+ struct wlr_scene_node *node;
+ bool highlight_transparent_region;
+ int x, y;
+};
+
+static void scene_entry_render(struct render_list_entry *entry, const struct render_data *data) {
+ struct wlr_scene_node *node = entry->node;
+
+ pixman_region32_t render_region;
+ pixman_region32_init(&render_region);
+ pixman_region32_copy(&render_region, &node->visible);
+ pixman_region32_translate(&render_region, -data->logical.x, -data->logical.y);
+ logical_to_buffer_coords(&render_region, data, true);
+ pixman_region32_intersect(&render_region, &render_region, &data->damage);
+ if (pixman_region32_empty(&render_region)) {
+ pixman_region32_fini(&render_region);
+ return;
+ }
+
+ int x = entry->x - data->logical.x;
+ int y = entry->y - data->logical.y;
+
+ struct wlr_box dst_box = {
+ .x = x,
+ .y = y,
+ };
+ scene_node_get_size(node, &dst_box.width, &dst_box.height);
+ transform_output_box(&dst_box, data);
+
+ pixman_region32_t opaque;
+ pixman_region32_init(&opaque);
+ scene_node_opaque_region(node, x, y, &opaque);
+ logical_to_buffer_coords(&opaque, data, false);
+ pixman_region32_subtract(&opaque, &render_region, &opaque);
+
+ enum wl_output_transform node_transform =
+ wlr_output_transform_compose(WL_OUTPUT_TRANSFORM_NORMAL, data->transform);
+
+ struct wlr_scene *scene = data->output->scene;
+ switch (node->type) {
+ case WLR_SCENE_NODE_TREE:
+ assert(false);
+ break;
+ case WLR_SCENE_NODE_RECT:;
+ struct wlr_scene_rect *scene_rect = wlr_scene_rect_from_node(node);
+ enum corner_location rect_corners = scene_rect->corners;
+
+ corner_location_transform(node_transform, &rect_corners);
+
+ // blur
+ bool has_alpha = !pixman_region32_empty(&opaque);
+ if (has_alpha && scene_rect->backdrop_blur &&
+ is_scene_blur_enabled(&scene->blur_data)) {
+ pixman_region32_t opaque_region;
+ pixman_region32_init(&opaque_region);
+ scene_node_opaque_region(node, x, y, &opaque_region);
+ logical_to_buffer_coords(&opaque_region, data, false);
+
+ /* TODO: should this be configurable? Borked when not 1.0, probably due to
+ lack of premultiplication in the frag shader
+ */
+ float blur_alpha = 1.0;
+
+ struct fx_render_blur_pass_options blur_options = {
+ .tex_options = {
+ .base = (struct wlr_render_texture_options) {
+ .texture = NULL,
+ .src_box = (struct wlr_fbox){0},
+ .dst_box = dst_box,
+ .transform = WL_OUTPUT_TRANSFORM_NORMAL,
+ .clip = &render_region,
+ .alpha = &blur_alpha,
+ .filter_mode = WLR_SCALE_FILTER_BILINEAR,
+ .blend_mode = WLR_RENDER_BLEND_MODE_PREMULTIPLIED,
+ },
+ .clip_box = &dst_box,
+ .corner_radius = scene_rect->corner_radius * data->scale,
+ .corners = rect_corners,
+ .discard_transparent = false,
+ },
+ .opaque_region = &opaque_region,
+ .use_optimized_blur = scene_rect->backdrop_blur_optimized,
+ .blur_data = &scene->blur_data,
+ .ignore_transparent = false,
+ };
+ // Render the actual blur behind the surface
+ fx_render_pass_add_blur(data->render_pass, &blur_options);
+
+ pixman_region32_fini(&opaque_region);
+ }
+
+ struct wlr_box rect_clipped_region_box = scene_rect->clipped_region.area;
+ int rect_clipped_region_corner_radius = scene_rect->clipped_region.corner_radius;
+ enum corner_location rect_clipped_corners = scene_rect->clipped_region.corners;
+
+ // Node relative -> Root relative
+ rect_clipped_region_box.x += x;
+ rect_clipped_region_box.y += y;
+
+ transform_output_box(&rect_clipped_region_box, data);
+ corner_location_transform(node_transform, &rect_clipped_corners);
+
+ struct fx_render_rect_options rect_options = {
+ .base = {
+ .box = dst_box,
+ .color = {
+ .r = scene_rect->color[0],
+ .g = scene_rect->color[1],
+ .b = scene_rect->color[2],
+ .a = scene_rect->color[3],
+ },
+ .clip = &render_region,
+ },
+ .clipped_region = {
+ .area = rect_clipped_region_box,
+ .corner_radius = rect_clipped_region_corner_radius * data->scale,
+ .corners = rect_clipped_corners,
+ },
+ };
+
+ if ((scene_rect->corner_radius && rect_corners != CORNER_LOCATION_NONE) || scene_rect->fade_inset > 0) {
+ struct fx_render_rounded_rect_options rounded_rect_options = {
+ .base = rect_options.base,
+ .corner_radius = scene_rect->corner_radius * data->scale,
+ .corners = rect_corners,
+ .clipped_region = rect_options.clipped_region,
+ .fade_inset = scene_rect->fade_inset * data->scale,
+ };
+ fx_render_pass_add_rounded_rect(data->render_pass, &rounded_rect_options);
+ } else {
+ fx_render_pass_add_rect(data->render_pass, &rect_options);
+ }
+ break;
+ case WLR_SCENE_NODE_OPTIMIZED_BLUR:;
+ struct wlr_scene_optimized_blur *scene_blur = wlr_scene_optimized_blur_from_node(node);
+ // Re-render the optimized blur buffer when needed
+ if (data->has_blur && is_scene_blur_enabled(&scene->blur_data)
+ && scene_blur->dirty) {
+ const float opacity = 1.0f;
+ enum wl_output_transform transform =
+ wlr_output_transform_invert(data->transform);
+ transform = wlr_output_transform_compose(transform, data->transform);
+ struct fx_render_blur_pass_options blur_options = {
+ .tex_options = {
+ .base = {
+ .transform = transform,
+ .alpha = &opacity,
+ .blend_mode = WLR_RENDER_BLEND_MODE_NONE,
+ .dst_box = dst_box,
+ },
+ .clip_box = &dst_box,
+ .corner_radius = 0,
+ .discard_transparent = false,
+ },
+ .blur_data = &scene->blur_data,
+ };
+ bool result = fx_render_pass_add_optimized_blur(data->render_pass, &blur_options);
+ if (result) {
+ scene_blur->dirty = false;
+ }
+ }
+ break;
+ case WLR_SCENE_NODE_SHADOW:;
+ struct wlr_scene_shadow *scene_shadow = wlr_scene_shadow_from_node(node);
+
+ struct wlr_box shadow_clipped_region_box = scene_shadow->clipped_region.area;
+ int shadow_clipped_region_corner_radius = scene_shadow->clipped_region.corner_radius;
+ enum corner_location shadow_clipped_corners = scene_shadow->clipped_region.corners;
+
+ // Node relative -> Root relative
+ shadow_clipped_region_box.x += x;
+ shadow_clipped_region_box.y += y;
+
+ transform_output_box(&shadow_clipped_region_box, data);
+ corner_location_transform(node_transform, &shadow_clipped_corners);
+
+ struct fx_render_box_shadow_options shadow_options = {
+ .box = dst_box,
+ .clipped_region = {
+ .area = shadow_clipped_region_box,
+ .corner_radius = shadow_clipped_region_corner_radius * data->scale,
+ .corners = shadow_clipped_corners,
+ },
+ .blur_sigma = scene_shadow->blur_sigma,
+ .corner_radius = scene_shadow->corner_radius * data->scale,
+ .color = {
+ .r = scene_shadow->color[0],
+ .g = scene_shadow->color[1],
+ .b = scene_shadow->color[2],
+ .a = scene_shadow->color[3],
+ },
+ .clip = &render_region,
+ };
+ fx_render_pass_add_box_shadow(data->render_pass, &shadow_options);
+ break;
+ case WLR_SCENE_NODE_BUFFER:;
+ struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_from_node(node);
+ enum corner_location buffer_corners = scene_buffer->corners;
+
+ if (scene_buffer->is_single_pixel_buffer) {
+ // Render the buffer as a rect, this is likely to be more efficient
+ wlr_render_pass_add_rect(&data->render_pass->base, &(struct wlr_render_rect_options){
+ .box = dst_box,
+ .color = {
+ .r = (float)scene_buffer->single_pixel_buffer_color[0] / (float)UINT32_MAX,
+ .g = (float)scene_buffer->single_pixel_buffer_color[1] / (float)UINT32_MAX,
+ .b = (float)scene_buffer->single_pixel_buffer_color[2] / (float)UINT32_MAX,
+ .a = (float)scene_buffer->single_pixel_buffer_color[3] /
+ (float)UINT32_MAX * scene_buffer->opacity,
+ },
+ .clip = &render_region,
+ });
+ break;
+ }
+
+ struct wlr_texture *texture = scene_buffer_get_texture(scene_buffer,
+ data->output->output->renderer);
+ if (texture == NULL) {
+ scene_output_damage(data->output, &render_region);
+ break;
+ }
+
+ enum wl_output_transform transform =
+ wlr_output_transform_invert(scene_buffer->transform);
+ transform = wlr_output_transform_compose(transform, data->transform);
+ corner_location_transform(transform, &buffer_corners);
+
+ // Blur
+ if (scene_buffer->backdrop_blur && is_scene_blur_enabled(&scene->blur_data)) {
+ pixman_region32_t opaque_region;
+ pixman_region32_init(&opaque_region);
+
+ bool has_alpha = !pixman_region32_empty(&opaque);
+ scene_node_opaque_region(node, x, y, &opaque_region);
+ logical_to_buffer_coords(&opaque_region, data, false);
+
+ if (has_alpha) {
+ // Translate the opaque_region by the potential clipping offset.
+ // Fixes GTK CSD offsetting the opaque_region
+ pixman_region32_translate(&opaque_region,
+ -scene_buffer->src_box.x, -scene_buffer->src_box.y);
+
+ // TODO: should I be configurable? We should probably move blur to a node
+ float blur_alpha = 1.0;
+ struct fx_render_blur_pass_options blur_options = {
+ .tex_options = {
+ .base = (struct wlr_render_texture_options) {
+ .texture = texture,
+ .src_box = scene_buffer->src_box,
+ .dst_box = dst_box,
+ .transform = WL_OUTPUT_TRANSFORM_NORMAL,
+ .clip = &render_region, // Render with the smaller region, clipping CSD
+ .alpha = &blur_alpha,
+ .filter_mode = WLR_SCALE_FILTER_BILINEAR,
+ },
+ .clip_box = &dst_box,
+ .corner_radius = scene_buffer->corner_radius * data->scale,
+ .corners = buffer_corners,
+ .discard_transparent = false,
+ },
+ .opaque_region = &opaque_region,
+ .use_optimized_blur = scene_buffer->backdrop_blur_optimized,
+ .blur_data = &scene->blur_data,
+ .ignore_transparent = scene_buffer->backdrop_blur_ignore_transparent,
+ };
+ // Render the actual blur behind the surface
+ fx_render_pass_add_blur(data->render_pass, &blur_options);
+
+ }
+ pixman_region32_fini(&opaque_region);
+ }
+
+ struct fx_render_texture_options tex_options = {
+ .base = (struct wlr_render_texture_options){
+ .texture = texture,
+ .src_box = scene_buffer->src_box,
+ .dst_box = dst_box,
+ .transform = transform,
+ .clip = &render_region, // Render with the smaller region, clipping CSD
+ .alpha = &scene_buffer->opacity,
+ .filter_mode = scene_buffer->filter_mode,
+ .blend_mode = !data->output->scene->calculate_visibility ||
+ !pixman_region32_empty(&opaque) ?
+ WLR_RENDER_BLEND_MODE_PREMULTIPLIED : WLR_RENDER_BLEND_MODE_NONE,
+ .wait_timeline = scene_buffer->wait_timeline,
+ .wait_point = scene_buffer->wait_point,
+ },
+ .clip_box = &dst_box,
+ .corners = buffer_corners,
+ .corner_radius = scene_buffer->corner_radius * data->scale,
+ };
+
+ fx_render_pass_add_texture(data->render_pass, &tex_options);
+
+ struct wlr_scene_output_sample_event sample_event = {
+ .output = data->output,
+ .direct_scanout = false,
+ };
+ wl_signal_emit_mutable(&scene_buffer->events.output_sample, &sample_event);
+ if (entry->highlight_transparent_region) {
+ wlr_render_pass_add_rect(&data->render_pass->base, &(struct wlr_render_rect_options){
+ .box = dst_box,
+ .color = { .r = 0, .g = 0.3, .b = 0, .a = 0.3 },
+ .clip = &opaque,
+ });
+ }
+ break;
+ }
+
+ pixman_region32_fini(&opaque);
+ pixman_region32_fini(&render_region);
+}
+
+static void scene_handle_linux_dmabuf_v1_destroy(struct wl_listener *listener,
+ void *data) {
+ struct wlr_scene *scene =
+ wl_container_of(listener, scene, linux_dmabuf_v1_destroy);
+ wl_list_remove(&scene->linux_dmabuf_v1_destroy.link);
+ wl_list_init(&scene->linux_dmabuf_v1_destroy.link);
+ scene->linux_dmabuf_v1 = NULL;
+}
+
+void wlr_scene_set_linux_dmabuf_v1(struct wlr_scene *scene,
+ struct wlr_linux_dmabuf_v1 *linux_dmabuf_v1) {
+ assert(scene->linux_dmabuf_v1 == NULL);
+ scene->linux_dmabuf_v1 = linux_dmabuf_v1;
+ scene->linux_dmabuf_v1_destroy.notify = scene_handle_linux_dmabuf_v1_destroy;
+ wl_signal_add(&linux_dmabuf_v1->events.destroy, &scene->linux_dmabuf_v1_destroy);
+}
+
+static void scene_handle_gamma_control_manager_v1_set_gamma(struct wl_listener *listener,
+ void *data) {
+ const struct wlr_gamma_control_manager_v1_set_gamma_event *event = data;
+ struct wlr_scene *scene =
+ wl_container_of(listener, scene, gamma_control_manager_v1_set_gamma);
+ struct wlr_scene_output *output = wlr_scene_get_scene_output(scene, event->output);
+ if (!output) {
+ // this scene might not own this output.
+ return;
+ }
+
+ output->gamma_lut_changed = true;
+ output->gamma_lut = event->control;
+ wlr_output_schedule_frame(output->output);
+}
+
+static void scene_handle_gamma_control_manager_v1_destroy(struct wl_listener *listener,
+ void *data) {
+ struct wlr_scene *scene =
+ wl_container_of(listener, scene, gamma_control_manager_v1_destroy);
+ wl_list_remove(&scene->gamma_control_manager_v1_destroy.link);
+ wl_list_init(&scene->gamma_control_manager_v1_destroy.link);
+ wl_list_remove(&scene->gamma_control_manager_v1_set_gamma.link);
+ wl_list_init(&scene->gamma_control_manager_v1_set_gamma.link);
+ scene->gamma_control_manager_v1 = NULL;
+
+ struct wlr_scene_output *output;
+ wl_list_for_each(output, &scene->outputs, link) {
+ output->gamma_lut_changed = false;
+ output->gamma_lut = NULL;
+ }
+}
+
+void wlr_scene_set_gamma_control_manager_v1(struct wlr_scene *scene,
+ struct wlr_gamma_control_manager_v1 *gamma_control) {
+ assert(scene->gamma_control_manager_v1 == NULL);
+ scene->gamma_control_manager_v1 = gamma_control;
+
+ scene->gamma_control_manager_v1_destroy.notify =
+ scene_handle_gamma_control_manager_v1_destroy;
+ wl_signal_add(&gamma_control->events.destroy, &scene->gamma_control_manager_v1_destroy);
+ scene->gamma_control_manager_v1_set_gamma.notify =
+ scene_handle_gamma_control_manager_v1_set_gamma;
+ wl_signal_add(&gamma_control->events.set_gamma, &scene->gamma_control_manager_v1_set_gamma);
+}
+
+static void scene_output_handle_destroy(struct wlr_addon *addon) {
+ struct wlr_scene_output *scene_output =
+ wl_container_of(addon, scene_output, addon);
+ wlr_scene_output_destroy(scene_output);
+}
+
+static const struct wlr_addon_interface output_addon_impl = {
+ .name = "wlr_scene_output",
+ .destroy = scene_output_handle_destroy,
+};
+
+static void scene_node_output_update(struct wlr_scene_node *node,
+ struct wl_list *outputs, struct wlr_scene_output *ignore,
+ struct wlr_scene_output *force) {
+ if (node->type == WLR_SCENE_NODE_TREE) {
+ struct wlr_scene_tree *scene_tree = wlr_scene_tree_from_node(node);
+ struct wlr_scene_node *child;
+ wl_list_for_each(child, &scene_tree->children, link) {
+ scene_node_output_update(child, outputs, ignore, force);
+ }
+ return;
+ }
+
+ update_node_update_outputs(node, outputs, ignore, force);
+}
+
+static void scene_output_update_geometry(struct wlr_scene_output *scene_output,
+ bool force_update) {
+ scene_output_damage_whole(scene_output);
+
+ scene_node_output_update(&scene_output->scene->tree.node,
+ &scene_output->scene->outputs, NULL, force_update ? scene_output : NULL);
+}
+
+static void scene_output_handle_commit(struct wl_listener *listener, void *data) {
+ struct wlr_scene_output *scene_output = wl_container_of(listener,
+ scene_output, output_commit);
+ struct wlr_output_event_commit *event = data;
+ const struct wlr_output_state *state = event->state;
+
+ // if the output has been committed with a certain damage, we know that region
+ // will be acknowledged by the backend so we don't need to keep track of it
+ // anymore
+ if (state->committed & WLR_OUTPUT_STATE_BUFFER) {
+ if (state->committed & WLR_OUTPUT_STATE_DAMAGE) {
+ pixman_region32_subtract(&scene_output->pending_commit_damage,
+ &scene_output->pending_commit_damage, &state->damage);
+ } else {
+ pixman_region32_fini(&scene_output->pending_commit_damage);
+ pixman_region32_init(&scene_output->pending_commit_damage);
+ }
+ }
+
+ bool force_update = state->committed & (
+ WLR_OUTPUT_STATE_TRANSFORM |
+ WLR_OUTPUT_STATE_SCALE |
+ WLR_OUTPUT_STATE_SUBPIXEL);
+
+ if (force_update || state->committed & (WLR_OUTPUT_STATE_MODE |
+ WLR_OUTPUT_STATE_ENABLED)) {
+ scene_output_update_geometry(scene_output, force_update);
+ }
+
+ if (scene_output->scene->debug_damage_option == WLR_SCENE_DEBUG_DAMAGE_HIGHLIGHT &&
+ !wl_list_empty(&scene_output->damage_highlight_regions)) {
+ wlr_output_schedule_frame(scene_output->output);
+ }
+
+ // Next time the output is enabled, try to re-apply the gamma LUT
+ if (scene_output->scene->gamma_control_manager_v1 &&
+ (state->committed & WLR_OUTPUT_STATE_ENABLED) &&
+ !scene_output->output->enabled) {
+ scene_output->gamma_lut_changed = true;
+ }
+}
+
+static void scene_output_handle_damage(struct wl_listener *listener, void *data) {
+ struct wlr_scene_output *scene_output = wl_container_of(listener,
+ scene_output, output_damage);
+ struct wlr_output *output = scene_output->output;
+ struct wlr_output_event_damage *event = data;
+
+ int width, height;
+ wlr_output_transformed_resolution(output, &width, &height);
+
+ pixman_region32_t damage;
+ pixman_region32_init(&damage);
+ pixman_region32_copy(&damage, event->damage);
+ wlr_region_transform(&damage, &damage,
+ wlr_output_transform_invert(output->transform), width, height);
+ scene_output_damage(scene_output, &damage);
+ pixman_region32_fini(&damage);
+}
+
+static void scene_output_handle_needs_frame(struct wl_listener *listener, void *data) {
+ struct wlr_scene_output *scene_output = wl_container_of(listener,
+ scene_output, output_needs_frame);
+ wlr_output_schedule_frame(scene_output->output);
+}
+
+struct wlr_scene_output *wlr_scene_output_create(struct wlr_scene *scene,
+ struct wlr_output *output) {
+ struct wlr_scene_output *scene_output = calloc(1, sizeof(*scene_output));
+ if (scene_output == NULL) {
+ return NULL;
+ }
+
+ scene_output->output = output;
+ scene_output->scene = scene;
+ wlr_addon_init(&scene_output->addon, &output->addons, scene, &output_addon_impl);
+
+ wlr_damage_ring_init(&scene_output->damage_ring);
+ pixman_region32_init(&scene_output->pending_commit_damage);
+ wl_list_init(&scene_output->damage_highlight_regions);
+
+ int prev_output_index = -1;
+ struct wl_list *prev_output_link = &scene->outputs;
+
+ struct wlr_scene_output *current_output;
+ wl_list_for_each(current_output, &scene->outputs, link) {
+ if (prev_output_index + 1 != current_output->index) {
+ break;
+ }
+
+ prev_output_index = current_output->index;
+ prev_output_link = ¤t_output->link;
+ }
+
+ int drm_fd = wlr_backend_get_drm_fd(output->backend);
+ if (drm_fd >= 0 && output->backend->features.timeline &&
+ output->renderer != NULL && output->renderer->features.timeline) {
+ scene_output->in_timeline = wlr_drm_syncobj_timeline_create(drm_fd);
+ if (scene_output->in_timeline == NULL) {
+ return NULL;
+ }
+ }
+
+ scene_output->index = prev_output_index + 1;
+ assert(scene_output->index < 64);
+ wl_list_insert(prev_output_link, &scene_output->link);
+
+ wl_signal_init(&scene_output->events.destroy);
+
+ scene_output->output_commit.notify = scene_output_handle_commit;
+ wl_signal_add(&output->events.commit, &scene_output->output_commit);
+
+ scene_output->output_damage.notify = scene_output_handle_damage;
+ wl_signal_add(&output->events.damage, &scene_output->output_damage);
+
+ scene_output->output_needs_frame.notify = scene_output_handle_needs_frame;
+ wl_signal_add(&output->events.needs_frame, &scene_output->output_needs_frame);
+
+ scene_output_update_geometry(scene_output, false);
+
+ return scene_output;
+}
+
+static void highlight_region_destroy(struct highlight_region *damage) {
+ wl_list_remove(&damage->link);
+ pixman_region32_fini(&damage->region);
+ free(damage);
+}
+
+void wlr_scene_output_destroy(struct wlr_scene_output *scene_output) {
+ if (scene_output == NULL) {
+ return;
+ }
+
+ wl_signal_emit_mutable(&scene_output->events.destroy, NULL);
+
+ scene_node_output_update(&scene_output->scene->tree.node,
+ &scene_output->scene->outputs, scene_output, NULL);
+
+ assert(wl_list_empty(&scene_output->events.destroy.listener_list));
+
+ struct highlight_region *damage, *tmp_damage;
+ wl_list_for_each_safe(damage, tmp_damage, &scene_output->damage_highlight_regions, link) {
+ highlight_region_destroy(damage);
+ }
+
+ wlr_addon_finish(&scene_output->addon);
+ wlr_damage_ring_finish(&scene_output->damage_ring);
+ pixman_region32_fini(&scene_output->pending_commit_damage);
+ wl_list_remove(&scene_output->link);
+ wl_list_remove(&scene_output->output_commit.link);
+ wl_list_remove(&scene_output->output_damage.link);
+ wl_list_remove(&scene_output->output_needs_frame.link);
+ wlr_drm_syncobj_timeline_unref(scene_output->in_timeline);
+ wl_array_release(&scene_output->render_list);
+ free(scene_output);
+}
+
+struct wlr_scene_output *wlr_scene_get_scene_output(struct wlr_scene *scene,
+ struct wlr_output *output) {
+ struct wlr_addon *addon =
+ wlr_addon_find(&output->addons, scene, &output_addon_impl);
+ if (addon == NULL) {
+ return NULL;
+ }
+ struct wlr_scene_output *scene_output =
+ wl_container_of(addon, scene_output, addon);
+ return scene_output;
+}
+
+void wlr_scene_output_set_position(struct wlr_scene_output *scene_output,
+ int lx, int ly) {
+ if (scene_output->x == lx && scene_output->y == ly) {
+ return;
+ }
+
+ scene_output->x = lx;
+ scene_output->y = ly;
+
+ scene_output_update_geometry(scene_output, false);
+}
+
+static bool scene_node_invisible(struct wlr_scene_node *node) {
+ if (node->type == WLR_SCENE_NODE_TREE) {
+ return true;
+ } else if (node->type == WLR_SCENE_NODE_RECT) {
+ struct wlr_scene_rect *rect = wlr_scene_rect_from_node(node);
+
+ return rect->color[3] == 0.f;
+ } else if (node->type == WLR_SCENE_NODE_SHADOW) {
+ struct wlr_scene_shadow *shadow = wlr_scene_shadow_from_node(node);
+
+ return shadow->color[3] == 0.f;
+ } else if (node->type == WLR_SCENE_NODE_OPTIMIZED_BLUR) {
+ return false;
+ } else if (node->type == WLR_SCENE_NODE_BUFFER) {
+ struct wlr_scene_buffer *buffer = wlr_scene_buffer_from_node(node);
+
+ return buffer->buffer == NULL && buffer->texture == NULL;
+ }
+
+ return false;
+}
+
+struct render_list_constructor_data {
+ struct wlr_box box;
+ struct wl_array *render_list;
+ bool calculate_visibility;
+ bool highlight_transparent_region;
+ bool fractional_scale;
+};
+
+static bool scene_buffer_is_black_opaque(struct wlr_scene_buffer *scene_buffer) {
+ return scene_buffer->is_single_pixel_buffer &&
+ scene_buffer->single_pixel_buffer_color[0] == 0 &&
+ scene_buffer->single_pixel_buffer_color[1] == 0 &&
+ scene_buffer->single_pixel_buffer_color[2] == 0 &&
+ scene_buffer->single_pixel_buffer_color[3] == UINT32_MAX &&
+ scene_buffer->opacity == 1.0;
+}
+
+static bool construct_render_list_iterator(struct wlr_scene_node *node,
+ int lx, int ly, void *_data) {
+ struct render_list_constructor_data *data = _data;
+
+ if (scene_node_invisible(node)) {
+ return false;
+ }
+
+ // While rendering, the background should always be black. If we see a
+ // black rect, we can ignore rendering everything under the rect, and
+ // unless fractional scale is used even the rect itself (to avoid running
+ // into issues regarding damage region expansion).
+ if (node->type == WLR_SCENE_NODE_RECT && data->calculate_visibility &&
+ (!data->fractional_scale || data->render_list->size == 0)) {
+ struct wlr_scene_rect *rect = wlr_scene_rect_from_node(node);
+ float *black = (float[4]){ 0.f, 0.f, 0.f, 1.f };
+
+ if (memcmp(rect->color, black, sizeof(float) * 4) == 0) {
+ return false;
+ }
+ }
+
+ // Apply the same special-case to black opaque single-pixel buffers
+ if (node->type == WLR_SCENE_NODE_BUFFER && data->calculate_visibility &&
+ (!data->fractional_scale || data->render_list->size == 0)) {
+ struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_from_node(node);
+
+ if (scene_buffer_is_black_opaque(scene_buffer)) {
+ return false;
+ }
+ }
+
+ pixman_region32_t intersection;
+ pixman_region32_init(&intersection);
+ pixman_region32_intersect_rect(&intersection, &node->visible,
+ data->box.x, data->box.y,
+ data->box.width, data->box.height);
+ if (pixman_region32_empty(&intersection)) {
+ pixman_region32_fini(&intersection);
+ return false;
+ }
+
+ pixman_region32_fini(&intersection);
+
+ struct render_list_entry *entry = wl_array_add(data->render_list, sizeof(*entry));
+ if (!entry) {
+ return false;
+ }
+
+ *entry = (struct render_list_entry){
+ .node = node,
+ .x = lx,
+ .y = ly,
+ .highlight_transparent_region = data->highlight_transparent_region,
+ };
+
+ return false;
+}
+
+static void scene_buffer_send_dmabuf_feedback(const struct wlr_scene *scene,
+ struct wlr_scene_buffer *scene_buffer,
+ const struct wlr_linux_dmabuf_feedback_v1_init_options *options) {
+ if (!scene->linux_dmabuf_v1) {
+ return;
+ }
+
+ struct wlr_scene_surface *surface = wlr_scene_surface_try_from_buffer(scene_buffer);
+ if (!surface) {
+ return;
+ }
+
+ // compare to the previous options so that we don't send
+ // duplicate feedback events.
+ if (memcmp(options, &scene_buffer->prev_feedback_options, sizeof(*options)) == 0) {
+ return;
+ }
+
+ scene_buffer->prev_feedback_options = *options;
+
+ struct wlr_linux_dmabuf_feedback_v1 feedback = {0};
+ if (!wlr_linux_dmabuf_feedback_v1_init_with_options(&feedback, options)) {
+ return;
+ }
+
+ wlr_linux_dmabuf_v1_set_surface_feedback(scene->linux_dmabuf_v1,
+ surface->surface, &feedback);
+
+ wlr_linux_dmabuf_feedback_v1_finish(&feedback);
+}
+
+enum scene_direct_scanout_result {
+ // This scene node is not a candidate for scanout
+ SCANOUT_INELIGIBLE,
+
+ // This scene node is a candidate for scanout, but is currently
+ // incompatible due to e.g. buffer mismatch, and if possible we'd like to
+ // resolve this incompatibility.
+ SCANOUT_CANDIDATE,
+
+ // Scanout is successful.
+ SCANOUT_SUCCESS,
+};
+
+static enum scene_direct_scanout_result scene_entry_try_direct_scanout(
+ struct render_list_entry *entry, struct wlr_output_state *state,
+ const struct render_data *data) {
+ struct wlr_scene_output *scene_output = data->output;
+ struct wlr_scene_node *node = entry->node;
+
+ if (!scene_output->scene->direct_scanout) {
+ return SCANOUT_INELIGIBLE;
+ }
+
+ if (node->type != WLR_SCENE_NODE_BUFFER) {
+ return SCANOUT_INELIGIBLE;
+ }
+
+ if (state->committed & (WLR_OUTPUT_STATE_MODE |
+ WLR_OUTPUT_STATE_ENABLED |
+ WLR_OUTPUT_STATE_RENDER_FORMAT)) {
+ // Legacy DRM will explode if we try to modeset with a direct scanout buffer
+ return SCANOUT_INELIGIBLE;
+ }
+
+ if (!wlr_output_is_direct_scanout_allowed(scene_output->output)) {
+ return SCANOUT_INELIGIBLE;
+ }
+
+ struct wlr_scene_buffer *buffer = wlr_scene_buffer_from_node(node);
+ if (buffer->buffer == NULL) {
+ return SCANOUT_INELIGIBLE;
+ }
+
+ // The native size of the buffer after any transform is applied
+ int default_width = buffer->buffer->width;
+ int default_height = buffer->buffer->height;
+ wlr_output_transform_coords(buffer->transform, &default_width, &default_height);
+ struct wlr_fbox default_box = {
+ .width = default_width,
+ .height = default_height,
+ };
+
+ if (buffer->transform != data->transform) {
+ return SCANOUT_INELIGIBLE;
+ }
+
+ // We want to ensure optimal buffer selection, but as direct-scanout can be enabled and disabled
+ // on a frame-by-frame basis, we wait for a few frames to send the new format recommendations.
+ // Maybe we should only send feedback in this case if tests fail.
+ if (scene_output->dmabuf_feedback_debounce >= DMABUF_FEEDBACK_DEBOUNCE_FRAMES
+ && buffer->primary_output == scene_output) {
+ struct wlr_linux_dmabuf_feedback_v1_init_options options = {
+ .main_renderer = scene_output->output->renderer,
+ .scanout_primary_output = scene_output->output,
+ };
+
+ scene_buffer_send_dmabuf_feedback(scene_output->scene, buffer, &options);
+ }
+
+ struct wlr_output_state pending;
+ wlr_output_state_init(&pending);
+ if (!wlr_output_state_copy(&pending, state)) {
+ return SCANOUT_CANDIDATE;
+ }
+
+ if (!wlr_fbox_empty(&buffer->src_box) &&
+ !wlr_fbox_equal(&buffer->src_box, &default_box)) {
+ pending.buffer_src_box = buffer->src_box;
+ }
+
+ // Translate the position from scene coordinates to output coordinates
+ pending.buffer_dst_box.x = entry->x - scene_output->x;
+ pending.buffer_dst_box.y = entry->y - scene_output->y;
+
+ scene_node_get_size(node, &pending.buffer_dst_box.width, &pending.buffer_dst_box.height);
+ transform_output_box(&pending.buffer_dst_box, data);
+
+ struct wlr_buffer *wlr_buffer = buffer->buffer;
+ struct wlr_client_buffer *client_buffer = wlr_client_buffer_get(wlr_buffer);
+ if (client_buffer != NULL && client_buffer->source != NULL && client_buffer->source->n_locks > 0) {
+ wlr_buffer = client_buffer->source;
+ }
+
+ wlr_output_state_set_buffer(&pending, wlr_buffer);
+ if (buffer->wait_timeline != NULL) {
+ wlr_output_state_set_wait_timeline(&pending, buffer->wait_timeline, buffer->wait_point);
+ }
+ if (!wlr_output_test_state(scene_output->output, &pending)) {
+ wlr_output_state_finish(&pending);
+ return SCANOUT_CANDIDATE;
+ }
+
+ wlr_output_state_copy(state, &pending);
+ wlr_output_state_finish(&pending);
+
+ struct wlr_scene_output_sample_event sample_event = {
+ .output = scene_output,
+ .direct_scanout = true,
+ };
+ wl_signal_emit_mutable(&buffer->events.output_sample, &sample_event);
+ return SCANOUT_SUCCESS;
+}
+
+bool wlr_scene_output_needs_frame(struct wlr_scene_output *scene_output) {
+ return scene_output->output->needs_frame ||
+ !pixman_region32_empty(&scene_output->pending_commit_damage) ||
+ scene_output->gamma_lut_changed;
+}
+
+static void apply_blur_region(struct wlr_scene_node *node,
+ struct wlr_scene_output *scene_output, pixman_region32_t *blur_region) {
+ int x, y;
+ wlr_scene_node_coords(node, &x, &y);
+
+ pixman_region32_t opaque_region;
+ pixman_region32_init(&opaque_region);
+ scene_node_opaque_region(node, x, y, &opaque_region);
+ // Add the buffer to the blur_region if it's not fully opaque
+ if (pixman_region32_empty(&opaque_region)) {
+ struct wlr_box node_box = {
+ .x = x - scene_output->x,
+ .y = y - scene_output->y,
+ };
+ scene_node_get_size(node, &node_box.width, &node_box.height);
+
+ struct wlr_output *output = scene_output->output;
+
+ int output_width, output_height;
+ wlr_output_transformed_resolution(output, &output_width, &output_height);
+
+ // Transform the box back to regular un-transformed units
+ scale_box(&node_box, output->scale);
+ wlr_box_transform(&node_box, &node_box,
+ wlr_output_transform_invert(output->transform),
+ output_width, output_height);
+
+ pixman_region32_union_rect(blur_region, blur_region,
+ node_box.x,
+ node_box.y,
+ node_box.width, node_box.height);
+ }
+ pixman_region32_fini(&opaque_region);
+}
+
+static bool scene_output_has_blur(int list_len,
+ struct render_list_entry *list_data, struct wlr_scene_output *scene_output,
+ pixman_region32_t *blur_region) {
+ if (scene_output->scene->blur_data.radius <= 0 ||
+ scene_output->scene->blur_data.num_passes <= 0) {
+ return false;
+ }
+
+ for (int i = list_len - 1; i >= 0; i--) {
+ struct wlr_scene_node *node = list_data[i].node;
+ switch (node->type) {
+ case WLR_SCENE_NODE_BUFFER:;
+ struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_from_node(node);
+ if (scene_buffer->backdrop_blur) {
+ apply_blur_region(node, scene_output, blur_region);
+ }
+ break;
+ case WLR_SCENE_NODE_RECT:;
+ struct wlr_scene_rect *scene_rect = wlr_scene_rect_from_node(node);
+ if (scene_rect->backdrop_blur) {
+ apply_blur_region(node, scene_output, blur_region);
+ }
+ break;
+ case WLR_SCENE_NODE_OPTIMIZED_BLUR:;
+ struct wlr_scene_optimized_blur *scene_blur = wlr_scene_optimized_blur_from_node(node);
+ if (scene_blur->dirty) {
+ apply_blur_region(node, scene_output, blur_region);
+ }
+ break;
+ default:
+ // TODO: Add support for other node types
+ break;
+ }
+ }
+ return !pixman_region32_empty(blur_region);
+}
+
+bool wlr_scene_output_commit(struct wlr_scene_output *scene_output,
+ const struct wlr_scene_output_state_options *options) {
+ if (!wlr_scene_output_needs_frame(scene_output)) {
+ return true;
+ }
+
+ bool ok = false;
+ struct wlr_output_state state;
+ wlr_output_state_init(&state);
+ if (!wlr_scene_output_build_state(scene_output, &state, options)) {
+ goto out;
+ }
+
+ ok = wlr_output_commit_state(scene_output->output, &state);
+ if (!ok) {
+ goto out;
+ }
+
+out:
+ wlr_output_state_finish(&state);
+ return ok;
+}
+
+static void scene_output_state_attempt_gamma(struct wlr_scene_output *scene_output,
+ struct wlr_output_state *state) {
+ if (!scene_output->gamma_lut_changed) {
+ return;
+ }
+
+ struct wlr_output_state gamma_pending = {0};
+ if (!wlr_output_state_copy(&gamma_pending, state)) {
+ return;
+ }
+
+ if (!wlr_gamma_control_v1_apply(scene_output->gamma_lut, &gamma_pending)) {
+ wlr_output_state_finish(&gamma_pending);
+ return;
+ }
+
+ scene_output->gamma_lut_changed = false;
+ if (!wlr_output_test_state(scene_output->output, &gamma_pending)) {
+ wlr_gamma_control_v1_send_failed_and_destroy(scene_output->gamma_lut);
+
+ scene_output->gamma_lut = NULL;
+ wlr_output_state_finish(&gamma_pending);
+ return;
+ }
+
+ wlr_output_state_copy(state, &gamma_pending);
+ wlr_output_state_finish(&gamma_pending);
+}
+
+bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output,
+ struct wlr_output_state *state, const struct wlr_scene_output_state_options *options) {
+ struct wlr_scene_output_state_options default_options = {0};
+ if (!options) {
+ options = &default_options;
+ }
+ struct wlr_scene_timer *timer = options->timer;
+ struct timespec start_time;
+ if (timer) {
+ clock_gettime(CLOCK_MONOTONIC, &start_time);
+ wlr_scene_timer_finish(timer);
+ *timer = (struct wlr_scene_timer){0};
+ }
+
+ if ((state->committed & WLR_OUTPUT_STATE_ENABLED) && !state->enabled) {
+ // if the state is being disabled, do nothing.
+ return true;
+ }
+
+ struct wlr_output *output = scene_output->output;
+ enum wlr_scene_debug_damage_option debug_damage =
+ scene_output->scene->debug_damage_option;
+
+ struct render_data render_data = {
+ .transform = output->transform,
+ .scale = output->scale,
+ .logical = { .x = scene_output->x, .y = scene_output->y },
+ .output = scene_output,
+ .has_blur = false,
+ };
+
+ int resolution_width, resolution_height;
+ output_pending_resolution(output, state,
+ &resolution_width, &resolution_height);
+
+ if (state->committed & WLR_OUTPUT_STATE_TRANSFORM) {
+ if (render_data.transform != state->transform) {
+ scene_output_damage_whole(scene_output);
+ }
+
+ render_data.transform = state->transform;
+ }
+
+ if (state->committed & WLR_OUTPUT_STATE_SCALE) {
+ if (render_data.scale != state->scale) {
+ scene_output_damage_whole(scene_output);
+ }
+
+ render_data.scale = state->scale;
+ }
+
+ render_data.trans_width = resolution_width;
+ render_data.trans_height = resolution_height;
+ wlr_output_transform_coords(render_data.transform,
+ &render_data.trans_width, &render_data.trans_height);
+
+ render_data.logical.width = render_data.trans_width / render_data.scale;
+ render_data.logical.height = render_data.trans_height / render_data.scale;
+
+ struct render_list_constructor_data list_con = {
+ .box = render_data.logical,
+ .render_list = &scene_output->render_list,
+ .calculate_visibility = scene_output->scene->calculate_visibility,
+ .highlight_transparent_region = scene_output->scene->highlight_transparent_region,
+ .fractional_scale = floor(render_data.scale) != render_data.scale,
+ };
+
+ list_con.render_list->size = 0;
+ scene_nodes_in_box(&scene_output->scene->tree.node, &list_con.box,
+ construct_render_list_iterator, &list_con);
+ array_realloc(list_con.render_list, list_con.render_list->size);
+
+ struct render_list_entry *list_data = list_con.render_list->data;
+ int list_len = list_con.render_list->size / sizeof(*list_data);
+
+ if (debug_damage == WLR_SCENE_DEBUG_DAMAGE_RERENDER) {
+ scene_output_damage_whole(scene_output);
+ }
+
+ struct timespec now;
+ if (debug_damage == WLR_SCENE_DEBUG_DAMAGE_HIGHLIGHT) {
+ struct wl_list *regions = &scene_output->damage_highlight_regions;
+ clock_gettime(CLOCK_MONOTONIC, &now);
+
+ // add the current frame's damage if there is damage
+ if (!pixman_region32_empty(&scene_output->damage_ring.current)) {
+ struct highlight_region *current_damage = calloc(1, sizeof(*current_damage));
+ if (current_damage) {
+ pixman_region32_init(¤t_damage->region);
+ pixman_region32_copy(¤t_damage->region,
+ &scene_output->damage_ring.current);
+ current_damage->when = now;
+ wl_list_insert(regions, ¤t_damage->link);
+ }
+ }
+
+ pixman_region32_t acc_damage;
+ pixman_region32_init(&acc_damage);
+ struct highlight_region *damage, *tmp_damage;
+ wl_list_for_each_safe(damage, tmp_damage, regions, link) {
+ // remove overlaping damage regions
+ pixman_region32_subtract(&damage->region, &damage->region, &acc_damage);
+ pixman_region32_union(&acc_damage, &acc_damage, &damage->region);
+
+ // if this damage is too old or has nothing in it, get rid of it
+ struct timespec time_diff;
+ timespec_sub(&time_diff, &now, &damage->when);
+ if (timespec_to_msec(&time_diff) >= HIGHLIGHT_DAMAGE_FADEOUT_TIME ||
+ pixman_region32_empty(&damage->region)) {
+ highlight_region_destroy(damage);
+ }
+ }
+
+ scene_output_damage(scene_output, &acc_damage);
+ pixman_region32_fini(&acc_damage);
+ }
+
+ wlr_output_state_set_damage(state, &scene_output->pending_commit_damage);
+
+ // We only want to try direct scanout if:
+ // - There is only one entry in the render list
+ // - There are no color transforms that need to be applied
+ // - Damage highlight debugging is not enabled
+ enum scene_direct_scanout_result scanout_result = SCANOUT_INELIGIBLE;
+ if (options->color_transform == NULL && list_len == 1
+ && debug_damage != WLR_SCENE_DEBUG_DAMAGE_HIGHLIGHT) {
+ scanout_result = scene_entry_try_direct_scanout(&list_data[0], state, &render_data);
+ }
+
+ if (scanout_result == SCANOUT_INELIGIBLE) {
+ if (scene_output->dmabuf_feedback_debounce > 0) {
+ // We cannot scan out, so count down towards sending composition dmabuf feedback
+ scene_output->dmabuf_feedback_debounce--;
+ }
+ } else if (scene_output->dmabuf_feedback_debounce < DMABUF_FEEDBACK_DEBOUNCE_FRAMES) {
+ // We either want to scan out or successfully scanned out, so count up towards sending
+ // scanout dmabuf feedback
+ scene_output->dmabuf_feedback_debounce++;
+ }
+
+ bool scanout = scanout_result == SCANOUT_SUCCESS;
+ if (scene_output->prev_scanout != scanout) {
+ scene_output->prev_scanout = scanout;
+ wlr_log(WLR_DEBUG, "Direct scan-out %s",
+ scanout ? "enabled" : "disabled");
+ }
+
+ if (scanout) {
+ scene_output_state_attempt_gamma(scene_output, state);
+
+ if (timer) {
+ struct timespec end_time, duration;
+ clock_gettime(CLOCK_MONOTONIC, &end_time);
+ timespec_sub(&duration, &end_time, &start_time);
+ timer->pre_render_duration = timespec_to_nsec(&duration);
+ }
+ return true;
+ }
+
+ struct wlr_swapchain *swapchain = options->swapchain;
+ if (!swapchain) {
+ if (!wlr_output_configure_primary_swapchain(output, state, &output->swapchain)) {
+ return false;
+ }
+
+ swapchain = output->swapchain;
+ }
+
+ struct wlr_buffer *buffer = wlr_swapchain_acquire(swapchain);
+ if (buffer == NULL) {
+ return false;
+ }
+
+ assert(buffer->width == resolution_width && buffer->height == resolution_height);
+
+ if (timer) {
+ timer->render_timer = wlr_render_timer_create(output->renderer);
+
+ struct timespec end_time, duration;
+ clock_gettime(CLOCK_MONOTONIC, &end_time);
+ timespec_sub(&duration, &end_time, &start_time);
+ timer->pre_render_duration = timespec_to_nsec(&duration);
+ }
+
+ scene_output->in_point++;
+ struct fx_gles_render_pass *render_pass =
+ fx_renderer_begin_buffer_pass(output->renderer, buffer, output,
+ &(struct fx_buffer_pass_options) {
+ .base = &(struct wlr_buffer_pass_options){
+ .timer = timer ? timer->render_timer : NULL,
+ .color_transform = options->color_transform,
+ .signal_timeline = scene_output->in_timeline,
+ .signal_point = scene_output->in_point,
+ },
+ .swapchain = swapchain,
+ }
+ );
+ if (render_pass == NULL) {
+ wlr_buffer_unlock(buffer);
+ return false;
+ }
+ struct fx_effect_framebuffers *effect_fbos = render_pass->fx_effect_framebuffers;
+
+ render_data.render_pass = render_pass;
+ pixman_region32_init(&render_data.damage);
+ wlr_damage_ring_rotate_buffer(&scene_output->damage_ring, buffer,
+ &render_data.damage);
+
+ // Blur artifact prevention
+ pixman_region32_t blur_region;
+ pixman_region32_init(&blur_region);
+ render_data.has_blur =
+ scene_output_has_blur(list_len, list_data, scene_output, &blur_region);
+ bool whole_output_blur_damaged = false;
+ // Expand the damage to compensate for blur
+ if (render_data.has_blur) {
+ int output_width = output->width;
+ int output_height = output->height;
+ struct blur_data *blur_data = &scene_output->scene->blur_data;
+ pixman_region32_t *damage = &render_data.damage;
+
+ // ensure that the damage isn't expanding past the output's size
+ int32_t damage_width = damage->extents.x2 - damage->extents.x1;
+ int32_t damage_height = damage->extents.y2 - damage->extents.y1;
+ if (damage_width > output_width || damage_height > output_height) {
+ pixman_region32_intersect_rect(damage, damage,
+ 0, 0, output_width, output_height);
+ // No need to compensate for blur artifacts when the damage spans
+ // the whole output
+ whole_output_blur_damaged = true;
+ }else {
+ // copy the surrounding content where the blur would display artifacts
+ // and draw it above the artifacts
+ pixman_region32_t extended_damage;
+ pixman_region32_init(&extended_damage);
+ pixman_region32_intersect(&extended_damage, damage, &blur_region);
+ // Expand the region to compensate for blur artifacts
+ wlr_region_expand(&extended_damage, &extended_damage, blur_data_calc_size(blur_data));
+ // Limit to the monitors viewport
+ pixman_region32_intersect_rect(&extended_damage, &extended_damage,
+ 0, 0, output_width, output_height);
+
+ // capture the padding pixels around the blur where artifacts will be drawn
+ pixman_region32_subtract(&effect_fbos->blur_padding_region,
+ &extended_damage, damage);
+ // Combine into the surface damage (we need to redraw the padding area as well)
+ pixman_region32_union(damage, damage, &extended_damage);
+ pixman_region32_fini(&extended_damage);
+
+ // Capture the padding pixels before blur for later use
+ fx_renderer_read_to_buffer(render_pass, &effect_fbos->blur_padding_region,
+ effect_fbos->blur_saved_pixels_buffer,
+ render_pass->buffer);
+ }
+ }
+ pixman_region32_fini(&blur_region);
+
+ pixman_region32_t background;
+ pixman_region32_init(&background);
+ pixman_region32_copy(&background, &render_data.damage);
+
+ // Cull areas of the background that are occluded by opaque regions of
+ // scene nodes above. Those scene nodes will just render atop having us
+ // never see the background.
+ if (scene_output->scene->calculate_visibility) {
+ for (int i = list_len - 1; i >= 0; i--) {
+ struct render_list_entry *entry = &list_data[i];
+
+ // We must only cull opaque regions that are visible by the node.
+ // The node's visibility will have the knowledge of a black rect
+ // that may have been omitted from the render list via the black
+ // rect optimization. In order to ensure we don't cull background
+ // rendering in that black rect region, consider the node's visibility.
+ pixman_region32_t opaque;
+ pixman_region32_init(&opaque);
+ scene_node_opaque_region(entry->node, entry->x, entry->y, &opaque);
+ pixman_region32_intersect(&opaque, &opaque, &entry->node->visible);
+
+ pixman_region32_translate(&opaque, -scene_output->x, -scene_output->y);
+ logical_to_buffer_coords(&opaque, &render_data, false);
+ pixman_region32_subtract(&background, &background, &opaque);
+ pixman_region32_fini(&opaque);
+ }
+
+ if (floor(render_data.scale) != render_data.scale) {
+ wlr_region_expand(&background, &background, 1);
+
+ // reintersect with the damage because we never want to render
+ // outside of the damage region
+ pixman_region32_intersect(&background, &background, &render_data.damage);
+ }
+ }
+
+ wlr_render_pass_add_rect(&render_pass->base, &(struct wlr_render_rect_options){
+ .box = { .width = buffer->width, .height = buffer->height },
+ .color = { .r = 0, .g = 0, .b = 0, .a = 1 },
+ .clip = &background,
+ });
+ pixman_region32_fini(&background);
+
+ for (int i = list_len - 1; i >= 0; i--) {
+ struct render_list_entry *entry = &list_data[i];
+ scene_entry_render(entry, &render_data);
+
+ if (entry->node->type == WLR_SCENE_NODE_BUFFER) {
+ struct wlr_scene_buffer *buffer = wlr_scene_buffer_from_node(entry->node);
+
+ // Direct scanout counts up to DMABUF_FEEDBACK_DEBOUNCE_FRAMES before sending new dmabuf
+ // feedback, and on composition we wait until it hits zero again. If we knew that an
+ // entry could never be a scanout candidate, we could send feedback to it
+ // unconditionally without debounce, but for now it is all or nothing
+ if (scene_output->dmabuf_feedback_debounce == 0 && buffer->primary_output == scene_output) {
+ struct wlr_linux_dmabuf_feedback_v1_init_options options = {
+ .main_renderer = output->renderer,
+ .scanout_primary_output = NULL,
+ };
+
+ scene_buffer_send_dmabuf_feedback(scene_output->scene, buffer, &options);
+ }
+ }
+ }
+
+ if (render_data.has_blur && !whole_output_blur_damaged) {
+ // Render the saved pixels over the blur artifacts
+ fx_renderer_read_to_buffer(render_pass, &effect_fbos->blur_padding_region,
+ render_pass->buffer, effect_fbos->blur_saved_pixels_buffer);
+ }
+
+ if (debug_damage == WLR_SCENE_DEBUG_DAMAGE_HIGHLIGHT) {
+ struct highlight_region *damage;
+ wl_list_for_each(damage, &scene_output->damage_highlight_regions, link) {
+ struct timespec time_diff;
+ timespec_sub(&time_diff, &now, &damage->when);
+ int64_t time_diff_ms = timespec_to_msec(&time_diff);
+ float alpha = 1.0 - (double)time_diff_ms / HIGHLIGHT_DAMAGE_FADEOUT_TIME;
+
+ wlr_render_pass_add_rect(&render_pass->base, &(struct wlr_render_rect_options){
+ .box = { .width = buffer->width, .height = buffer->height },
+ .color = { .r = alpha * 0.5, .g = 0, .b = 0, .a = alpha * 0.5 },
+ .clip = &damage->region,
+ });
+ }
+ }
+
+ wlr_output_add_software_cursors_to_render_pass(output, &render_pass->base, &render_data.damage);
+ pixman_region32_fini(&render_data.damage);
+
+ if (!wlr_render_pass_submit(&render_pass->base)) {
+ wlr_buffer_unlock(buffer);
+
+ // if we failed to render the buffer, it will have undefined contents
+ // Trash the damage ring
+ wlr_damage_ring_add_whole(&scene_output->damage_ring);
+ return false;
+ }
+
+ wlr_output_state_set_buffer(state, buffer);
+ wlr_buffer_unlock(buffer);
+
+ if (scene_output->in_timeline != NULL) {
+ wlr_output_state_set_wait_timeline(state, scene_output->in_timeline,
+ scene_output->in_point);
+ }
+
+ scene_output_state_attempt_gamma(scene_output, state);
+
+ return true;
+}
+
+int64_t wlr_scene_timer_get_duration_ns(struct wlr_scene_timer *timer) {
+ int64_t pre_render = timer->pre_render_duration;
+ if (!timer->render_timer) {
+ return pre_render;
+ }
+ int64_t render = wlr_render_timer_get_duration_ns(timer->render_timer);
+ return render != -1 ? pre_render + render : -1;
+}
+
+void wlr_scene_timer_finish(struct wlr_scene_timer *timer) {
+ if (timer->render_timer) {
+ wlr_render_timer_destroy(timer->render_timer);
+ }
+}
+
+static void scene_node_send_frame_done(struct wlr_scene_node *node,
+ struct wlr_scene_output *scene_output, struct timespec *now) {
+ if (!node->enabled) {
+ return;
+ }
+
+ if (node->type == WLR_SCENE_NODE_BUFFER) {
+ struct wlr_scene_buffer *scene_buffer =
+ wlr_scene_buffer_from_node(node);
+
+ if (scene_buffer->primary_output == scene_output) {
+ wlr_scene_buffer_send_frame_done(scene_buffer, now);
+ }
+ } else if (node->type == WLR_SCENE_NODE_TREE) {
+ struct wlr_scene_tree *scene_tree = wlr_scene_tree_from_node(node);
+ struct wlr_scene_node *child;
+ wl_list_for_each(child, &scene_tree->children, link) {
+ scene_node_send_frame_done(child, scene_output, now);
+ }
+ }
+}
+
+void wlr_scene_output_send_frame_done(struct wlr_scene_output *scene_output,
+ struct timespec *now) {
+ scene_node_send_frame_done(&scene_output->scene->tree.node,
+ scene_output, now);
+}
+
+static void scene_output_for_each_scene_buffer(const struct wlr_box *output_box,
+ struct wlr_scene_node *node, int lx, int ly,
+ wlr_scene_buffer_iterator_func_t user_iterator, void *user_data) {
+ if (!node->enabled) {
+ return;
+ }
+
+ lx += node->x;
+ ly += node->y;
+
+ if (node->type == WLR_SCENE_NODE_BUFFER) {
+ struct wlr_box node_box = { .x = lx, .y = ly };
+ scene_node_get_size(node, &node_box.width, &node_box.height);
+
+ struct wlr_box intersection;
+ if (wlr_box_intersection(&intersection, output_box, &node_box)) {
+ struct wlr_scene_buffer *scene_buffer =
+ wlr_scene_buffer_from_node(node);
+ user_iterator(scene_buffer, lx, ly, user_data);
+ }
+ } else if (node->type == WLR_SCENE_NODE_TREE) {
+ struct wlr_scene_tree *scene_tree = wlr_scene_tree_from_node(node);
+ struct wlr_scene_node *child;
+ wl_list_for_each(child, &scene_tree->children, link) {
+ scene_output_for_each_scene_buffer(output_box, child, lx, ly,
+ user_iterator, user_data);
+ }
+ }
+}
+
+void wlr_scene_output_for_each_buffer(struct wlr_scene_output *scene_output,
+ wlr_scene_buffer_iterator_func_t iterator, void *user_data) {
+ struct wlr_box box = { .x = scene_output->x, .y = scene_output->y };
+ wlr_output_effective_resolution(scene_output->output,
+ &box.width, &box.height);
+ scene_output_for_each_scene_buffer(&box, &scene_output->scene->tree.node, 0, 0,
+ iterator, user_data);
+}
diff --git a/scenefx/util/array.c b/scenefx/util/array.c
new file mode 100644
index 0000000..ec16a7b
--- /dev/null
+++ b/scenefx/util/array.c
@@ -0,0 +1,40 @@
+#include "util/array.h"
+#include <assert.h>
+#include <string.h>
+
+void array_remove_at(struct wl_array *arr, size_t offset, size_t size) {
+ assert(arr->size >= offset + size);
+
+ char *data = arr->data;
+ memmove(&data[offset], &data[offset + size], arr->size - offset - size);
+ arr->size -= size;
+}
+
+bool array_realloc(struct wl_array *arr, size_t size) {
+ // If the size is less than 1/4th of the allocation size, we shrink it.
+ // 1/4th is picked to provide hysteresis, without which an array with size
+ // arr->alloc would constantly reallocate if an element is added and then
+ // removed continously.
+ size_t alloc;
+ if (arr->alloc > 0 && size > arr->alloc / 4) {
+ alloc = arr->alloc;
+ } else {
+ alloc = 16;
+ }
+
+ while (alloc < size) {
+ alloc *= 2;
+ }
+
+ if (alloc == arr->alloc) {
+ return true;
+ }
+
+ void *data = realloc(arr->data, alloc);
+ if (data == NULL) {
+ return false;
+ }
+ arr->data = data;
+ arr->alloc = alloc;
+ return true;
+}
diff --git a/scenefx/util/env.c b/scenefx/util/env.c
new file mode 100644
index 0000000..77911a2
--- /dev/null
+++ b/scenefx/util/env.c
@@ -0,0 +1,38 @@
+#include <stdlib.h>
+#include <string.h>
+#include <wlr/util/log.h>
+#include "util/env.h"
+
+bool env_parse_bool(const char *option) {
+ const char *env = getenv(option);
+ if (env) {
+ wlr_log(WLR_INFO, "Loading %s option: %s", option, env);
+ }
+
+ if (!env || strcmp(env, "0") == 0) {
+ return false;
+ } else if (strcmp(env, "1") == 0) {
+ return true;
+ }
+
+ wlr_log(WLR_ERROR, "Unknown %s option: %s", option, env);
+ return false;
+}
+
+size_t env_parse_switch(const char *option, const char **switches) {
+ const char *env = getenv(option);
+ if (env) {
+ wlr_log(WLR_INFO, "Loading %s option: %s", option, env);
+ } else {
+ return 0;
+ }
+
+ for (ssize_t i = 0; switches[i]; i++) {
+ if (strcmp(env, switches[i]) == 0) {
+ return i;
+ }
+ }
+
+ wlr_log(WLR_ERROR, "Unknown %s option: %s", option, env);
+ return 0;
+}
diff --git a/scenefx/util/matrix.c b/scenefx/util/matrix.c
new file mode 100644
index 0000000..640787c
--- /dev/null
+++ b/scenefx/util/matrix.c
@@ -0,0 +1,165 @@
+#include <assert.h>
+#include <string.h>
+#include <wayland-server-protocol.h>
+#include <wlr/util/box.h>
+#include "util/matrix.h"
+
+void wlr_matrix_identity(float mat[static 9]) {
+ static const float identity[9] = {
+ 1.0f, 0.0f, 0.0f,
+ 0.0f, 1.0f, 0.0f,
+ 0.0f, 0.0f, 1.0f,
+ };
+ memcpy(mat, identity, sizeof(identity));
+}
+
+void wlr_matrix_multiply(float mat[static 9], const float a[static 9],
+ const float b[static 9]) {
+ float product[9];
+
+ product[0] = a[0]*b[0] + a[1]*b[3] + a[2]*b[6];
+ product[1] = a[0]*b[1] + a[1]*b[4] + a[2]*b[7];
+ product[2] = a[0]*b[2] + a[1]*b[5] + a[2]*b[8];
+
+ product[3] = a[3]*b[0] + a[4]*b[3] + a[5]*b[6];
+ product[4] = a[3]*b[1] + a[4]*b[4] + a[5]*b[7];
+ product[5] = a[3]*b[2] + a[4]*b[5] + a[5]*b[8];
+
+ product[6] = a[6]*b[0] + a[7]*b[3] + a[8]*b[6];
+ product[7] = a[6]*b[1] + a[7]*b[4] + a[8]*b[7];
+ product[8] = a[6]*b[2] + a[7]*b[5] + a[8]*b[8];
+
+ memcpy(mat, product, sizeof(product));
+}
+
+void wlr_matrix_translate(float mat[static 9], float x, float y) {
+ float translate[9] = {
+ 1.0f, 0.0f, x,
+ 0.0f, 1.0f, y,
+ 0.0f, 0.0f, 1.0f,
+ };
+ wlr_matrix_multiply(mat, mat, translate);
+}
+
+void wlr_matrix_scale(float mat[static 9], float x, float y) {
+ float scale[9] = {
+ x, 0.0f, 0.0f,
+ 0.0f, y, 0.0f,
+ 0.0f, 0.0f, 1.0f,
+ };
+ wlr_matrix_multiply(mat, mat, scale);
+}
+
+static const float transforms[][9] = {
+ [WL_OUTPUT_TRANSFORM_NORMAL] = {
+ 1.0f, 0.0f, 0.0f,
+ 0.0f, 1.0f, 0.0f,
+ 0.0f, 0.0f, 1.0f,
+ },
+ [WL_OUTPUT_TRANSFORM_90] = {
+ 0.0f, 1.0f, 0.0f,
+ -1.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 1.0f,
+ },
+ [WL_OUTPUT_TRANSFORM_180] = {
+ -1.0f, 0.0f, 0.0f,
+ 0.0f, -1.0f, 0.0f,
+ 0.0f, 0.0f, 1.0f,
+ },
+ [WL_OUTPUT_TRANSFORM_270] = {
+ 0.0f, -1.0f, 0.0f,
+ 1.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 1.0f,
+ },
+ [WL_OUTPUT_TRANSFORM_FLIPPED] = {
+ -1.0f, 0.0f, 0.0f,
+ 0.0f, 1.0f, 0.0f,
+ 0.0f, 0.0f, 1.0f,
+ },
+ [WL_OUTPUT_TRANSFORM_FLIPPED_90] = {
+ 0.0f, 1.0f, 0.0f,
+ 1.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 1.0f,
+ },
+ [WL_OUTPUT_TRANSFORM_FLIPPED_180] = {
+ 1.0f, 0.0f, 0.0f,
+ 0.0f, -1.0f, 0.0f,
+ 0.0f, 0.0f, 1.0f,
+ },
+ [WL_OUTPUT_TRANSFORM_FLIPPED_270] = {
+ 0.0f, -1.0f, 0.0f,
+ -1.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 1.0f,
+ },
+};
+
+void wlr_matrix_transform(float mat[static 9],
+ enum wl_output_transform transform) {
+ wlr_matrix_multiply(mat, mat, transforms[transform]);
+}
+
+void matrix_projection(float mat[static 9], int width, int height,
+ enum wl_output_transform transform) {
+ memset(mat, 0, sizeof(*mat) * 9);
+
+ const float *t = transforms[transform];
+ float x = 2.0f / width;
+ float y = 2.0f / height;
+
+ // Rotation + reflection
+ mat[0] = x * t[0];
+ mat[1] = x * t[1];
+ mat[3] = y * -t[3];
+ mat[4] = y * -t[4];
+
+ // Translation
+ mat[2] = -copysign(1.0f, mat[0] + mat[1]);
+ mat[5] = -copysign(1.0f, mat[3] + mat[4]);
+
+ // Identity
+ mat[8] = 1.0f;
+}
+
+void wlr_matrix_project_box(float mat[static 9], const struct wlr_box *box,
+ enum wl_output_transform transform, const float projection[static 9]) {
+ int x = box->x;
+ int y = box->y;
+ int width = box->width;
+ int height = box->height;
+
+ wlr_matrix_identity(mat);
+ wlr_matrix_translate(mat, x, y);
+
+ wlr_matrix_scale(mat, width, height);
+
+ if (transform != WL_OUTPUT_TRANSFORM_NORMAL) {
+ wlr_matrix_translate(mat, 0.5, 0.5);
+ wlr_matrix_transform(mat, transform);
+ wlr_matrix_translate(mat, -0.5, -0.5);
+ }
+
+ wlr_matrix_multiply(mat, projection, mat);
+}
+
+void matrix_invert(float out[static 9], float m[static 9]) {
+ float a = m[0], b = m[1], c = m[2], d = m[3], e = m[4], f = m[5], g = m[6], h = m[7], i = m[8];
+
+ // See: https://en.wikipedia.org/wiki/Determinant
+ float det = a*e*i + b*f*g + c*d*h - c*e*g - b*d*i - a*f*h;
+ assert(det != 0);
+ float inv_det = 1 / det;
+
+ // See: https://en.wikipedia.org/wiki/Invertible_matrix#Inversion_of_3_%C3%97_3_matrices
+ float result[] = {
+ inv_det * (e*i - f*h),
+ inv_det * -(b*i - c*h),
+ inv_det * (b*f - c*e),
+ inv_det * -(d*i - f*g),
+ inv_det * (a*i - c*g),
+ inv_det * -(a*f - c*d),
+ inv_det * (d*h - e*g),
+ inv_det * -(a*h - b*g),
+ inv_det * (a*e - b*d),
+ };
+ memcpy(out, result, sizeof(result));
+}
diff --git a/scenefx/util/meson.build b/scenefx/util/meson.build
new file mode 100644
index 0000000..63033d1
--- /dev/null
+++ b/scenefx/util/meson.build
@@ -0,0 +1,6 @@
+scenefx_files += files(
+ 'array.c',
+ 'env.c',
+ 'matrix.c',
+ 'time.c',
+)
diff --git a/scenefx/util/time.c b/scenefx/util/time.c
new file mode 100644
index 0000000..1a8f329
--- /dev/null
+++ b/scenefx/util/time.c
@@ -0,0 +1,33 @@
+#include <stdint.h>
+#include <time.h>
+
+#include "util/time.h"
+
+int64_t timespec_to_msec(const struct timespec *a) {
+ return (int64_t)a->tv_sec * 1000 + a->tv_nsec / 1000000;
+}
+
+int64_t timespec_to_nsec(const struct timespec *a) {
+ return (int64_t)a->tv_sec * NSEC_PER_SEC + a->tv_nsec;
+}
+
+void timespec_from_nsec(struct timespec *r, int64_t nsec) {
+ r->tv_sec = nsec / NSEC_PER_SEC;
+ r->tv_nsec = nsec % NSEC_PER_SEC;
+}
+
+int64_t get_current_time_msec(void) {
+ struct timespec now;
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ return timespec_to_msec(&now);
+}
+
+void timespec_sub(struct timespec *r, const struct timespec *a,
+ const struct timespec *b) {
+ r->tv_sec = a->tv_sec - b->tv_sec;
+ r->tv_nsec = a->tv_nsec - b->tv_nsec;
+ if (r->tv_nsec < 0) {
+ r->tv_sec--;
+ r->tv_nsec += NSEC_PER_SEC;
+ }
+}
diff --git a/src/bin/cce.rs b/src/bin/cce.rs
index e3b5997..6b9b97a 100644
--- a/src/bin/cce.rs
+++ b/src/bin/cce.rs
@@ -11,17 +11,17 @@ fn main() {
"control" => {
let mut control_args = vec!["cce-ctl".to_string()];
control_args.extend(args.iter().skip(2).cloned());
- cce::run_cce_ctl(control_args);
+ cce_fx::run_cce_ctl(control_args);
}
"--help" | "-h" | "help" => {
print_help();
}
_ => {
- cce::run_server();
+ cce_fx::run_server();
}
}
} else {
- cce::run_server();
+ cce_fx::run_server();
}
}
diff --git a/src/bin/wallpaper.rs b/src/bin/wallpaper.rs
new file mode 100644
index 0000000..4a2de94
--- /dev/null
+++ b/src/bin/wallpaper.rs
@@ -0,0 +1,10 @@
+use std::env;
+
+fn main() {
+ println!("cce-wallpaper: standalone background renderer service starting...");
+ if let Ok(display) = env::var("WAYLAND_DISPLAY") {
+ println!("Connected to compositor WAYLAND_DISPLAY: {}", display);
+ } else {
+ println!("Error: WAYLAND_DISPLAY environment variable not set.");
+ }
+}
diff --git a/src/server/output.rs b/src/server/output.rs
index 6552be8..ed91059 100644
--- a/src/server/output.rs
+++ b/src/server/output.rs
@@ -651,44 +651,15 @@ impl Output {
if rh <= 0 {
continue;
}
-
let draw_fade = cell_fade_inset > 0 && !draw_low_res;
- if !draw_fade {
- let _cell_rect = get_rect(rw, rh, cell_color.as_ptr(), rel_x, rel_y, cell_corner_radius);
- } else {
- let step = if draw_low_res { 8 } else { 1 };
- let inset_scaled = (cell_fade_inset as f64 * zoom) as i32;
-
- // 0. Draw the cell-sized gap color background rect behind the fade layers
- let _cell_bg_rect = get_rect(rw, rh, gap_color_arr.as_ptr(), rel_x, rel_y, cell_corner_radius);
-
- // 1. Draw the fade layers
- let mut i = step;
- while i < inset_scaled {
- let rect_w = rw - 2 * i;
- let rect_h = rh - 2 * i;
- if rect_w <= 0 || rect_h <= 0 {
- break;
- }
- let progress = i as f32 / inset_scaled as f32;
- let alpha = cell_color[3] * progress;
- let layer_color = [
- cell_color[0] * alpha,
- cell_color[1] * alpha,
- cell_color[2] * alpha,
- alpha,
- ];
-
- let _cell_rect = get_rect(rect_w, rect_h, layer_color.as_ptr(), rel_x + i, rel_y + i, cell_corner_radius);
- i += step;
- }
-
- // 2. Draw the fully opaque core
- let core_w = rw - 2 * inset_scaled;
- let core_h = rh - 2 * inset_scaled;
- if core_w > 0 && core_h > 0 {
- let _cell_rect = get_rect(core_w, core_h, cell_color.as_ptr(), rel_x + inset_scaled, rel_y + inset_scaled, cell_corner_radius);
- }
+ let cell_rect = get_rect(rw, rh, cell_color.as_ptr(), rel_x, rel_y, cell_corner_radius);
+ if !cell_rect.is_null() {
+ let inset_scaled = if draw_fade {
+ (cell_fade_inset as f64 * zoom) as i32
+ } else {
+ 0
+ };
+ ffi::wlr_scene_rect_set_fade_inset(cell_rect, inset_scaled);
}
}
}