gtk/gdk/wayland/gdksubsurface-wayland.c
Benjamin Otte 9bc0ad9a13 gdk: Handle subsurface opaque region with transparency
We accept transparent subsurfaces for passthrough now, when they are
above the surface.
But we did not unset the opaque region to empty when the texture is
transprent.
2023-12-11 07:32:06 +01:00

488 lines
16 KiB
C

/*
* Copyright © 2023 Red Hat, Inc
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "gdksubsurface-wayland-private.h"
#include "gdkmemoryformatprivate.h"
#include "gdkdisplay-wayland.h"
#include "gdkprivate-wayland.h"
#include "gdkdmabuftextureprivate.h"
#include "gdksurface-wayland-private.h"
#include "gdksubsurfaceprivate.h"
#include "linux-dmabuf-unstable-v1-client-protocol.h"
G_DEFINE_TYPE (GdkWaylandSubsurface, gdk_wayland_subsurface, GDK_TYPE_SUBSURFACE)
static void
gdk_wayland_subsurface_init (GdkWaylandSubsurface *self)
{
}
static void
gdk_wayland_subsurface_finalize (GObject *object)
{
GdkWaylandSubsurface *self = GDK_WAYLAND_SUBSURFACE (object);
g_clear_object (&self->texture);
g_clear_pointer (&self->frame_callback, wl_callback_destroy);
g_clear_pointer (&self->opaque_region, wl_region_destroy);
g_clear_pointer (&self->viewport, wp_viewport_destroy);
g_clear_pointer (&self->subsurface, wl_subsurface_destroy);
g_clear_pointer (&self->surface, wl_surface_destroy);
g_clear_pointer (&self->subsurface, wl_subsurface_destroy);
G_OBJECT_CLASS (gdk_wayland_subsurface_parent_class)->finalize (object);
}
static void
dmabuf_buffer_release (void *data,
struct wl_buffer *buffer)
{
GdkTexture *texture = data;
g_object_unref (texture);
wl_buffer_destroy (buffer);
}
static const struct wl_buffer_listener dmabuf_buffer_listener = {
dmabuf_buffer_release,
};
typedef struct {
struct wl_buffer *buffer;
gboolean done;
} CreateBufferData;
static void
params_buffer_created (void *data,
struct zwp_linux_buffer_params_v1 *params,
struct wl_buffer *buffer)
{
CreateBufferData *cd = data;
cd->buffer = buffer;
cd->done = TRUE;
}
static void
params_buffer_failed (void *data,
struct zwp_linux_buffer_params_v1 *params)
{
CreateBufferData *cd = data;
cd->buffer = NULL;
cd->done = TRUE;
}
static const struct zwp_linux_buffer_params_v1_listener params_listener = {
params_buffer_created,
params_buffer_failed,
};
static struct wl_buffer *
get_wl_buffer (GdkWaylandSubsurface *self,
GdkTexture *texture)
{
GdkWaylandDisplay *display = GDK_WAYLAND_DISPLAY (gdk_surface_get_display (GDK_SUBSURFACE (self)->parent));
const GdkDmabuf *dmabuf;
struct zwp_linux_buffer_params_v1 *params;
struct wl_buffer *buffer;
CreateBufferData cd = { NULL, FALSE };
struct wl_event_queue *event_queue;
dmabuf = gdk_dmabuf_texture_get_dmabuf (GDK_DMABUF_TEXTURE (texture));
params = zwp_linux_dmabuf_v1_create_params (display->linux_dmabuf);
for (gsize i = 0; i < dmabuf->n_planes; i++)
zwp_linux_buffer_params_v1_add (params,
dmabuf->planes[i].fd,
i,
dmabuf->planes[i].offset,
dmabuf->planes[i].stride,
dmabuf->modifier >> 32,
dmabuf->modifier & 0xffffffff);
event_queue = wl_display_create_queue (display->wl_display);
wl_proxy_set_queue ((struct wl_proxy *) params, event_queue);
zwp_linux_buffer_params_v1_add_listener (params, &params_listener, &cd);
zwp_linux_buffer_params_v1_create (params,
gdk_texture_get_width (texture),
gdk_texture_get_height (texture),
dmabuf->fourcc,
0);
while (!cd.done)
gdk_wayland_display_dispatch_queue (GDK_DISPLAY (display), event_queue);
wl_event_queue_destroy (event_queue);
zwp_linux_buffer_params_v1_destroy (params);
buffer = cd.buffer;
if (buffer)
{
wl_proxy_set_queue ((struct wl_proxy *) buffer, NULL);
wl_buffer_add_listener (buffer, &dmabuf_buffer_listener, g_object_ref (texture));
}
return buffer;
}
static gboolean
gdk_wayland_subsurface_attach (GdkSubsurface *sub,
GdkTexture *texture,
const graphene_rect_t *rect,
gboolean above,
GdkSubsurface *sibling)
{
GdkWaylandSubsurface *self = GDK_WAYLAND_SUBSURFACE (sub);
GdkWaylandSurface *parent = GDK_WAYLAND_SURFACE (sub->parent);
struct wl_buffer *buffer = NULL;
gboolean result = FALSE;
GdkWaylandSubsurface *sib = sibling ? GDK_WAYLAND_SUBSURFACE (sibling) : NULL;
gboolean will_be_above;
double scale;
graphene_rect_t device_rect;
cairo_rectangle_int_t device_dest;
if (sib)
will_be_above = sib->above_parent;
else
will_be_above = above;
if (sub->parent == NULL)
{
g_warning ("Can't attach to destroyed subsurface %p", self);
return FALSE;
}
self->dest.x = rect->origin.x;
self->dest.y = rect->origin.y;
self->dest.width = rect->size.width;
self->dest.height = rect->size.height;
scale = gdk_fractional_scale_to_double (&parent->scale);
device_rect.origin.x = rect->origin.x * scale;
device_rect.origin.y = rect->origin.y * scale;
device_rect.size.width = rect->size.width * scale;
device_rect.size.height = rect->size.height * scale;
device_dest.x = device_rect.origin.x;
device_dest.y = device_rect.origin.y;
device_dest.width = device_rect.size.width;
device_dest.height = device_rect.size.height;
if (self->dest.x != rect->origin.x ||
self->dest.y != rect->origin.y ||
self->dest.width != rect->size.width ||
self->dest.height != rect->size.height)
{
GDK_DISPLAY_DEBUG (gdk_surface_get_display (sub->parent), OFFLOAD,
"Non-integer coordinates %g %g %g %g for %dx%d texture, hiding subsurface %p",
rect->origin.x, rect->origin.y,
rect->size.width, rect->size.height,
gdk_texture_get_width (texture),
gdk_texture_get_height (texture),
self);
}
else if (device_dest.x != device_rect.origin.x ||
device_dest.y != device_rect.origin.y ||
device_dest.width != device_rect.size.width ||
device_dest.height != device_rect.size.height)
{
GDK_DISPLAY_DEBUG (gdk_surface_get_display (sub->parent), OFFLOAD,
"Non-integral device coordinates %g %g %g %g (fractional scale %.2f), hiding subsurface %p",
device_rect.origin.x, device_rect.origin.y,
device_rect.size.width, device_rect.size.width,
scale,
self);
}
else if (!GDK_IS_DMABUF_TEXTURE (texture))
{
GDK_DISPLAY_DEBUG (gdk_surface_get_display (sub->parent), OFFLOAD,
"%dx%d %s is not a GdkDmabufTexture, hiding subsurface %p",
gdk_texture_get_width (texture),
gdk_texture_get_height (texture),
G_OBJECT_TYPE_NAME (texture),
self);
}
else if (!will_be_above &&
gdk_memory_format_alpha (gdk_texture_get_format (texture)) != GDK_MEMORY_ALPHA_OPAQUE)
{
GDK_DISPLAY_DEBUG (gdk_surface_get_display (sub->parent), OFFLOAD,
"Cannot offload non-opaque %dx%d texture below, hiding subsurface %p",
gdk_texture_get_width (texture),
gdk_texture_get_height (texture),
self);
}
else
{
gboolean was_transparent;
if (self->texture)
was_transparent = gdk_memory_format_alpha (gdk_texture_get_format (self->texture)) != GDK_MEMORY_ALPHA_OPAQUE;
else
was_transparent = FALSE;
if (g_set_object (&self->texture, texture))
{
buffer = get_wl_buffer (self, texture);
if (buffer != NULL)
{
gboolean is_transparent;
is_transparent = gdk_memory_format_alpha (gdk_texture_get_format (texture)) != GDK_MEMORY_ALPHA_OPAQUE;
if (is_transparent != was_transparent)
{
if (is_transparent)
wl_surface_set_opaque_region (self->surface, NULL);
else
wl_surface_set_opaque_region (self->surface, self->opaque_region);
}
GDK_DISPLAY_DEBUG (gdk_surface_get_display (sub->parent), OFFLOAD,
"Attached %dx%d texture to subsurface %p at %d %d %d %d",
gdk_texture_get_width (texture),
gdk_texture_get_height (texture),
self,
self->dest.x, self->dest.y,
self->dest.width, self->dest.height);
result = TRUE;
}
else
{
GDK_DISPLAY_DEBUG (gdk_surface_get_display (sub->parent), OFFLOAD,
"Compositor failed to create wl_buffer for %dx%d texture, hiding subsurface %p",
gdk_texture_get_width (texture),
gdk_texture_get_height (texture),
self);
}
}
else
{
buffer = NULL;
GDK_DISPLAY_DEBUG (gdk_surface_get_display (sub->parent), OFFLOAD,
"Moved %dx%d texture in subsurface %p to %d %d %d %d",
gdk_texture_get_width (texture),
gdk_texture_get_height (texture),
self,
self->dest.x, self->dest.y,
self->dest.width, self->dest.height);
result = TRUE;
}
}
if (result)
{
wl_subsurface_set_position (self->subsurface, self->dest.x, self->dest.y);
wp_viewport_set_destination (self->viewport, self->dest.width, self->dest.height);
if (buffer)
{
wl_surface_attach (self->surface, buffer, 0, 0);
wl_surface_damage_buffer (self->surface,
0, 0,
gdk_texture_get_width (texture),
gdk_texture_get_height (texture));
}
result = TRUE;
}
else
{
g_set_object (&self->texture, NULL);
wl_surface_attach (self->surface, NULL, 0, 0);
}
if (sib)
{
if (above)
wl_subsurface_place_above (self->subsurface, sib->surface);
else
wl_subsurface_place_below (self->subsurface, sib->surface);
self->above_parent = sib->above_parent;
}
else
{
if (above)
wl_subsurface_place_above (self->subsurface,
GDK_WAYLAND_SURFACE (sub->parent)->display_server.wl_surface);
else
wl_subsurface_place_below (self->subsurface,
GDK_WAYLAND_SURFACE (sub->parent)->display_server.wl_surface);
self->above_parent = above;
}
wl_surface_commit (self->surface);
((GdkWaylandSurface *)sub->parent)->has_pending_subsurface_commits = TRUE;
GDK_WAYLAND_SURFACE (sub->parent)->opaque_region_dirty = TRUE;
return result;
}
static void
gdk_wayland_subsurface_detach (GdkSubsurface *sub)
{
GdkWaylandSubsurface *self = GDK_WAYLAND_SUBSURFACE (sub);
if (sub->parent == NULL)
{
g_warning ("Can't draw to destroyed subsurface %p", self);
return;
}
g_set_object (&self->texture, NULL);
wl_surface_attach (self->surface, NULL, 0, 0);
wl_surface_set_opaque_region (self->surface, self->opaque_region);
wl_surface_commit (self->surface);
((GdkWaylandSurface *)sub->parent)->has_pending_subsurface_commits = TRUE;
GDK_WAYLAND_SURFACE (sub->parent)->opaque_region_dirty = TRUE;
}
static GdkTexture *
gdk_wayland_subsurface_get_texture (GdkSubsurface *sub)
{
GdkWaylandSubsurface *self = GDK_WAYLAND_SUBSURFACE (sub);
return self->texture;
}
static void
gdk_wayland_subsurface_get_rect (GdkSubsurface *sub,
graphene_rect_t *rect)
{
GdkWaylandSubsurface *self = GDK_WAYLAND_SUBSURFACE (sub);
rect->origin.x = self->dest.x;
rect->origin.y = self->dest.y;
rect->size.width = self->dest.width;
rect->size.height = self->dest.height;
}
static gboolean
gdk_wayland_subsurface_is_above_parent (GdkSubsurface *sub)
{
GdkWaylandSubsurface *self = (GdkWaylandSubsurface *)sub;
return self->above_parent;
}
static void
gdk_wayland_subsurface_class_init (GdkWaylandSubsurfaceClass *class)
{
GObjectClass *object_class = G_OBJECT_CLASS (class);
GdkSubsurfaceClass *subsurface_class = GDK_SUBSURFACE_CLASS (class);
object_class->finalize = gdk_wayland_subsurface_finalize;
subsurface_class->attach = gdk_wayland_subsurface_attach;
subsurface_class->detach = gdk_wayland_subsurface_detach;
subsurface_class->get_texture = gdk_wayland_subsurface_get_texture;
subsurface_class->get_rect = gdk_wayland_subsurface_get_rect;
subsurface_class->is_above_parent = gdk_wayland_subsurface_is_above_parent;
};
static void
frame_callback (void *data,
struct wl_callback *callback,
uint32_t time)
{
GdkSubsurface *sub = data;
g_assert (((GdkWaylandSubsurface *)sub)->frame_callback == callback);
g_assert (!GDK_SURFACE_DESTROYED (sub->parent));
gdk_wayland_surface_frame_callback (sub->parent, time);
}
static const struct wl_callback_listener frame_listener = {
frame_callback
};
void
gdk_wayland_subsurface_request_frame (GdkSubsurface *sub)
{
GdkWaylandSubsurface *self = (GdkWaylandSubsurface *)sub;
self->frame_callback = wl_surface_frame (self->surface);
wl_proxy_set_queue ((struct wl_proxy *) self->frame_callback, NULL);
wl_callback_add_listener (self->frame_callback, &frame_listener, self);
wl_surface_commit (self->surface);
}
void
gdk_wayland_subsurface_clear_frame_callback (GdkSubsurface *sub)
{
GdkWaylandSubsurface *self = (GdkWaylandSubsurface *)sub;
g_clear_pointer (&self->frame_callback, wl_callback_destroy);
}
GdkSubsurface *
gdk_wayland_surface_create_subsurface (GdkSurface *surface)
{
GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
GdkDisplay *display = gdk_surface_get_display (surface);
GdkWaylandDisplay *disp = GDK_WAYLAND_DISPLAY (display);
GdkWaylandSubsurface *sub;
struct wl_region *region;
if (disp->viewporter == NULL)
{
GDK_DISPLAY_DEBUG (display, OFFLOAD, "Can't use subsurfaces without viewporter");
return NULL;
}
sub = g_object_new (GDK_TYPE_WAYLAND_SUBSURFACE, NULL);
sub->surface = wl_compositor_create_surface (disp->compositor);
sub->subsurface = wl_subcompositor_get_subsurface (disp->subcompositor,
sub->surface,
impl->display_server.wl_surface);
sub->viewport = wp_viewporter_get_viewport (disp->viewporter, sub->surface);
/* No input, please */
region = wl_compositor_create_region (disp->compositor);
wl_surface_set_input_region (sub->surface, region);
wl_region_destroy (region);
/* Keep a max-sized opaque region so we don't have to update it
* when the size of the texture changes.
*/
sub->opaque_region = wl_compositor_create_region (disp->compositor);
wl_region_add (sub->opaque_region, 0, 0, G_MAXINT, G_MAXINT);
wl_surface_set_opaque_region (sub->surface, sub->opaque_region);
sub->above_parent = TRUE;
GDK_DISPLAY_DEBUG (display, OFFLOAD, "Subsurface %p of surface %p created", sub, impl);
return GDK_SUBSURFACE (sub);
}