mirror of
https://gitlab.gnome.org/GNOME/gtk.git
synced 2025-01-15 14:50:06 +00:00
5409f0b350
Sometimes the GPU is still busy when the next frame starts (like when no-vsync benchmarking), so we need to keep all those resources alone and create new ones. That's what the render object is for, so we just create another one. However, when we create too many, we'll starve the CPU. So we'll limit it. Currently, that limit is at 4, but I've never reached it (I've also not starved the GPU yet), so that number may want to be set lower/higher in the future. Note that this is different from the number of outstanding buffers, as those are not busy on the GPU but on the compositor, and as such a buffer may have not finished rendering but have been returend from the compositor (very busy GPU) or have finished rendering but not been returned from the compositor (very idle GPU).
506 lines
16 KiB
C
506 lines
16 KiB
C
#include "config.h"
|
|
|
|
#include "gskvulkanrendererprivate.h"
|
|
|
|
#include "gskdebugprivate.h"
|
|
#include "gskprivate.h"
|
|
#include "gskrendererprivate.h"
|
|
#include "gskrendernodeprivate.h"
|
|
#include "gskvulkanbufferprivate.h"
|
|
#include "gskvulkanimageprivate.h"
|
|
#include "gskvulkanpipelineprivate.h"
|
|
#include "gskvulkanrenderprivate.h"
|
|
#include "gskvulkanglyphcacheprivate.h"
|
|
|
|
#include "gdk/gdktextureprivate.h"
|
|
#include "gdk/gdkprofilerprivate.h"
|
|
|
|
#include <graphene.h>
|
|
|
|
#define GSK_VULKAN_MAX_RENDERS 4
|
|
|
|
typedef struct _GskVulkanTextureData GskVulkanTextureData;
|
|
|
|
struct _GskVulkanTextureData {
|
|
GdkTexture *texture;
|
|
GskVulkanImage *image;
|
|
GskVulkanRenderer *renderer;
|
|
};
|
|
|
|
#ifdef G_ENABLE_DEBUG
|
|
typedef struct {
|
|
GQuark frames;
|
|
GQuark render_passes;
|
|
GQuark fallback_pixels;
|
|
GQuark texture_pixels;
|
|
} ProfileCounters;
|
|
|
|
typedef struct {
|
|
GQuark cpu_time;
|
|
GQuark gpu_time;
|
|
} ProfileTimers;
|
|
|
|
static guint texture_pixels_counter;
|
|
static guint fallback_pixels_counter;
|
|
#endif
|
|
|
|
struct _GskVulkanRenderer
|
|
{
|
|
GskRenderer parent_instance;
|
|
|
|
GdkVulkanContext *vulkan;
|
|
|
|
guint n_targets;
|
|
GskVulkanImage **targets;
|
|
|
|
GskVulkanRender *renders[GSK_VULKAN_MAX_RENDERS];
|
|
|
|
GSList *textures;
|
|
|
|
GskVulkanGlyphCache *glyph_cache;
|
|
|
|
#ifdef G_ENABLE_DEBUG
|
|
ProfileCounters profile_counters;
|
|
ProfileTimers profile_timers;
|
|
#endif
|
|
};
|
|
|
|
struct _GskVulkanRendererClass
|
|
{
|
|
GskRendererClass parent_class;
|
|
};
|
|
|
|
G_DEFINE_TYPE (GskVulkanRenderer, gsk_vulkan_renderer, GSK_TYPE_RENDERER)
|
|
|
|
static cairo_region_t *
|
|
get_render_region (GskVulkanRenderer *self)
|
|
{
|
|
const cairo_region_t *damage;
|
|
cairo_region_t *render_region;
|
|
cairo_region_t *scaled_damage;
|
|
GdkRectangle whole_surface;
|
|
GdkRectangle extents;
|
|
GdkSurface *surface;
|
|
double scale;
|
|
|
|
render_region = NULL;
|
|
surface = gdk_draw_context_get_surface (GDK_DRAW_CONTEXT (self->vulkan));
|
|
scale = gdk_surface_get_scale (surface);
|
|
|
|
whole_surface.x = 0;
|
|
whole_surface.y = 0;
|
|
whole_surface.width = gdk_surface_get_width (surface);
|
|
whole_surface.height = gdk_surface_get_height (surface);
|
|
|
|
damage = gdk_draw_context_get_frame_region (GDK_DRAW_CONTEXT (self->vulkan));
|
|
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),
|
|
});
|
|
}
|
|
|
|
if (cairo_region_contains_rectangle (scaled_damage, &whole_surface) == CAIRO_REGION_OVERLAP_IN)
|
|
goto out;
|
|
|
|
cairo_region_get_extents (scaled_damage, &extents);
|
|
if (gdk_rectangle_equal (&extents, &whole_surface))
|
|
goto out;
|
|
|
|
render_region = cairo_region_create_rectangle (&extents);
|
|
|
|
out:
|
|
g_clear_pointer (&scaled_damage, cairo_region_destroy);
|
|
|
|
return g_steal_pointer (&render_region);
|
|
}
|
|
|
|
static void
|
|
gsk_vulkan_renderer_free_targets (GskVulkanRenderer *self)
|
|
{
|
|
guint i;
|
|
|
|
for (i = 0; i < self->n_targets; i++)
|
|
{
|
|
g_object_unref (self->targets[i]);
|
|
}
|
|
|
|
g_clear_pointer (&self->targets, g_free);
|
|
self->n_targets = 0;
|
|
}
|
|
|
|
static void
|
|
gsk_vulkan_renderer_update_images_cb (GdkVulkanContext *context,
|
|
GskVulkanRenderer *self)
|
|
{
|
|
GdkSurface *window;
|
|
double scale;
|
|
gsize width, height;
|
|
guint i;
|
|
|
|
gsk_vulkan_renderer_free_targets (self);
|
|
|
|
self->n_targets = gdk_vulkan_context_get_n_images (context);
|
|
self->targets = g_new (GskVulkanImage *, self->n_targets);
|
|
|
|
window = gsk_renderer_get_surface (GSK_RENDERER (self));
|
|
scale = gdk_surface_get_scale (window);
|
|
width = (int) ceil (gdk_surface_get_width (window) * scale);
|
|
height = (int) ceil (gdk_surface_get_height (window) * scale);
|
|
|
|
for (i = 0; i < self->n_targets; i++)
|
|
{
|
|
self->targets[i] = gsk_vulkan_image_new_for_swapchain (self->vulkan,
|
|
gdk_vulkan_context_get_image (context, i),
|
|
gdk_vulkan_context_get_image_format (self->vulkan),
|
|
width, height);
|
|
}
|
|
}
|
|
|
|
static GskVulkanRender *
|
|
gsk_vulkan_renderer_get_render (GskVulkanRenderer *self)
|
|
{
|
|
VkFence fences[G_N_ELEMENTS (self->renders)];
|
|
VkDevice device;
|
|
guint i;
|
|
|
|
device = gdk_vulkan_context_get_device (self->vulkan);
|
|
|
|
while (TRUE)
|
|
{
|
|
for (i = 0; i < G_N_ELEMENTS (self->renders); i++)
|
|
{
|
|
if (self->renders[i] == NULL)
|
|
{
|
|
self->renders[i] = gsk_vulkan_render_new (GSK_RENDERER (self), self->vulkan);
|
|
return self->renders[i];
|
|
}
|
|
|
|
fences[i] = gsk_vulkan_render_get_fence (self->renders[i]);
|
|
if (vkGetFenceStatus (device, fences[i]) == VK_SUCCESS)
|
|
return self->renders[i];
|
|
}
|
|
|
|
GSK_VK_CHECK (vkWaitForFences, device,
|
|
G_N_ELEMENTS (fences),
|
|
fences,
|
|
VK_FALSE,
|
|
INT64_MAX);
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gsk_vulkan_renderer_realize (GskRenderer *renderer,
|
|
GdkSurface *surface,
|
|
GError **error)
|
|
{
|
|
GskVulkanRenderer *self = GSK_VULKAN_RENDERER (renderer);
|
|
|
|
if (surface == NULL)
|
|
{
|
|
g_set_error (error, GDK_VULKAN_ERROR, GDK_VULKAN_ERROR_UNSUPPORTED,
|
|
"The Vulkan renderer does not support surfaceless rendering yet.");
|
|
return FALSE;
|
|
}
|
|
|
|
self->vulkan = gdk_surface_create_vulkan_context (surface, error);
|
|
if (self->vulkan == NULL)
|
|
return FALSE;
|
|
|
|
g_signal_connect (self->vulkan,
|
|
"images-updated",
|
|
G_CALLBACK (gsk_vulkan_renderer_update_images_cb),
|
|
self);
|
|
gsk_vulkan_renderer_update_images_cb (self->vulkan, self);
|
|
|
|
self->glyph_cache = gsk_vulkan_glyph_cache_new (renderer, self->vulkan);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
gsk_vulkan_renderer_unrealize (GskRenderer *renderer)
|
|
{
|
|
GskVulkanRenderer *self = GSK_VULKAN_RENDERER (renderer);
|
|
GSList *l;
|
|
guint i;
|
|
|
|
g_clear_object (&self->glyph_cache);
|
|
|
|
for (l = self->textures; l; l = l->next)
|
|
{
|
|
GskVulkanTextureData *data = l->data;
|
|
|
|
data->renderer = NULL;
|
|
gdk_texture_clear_render_data (data->texture);
|
|
}
|
|
g_clear_pointer (&self->textures, g_slist_free);
|
|
|
|
for (i = 0; i < G_N_ELEMENTS (self->renders); i++)
|
|
g_clear_pointer (&self->renders[i], gsk_vulkan_render_free);
|
|
|
|
gsk_vulkan_renderer_free_targets (self);
|
|
g_signal_handlers_disconnect_by_func(self->vulkan,
|
|
gsk_vulkan_renderer_update_images_cb,
|
|
self);
|
|
|
|
g_clear_object (&self->vulkan);
|
|
}
|
|
|
|
static GdkTexture *
|
|
gsk_vulkan_renderer_render_texture (GskRenderer *renderer,
|
|
GskRenderNode *root,
|
|
const graphene_rect_t *viewport)
|
|
{
|
|
GskVulkanRenderer *self = GSK_VULKAN_RENDERER (renderer);
|
|
GskVulkanRender *render;
|
|
GskVulkanImage *image;
|
|
GdkTexture *texture;
|
|
graphene_rect_t rounded_viewport;
|
|
#ifdef G_ENABLE_DEBUG
|
|
GskProfiler *profiler;
|
|
gint64 cpu_time;
|
|
gint64 start_time G_GNUC_UNUSED;
|
|
#endif
|
|
|
|
#ifdef G_ENABLE_DEBUG
|
|
profiler = gsk_renderer_get_profiler (renderer);
|
|
gsk_profiler_counter_set (profiler, self->profile_counters.fallback_pixels, 0);
|
|
gsk_profiler_counter_set (profiler, self->profile_counters.texture_pixels, 0);
|
|
gsk_profiler_counter_set (profiler, self->profile_counters.render_passes, 0);
|
|
gsk_profiler_timer_begin (profiler, self->profile_timers.cpu_time);
|
|
#endif
|
|
|
|
render = gsk_vulkan_render_new (renderer, self->vulkan);
|
|
|
|
rounded_viewport = GRAPHENE_RECT_INIT (viewport->origin.x,
|
|
viewport->origin.y,
|
|
ceil (viewport->size.width),
|
|
ceil (viewport->size.height));
|
|
image = gsk_vulkan_image_new_for_framebuffer (self->vulkan,
|
|
rounded_viewport.size.width,
|
|
rounded_viewport.size.height);
|
|
|
|
gsk_vulkan_render_reset (render, image, &rounded_viewport, NULL);
|
|
gsk_vulkan_render_add_node (render, root);
|
|
gsk_vulkan_render_upload (render);
|
|
gsk_vulkan_render_draw (render);
|
|
|
|
texture = gsk_vulkan_render_download_target (render);
|
|
|
|
g_object_unref (image);
|
|
gsk_vulkan_render_free (render);
|
|
|
|
#ifdef G_ENABLE_DEBUG
|
|
start_time = gsk_profiler_timer_get_start (profiler, self->profile_timers.cpu_time);
|
|
cpu_time = gsk_profiler_timer_end (profiler, self->profile_timers.cpu_time);
|
|
gsk_profiler_timer_set (profiler, self->profile_timers.cpu_time, cpu_time);
|
|
|
|
gsk_profiler_push_samples (profiler);
|
|
|
|
if (GDK_PROFILER_IS_RUNNING)
|
|
{
|
|
gdk_profiler_add_mark (start_time * 1000, cpu_time * 1000, "render", "");
|
|
gdk_profiler_set_int_counter (texture_pixels_counter,
|
|
gsk_profiler_counter_get (profiler, self->profile_counters.texture_pixels));
|
|
gdk_profiler_set_int_counter (fallback_pixels_counter,
|
|
gsk_profiler_counter_get (profiler, self->profile_counters.fallback_pixels));
|
|
}
|
|
#endif
|
|
|
|
return texture;
|
|
}
|
|
|
|
static void
|
|
gsk_vulkan_renderer_render (GskRenderer *renderer,
|
|
GskRenderNode *root,
|
|
const cairo_region_t *region)
|
|
{
|
|
GskVulkanRenderer *self = GSK_VULKAN_RENDERER (renderer);
|
|
GskVulkanRender *render;
|
|
cairo_region_t *render_region;
|
|
#ifdef G_ENABLE_DEBUG
|
|
GskProfiler *profiler;
|
|
gint64 cpu_time;
|
|
#endif
|
|
uint32_t draw_index;
|
|
|
|
#ifdef G_ENABLE_DEBUG
|
|
profiler = gsk_renderer_get_profiler (renderer);
|
|
gsk_profiler_counter_set (profiler, self->profile_counters.fallback_pixels, 0);
|
|
gsk_profiler_counter_set (profiler, self->profile_counters.texture_pixels, 0);
|
|
gsk_profiler_counter_set (profiler, self->profile_counters.render_passes, 0);
|
|
gsk_profiler_timer_begin (profiler, self->profile_timers.cpu_time);
|
|
#endif
|
|
|
|
gdk_draw_context_begin_frame (GDK_DRAW_CONTEXT (self->vulkan), region);
|
|
render = gsk_vulkan_renderer_get_render (self);
|
|
|
|
render_region = get_render_region (self);
|
|
draw_index = gdk_vulkan_context_get_draw_index (self->vulkan);
|
|
|
|
gsk_vulkan_render_reset (render, self->targets[draw_index], NULL, render_region);
|
|
gsk_vulkan_render_add_node (render, root);
|
|
gsk_vulkan_render_upload (render);
|
|
gsk_vulkan_render_draw (render);
|
|
|
|
#ifdef G_ENABLE_DEBUG
|
|
gsk_profiler_counter_inc (profiler, self->profile_counters.frames);
|
|
|
|
cpu_time = gsk_profiler_timer_end (profiler, self->profile_timers.cpu_time);
|
|
gsk_profiler_timer_set (profiler, self->profile_timers.cpu_time, cpu_time);
|
|
|
|
gsk_profiler_push_samples (profiler);
|
|
#endif
|
|
|
|
gdk_draw_context_end_frame (GDK_DRAW_CONTEXT (self->vulkan));
|
|
|
|
g_clear_pointer (&render_region, cairo_region_destroy);
|
|
}
|
|
|
|
static void
|
|
gsk_vulkan_renderer_class_init (GskVulkanRendererClass *klass)
|
|
{
|
|
GskRendererClass *renderer_class = GSK_RENDERER_CLASS (klass);
|
|
|
|
renderer_class->realize = gsk_vulkan_renderer_realize;
|
|
renderer_class->unrealize = gsk_vulkan_renderer_unrealize;
|
|
renderer_class->render = gsk_vulkan_renderer_render;
|
|
renderer_class->render_texture = gsk_vulkan_renderer_render_texture;
|
|
}
|
|
|
|
static void
|
|
gsk_vulkan_renderer_init (GskVulkanRenderer *self)
|
|
{
|
|
#ifdef G_ENABLE_DEBUG
|
|
GskProfiler *profiler = gsk_renderer_get_profiler (GSK_RENDERER (self));
|
|
#endif
|
|
|
|
gsk_ensure_resources ();
|
|
|
|
#ifdef G_ENABLE_DEBUG
|
|
self->profile_counters.frames = gsk_profiler_add_counter (profiler, "frames", "Frames", FALSE);
|
|
self->profile_counters.render_passes = gsk_profiler_add_counter (profiler, "render-passes", "Render passes", FALSE);
|
|
self->profile_counters.fallback_pixels = gsk_profiler_add_counter (profiler, "fallback-pixels", "Fallback pixels", TRUE);
|
|
self->profile_counters.texture_pixels = gsk_profiler_add_counter (profiler, "texture-pixels", "Texture pixels", TRUE);
|
|
|
|
self->profile_timers.cpu_time = gsk_profiler_add_timer (profiler, "cpu-time", "CPU time", FALSE, TRUE);
|
|
if (GSK_RENDERER_DEBUG_CHECK (GSK_RENDERER (self), SYNC))
|
|
self->profile_timers.gpu_time = gsk_profiler_add_timer (profiler, "gpu-time", "GPU time", FALSE, TRUE);
|
|
|
|
if (texture_pixels_counter == 0)
|
|
{
|
|
texture_pixels_counter = gdk_profiler_define_int_counter ("texture-pixels", "Texture Pixels");
|
|
fallback_pixels_counter = gdk_profiler_define_int_counter ("fallback-pixels", "Fallback Pixels");
|
|
}
|
|
|
|
#endif
|
|
}
|
|
|
|
static void
|
|
gsk_vulkan_renderer_clear_texture (gpointer p)
|
|
{
|
|
GskVulkanTextureData *data = p;
|
|
|
|
if (data->renderer != NULL)
|
|
data->renderer->textures = g_slist_remove (data->renderer->textures, data);
|
|
|
|
g_object_unref (data->image);
|
|
|
|
g_free (data);
|
|
}
|
|
|
|
GskVulkanImage *
|
|
gsk_vulkan_renderer_ref_texture_image (GskVulkanRenderer *self,
|
|
GdkTexture *texture,
|
|
GskVulkanUploader *uploader)
|
|
{
|
|
GskVulkanTextureData *data;
|
|
cairo_surface_t *surface;
|
|
GskVulkanImage *image;
|
|
|
|
data = gdk_texture_get_render_data (texture, self);
|
|
if (data)
|
|
return g_object_ref (data->image);
|
|
|
|
surface = gdk_texture_download_surface (texture);
|
|
image = gsk_vulkan_image_new_from_data (uploader,
|
|
cairo_image_surface_get_data (surface),
|
|
cairo_image_surface_get_width (surface),
|
|
cairo_image_surface_get_height (surface),
|
|
cairo_image_surface_get_stride (surface));
|
|
cairo_surface_destroy (surface);
|
|
|
|
data = g_new0 (GskVulkanTextureData, 1);
|
|
data->image = image;
|
|
data->texture = texture;
|
|
data->renderer = self;
|
|
|
|
if (gdk_texture_set_render_data (texture, self, data, gsk_vulkan_renderer_clear_texture))
|
|
{
|
|
g_object_ref (data->image);
|
|
self->textures = g_slist_prepend (self->textures, data);
|
|
}
|
|
else
|
|
{
|
|
g_free (data);
|
|
}
|
|
|
|
return image;
|
|
}
|
|
|
|
GskVulkanImage *
|
|
gsk_vulkan_renderer_ref_glyph_image (GskVulkanRenderer *self,
|
|
GskVulkanUploader *uploader,
|
|
guint index)
|
|
{
|
|
return g_object_ref (gsk_vulkan_glyph_cache_get_glyph_image (self->glyph_cache, uploader, index));
|
|
}
|
|
|
|
guint
|
|
gsk_vulkan_renderer_cache_glyph (GskVulkanRenderer *self,
|
|
PangoFont *font,
|
|
PangoGlyph glyph,
|
|
int x,
|
|
int y,
|
|
float scale)
|
|
{
|
|
return gsk_vulkan_glyph_cache_lookup (self->glyph_cache, TRUE, font, glyph, x, y, scale)->texture_index;
|
|
}
|
|
|
|
GskVulkanCachedGlyph *
|
|
gsk_vulkan_renderer_get_cached_glyph (GskVulkanRenderer *self,
|
|
PangoFont *font,
|
|
PangoGlyph glyph,
|
|
int x,
|
|
int y,
|
|
float scale)
|
|
{
|
|
return gsk_vulkan_glyph_cache_lookup (self->glyph_cache, FALSE, font, glyph, x, y, scale);
|
|
}
|
|
|
|
/**
|
|
* gsk_vulkan_renderer_new:
|
|
*
|
|
* Creates a new Vulkan renderer.
|
|
*
|
|
* The Vulkan renderer is a renderer that uses the Vulkan library for
|
|
* rendering.
|
|
*
|
|
* This function is only available when GTK was compiled with Vulkan
|
|
* support.
|
|
*
|
|
* Returns: a new Vulkan renderer
|
|
**/
|
|
GskRenderer *
|
|
gsk_vulkan_renderer_new (void)
|
|
{
|
|
return g_object_new (GSK_TYPE_VULKAN_RENDERER, NULL);
|
|
}
|