gtk/gsk/gpu/gskgpurenderer.c
Benjamin Otte 16c7003acb gdk: Pass the opaque rect to begin_frame() actually
We know it at begin_frame() time, so if we pass it there instead of
end_frame(), we can use it then to make decisions about opacity.

For example, we could notice that the whole surface is opaque and choose
an RGBx format.
We don't do that yet, but now we could.
2024-08-10 01:40:46 +02:00

531 lines
18 KiB
C

#include "config.h"
#include "gskgpurendererprivate.h"
#include "gskdebugprivate.h"
#include "gskgpudeviceprivate.h"
#include "gskgpuframeprivate.h"
#include "gskprivate.h"
#include "gskrendererprivate.h"
#include "gskrendernodeprivate.h"
#include "gskgpuimageprivate.h"
#include "gdk/gdkdebugprivate.h"
#include "gdk/gdkdisplayprivate.h"
#include "gdk/gdkdmabuftextureprivate.h"
#include "gdk/gdkdrawcontextprivate.h"
#include "gdk/gdkprofilerprivate.h"
#include "gdk/gdktextureprivate.h"
#include "gdk/gdktexturedownloaderprivate.h"
#include "gdk/gdkdrawcontextprivate.h"
#include "gdk/gdkcolorstateprivate.h"
#include <graphene.h>
#define GSK_GPU_MAX_FRAMES 4
static const GdkDebugKey gsk_gpu_optimization_keys[] = {
{ "clear", GSK_GPU_OPTIMIZE_CLEAR, "Use shaders instead of vkCmdClearAttachment()/glClear()" },
{ "merge", GSK_GPU_OPTIMIZE_MERGE, "Use one vkCmdDraw()/glDrawArrays() per operation" },
{ "blit", GSK_GPU_OPTIMIZE_BLIT, "Use shaders instead of vkCmdBlit()/glBlitFramebuffer()" },
{ "gradients", GSK_GPU_OPTIMIZE_GRADIENTS, "Don't supersample gradients" },
{ "mipmap", GSK_GPU_OPTIMIZE_MIPMAP, "Avoid creating mipmaps" },
{ "to-image", GSK_GPU_OPTIMIZE_TO_IMAGE, "Don't fast-path creation of images for nodes" },
{ "occlusion", GSK_GPU_OPTIMIZE_OCCLUSION_CULLING, "Disable occlusion culling via opaque node tracking" },
};
typedef struct _GskGpuRendererPrivate GskGpuRendererPrivate;
struct _GskGpuRendererPrivate
{
GskGpuDevice *device;
GdkDrawContext *context;
GskGpuOptimizations optimizations;
GskGpuFrame *frames[GSK_GPU_MAX_FRAMES];
};
static void gsk_gpu_renderer_dmabuf_downloader_init (GdkDmabufDownloaderInterface *iface);
G_DEFINE_TYPE_EXTENDED (GskGpuRenderer, gsk_gpu_renderer, GSK_TYPE_RENDERER, 0,
G_ADD_PRIVATE (GskGpuRenderer)
G_IMPLEMENT_INTERFACE (GDK_TYPE_DMABUF_DOWNLOADER,
gsk_gpu_renderer_dmabuf_downloader_init))
static void
gsk_gpu_renderer_make_current (GskGpuRenderer *self)
{
GSK_GPU_RENDERER_GET_CLASS (self)->make_current (self);
}
static GskGpuFrame *
gsk_gpu_renderer_create_frame (GskGpuRenderer *self)
{
GskGpuRendererPrivate *priv = gsk_gpu_renderer_get_instance_private (self);
GskGpuRendererClass *klass = GSK_GPU_RENDERER_GET_CLASS (self);
GskGpuFrame *result;
result = g_object_new (klass->frame_type, NULL);
gsk_gpu_frame_setup (result, self, priv->device, priv->optimizations);
return result;
}
static void
gsk_gpu_renderer_dmabuf_downloader_close (GdkDmabufDownloader *downloader)
{
gsk_renderer_unrealize (GSK_RENDERER (downloader));
}
static gboolean
gsk_gpu_renderer_dmabuf_downloader_supports (GdkDmabufDownloader *downloader,
GdkDmabufTexture *texture,
GError **error)
{
GskGpuRenderer *self = GSK_GPU_RENDERER (downloader);
const GdkDmabuf *dmabuf;
GdkDmabufFormats *formats;
dmabuf = gdk_dmabuf_texture_get_dmabuf (texture);
formats = GSK_GPU_RENDERER_GET_CLASS (self)->get_dmabuf_formats (self);
if (!gdk_dmabuf_formats_contains (formats, dmabuf->fourcc, dmabuf->modifier))
{
g_set_error (error,
GDK_DMABUF_ERROR, GDK_DMABUF_ERROR_UNSUPPORTED_FORMAT,
"Unsupported dmabuf format: %.4s:%#" G_GINT64_MODIFIER "x",
(char *) &dmabuf->fourcc, dmabuf->modifier);
return FALSE;
}
return TRUE;
}
static void
gsk_gpu_renderer_dmabuf_downloader_download (GdkDmabufDownloader *downloader,
GdkDmabufTexture *texture,
GdkMemoryFormat format,
GdkColorState *color_state,
guchar *data,
gsize stride)
{
GskGpuRenderer *self = GSK_GPU_RENDERER (downloader);
GskGpuFrame *frame;
gsk_gpu_renderer_make_current (self);
frame = gsk_gpu_renderer_create_frame (self);
gsk_gpu_frame_download_texture (frame,
g_get_monotonic_time (),
GDK_TEXTURE (texture),
format,
color_state,
data,
stride);
g_object_unref (frame);
}
static void
gsk_gpu_renderer_dmabuf_downloader_init (GdkDmabufDownloaderInterface *iface)
{
iface->close = gsk_gpu_renderer_dmabuf_downloader_close;
iface->supports = gsk_gpu_renderer_dmabuf_downloader_supports;
iface->download = gsk_gpu_renderer_dmabuf_downloader_download;
}
static cairo_region_t *
get_render_region (GskGpuRenderer *self)
{
GskGpuRendererPrivate *priv = gsk_gpu_renderer_get_instance_private (self);
const cairo_region_t *damage;
cairo_region_t *scaled_damage;
double scale;
scale = gsk_gpu_renderer_get_scale (self);
damage = gdk_draw_context_get_frame_region (priv->context);
scaled_damage = cairo_region_create ();
for (int i = 0; i < cairo_region_num_rectangles (damage); i++)
{
cairo_rectangle_int_t rect;
cairo_region_get_rectangle (damage, i, &rect);
cairo_region_union_rectangle (scaled_damage, &(cairo_rectangle_int_t) {
.x = (int) floor (rect.x * scale),
.y = (int) floor (rect.y * scale),
.width = (int) ceil ((rect.x + rect.width) * scale) - floor (rect.x * scale),
.height = (int) ceil ((rect.y + rect.height) * scale) - floor (rect.y * scale),
});
}
return scaled_damage;
}
static GskGpuFrame *
gsk_gpu_renderer_get_frame (GskGpuRenderer *self)
{
GskGpuRendererPrivate *priv = gsk_gpu_renderer_get_instance_private (self);
GskGpuFrame *earliest_frame = NULL;
gint64 earliest_time = G_MAXINT64;
guint i;
for (i = 0; i < G_N_ELEMENTS (priv->frames); i++)
{
gint64 timestamp;
if (priv->frames[i] == NULL)
{
priv->frames[i] = gsk_gpu_renderer_create_frame (self);
return priv->frames[i];
}
if (!gsk_gpu_frame_is_busy (priv->frames[i]))
return priv->frames[i];
timestamp = gsk_gpu_frame_get_timestamp (priv->frames[i]);
if (timestamp < earliest_time)
{
earliest_time = timestamp;
earliest_frame = priv->frames[i];
}
}
g_assert (earliest_frame);
gsk_gpu_frame_wait (earliest_frame);
return earliest_frame;
}
static gboolean
gsk_gpu_renderer_realize (GskRenderer *renderer,
GdkDisplay *display,
GdkSurface *surface,
GError **error)
{
GskGpuRenderer *self = GSK_GPU_RENDERER (renderer);
GskGpuRendererPrivate *priv = gsk_gpu_renderer_get_instance_private (self);
GskGpuOptimizations context_optimizations;
priv->device = GSK_GPU_RENDERER_GET_CLASS (self)->get_device (display, error);
if (priv->device == NULL)
return FALSE;
priv->context = GSK_GPU_RENDERER_GET_CLASS (self)->create_context (self, display, surface, &context_optimizations, error);
if (priv->context == NULL)
{
g_clear_object (&priv->device);
return FALSE;
}
priv->optimizations &= context_optimizations;
return TRUE;
}
static void
gsk_gpu_renderer_unrealize (GskRenderer *renderer)
{
GskGpuRenderer *self = GSK_GPU_RENDERER (renderer);
GskGpuRendererPrivate *priv = gsk_gpu_renderer_get_instance_private (self);
gsize i;
gsk_gpu_renderer_make_current (self);
for (i = 0; i < G_N_ELEMENTS (priv->frames); i++)
{
if (priv->frames[i] == NULL)
break;
if (gsk_gpu_frame_is_busy (priv->frames[i]))
gsk_gpu_frame_wait (priv->frames[i]);
g_clear_object (&priv->frames[i]);
}
g_clear_object (&priv->context);
g_clear_object (&priv->device);
}
static GdkTexture *
gsk_gpu_renderer_fallback_render_texture (GskGpuRenderer *self,
GskRenderNode *root,
const graphene_rect_t *rounded_viewport)
{
GskGpuRendererPrivate *priv = gsk_gpu_renderer_get_instance_private (self);
GskGpuImage *image;
gsize width, height, max_size, image_width, image_height;
gsize x, y, size, bpp, stride;
GdkMemoryFormat format;
GdkMemoryDepth depth;
GdkColorState *color_state;
GBytes *bytes;
guchar *data;
GdkTexture *texture;
GdkTextureDownloader downloader;
cairo_region_t *clip_region;
GskGpuFrame *frame;
max_size = gsk_gpu_device_get_max_image_size (priv->device);
depth = gsk_render_node_get_preferred_depth (root);
do
{
image = gsk_gpu_device_create_download_image (priv->device,
gsk_render_node_get_preferred_depth (root),
MIN (max_size, rounded_viewport->size.width),
MIN (max_size, rounded_viewport->size.height));
max_size /= 2;
}
while (image == NULL);
format = gsk_gpu_image_get_format (image);
bpp = gdk_memory_format_bytes_per_pixel (format);
image_width = gsk_gpu_image_get_width (image);
image_height = gsk_gpu_image_get_height (image);
width = rounded_viewport->size.width;
height = rounded_viewport->size.height;
stride = width * bpp;
size = stride * height;
data = g_malloc_n (stride, height);
for (y = 0; y < height; y += image_height)
{
for (x = 0; x < width; x += image_width)
{
texture = NULL;
if (image == NULL)
image = gsk_gpu_device_create_download_image (priv->device,
depth,
MIN (image_width, width - x),
MIN (image_height, height - y));
if (gsk_gpu_image_get_flags (image) & GSK_GPU_IMAGE_SRGB)
color_state = GDK_COLOR_STATE_SRGB_LINEAR;
else
color_state = GDK_COLOR_STATE_SRGB;
clip_region = cairo_region_create_rectangle (&(cairo_rectangle_int_t) {
0, 0,
gsk_gpu_image_get_width (image),
gsk_gpu_image_get_height (image)
});
frame = gsk_gpu_renderer_create_frame (self);
gsk_gpu_frame_render (frame,
g_get_monotonic_time (),
image,
color_state,
clip_region,
root,
&GRAPHENE_RECT_INIT (rounded_viewport->origin.x + x,
rounded_viewport->origin.y + y,
image_width,
image_height),
&texture);
g_object_unref (frame);
g_assert (texture);
gdk_texture_downloader_init (&downloader, texture);
gdk_texture_downloader_set_format (&downloader, format);
gdk_texture_downloader_download_into (&downloader,
data + stride * y + x * bpp,
stride);
gdk_texture_downloader_finish (&downloader);
g_object_unref (texture);
g_clear_object (&image);
/* Let's GC like a madman, we draw oversized stuff and don't want to OOM */
gsk_gpu_device_maybe_gc (priv->device);
}
}
bytes = g_bytes_new_take (data, size);
texture = gdk_memory_texture_new (width, height, GDK_MEMORY_DEFAULT, bytes, stride);
g_bytes_unref (bytes);
return texture;
}
static GdkTexture *
gsk_gpu_renderer_render_texture (GskRenderer *renderer,
GskRenderNode *root,
const graphene_rect_t *viewport)
{
GskGpuRenderer *self = GSK_GPU_RENDERER (renderer);
GskGpuRendererPrivate *priv = gsk_gpu_renderer_get_instance_private (self);
GskGpuFrame *frame;
GskGpuImage *image;
GdkTexture *texture;
graphene_rect_t rounded_viewport;
GdkColorState *color_state;
cairo_region_t *clip_region;
gsk_gpu_device_maybe_gc (priv->device);
gsk_gpu_renderer_make_current (self);
rounded_viewport = GRAPHENE_RECT_INIT (viewport->origin.x,
viewport->origin.y,
ceil (viewport->size.width),
ceil (viewport->size.height));
image = gsk_gpu_device_create_download_image (priv->device,
gsk_render_node_get_preferred_depth (root),
rounded_viewport.size.width,
rounded_viewport.size.height);
if (image == NULL)
return gsk_gpu_renderer_fallback_render_texture (self, root, &rounded_viewport);
if (gsk_gpu_image_get_flags (image) & GSK_GPU_IMAGE_SRGB)
color_state = GDK_COLOR_STATE_SRGB_LINEAR;
else
color_state = GDK_COLOR_STATE_SRGB;
frame = gsk_gpu_renderer_create_frame (self);
clip_region = cairo_region_create_rectangle (&(cairo_rectangle_int_t) {
0, 0,
gsk_gpu_image_get_width (image),
gsk_gpu_image_get_height (image)
});
texture = NULL;
gsk_gpu_frame_render (frame,
g_get_monotonic_time (),
image,
color_state,
clip_region,
root,
&rounded_viewport,
&texture);
g_object_unref (frame);
g_object_unref (image);
gsk_gpu_device_queue_gc (priv->device);
/* check that callback setting texture was actually called, as its technically async */
g_assert (texture);
return texture;
}
static void
gsk_gpu_renderer_render (GskRenderer *renderer,
GskRenderNode *root,
const cairo_region_t *region)
{
GskGpuRenderer *self = GSK_GPU_RENDERER (renderer);
GskGpuRendererPrivate *priv = gsk_gpu_renderer_get_instance_private (self);
GskGpuFrame *frame;
GskGpuImage *backbuffer;
cairo_region_t *render_region;
graphene_rect_t opaque_tmp;
const graphene_rect_t *opaque;
double scale;
GdkMemoryDepth depth;
if (cairo_region_is_empty (region))
{
gdk_draw_context_empty_frame (priv->context);
return;
}
gsk_gpu_device_maybe_gc (priv->device);
gsk_gpu_renderer_make_current (self);
depth = gsk_render_node_get_preferred_depth (root);
frame = gsk_gpu_renderer_get_frame (self);
scale = gsk_gpu_renderer_get_scale (self);
if (gsk_render_node_get_opaque_rect (root, &opaque_tmp))
opaque = &opaque_tmp;
else
opaque = NULL;
gsk_gpu_frame_begin (frame, priv->context, depth, region, opaque);
backbuffer = GSK_GPU_RENDERER_GET_CLASS (self)->get_backbuffer (self);
render_region = get_render_region (self);
gsk_gpu_frame_render (frame,
g_get_monotonic_time (),
backbuffer,
gdk_draw_context_get_color_state (priv->context),
render_region,
root,
&GRAPHENE_RECT_INIT (
0, 0,
gsk_gpu_image_get_width (backbuffer) / scale,
gsk_gpu_image_get_height (backbuffer) / scale
),
NULL);
gsk_gpu_frame_end (frame, priv->context);
gsk_gpu_device_queue_gc (priv->device);
}
static double
gsk_gpu_renderer_real_get_scale (GskGpuRenderer *self)
{
GskGpuRendererPrivate *priv = gsk_gpu_renderer_get_instance_private (self);
GdkSurface *surface;
surface = gdk_draw_context_get_surface (priv->context);
return gdk_surface_get_scale (surface);
}
static void
gsk_gpu_renderer_class_init (GskGpuRendererClass *klass)
{
GskRendererClass *renderer_class = GSK_RENDERER_CLASS (klass);
renderer_class->supports_offload = TRUE;
renderer_class->realize = gsk_gpu_renderer_realize;
renderer_class->unrealize = gsk_gpu_renderer_unrealize;
renderer_class->render = gsk_gpu_renderer_render;
renderer_class->render_texture = gsk_gpu_renderer_render_texture;
gsk_ensure_resources ();
klass->optimizations = -1;
klass->optimizations &= ~gdk_parse_debug_var ("GSK_GPU_DISABLE",
gsk_gpu_optimization_keys,
G_N_ELEMENTS (gsk_gpu_optimization_keys));
klass->get_scale = gsk_gpu_renderer_real_get_scale;
}
static void
gsk_gpu_renderer_init (GskGpuRenderer *self)
{
GskGpuRendererPrivate *priv = gsk_gpu_renderer_get_instance_private (self);
priv->optimizations = GSK_GPU_RENDERER_GET_CLASS (self)->optimizations;
}
GdkDrawContext *
gsk_gpu_renderer_get_context (GskGpuRenderer *self)
{
GskGpuRendererPrivate *priv = gsk_gpu_renderer_get_instance_private (self);
return priv->context;
}
GskGpuDevice *
gsk_gpu_renderer_get_device (GskGpuRenderer *self)
{
GskGpuRendererPrivate *priv = gsk_gpu_renderer_get_instance_private (self);
return priv->device;
}
double
gsk_gpu_renderer_get_scale (GskGpuRenderer *self)
{
return GSK_GPU_RENDERER_GET_CLASS (self)->get_scale (self);
}