From 716dc0e1bfdf854f4956430c543e6eeb2967b743 Mon Sep 17 00:00:00 2001 From: Frank Praznik Date: Tue, 14 May 2024 13:10:09 -0400 Subject: [PATCH] wayland: Implement SetWindowOpacity via the alpha modifier protocol The wp_alpha_modifier_v1 protocol allows for a global blending factor to be specified for an entire surface. Use this to add support for SDL_SetWindowOpacity(). --- src/video/wayland/SDL_waylandvideo.c | 9 +++ src/video/wayland/SDL_waylandvideo.h | 1 + src/video/wayland/SDL_waylandwindow.c | 50 +++++++++--- src/video/wayland/SDL_waylandwindow.h | 2 + wayland-protocols/alpha-modifier-v1.xml | 103 ++++++++++++++++++++++++ 5 files changed, 155 insertions(+), 10 deletions(-) create mode 100644 wayland-protocols/alpha-modifier-v1.xml diff --git a/src/video/wayland/SDL_waylandvideo.c b/src/video/wayland/SDL_waylandvideo.c index 5f50c79a90..0567ae5bda 100644 --- a/src/video/wayland/SDL_waylandvideo.c +++ b/src/video/wayland/SDL_waylandvideo.c @@ -43,6 +43,7 @@ #include +#include "alpha-modifier-v1-client-protocol.h" #include "cursor-shape-v1-client-protocol.h" #include "fractional-scale-v1-client-protocol.h" #include "idle-inhibit-unstable-v1-client-protocol.h" @@ -487,6 +488,7 @@ static SDL_VideoDevice *Wayland_CreateDevice(void) device->SetWindowMinimumSize = Wayland_SetWindowMinimumSize; device->SetWindowMaximumSize = Wayland_SetWindowMaximumSize; device->SetWindowModalFor = Wayland_SetWindowModalFor; + device->SetWindowOpacity = Wayland_SetWindowOpacity; device->SetWindowTitle = Wayland_SetWindowTitle; device->GetWindowSizeInPixels = Wayland_GetWindowSizeInPixels; device->GetDisplayForWindow = Wayland_GetDisplayForWindow; @@ -1107,6 +1109,8 @@ static void display_handle_global(void *data, struct wl_registry *registry, uint d->zxdg_exporter_v2 = wl_registry_bind(d->registry, id, &zxdg_exporter_v2_interface, 1); } else if (SDL_strcmp(interface, "xdg_wm_dialog_v1") == 0) { d->xdg_wm_dialog_v1 = wl_registry_bind(d->registry, id, &xdg_wm_dialog_v1_interface, 1); + } else if (SDL_strcmp(interface, "wp_alpha_modifier_v1") == 0) { + d->wp_alpha_modifier_v1 = wl_registry_bind(d->registry, id, &wp_alpha_modifier_v1_interface, 1); } else if (SDL_strcmp(interface, "kde_output_order_v1") == 0) { d->kde_output_order = wl_registry_bind(d->registry, id, &kde_output_order_v1_interface, 1); kde_output_order_v1_add_listener(d->kde_output_order, &kde_output_order_listener, d); @@ -1369,6 +1373,11 @@ static void Wayland_VideoCleanup(SDL_VideoDevice *_this) data->xdg_wm_dialog_v1 = NULL; } + if (data->wp_alpha_modifier_v1) { + wp_alpha_modifier_v1_destroy(data->wp_alpha_modifier_v1); + data->wp_alpha_modifier_v1 = NULL; + } + if (data->kde_output_order) { Wayland_FlushOutputOrder(data); kde_output_order_v1_destroy(data->kde_output_order); diff --git a/src/video/wayland/SDL_waylandvideo.h b/src/video/wayland/SDL_waylandvideo.h index abf33d614b..f5cc778b9e 100644 --- a/src/video/wayland/SDL_waylandvideo.h +++ b/src/video/wayland/SDL_waylandvideo.h @@ -81,6 +81,7 @@ struct SDL_VideoData struct zwp_input_timestamps_manager_v1 *input_timestamps_manager; struct zxdg_exporter_v2 *zxdg_exporter_v2; struct xdg_wm_dialog_v1 *xdg_wm_dialog_v1; + struct wp_alpha_modifier_v1 *wp_alpha_modifier_v1; struct kde_output_order_v1 *kde_output_order; struct xkb_context *xkb_context; diff --git a/src/video/wayland/SDL_waylandwindow.c b/src/video/wayland/SDL_waylandwindow.c index c26ab779bc..61d8b64c14 100644 --- a/src/video/wayland/SDL_waylandwindow.c +++ b/src/video/wayland/SDL_waylandwindow.c @@ -32,6 +32,7 @@ #include "SDL_waylandvideo.h" #include "../../SDL_hints_c.h" +#include "alpha-modifier-v1-client-protocol.h" #include "xdg-shell-client-protocol.h" #include "xdg-decoration-unstable-v1-client-protocol.h" #include "idle-inhibit-unstable-v1-client-protocol.h" @@ -279,10 +280,24 @@ static void RepositionPopup(SDL_Window *window, SDL_bool use_current_position) } } +static void SetSurfaceOpaqueRegion(SDL_WindowData *wind, SDL_bool is_opaque) +{ + SDL_VideoData *viddata = wind->waylandData; + + if (is_opaque) { + struct wl_region *region = wl_compositor_create_region(viddata->compositor); + wl_region_add(region, 0, 0, + wind->current.logical_width, wind->current.logical_height); + wl_surface_set_opaque_region(wind->surface, region); + wl_region_destroy(region); + } else { + wl_surface_set_opaque_region(wind->surface, NULL); + } +} + static void ConfigureWindowGeometry(SDL_Window *window) { SDL_WindowData *data = window->driverdata; - SDL_VideoData *viddata = data->waylandData; const int old_pixel_width = data->current.pixel_width; const int old_pixel_height = data->current.pixel_height; int window_width, window_height; @@ -391,20 +406,12 @@ static void ConfigureWindowGeometry(SDL_Window *window) * need to be recalculated if the output size has changed. */ if (window_size_changed) { - struct wl_region *region; - /* libdecor does this internally on frame commits, so it's only needed for xdg surfaces. */ if (data->shell_surface_type != WAYLAND_SURFACE_LIBDECOR && data->shell_surface.xdg.surface) { xdg_surface_set_window_geometry(data->shell_surface.xdg.surface, 0, 0, data->current.logical_width, data->current.logical_height); } - if (!(window->flags & SDL_WINDOW_TRANSPARENT)) { - region = wl_compositor_create_region(viddata->compositor); - wl_region_add(region, 0, 0, - data->current.logical_width, data->current.logical_height); - wl_surface_set_opaque_region(data->surface, region); - wl_region_destroy(region); - } + SetSurfaceOpaqueRegion(data, !(window->flags & SDL_WINDOW_TRANSPARENT) && window->opacity == 1.0f); /* Ensure that child popup windows are still in bounds. */ for (SDL_Window *child = window->first_child; child; child = child->next_sibling) { @@ -2302,6 +2309,11 @@ int Wayland_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_Propert } } + if (!custom_surface_role && c->wp_alpha_modifier_v1) { + data->wp_alpha_modifier_surface_v1 = wp_alpha_modifier_v1_get_surface(c->wp_alpha_modifier_v1, data->surface); + wp_alpha_modifier_surface_v1_set_multiplier(data->wp_alpha_modifier_surface_v1, SDL_MAX_UINT32); + } + /* Must be called before EGL configuration to set the drawable backbuffer size. */ ConfigureWindowGeometry(window); @@ -2494,6 +2506,20 @@ SDL_DisplayID Wayland_GetDisplayForWindow(SDL_VideoDevice *_this, SDL_Window *wi return 0; } +int Wayland_SetWindowOpacity(SDL_VideoDevice *_this, SDL_Window *window, float opacity) +{ + SDL_WindowData *wind = window->driverdata; + + if (wind->wp_alpha_modifier_surface_v1) { + SetSurfaceOpaqueRegion(wind, !(window->flags & SDL_WINDOW_TRANSPARENT) && opacity == 1.0f); + wp_alpha_modifier_surface_v1_set_multiplier(wind->wp_alpha_modifier_surface_v1, (Uint32)((double)SDL_MAX_UINT32 * (double)opacity)); + + return 0; + } + + return SDL_SetError("wayland: set window opacity failed; compositor lacks support for the required wp_alpha_modifier_v1 protocol"); +} + void Wayland_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window) { SDL_WindowData *wind = window->driverdata; @@ -2614,6 +2640,10 @@ void Wayland_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window) wp_fractional_scale_v1_destroy(wind->fractional_scale); } + if (wind->wp_alpha_modifier_surface_v1) { + wp_alpha_modifier_surface_v1_destroy(wind->wp_alpha_modifier_surface_v1); + } + SDL_free(wind->outputs); SDL_free(wind->app_id); diff --git a/src/video/wayland/SDL_waylandwindow.h b/src/video/wayland/SDL_waylandwindow.h index 2469a5f394..21c7e5b868 100644 --- a/src/video/wayland/SDL_waylandwindow.h +++ b/src/video/wayland/SDL_waylandwindow.h @@ -97,6 +97,7 @@ struct SDL_WindowData struct wp_fractional_scale_v1 *fractional_scale; struct zxdg_exported_v2 *exported; struct xdg_dialog_v1 *xdg_dialog_v1; + struct wp_alpha_modifier_surface_v1 *wp_alpha_modifier_surface_v1; SDL_AtomicInt swap_interval_ready; @@ -193,6 +194,7 @@ extern void Wayland_SetWindowMaximumSize(SDL_VideoDevice *_this, SDL_Window *win extern void Wayland_GetWindowSizeInPixels(SDL_VideoDevice *_this, SDL_Window *window, int *w, int *h); extern SDL_DisplayID Wayland_GetDisplayForWindow(SDL_VideoDevice *_this, SDL_Window *window); extern int Wayland_SetWindowModalFor(SDL_VideoDevice *_this, SDL_Window *modal_window, SDL_Window *parent_window); +extern int Wayland_SetWindowOpacity(SDL_VideoDevice *_this, SDL_Window *window, float opacity); extern void Wayland_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window); extern void Wayland_ShowWindowSystemMenu(SDL_Window *window, int x, int y); extern void Wayland_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window); diff --git a/wayland-protocols/alpha-modifier-v1.xml b/wayland-protocols/alpha-modifier-v1.xml new file mode 100644 index 0000000000..932fa6f2a9 --- /dev/null +++ b/wayland-protocols/alpha-modifier-v1.xml @@ -0,0 +1,103 @@ + + + + Copyright © 2024 Xaver Hugl + + 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 (including the next + paragraph) 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. + + + + + This interface allows a client to set a factor for the alpha values on a + surface, which can be used to offload such operations to the compositor, + which can in turn for example offload them to KMS. + + Warning! The protocol described in this file is currently in the testing + phase. Backward compatible changes may be added together with the + corresponding interface version bump. Backward incompatible changes can + only be done by creating a new major version of the extension. + + + + + Destroy the alpha modifier manager. This doesn't destroy objects + created with the manager. + + + + + + + + + + Create a new alpha modifier surface object associated with the + given wl_surface. If there is already such an object associated with + the wl_surface, the already_constructed error will be raised. + + + + + + + + + This interface allows the client to set a factor for the alpha values on + a surface, which can be used to offload such operations to the compositor. + The default factor is UINT32_MAX. + + This object has to be destroyed before the associated wl_surface. Once the + wl_surface is destroyed, all request on this object will raise the + no_surface error. + + + + + + + + + This destroys the object, and is equivalent to set_multiplier with + a value of UINT32_MAX, with the same double-buffered semantics as + set_multiplier. + + + + + + Sets the alpha multiplier for the surface. The alpha multiplier is + double-buffered state, see wl_surface.commit for details. + + This factor is applied in the compositor's blending space, as an + additional step after the processing of per-pixel alpha values for the + wl_surface. The exact meaning of the factor is thus undefined, unless + the blending space is specified in a different extension. + + This multiplier is applied even if the buffer attached to the + wl_surface doesn't have an alpha channel; in that case an alpha value + of one is used instead. + + Zero means completely transparent, UINT32_MAX means completely opaque. + + + + +