mirror of
https://gitlab.gnome.org/GNOME/gtk.git
synced 2024-12-27 06:00:22 +00:00
2d230f79a6
Move this from GSK_DEBUG=offload-disable to GDK_DISABLE=offload.
826 lines
23 KiB
C
826 lines
23 KiB
C
/* GSK - The GTK Scene Kit
|
|
* Copyright 2016 Endless
|
|
*
|
|
* 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/>.
|
|
*/
|
|
|
|
/**
|
|
* GskRenderer:
|
|
*
|
|
* `GskRenderer` is a class that renders a scene graph defined via a
|
|
* tree of [class@Gsk.RenderNode] instances.
|
|
*
|
|
* Typically you will use a `GskRenderer` instance to repeatedly call
|
|
* [method@Gsk.Renderer.render] to update the contents of its associated
|
|
* [class@Gdk.Surface].
|
|
*
|
|
* It is necessary to realize a `GskRenderer` instance using
|
|
* [method@Gsk.Renderer.realize] before calling [method@Gsk.Renderer.render],
|
|
* in order to create the appropriate windowing system resources needed
|
|
* to render the scene.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "gskrendererprivate.h"
|
|
|
|
#include "gskcairorenderer.h"
|
|
#include "gskdebugprivate.h"
|
|
#include "gskprofilerprivate.h"
|
|
#include "gskrendernodeprivate.h"
|
|
#include "gskoffloadprivate.h"
|
|
|
|
#include "gskenumtypes.h"
|
|
|
|
#include "gl/gskglrenderer.h"
|
|
#include "gpu/gskvulkanrenderer.h"
|
|
#include "gdk/gdkvulkancontextprivate.h"
|
|
#include "gdk/gdkdisplayprivate.h"
|
|
|
|
#include <graphene-gobject.h>
|
|
#include <cairo-gobject.h>
|
|
#include <gdk/gdk.h>
|
|
#include "gdk/gdkdebugprivate.h"
|
|
|
|
#ifdef GDK_WINDOWING_X11
|
|
#include <gdk/x11/gdkx.h>
|
|
#endif
|
|
#ifdef GDK_WINDOWING_WAYLAND
|
|
#include <gdk/wayland/gdkwayland.h>
|
|
#endif
|
|
#ifdef GDK_WINDOWING_BROADWAY
|
|
#include "broadway/gskbroadwayrenderer.h"
|
|
#endif
|
|
#ifdef GDK_WINDOWING_MACOS
|
|
#include <gdk/macos/gdkmacos.h>
|
|
#endif
|
|
#ifdef GDK_WINDOWING_WIN32
|
|
#include <gdk/win32/gdkwin32.h>
|
|
|
|
/* Remove these lines when OpenGL/ES 2.0 shader is ready */
|
|
#include "win32/gdkprivate-win32.h"
|
|
#include "win32/gdkdisplay-win32.h"
|
|
#endif
|
|
|
|
typedef struct
|
|
{
|
|
GObject parent_instance;
|
|
|
|
GdkSurface *surface;
|
|
GskRenderNode *prev_node;
|
|
|
|
GskProfiler *profiler;
|
|
|
|
GskDebugFlags debug_flags;
|
|
|
|
unsigned int is_realized : 1;
|
|
} GskRendererPrivate;
|
|
|
|
G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GskRenderer, gsk_renderer, G_TYPE_OBJECT)
|
|
|
|
enum {
|
|
PROP_0,
|
|
PROP_REALIZED,
|
|
PROP_SURFACE,
|
|
|
|
N_PROPS
|
|
};
|
|
|
|
static GParamSpec *gsk_renderer_properties[N_PROPS];
|
|
|
|
#define GSK_RENDERER_WARN_NOT_IMPLEMENTED_METHOD(obj,method) \
|
|
g_critical ("Renderer of type '%s' does not implement GskRenderer::" # method, G_OBJECT_TYPE_NAME (obj))
|
|
|
|
static gboolean
|
|
gsk_renderer_real_realize (GskRenderer *self,
|
|
GdkDisplay *display,
|
|
GdkSurface *surface,
|
|
GError **error)
|
|
{
|
|
GSK_RENDERER_WARN_NOT_IMPLEMENTED_METHOD (self, realize);
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
gsk_renderer_real_unrealize (GskRenderer *self)
|
|
{
|
|
GSK_RENDERER_WARN_NOT_IMPLEMENTED_METHOD (self, unrealize);
|
|
}
|
|
|
|
static GdkTexture *
|
|
gsk_renderer_real_render_texture (GskRenderer *self,
|
|
GskRenderNode *root,
|
|
const graphene_rect_t *viewport)
|
|
{
|
|
GSK_RENDERER_WARN_NOT_IMPLEMENTED_METHOD (self, render_texture);
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
gsk_renderer_real_render (GskRenderer *self,
|
|
GskRenderNode *root,
|
|
const cairo_region_t *region)
|
|
{
|
|
GSK_RENDERER_WARN_NOT_IMPLEMENTED_METHOD (self, render);
|
|
}
|
|
|
|
static void
|
|
gsk_renderer_dispose (GObject *gobject)
|
|
{
|
|
GskRenderer *self = GSK_RENDERER (gobject);
|
|
GskRendererPrivate *priv = gsk_renderer_get_instance_private (self);
|
|
|
|
/* We can't just unrealize here because superclasses have already run dispose.
|
|
* So we insist that unrealize must be called before unreffing. */
|
|
g_assert (!priv->is_realized);
|
|
|
|
g_clear_object (&priv->profiler);
|
|
|
|
G_OBJECT_CLASS (gsk_renderer_parent_class)->dispose (gobject);
|
|
}
|
|
|
|
static void
|
|
gsk_renderer_get_property (GObject *gobject,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
GskRenderer *self = GSK_RENDERER (gobject);
|
|
GskRendererPrivate *priv = gsk_renderer_get_instance_private (self);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_REALIZED:
|
|
g_value_set_boolean (value, priv->is_realized);
|
|
break;
|
|
|
|
case PROP_SURFACE:
|
|
g_value_set_object (value, priv->surface);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gsk_renderer_class_init (GskRendererClass *klass)
|
|
{
|
|
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
|
|
|
klass->realize = gsk_renderer_real_realize;
|
|
klass->unrealize = gsk_renderer_real_unrealize;
|
|
klass->render = gsk_renderer_real_render;
|
|
klass->render_texture = gsk_renderer_real_render_texture;
|
|
|
|
gobject_class->get_property = gsk_renderer_get_property;
|
|
gobject_class->dispose = gsk_renderer_dispose;
|
|
|
|
/**
|
|
* GskRenderer:realized: (attributes org.gtk.Property.get=gsk_renderer_is_realized)
|
|
*
|
|
* Whether the renderer has been associated with a surface or draw context.
|
|
*/
|
|
gsk_renderer_properties[PROP_REALIZED] =
|
|
g_param_spec_boolean ("realized", NULL, NULL,
|
|
FALSE,
|
|
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
|
|
|
/**
|
|
* GskRenderer:surface: (attributes org.gtk.Property.get=gsk_renderer_get_surface)
|
|
*
|
|
* The surface associated with renderer.
|
|
*/
|
|
gsk_renderer_properties[PROP_SURFACE] =
|
|
g_param_spec_object ("surface", NULL, NULL,
|
|
GDK_TYPE_SURFACE,
|
|
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
|
|
|
g_object_class_install_properties (gobject_class, N_PROPS, gsk_renderer_properties);
|
|
}
|
|
|
|
static void
|
|
gsk_renderer_init (GskRenderer *self)
|
|
{
|
|
GskRendererPrivate *priv = gsk_renderer_get_instance_private (self);
|
|
|
|
priv->profiler = gsk_profiler_new ();
|
|
priv->debug_flags = gsk_get_debug_flags ();
|
|
}
|
|
|
|
/**
|
|
* gsk_renderer_get_surface: (attributes org.gtk.Method.get_property=surface)
|
|
* @renderer: a `GskRenderer`
|
|
*
|
|
* Retrieves the `GdkSurface` set using gsk_enderer_realize().
|
|
*
|
|
* If the renderer has not been realized yet, %NULL will be returned.
|
|
*
|
|
* Returns: (transfer none) (nullable): a `GdkSurface`
|
|
*/
|
|
GdkSurface *
|
|
gsk_renderer_get_surface (GskRenderer *renderer)
|
|
{
|
|
GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDERER (renderer), NULL);
|
|
|
|
return priv->surface;
|
|
}
|
|
|
|
/**
|
|
* gsk_renderer_is_realized: (attributes org.gtk.Method.get_property=realized)
|
|
* @renderer: a `GskRenderer`
|
|
*
|
|
* Checks whether the @renderer is realized or not.
|
|
*
|
|
* Returns: %TRUE if the `GskRenderer` was realized, and %FALSE otherwise
|
|
*/
|
|
gboolean
|
|
gsk_renderer_is_realized (GskRenderer *renderer)
|
|
{
|
|
GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDERER (renderer), FALSE);
|
|
|
|
return priv->is_realized;
|
|
}
|
|
|
|
static gboolean
|
|
gsk_renderer_do_realize (GskRenderer *renderer,
|
|
GdkDisplay *display,
|
|
GdkSurface *surface,
|
|
GError **error)
|
|
{
|
|
GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
|
|
|
|
if (surface)
|
|
priv->surface = g_object_ref (surface);
|
|
|
|
if (!GSK_RENDERER_GET_CLASS (renderer)->realize (renderer, display, surface, error))
|
|
{
|
|
g_clear_object (&priv->surface);
|
|
return FALSE;
|
|
}
|
|
|
|
priv->is_realized = TRUE;
|
|
|
|
g_object_notify (G_OBJECT (renderer), "realized");
|
|
if (surface)
|
|
g_object_notify (G_OBJECT (renderer), "surface");
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* gsk_renderer_realize:
|
|
* @renderer: a `GskRenderer`
|
|
* @surface: (nullable): the `GdkSurface` renderer will be used on
|
|
* @error: return location for an error
|
|
*
|
|
* Creates the resources needed by the @renderer to render the scene
|
|
* graph.
|
|
*
|
|
* Since GTK 4.6, the surface may be `NULL`, which allows using
|
|
* renderers without having to create a surface.
|
|
* Since GTK 4.14, it is recommended to use [method@Gsk.Renderer.realize_for_display]
|
|
* instead.
|
|
*
|
|
* Note that it is mandatory to call [method@Gsk.Renderer.unrealize] before
|
|
* destroying the renderer.
|
|
*
|
|
* Returns: Whether the renderer was successfully realized
|
|
*/
|
|
gboolean
|
|
gsk_renderer_realize (GskRenderer *renderer,
|
|
GdkSurface *surface,
|
|
GError **error)
|
|
{
|
|
g_return_val_if_fail (GSK_IS_RENDERER (renderer), FALSE);
|
|
g_return_val_if_fail (!gsk_renderer_is_realized (renderer), FALSE);
|
|
g_return_val_if_fail (surface == NULL || GDK_IS_SURFACE (surface), FALSE);
|
|
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
|
|
|
if (surface == NULL)
|
|
{
|
|
return gsk_renderer_do_realize (renderer,
|
|
gdk_display_get_default (),
|
|
NULL,
|
|
error);
|
|
}
|
|
else
|
|
{
|
|
return gsk_renderer_do_realize (renderer,
|
|
gdk_surface_get_display (surface),
|
|
surface,
|
|
error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* gsk_renderer_realize_for_display:
|
|
* @renderer: a `GskRenderer`
|
|
* @display: the `GdkDisplay` renderer will be used on
|
|
* @error: return location for an error
|
|
*
|
|
* Creates the resources needed by the @renderer to render the scene
|
|
* graph.
|
|
*
|
|
* Note that it is mandatory to call [method@Gsk.Renderer.unrealize] before
|
|
* destroying the renderer.
|
|
*
|
|
* Returns: Whether the renderer was successfully realized
|
|
*
|
|
* Since: 4.14
|
|
*/
|
|
gboolean
|
|
gsk_renderer_realize_for_display (GskRenderer *renderer,
|
|
GdkDisplay *display,
|
|
GError **error)
|
|
{
|
|
g_return_val_if_fail (GSK_IS_RENDERER (renderer), FALSE);
|
|
g_return_val_if_fail (!gsk_renderer_is_realized (renderer), FALSE);
|
|
g_return_val_if_fail (display == NULL || GDK_IS_DISPLAY (display), FALSE);
|
|
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
|
|
|
return gsk_renderer_do_realize (renderer, display, NULL, error);
|
|
}
|
|
|
|
/**
|
|
* gsk_renderer_unrealize:
|
|
* @renderer: a `GskRenderer`
|
|
*
|
|
* Releases all the resources created by gsk_renderer_realize().
|
|
*/
|
|
void
|
|
gsk_renderer_unrealize (GskRenderer *renderer)
|
|
{
|
|
GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
|
|
gboolean has_surface;
|
|
|
|
g_return_if_fail (GSK_IS_RENDERER (renderer));
|
|
|
|
if (!priv->is_realized)
|
|
return;
|
|
|
|
has_surface = priv->surface != NULL;
|
|
|
|
GSK_RENDERER_GET_CLASS (renderer)->unrealize (renderer);
|
|
|
|
g_clear_object (&priv->surface);
|
|
g_clear_pointer (&priv->prev_node, gsk_render_node_unref);
|
|
|
|
priv->is_realized = FALSE;
|
|
|
|
g_object_notify (G_OBJECT (renderer), "realized");
|
|
if (has_surface)
|
|
g_object_notify (G_OBJECT (renderer), "surface");
|
|
}
|
|
|
|
/**
|
|
* gsk_renderer_render_texture:
|
|
* @renderer: a realized `GskRenderer`
|
|
* @root: a `GskRenderNode`
|
|
* @viewport: (nullable): the section to draw or %NULL to use @root's bounds
|
|
*
|
|
* Renders the scene graph, described by a tree of `GskRenderNode` instances,
|
|
* to a `GdkTexture`.
|
|
*
|
|
* The @renderer will acquire a reference on the `GskRenderNode` tree while
|
|
* the rendering is in progress.
|
|
*
|
|
* If you want to apply any transformations to @root, you should put it into a
|
|
* transform node and pass that node instead.
|
|
*
|
|
* Returns: (transfer full): a `GdkTexture` with the rendered contents of @root.
|
|
*/
|
|
GdkTexture *
|
|
gsk_renderer_render_texture (GskRenderer *renderer,
|
|
GskRenderNode *root,
|
|
const graphene_rect_t *viewport)
|
|
{
|
|
GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
|
|
graphene_rect_t real_viewport;
|
|
GdkTexture *texture;
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDERER (renderer), NULL);
|
|
g_return_val_if_fail (priv->is_realized, NULL);
|
|
g_return_val_if_fail (GSK_IS_RENDER_NODE (root), NULL);
|
|
|
|
if (viewport == NULL)
|
|
{
|
|
gsk_render_node_get_bounds (root, &real_viewport);
|
|
viewport = &real_viewport;
|
|
}
|
|
g_return_val_if_fail (viewport->size.width > 0, NULL);
|
|
g_return_val_if_fail (viewport->size.height > 0, NULL);
|
|
|
|
texture = GSK_RENDERER_GET_CLASS (renderer)->render_texture (renderer, root, viewport);
|
|
|
|
return texture;
|
|
}
|
|
|
|
/**
|
|
* gsk_renderer_render:
|
|
* @renderer: a realized `GskRenderer`
|
|
* @root: a `GskRenderNode`
|
|
* @region: (nullable): the `cairo_region_t` that must be redrawn or %NULL
|
|
* for the whole window
|
|
*
|
|
* Renders the scene graph, described by a tree of `GskRenderNode` instances
|
|
* to the renderer's surface, ensuring that the given @region gets redrawn.
|
|
*
|
|
* If the renderer has no associated surface, this function does nothing.
|
|
*
|
|
* Renderers must ensure that changes of the contents given by the @root
|
|
* node as well as the area given by @region are redrawn. They are however
|
|
* free to not redraw any pixel outside of @region if they can guarantee that
|
|
* it didn't change.
|
|
*
|
|
* The @renderer will acquire a reference on the `GskRenderNode` tree while
|
|
* the rendering is in progress.
|
|
*/
|
|
void
|
|
gsk_renderer_render (GskRenderer *renderer,
|
|
GskRenderNode *root,
|
|
const cairo_region_t *region)
|
|
{
|
|
GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
|
|
GskRendererClass *renderer_class;
|
|
cairo_region_t *clip;
|
|
GskOffload *offload;
|
|
|
|
g_return_if_fail (GSK_IS_RENDERER (renderer));
|
|
g_return_if_fail (priv->is_realized);
|
|
g_return_if_fail (GSK_IS_RENDER_NODE (root));
|
|
|
|
if (priv->surface == NULL)
|
|
return;
|
|
|
|
renderer_class = GSK_RENDERER_GET_CLASS (renderer);
|
|
|
|
clip = cairo_region_copy (region);
|
|
|
|
if (renderer_class->supports_offload && gdk_has_feature (GDK_FEATURE_OFFLOAD))
|
|
offload = gsk_offload_new (priv->surface, root, clip);
|
|
else
|
|
offload = NULL;
|
|
|
|
if (region == NULL || priv->prev_node == NULL || GSK_RENDERER_DEBUG_CHECK (renderer, FULL_REDRAW))
|
|
{
|
|
cairo_region_union_rectangle (clip,
|
|
&(GdkRectangle) {
|
|
0, 0,
|
|
gdk_surface_get_width (priv->surface),
|
|
gdk_surface_get_height (priv->surface)
|
|
});
|
|
}
|
|
else
|
|
{
|
|
gsk_render_node_diff (priv->prev_node, root, &(GskDiffData) { clip, priv->surface });
|
|
}
|
|
|
|
renderer_class->render (renderer, root, clip);
|
|
|
|
g_clear_pointer (&priv->prev_node, gsk_render_node_unref);
|
|
cairo_region_destroy (clip);
|
|
g_clear_pointer (&offload, gsk_offload_free);
|
|
priv->prev_node = gsk_render_node_ref (root);
|
|
}
|
|
|
|
/*< private >
|
|
* gsk_renderer_get_profiler:
|
|
* @renderer: a `GskRenderer`
|
|
*
|
|
* Retrieves a pointer to the `GskProfiler` instance of the renderer.
|
|
*
|
|
* Returns: (transfer none): the profiler
|
|
*/
|
|
GskProfiler *
|
|
gsk_renderer_get_profiler (GskRenderer *renderer)
|
|
{
|
|
GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDERER (renderer), NULL);
|
|
|
|
return priv->profiler;
|
|
}
|
|
|
|
static GType
|
|
get_renderer_for_name (const char *renderer_name)
|
|
{
|
|
if (renderer_name == NULL)
|
|
return G_TYPE_INVALID;
|
|
#ifdef GDK_WINDOWING_BROADWAY
|
|
else if (g_ascii_strcasecmp (renderer_name, "broadway") == 0)
|
|
return GSK_TYPE_BROADWAY_RENDERER;
|
|
#endif
|
|
else if (g_ascii_strcasecmp (renderer_name, "cairo") == 0)
|
|
return GSK_TYPE_CAIRO_RENDERER;
|
|
else if (g_ascii_strcasecmp (renderer_name, "opengl") == 0 ||
|
|
g_ascii_strcasecmp (renderer_name, "gl") == 0)
|
|
return GSK_TYPE_GL_RENDERER;
|
|
else if (g_ascii_strcasecmp (renderer_name, "ngl") == 0)
|
|
return gsk_ngl_renderer_get_type ();
|
|
#ifdef GDK_RENDERING_VULKAN
|
|
else if (g_ascii_strcasecmp (renderer_name, "vulkan") == 0)
|
|
return GSK_TYPE_VULKAN_RENDERER;
|
|
#endif
|
|
else if (g_ascii_strcasecmp (renderer_name, "help") == 0)
|
|
{
|
|
g_print ("Supported arguments for GSK_RENDERER environment variable:\n");
|
|
#ifdef GDK_WINDOWING_BROADWAY
|
|
g_print (" broadway - Use the Broadway specific renderer\n");
|
|
#else
|
|
g_print (" broadway - Disabled during GTK build\n");
|
|
#endif
|
|
g_print (" cairo - Use the Cairo fallback renderer\n");
|
|
g_print (" opengl - Use the OpenGL renderer\n");
|
|
g_print (" gl - Use the OpenGL renderer\n");
|
|
g_print (" ngl - Use the new OpenGL renderer\n");
|
|
#ifdef GDK_RENDERING_VULKAN
|
|
g_print (" vulkan - Use the Vulkan renderer\n");
|
|
#else
|
|
g_print (" vulkan - Disabled during GTK build\n");
|
|
#endif
|
|
g_print (" help - Print this help\n\n");
|
|
g_print ("Other arguments will cause a warning and be ignored.\n");
|
|
}
|
|
else
|
|
{
|
|
g_warning ("Unrecognized renderer \"%s\". Try GSK_RENDERER=help", renderer_name);
|
|
}
|
|
|
|
return G_TYPE_INVALID;
|
|
}
|
|
|
|
static GType
|
|
get_renderer_for_display (GdkSurface *surface)
|
|
{
|
|
GdkDisplay *display = gdk_surface_get_display (surface);
|
|
const char *renderer_name;
|
|
|
|
renderer_name = g_object_get_data (G_OBJECT (display), "gsk-renderer");
|
|
return get_renderer_for_name (renderer_name);
|
|
}
|
|
|
|
static GType
|
|
get_renderer_for_env_var (GdkSurface *surface)
|
|
{
|
|
static GType env_var_type = G_TYPE_INVALID;
|
|
|
|
if (env_var_type == G_TYPE_INVALID)
|
|
{
|
|
const char *renderer_name = g_getenv ("GSK_RENDERER");
|
|
env_var_type = get_renderer_for_name (renderer_name);
|
|
if (env_var_type != G_TYPE_INVALID)
|
|
GSK_DEBUG (RENDERER,
|
|
"Environment variable GSK_RENDERER=%s set, trying %s",
|
|
renderer_name,
|
|
g_type_name (env_var_type));
|
|
}
|
|
|
|
return env_var_type;
|
|
}
|
|
|
|
static GType
|
|
get_renderer_for_backend (GdkSurface *surface)
|
|
{
|
|
#ifdef GDK_WINDOWING_BROADWAY
|
|
if (GDK_IS_BROADWAY_SURFACE (surface))
|
|
return GSK_TYPE_BROADWAY_RENDERER;
|
|
#endif
|
|
|
|
return G_TYPE_INVALID;
|
|
}
|
|
|
|
static gboolean
|
|
gl_supported_platform (GdkSurface *surface,
|
|
GType renderer_type,
|
|
gboolean as_fallback)
|
|
{
|
|
GdkDisplay *display = gdk_surface_get_display (surface);
|
|
GdkGLContext *context;
|
|
GError *error = NULL;
|
|
|
|
if (!gdk_display_prepare_gl (display, &error))
|
|
{
|
|
if (!as_fallback)
|
|
GSK_DEBUG (RENDERER, "Not using GL: %s", error->message);
|
|
g_clear_error (&error);
|
|
return FALSE;
|
|
}
|
|
|
|
if (as_fallback)
|
|
return TRUE;
|
|
|
|
context = gdk_display_get_gl_context (display);
|
|
gdk_gl_context_make_current (context);
|
|
|
|
if (strstr ((const char *) glGetString (GL_RENDERER), "llvmpipe") != NULL)
|
|
{
|
|
GSK_DEBUG (RENDERER, "Not using '%s': renderer is llvmpipe", g_type_name (renderer_type));
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static GType
|
|
get_renderer_for_gl (GdkSurface *surface)
|
|
{
|
|
if (!gl_supported_platform (surface, gsk_ngl_renderer_get_type (), FALSE))
|
|
return G_TYPE_INVALID;
|
|
|
|
return gsk_ngl_renderer_get_type ();
|
|
}
|
|
|
|
static GType
|
|
get_renderer_for_gl_fallback (GdkSurface *surface)
|
|
{
|
|
if (!gl_supported_platform (surface, GSK_TYPE_GL_RENDERER, TRUE))
|
|
return G_TYPE_INVALID;
|
|
|
|
return GSK_TYPE_GL_RENDERER;
|
|
}
|
|
|
|
#ifdef GDK_RENDERING_VULKAN
|
|
static gboolean
|
|
vulkan_supported_platform (GdkSurface *surface,
|
|
GType renderer_type,
|
|
gboolean as_fallback)
|
|
{
|
|
GdkDisplay *display = gdk_surface_get_display (surface);
|
|
VkPhysicalDeviceProperties props;
|
|
GError *error = NULL;
|
|
|
|
if (!gdk_display_init_vulkan (display, &error))
|
|
{
|
|
if (!as_fallback)
|
|
GSK_DEBUG (RENDERER, "Not using Vulkan: %s", error->message);
|
|
g_clear_error (&error);
|
|
return FALSE;
|
|
}
|
|
|
|
vkGetPhysicalDeviceProperties (display->vk_physical_device, &props);
|
|
|
|
if (props.deviceType == VK_PHYSICAL_DEVICE_TYPE_CPU)
|
|
{
|
|
if (!as_fallback)
|
|
GSK_DEBUG (RENDERER, "Not using '%s': device is CPU", g_type_name (renderer_type));
|
|
return FALSE;
|
|
}
|
|
|
|
gdk_display_init_dmabuf (display);
|
|
if (!display->vk_dmabuf_formats ||
|
|
gdk_dmabuf_formats_get_n_formats (display->vk_dmabuf_formats) == 0)
|
|
{
|
|
if (!as_fallback)
|
|
GSK_DEBUG (RENDERER, "Not using '%s': no dmabuf support", g_type_name (renderer_type));
|
|
return FALSE;
|
|
}
|
|
|
|
if (as_fallback)
|
|
return TRUE;
|
|
|
|
#ifdef GDK_WINDOWING_WAYLAND
|
|
if (GDK_IS_WAYLAND_DISPLAY (gdk_surface_get_display (surface)))
|
|
return TRUE;
|
|
#endif
|
|
|
|
GSK_DEBUG (RENDERER, "Not using '%s': platform is not Wayland", g_type_name (renderer_type));
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static GType
|
|
get_renderer_for_vulkan (GdkSurface *surface)
|
|
{
|
|
if (!vulkan_supported_platform (surface, GSK_TYPE_VULKAN_RENDERER, FALSE))
|
|
return G_TYPE_INVALID;
|
|
|
|
return GSK_TYPE_VULKAN_RENDERER;
|
|
}
|
|
|
|
static GType
|
|
get_renderer_for_vulkan_fallback (GdkSurface *surface)
|
|
{
|
|
if (!vulkan_supported_platform (surface, GSK_TYPE_VULKAN_RENDERER, TRUE))
|
|
return G_TYPE_INVALID;
|
|
|
|
return GSK_TYPE_VULKAN_RENDERER;
|
|
}
|
|
#endif
|
|
|
|
static GType
|
|
get_renderer_fallback (GdkSurface *surface)
|
|
{
|
|
return GSK_TYPE_CAIRO_RENDERER;
|
|
}
|
|
|
|
static struct {
|
|
GType (* get_renderer) (GdkSurface *surface);
|
|
} renderer_possibilities[] = {
|
|
{ get_renderer_for_display },
|
|
{ get_renderer_for_env_var },
|
|
{ get_renderer_for_backend },
|
|
#ifdef GDK_RENDERING_VULKAN
|
|
{ get_renderer_for_vulkan },
|
|
#endif
|
|
{ get_renderer_for_gl },
|
|
#ifdef GDK_RENDERING_VULKAN
|
|
{ get_renderer_for_vulkan_fallback },
|
|
#endif
|
|
{ get_renderer_for_gl_fallback },
|
|
{ get_renderer_fallback },
|
|
};
|
|
|
|
/**
|
|
* gsk_renderer_new_for_surface:
|
|
* @surface: a `GdkSurface`
|
|
*
|
|
* Creates an appropriate `GskRenderer` instance for the given @surface.
|
|
*
|
|
* If the `GSK_RENDERER` environment variable is set, GSK will
|
|
* try that renderer first, before trying the backend-specific
|
|
* default. The ultimate fallback is the cairo renderer.
|
|
*
|
|
* The renderer will be realized before it is returned.
|
|
*
|
|
* Returns: (transfer full) (nullable): a `GskRenderer`
|
|
*/
|
|
GskRenderer *
|
|
gsk_renderer_new_for_surface (GdkSurface *surface)
|
|
{
|
|
GType renderer_type;
|
|
GskRenderer *renderer;
|
|
GError *error = NULL;
|
|
guint i;
|
|
|
|
g_return_val_if_fail (GDK_IS_SURFACE (surface), NULL);
|
|
|
|
for (i = 0; i < G_N_ELEMENTS (renderer_possibilities); i++)
|
|
{
|
|
renderer_type = renderer_possibilities[i].get_renderer (surface);
|
|
if (renderer_type == G_TYPE_INVALID)
|
|
continue;
|
|
|
|
renderer = g_object_new (renderer_type, NULL);
|
|
|
|
if (gsk_renderer_realize (renderer, surface, &error))
|
|
{
|
|
GSK_DEBUG (RENDERER,
|
|
"Using renderer '%s' for surface '%s'",
|
|
G_OBJECT_TYPE_NAME (renderer),
|
|
G_OBJECT_TYPE_NAME (surface));
|
|
return renderer;
|
|
}
|
|
|
|
GSK_DEBUG (RENDERER,
|
|
"Failed to realize renderer '%s' for surface '%s': %s",
|
|
G_OBJECT_TYPE_NAME (renderer),
|
|
G_OBJECT_TYPE_NAME (surface),
|
|
error->message);
|
|
|
|
g_object_unref (renderer);
|
|
g_clear_error (&error);
|
|
}
|
|
|
|
g_assert_not_reached ();
|
|
return NULL;
|
|
}
|
|
|
|
GskDebugFlags
|
|
gsk_renderer_get_debug_flags (GskRenderer *renderer)
|
|
{
|
|
GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
|
|
|
|
g_return_val_if_fail (GSK_IS_RENDERER (renderer), 0);
|
|
|
|
return priv->debug_flags;
|
|
}
|
|
|
|
void
|
|
gsk_renderer_set_debug_flags (GskRenderer *renderer,
|
|
GskDebugFlags flags)
|
|
{
|
|
GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer);
|
|
|
|
g_return_if_fail (GSK_IS_RENDERER (renderer));
|
|
|
|
priv->debug_flags = flags;
|
|
}
|
|
|