/* GTK - The GIMP Toolkit
*
* Copyright (C) 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 .
*
* Author:
* Matthias Clasen
*/
#include "config.h"
#include "gtkbinlayout.h"
#include "gtkgraphicsoffload.h"
#include "gtksnapshotprivate.h"
#include "gtkwidgetprivate.h"
#include "gtkprivate.h"
#include "gdk/gdksurfaceprivate.h"
#include "gdk/gdksubsurfaceprivate.h"
#include "gtktypebuiltins.h"
/**
* GtkGraphicsOffload:
*
* A widget that allows to bypass gsk rendering for its child by passing the content
* directly to the compositor.
*
* Graphics offload is an optimization to reduce overhead and battery use that is
* most useful for video content. It only works on some platforms and in certain
* situations. GTK will automatically fall back to normal rendering if it doesn't.
*
* Graphics offload is most efficient if there are no controls drawn on top of the
* video content.
*
* You should consider using graphics offload for your main widget if it shows
* frequently changing content (such as a video, or a VM display) and you provide
* the content in the form of dmabuf textures (see [class@Gdk.DmabufTextureBuilder]),
* in particular if it may be fullscreen.
*
* Numerous factors can prohibit graphics offload:
*
* - Unsupported platforms. Currently, graphics offload only works on Linux with Wayland.
*
* - Clipping, such as rounded corners that cause the video content to not be rectangular
*
* - Unsupported dmabuf formats (see [method@Gdk.Display.get_dmabuf_formats])
*
* - Translucent video content (content with an alpha channel, even if it isn't used)
*
* - Transforms that are more complex than translations and scales
*
* - Filters such as opacity, grayscale or similar
*
* To investigate problems related graphics offload, GTK offers debug flags to print
* out information about graphics offload and dmabuf use:
*
* GDK_DEBUG=offload
* GDK_DEBUG=dmabuf
*
* The GTK inspector provides a visual debugging tool for graphics offload.
*/
struct _GtkGraphicsOffload
{
GtkWidget parent_instance;
GtkWidget *child;
GdkSubsurface *subsurface;
GtkGraphicsOffloadEnabled enabled;
};
struct _GtkGraphicsOffloadClass
{
GtkWidgetClass parent_class;
};
enum
{
PROP_0,
PROP_CHILD,
PROP_ENABLED,
LAST_PROP,
};
static GParamSpec *properties[LAST_PROP] = { NULL, };
G_DEFINE_TYPE (GtkGraphicsOffload, gtk_graphics_offload, GTK_TYPE_WIDGET)
static void
gtk_graphics_offload_init (GtkGraphicsOffload *self)
{
self->enabled = GTK_GRAPHICS_OFFLOAD_ENABLED;
}
static void
gtk_graphics_offload_dispose (GObject *object)
{
GtkGraphicsOffload *self = GTK_GRAPHICS_OFFLOAD (object);
g_clear_pointer (&self->child, gtk_widget_unparent);
G_OBJECT_CLASS (gtk_graphics_offload_parent_class)->dispose (object);
}
static void
gtk_graphics_offload_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
GtkGraphicsOffload *self = GTK_GRAPHICS_OFFLOAD (object);
switch (property_id)
{
case PROP_CHILD:
gtk_graphics_offload_set_child (self, g_value_get_object (value));
break;
case PROP_ENABLED:
gtk_graphics_offload_set_enabled (self, g_value_get_enum (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
}
static void
gtk_graphics_offload_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
GtkGraphicsOffload *self = GTK_GRAPHICS_OFFLOAD (object);
switch (property_id)
{
case PROP_CHILD:
g_value_set_object (value, gtk_graphics_offload_get_child (self));
break;
case PROP_ENABLED:
g_value_set_enum (value, gtk_graphics_offload_get_enabled (self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
}
static void
sync_subsurface (GtkGraphicsOffload *self)
{
GtkWidget *widget = GTK_WIDGET (self);
if (gtk_widget_get_realized (widget) && self->enabled != GTK_GRAPHICS_OFFLOAD_DISABLED)
{
if (!self->subsurface)
self->subsurface = gdk_surface_create_subsurface (gtk_widget_get_surface (widget));
}
else
{
g_clear_object (&self->subsurface);
}
}
static void
gtk_graphics_offload_realize (GtkWidget *widget)
{
GtkGraphicsOffload *self = GTK_GRAPHICS_OFFLOAD (widget);
GTK_WIDGET_CLASS (gtk_graphics_offload_parent_class)->realize (widget);
sync_subsurface (self);
}
static void
gtk_graphics_offload_unrealize (GtkWidget *widget)
{
GtkGraphicsOffload *self = GTK_GRAPHICS_OFFLOAD (widget);
GTK_WIDGET_CLASS (gtk_graphics_offload_parent_class)->unrealize (widget);
sync_subsurface (self);
}
static void
gtk_graphics_offload_snapshot (GtkWidget *widget,
GtkSnapshot *snapshot)
{
GtkGraphicsOffload *self = GTK_GRAPHICS_OFFLOAD (widget);
if (self->subsurface)
gtk_snapshot_push_subsurface (snapshot, self->subsurface);
gtk_widget_snapshot_child (widget, self->child, snapshot);
if (self->subsurface)
gtk_snapshot_pop (snapshot);
}
static void
gtk_graphics_offload_class_init (GtkGraphicsOffloadClass *class)
{
GObjectClass *object_class = G_OBJECT_CLASS (class);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
object_class->dispose = gtk_graphics_offload_dispose;
object_class->set_property = gtk_graphics_offload_set_property;
object_class->get_property = gtk_graphics_offload_get_property;
widget_class->realize = gtk_graphics_offload_realize;
widget_class->unrealize = gtk_graphics_offload_unrealize;
widget_class->snapshot = gtk_graphics_offload_snapshot;
/**
* GtkGraphicsOffload:child: (attributes org.gtk.Property.get=gtk_graphics_offload_get_child org.gtk.Property.set=gtk_graphics_offload_set_child)
*
* The child widget.
*
* Since: 4.14
*/
properties[PROP_CHILD] = g_param_spec_object ("child", NULL, NULL,
GTK_TYPE_WIDGET,
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkGraphicsOffload:enabled: (attributes org.gtk.Property.get=gtk_graphics_offload_get_enabled org.gtk.Property.set=gtk_graphics_offload_set_enabled)
*
* Whether graphics offload is enabled.
*
* Since: 4.14
*/
properties[PROP_ENABLED] = g_param_spec_enum ("enabled", NULL, NULL,
GTK_TYPE_GRAPHICS_OFFLOAD_ENABLED,
GTK_GRAPHICS_OFFLOAD_ENABLED,
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
g_object_class_install_properties (object_class, LAST_PROP, properties);
gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
gtk_widget_class_set_css_name (widget_class, "graphicsoffload");
}
/**
* gtk_graphics_offload_new:
* @child: (nullable): the child widget
*
* Creates a new GtkGraphicsOffload widget.
*
* Returns: (transfer full): the new widget
*
* Since: 4.14
*/
GtkWidget *
gtk_graphics_offload_new (GtkWidget *child)
{
return g_object_new (GTK_TYPE_GRAPHICS_OFFLOAD,
"child", child,
NULL);
}
/**
* gtk_graphics_offload_set_child:
* @self: a `GtkGraphicsOffload`
* @child: (nullable): the child widget
*
* Sets the child of @self.
*
* Since: 4.14
*/
void
gtk_graphics_offload_set_child (GtkGraphicsOffload *self,
GtkWidget *child)
{
g_return_if_fail (GTK_IS_GRAPHICS_OFFLOAD (self));
g_return_if_fail (child == NULL || self->child == child || (GTK_IS_WIDGET (child) &>k_widget_get_parent (child) == NULL));
if (self->child == child)
return;
g_clear_pointer (&self->child, gtk_widget_unparent);
if (child)
{
self->child = child;
gtk_widget_set_parent (child, GTK_WIDGET (self));
}
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_CHILD]);
}
/**
* gtk_graphics_offload_get_child:
* @self: a `GtkGraphicsOffload`
*
* Gets the child of @self.
*
* Returns: (nullable) (transfer none): the child widget
*
* Since: 4.14
*/
GtkWidget *
gtk_graphics_offload_get_child (GtkGraphicsOffload *self)
{
g_return_val_if_fail (GTK_IS_GRAPHICS_OFFLOAD (self), NULL);
return self->child;
}
/**
* gtk_graphics_offload_set_enabled:
* @self: a `GtkGraphicsOffload`
* @enabled: whether to enable offload
*
* Sets whether this GtkGraphicsOffload widget will attempt
* to offload the content of its child widget.
*
* Since: 4.14
*/
void
gtk_graphics_offload_set_enabled (GtkGraphicsOffload *self,
GtkGraphicsOffloadEnabled enabled)
{
g_return_if_fail (GTK_IS_GRAPHICS_OFFLOAD (self));
if (self->enabled == enabled)
return;
self->enabled = enabled;
sync_subsurface (self);
gtk_widget_queue_draw (GTK_WIDGET (self));
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ENABLED]);
}
/**
* gtk_graphics_offload_get_enabled:
* @self: a `GtkGraphicsOffload`
*
* Returns whether offload is enabled for @self.
*
* Returns: whether offload is enabled
*
* Since: 4.14
*/
GtkGraphicsOffloadEnabled
gtk_graphics_offload_get_enabled (GtkGraphicsOffload *self)
{
g_return_val_if_fail (GTK_IS_GRAPHICS_OFFLOAD (self), TRUE);
return self->enabled;
}