mirror of
https://gitlab.gnome.org/GNOME/libmks.git
synced 2024-08-25 01:31:16 +00:00
280 lines
8.5 KiB
C
280 lines
8.5 KiB
C
/*
|
|
* mks-dmabuf-paintable.c
|
|
*
|
|
* Copyright 2023 Christian Hergert <chergert@redhat.com>
|
|
* Copyright 2023 Bilal Elmoussaoui <belmouss@redhat.com>
|
|
*
|
|
* 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.1 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 General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1-or-later
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <gtk/gtk.h>
|
|
|
|
#include "mks-dmabuf-paintable-private.h"
|
|
#include "mks-gl-context-private.h"
|
|
|
|
/*
|
|
* MksDmabufPaintable is a GdkPaintable that gets created the first time
|
|
* `ScanoutDMABUF` is called.
|
|
*
|
|
* The scanout data is then stored until we receive a `UpdateDMABUF` call
|
|
* so we can pass the damage region to `GdkGLTextureBuilder`.
|
|
*/
|
|
|
|
typedef struct _MksDmabufTextureData
|
|
{
|
|
GdkGLContext *gl_context;
|
|
GLuint texture_id;
|
|
} MksDmabufTextureData;
|
|
|
|
struct _MksDmabufPaintable
|
|
{
|
|
GObject parent_instance;
|
|
GdkTexture *texture;
|
|
GdkGLTextureBuilder *builder;
|
|
guint width;
|
|
guint height;
|
|
guint dmabuf_updated : 1;
|
|
};
|
|
|
|
static MksDmabufTextureData *
|
|
mks_dmabuf_texture_data_new (GdkGLContext *gl_context,
|
|
GLuint texture_id)
|
|
{
|
|
MksDmabufTextureData *texture_data;
|
|
|
|
g_assert (GDK_IS_GL_CONTEXT (gl_context));
|
|
g_assert (texture_id > 0);
|
|
|
|
texture_data = g_new0 (MksDmabufTextureData, 1);
|
|
texture_data->gl_context = g_object_ref (gl_context);
|
|
texture_data->texture_id = texture_id;
|
|
|
|
return texture_data;
|
|
}
|
|
|
|
static void
|
|
mks_dmabuf_texture_data_free (gpointer data)
|
|
{
|
|
MksDmabufTextureData *texture_data = data;
|
|
|
|
gdk_gl_context_make_current (texture_data->gl_context);
|
|
glDeleteTextures (1, &texture_data->texture_id);
|
|
|
|
texture_data->texture_id = 0;
|
|
g_clear_object (&texture_data->gl_context);
|
|
|
|
g_free (texture_data);
|
|
}
|
|
|
|
static int
|
|
mks_dmabuf_paintable_get_intrinsic_width (GdkPaintable *paintable)
|
|
{
|
|
return MKS_DMABUF_PAINTABLE (paintable)->width;
|
|
}
|
|
|
|
static int
|
|
mks_dmabuf_paintable_get_intrinsic_height (GdkPaintable *paintable)
|
|
{
|
|
return MKS_DMABUF_PAINTABLE (paintable)->height;
|
|
}
|
|
|
|
static double
|
|
mks_dmabuf_paintable_get_intrinsic_aspect_ratio (GdkPaintable *paintable)
|
|
{
|
|
MksDmabufPaintable *self = MKS_DMABUF_PAINTABLE (paintable);
|
|
|
|
if (self->height != 0)
|
|
return (double)self->width / (double)self->height;
|
|
else
|
|
return 0.;
|
|
}
|
|
|
|
static void
|
|
mks_dmabuf_paintable_snapshot (GdkPaintable *paintable,
|
|
GdkSnapshot *snapshot,
|
|
double width,
|
|
double height)
|
|
{
|
|
MksDmabufPaintable *self = (MksDmabufPaintable *)paintable;
|
|
g_autoptr(GdkTexture) texture = NULL;
|
|
GdkGLContext *gl_context;
|
|
GLuint texture_id;
|
|
graphene_rect_t area;
|
|
|
|
g_assert (MKS_IS_DMABUF_PAINTABLE (self));
|
|
g_assert (GDK_IS_SNAPSHOT (snapshot));
|
|
|
|
/**
|
|
* If the widget gets resized, snapshot would be called even
|
|
* if we didn't receive a new DMABufUpdate call.
|
|
* So only create a new GLTexture when that happens
|
|
*/
|
|
if (self->dmabuf_updated)
|
|
{
|
|
texture_id = gdk_gl_texture_builder_get_id (self->builder);
|
|
gl_context = gdk_gl_texture_builder_get_context (self->builder);
|
|
|
|
|
|
gdk_gl_texture_builder_set_update_texture (self->builder, self->texture);
|
|
texture = gdk_gl_texture_builder_build (self->builder,
|
|
mks_dmabuf_texture_data_free,
|
|
mks_dmabuf_texture_data_new (gl_context,
|
|
texture_id));
|
|
// Clear up the update region to not union it with the next UpdateDMABuf call
|
|
gdk_gl_texture_builder_set_update_region (self->builder, NULL);
|
|
g_set_object (&self->texture, texture);
|
|
self->dmabuf_updated = FALSE;
|
|
}
|
|
|
|
area = GRAPHENE_RECT_INIT (0, 0, width, height);
|
|
gtk_snapshot_append_texture (snapshot, self->texture, &area);
|
|
}
|
|
|
|
static void
|
|
paintable_iface_init (GdkPaintableInterface *iface)
|
|
{
|
|
iface->get_intrinsic_width = mks_dmabuf_paintable_get_intrinsic_width;
|
|
iface->get_intrinsic_height = mks_dmabuf_paintable_get_intrinsic_height;
|
|
iface->get_intrinsic_aspect_ratio = mks_dmabuf_paintable_get_intrinsic_aspect_ratio;
|
|
iface->snapshot = mks_dmabuf_paintable_snapshot;
|
|
}
|
|
|
|
G_DEFINE_FINAL_TYPE_WITH_CODE (MksDmabufPaintable, mks_dmabuf_paintable, G_TYPE_OBJECT,
|
|
G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE, paintable_iface_init))
|
|
|
|
static void
|
|
mks_dmabuf_paintable_dispose (GObject *object)
|
|
{
|
|
MksDmabufPaintable *self = (MksDmabufPaintable *)object;
|
|
|
|
g_clear_object (&self->texture);
|
|
g_clear_object (&self->builder);
|
|
|
|
G_OBJECT_CLASS (mks_dmabuf_paintable_parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
mks_dmabuf_paintable_class_init (MksDmabufPaintableClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
|
|
object_class->dispose = mks_dmabuf_paintable_dispose;
|
|
}
|
|
|
|
static void
|
|
mks_dmabuf_paintable_init (MksDmabufPaintable *self)
|
|
{
|
|
}
|
|
|
|
gboolean
|
|
mks_dmabuf_paintable_import (MksDmabufPaintable *self,
|
|
GdkGLContext *gl_context,
|
|
MksDmabufScanoutData *data,
|
|
cairo_region_t *region,
|
|
GError **error)
|
|
{
|
|
cairo_region_t *accumulated_damages;
|
|
cairo_region_t *previous_region;
|
|
GLuint texture_id;
|
|
guint zero = 0;
|
|
|
|
g_return_val_if_fail (MKS_IS_DMABUF_PAINTABLE (self), FALSE);
|
|
g_return_val_if_fail (!gl_context || GDK_IS_GL_CONTEXT (gl_context), FALSE);
|
|
|
|
if (data->dmabuf_fd < 0)
|
|
{
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_ARGUMENT,
|
|
"invalid dmabuf_fd (%d)",
|
|
data->dmabuf_fd);
|
|
return FALSE;
|
|
}
|
|
|
|
if (data->width == 0 || data->height == 0 || data->stride == 0)
|
|
{
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_ARGUMENT,
|
|
"invalid width/height/stride (%u/%u/%u)",
|
|
data->width, data->height, data->stride);
|
|
return FALSE;
|
|
}
|
|
|
|
if (self->width != data->width || self->height != data->height)
|
|
{
|
|
self->width = data->width;
|
|
self->height = data->height;
|
|
gdk_paintable_invalidate_size (GDK_PAINTABLE (self));
|
|
}
|
|
|
|
if (!(texture_id = mks_gl_context_import_dmabuf (gl_context,
|
|
data->fourcc, data->width, data->height,
|
|
1, &data->dmabuf_fd, &data->stride, &zero,
|
|
&data->modifier)))
|
|
{
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"Failed to import dmabuf into GL texture");
|
|
return FALSE;
|
|
}
|
|
|
|
accumulated_damages = cairo_region_create ();
|
|
|
|
if (region != NULL)
|
|
cairo_region_union (accumulated_damages, region);
|
|
|
|
if (self->builder != NULL)
|
|
{
|
|
previous_region = gdk_gl_texture_builder_get_update_region (self->builder);
|
|
if (previous_region != NULL)
|
|
cairo_region_union (accumulated_damages, previous_region);
|
|
}
|
|
|
|
g_clear_object (&self->builder);
|
|
|
|
self->builder = gdk_gl_texture_builder_new ();
|
|
gdk_gl_texture_builder_set_width (self->builder, self->width);
|
|
gdk_gl_texture_builder_set_height (self->builder, self->height);
|
|
gdk_gl_texture_builder_set_context (self->builder, gl_context);
|
|
gdk_gl_texture_builder_set_id (self->builder, texture_id);
|
|
|
|
if (cairo_region_num_rectangles (accumulated_damages) > 0)
|
|
gdk_gl_texture_builder_set_update_region (self->builder,
|
|
accumulated_damages);
|
|
|
|
g_clear_pointer (&accumulated_damages, cairo_region_destroy);
|
|
self->dmabuf_updated = TRUE;
|
|
gdk_paintable_invalidate_contents (GDK_PAINTABLE (self));
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
MksDmabufPaintable *
|
|
mks_dmabuf_paintable_new (void)
|
|
{
|
|
g_autoptr(MksDmabufPaintable) self = NULL;
|
|
|
|
self = g_object_new (MKS_TYPE_DMABUF_PAINTABLE, NULL);
|
|
self->dmabuf_updated = FALSE;
|
|
self->width = 0;
|
|
self->height = 0;
|
|
return g_steal_pointer (&self);
|
|
}
|