libmks/lib/mks-dmabuf-paintable.c
Bilal Elmoussaoui 640587ed0f dmabuf-paintable: Build the texture when snapshot is called
Always creating the texture even if the app is not being displayed
(minimized / different virtual monitor) or
if the GdkFrameClock drops a frame we end up doing a comparison with a
very old frame causing full redraws in certain cases or even artifacts
in others.

Instead, we only build the texture once snapshot is called and accumulate
the damage area until that happens to avoid updating the wrong area
2023-08-15 19:37:57 +02:00

264 lines
8.0 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;
};
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);
return (double)self->width / (double)self->height;
}
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));
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);
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);
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->width = 0;
self->height = 0;
return g_steal_pointer (&self);
}