469 lines
15 KiB
C
469 lines
15 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, ¶ms_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
|
||
|
{
|
||
|
if (g_set_object (&self->texture, texture))
|
||
|
{
|
||
|
buffer = get_wl_buffer (self, texture);
|
||
|
if (buffer != NULL)
|
||
|
{
|
||
|
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_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);
|
||
|
}
|
||
|
|