gl backend: Avoid roundtripping via surface when updloading

Do custom uploads rather than using gdk_cairo_surface_upload_to_gl(),
because this way we avoids a roundtrip (memcpy and possibly conversion)
to the cairo image surface format.
This commit is contained in:
Alexander Larsson 2020-09-24 16:58:46 +02:00
parent c71921a6be
commit 90fc671ef8
5 changed files with 86 additions and 42 deletions

View File

@ -38,7 +38,7 @@ struct _GdkMemoryTextureClass
G_DEFINE_TYPE (GdkMemoryTexture, gdk_memory_texture, GDK_TYPE_TEXTURE)
static gsize
gsize
gdk_memory_format_bytes_per_pixel (GdkMemoryFormat format)
{
switch (format)

View File

@ -31,6 +31,8 @@ G_BEGIN_DECLS
#define GDK_MEMORY_CAIRO_FORMAT_ARGB32 GDK_MEMORY_DEFAULT
gsize gdk_memory_format_bytes_per_pixel (GdkMemoryFormat format);
GdkMemoryFormat gdk_memory_texture_get_format (GdkMemoryTexture *self);
const guchar * gdk_memory_texture_get_data (GdkMemoryTexture *self);
gsize gdk_memory_texture_get_stride (GdkMemoryTexture *self);

View File

@ -7,6 +7,7 @@
#include "gdk/gdkglcontextprivate.h"
#include "gdk/gdktextureprivate.h"
#include "gdk/gdkgltextureprivate.h"
#include "gdkmemorytextureprivate.h"
#include <gdk/gdk.h>
#include <epoxy/gl.h>
@ -58,6 +59,54 @@ struct _GskGLDriver
G_DEFINE_TYPE (GskGLDriver, gsk_gl_driver, G_TYPE_OBJECT)
static void
upload_gdk_texture (GdkTexture *source_texture,
int target,
int x_offset,
int y_offset,
int width,
int height)
{
cairo_surface_t *surface = NULL;
GdkMemoryFormat data_format;
const guchar *data;
gsize data_stride;
gsize bpp;
g_return_if_fail (source_texture != NULL);
g_return_if_fail (x_offset + width <= gdk_texture_get_width (source_texture));
g_return_if_fail (y_offset + height <= gdk_texture_get_height (source_texture));
/* Note: GdkGLTextures are already handled before we reach this and reused as-is */
if (GDK_IS_MEMORY_TEXTURE (source_texture))
{
GdkMemoryTexture *memory_texture = GDK_MEMORY_TEXTURE (source_texture);
data = gdk_memory_texture_get_data (memory_texture);
data_format = gdk_memory_texture_get_format (memory_texture);
data_stride = gdk_memory_texture_get_stride (memory_texture);
}
else
{
/* Fall back to downloading to a surface */
surface = gdk_texture_download_surface (source_texture);
cairo_surface_flush (surface);
data = cairo_image_surface_get_data (surface);
data_format = GDK_MEMORY_DEFAULT;
data_stride = cairo_image_surface_get_stride (surface);
}
bpp = gdk_memory_format_bytes_per_pixel (data_format);
gdk_gl_context_upload_texture (gdk_gl_context_get_current (),
data + x_offset * bpp + y_offset * data_stride,
width, height, data_stride,
data_format, target);
if (surface)
cairo_surface_destroy (surface);
}
static Texture *
texture_new (void)
{
@ -408,32 +457,15 @@ gsk_gl_driver_slice_texture (GskGLDriver *self,
slices = g_new0 (TextureSlice, cols * rows);
/* TODO: (Perf):
* We still create a surface here, which should obviously be unnecessary
* and we should eventually remove it and upload the data directly.
*/
for (col = 0; col < cols; col ++)
{
const int slice_width = MIN (max_texture_size, texture->width - x);
const int stride = slice_width * 4;
for (row = 0; row < rows; row ++)
{
const int slice_height = MIN (max_texture_size, texture->height - y);
const int slice_index = (col * rows) + row;
guchar *data;
guint texture_id;
cairo_surface_t *surface;
data = g_malloc (sizeof (guchar) * stride * slice_height);
gdk_texture_download_area (texture,
&(GdkRectangle){x, y, slice_width, slice_height},
data, stride);
surface = cairo_image_surface_create_for_data (data,
CAIRO_FORMAT_ARGB32,
slice_width, slice_height,
stride);
glGenTextures (1, &texture_id);
@ -442,7 +474,7 @@ gsk_gl_driver_slice_texture (GskGLDriver *self,
#endif
glBindTexture (GL_TEXTURE_2D, texture_id);
gsk_gl_driver_set_texture_parameters (self, GL_NEAREST, GL_NEAREST);
gdk_cairo_surface_upload_to_gl (surface, GL_TEXTURE_2D, slice_width, slice_height, NULL);
upload_gdk_texture (texture, GL_TEXTURE_2D, x, y, slice_width, slice_height);
#ifdef G_ENABLE_DEBUG
gsk_profiler_counter_inc (self->profiler, self->counters.surface_uploads);
@ -451,9 +483,6 @@ gsk_gl_driver_slice_texture (GskGLDriver *self,
slices[slice_index].rect = (GdkRectangle){x, y, slice_width, slice_height};
slices[slice_index].texture_id = texture_id;
g_free (data);
cairo_surface_destroy (surface);
y += slice_height;
}
@ -486,7 +515,8 @@ gsk_gl_driver_get_texture_for_texture (GskGLDriver *self,
int mag_filter)
{
Texture *t;
cairo_surface_t *surface;
GdkTexture *downloaded_texture = NULL;
GdkTexture *source_texture;
if (GDK_IS_GL_TEXTURE (texture))
{
@ -494,14 +524,20 @@ gsk_gl_driver_get_texture_for_texture (GskGLDriver *self,
if (texture_context != self->gl_context)
{
cairo_surface_t *surface;
/* In this case, we have to temporarily make the texture's context the current one,
* download its data into our context and then create a texture from it. */
if (texture_context)
gdk_gl_context_make_current (texture_context);
surface = gdk_texture_download_surface (texture);
downloaded_texture = gdk_texture_new_for_surface (surface);
cairo_surface_destroy (surface);
gdk_gl_context_make_current (self->gl_context);
source_texture = downloaded_texture;
}
else
{
@ -519,7 +555,7 @@ gsk_gl_driver_get_texture_for_texture (GskGLDriver *self,
return t->texture_id;
}
surface = gdk_texture_download_surface (texture);
source_texture = texture;
}
t = create_texture (self, gdk_texture_get_width (texture), gdk_texture_get_height (texture));
@ -528,15 +564,16 @@ gsk_gl_driver_get_texture_for_texture (GskGLDriver *self,
t->user = texture;
gsk_gl_driver_bind_source_texture (self, t->texture_id);
gsk_gl_driver_init_texture_with_surface (self,
t->texture_id,
surface,
min_filter,
mag_filter);
gsk_gl_driver_init_texture (self,
t->texture_id,
source_texture,
min_filter,
mag_filter);
gdk_gl_context_label_object_printf (self->gl_context, GL_TEXTURE, t->texture_id,
"GdkTexture<%p> %d", texture, t->texture_id);
cairo_surface_destroy (surface);
if (downloaded_texture)
g_object_unref (downloaded_texture);
return t->texture_id;
}
@ -769,11 +806,11 @@ filter_uses_mipmaps (int filter)
}
void
gsk_gl_driver_init_texture_with_surface (GskGLDriver *self,
int texture_id,
cairo_surface_t *surface,
int min_filter,
int mag_filter)
gsk_gl_driver_init_texture (GskGLDriver *self,
int texture_id,
GdkTexture *texture,
int min_filter,
int mag_filter)
{
Texture *t;
@ -794,7 +831,7 @@ gsk_gl_driver_init_texture_with_surface (GskGLDriver *self,
gsk_gl_driver_set_texture_parameters (self, min_filter, mag_filter);
gdk_cairo_surface_upload_to_gl (surface, GL_TEXTURE_2D, t->width, t->height, NULL);
upload_gdk_texture (texture, GL_TEXTURE_2D, 0, 0, t->width, t->height);
#ifdef G_ENABLE_DEBUG
gsk_profiler_counter_inc (self->profiler, self->counters.surface_uploads);

View File

@ -63,9 +63,9 @@ void gsk_gl_driver_init_texture_empty (GskGLDriver *driver
int texture_id,
int min_filter,
int max_filter);
void gsk_gl_driver_init_texture_with_surface (GskGLDriver *driver,
void gsk_gl_driver_init_texture (GskGLDriver *driver,
int texture_id,
cairo_surface_t *surface,
GdkTexture *texture,
int min_filter,
int mag_filter);

View File

@ -564,6 +564,7 @@ render_fallback_node (GskGLRenderer *self,
GskRenderNode *node,
RenderOpBuilder *builder)
{
GdkTexture *texture;
const float scale = ops_get_scale (builder);
const int surface_width = ceilf (node->bounds.size.width * scale);
const int surface_height = ceilf (node->bounds.size.height * scale);
@ -645,15 +646,18 @@ render_fallback_node (GskGLRenderer *self,
#endif
cairo_destroy (cr);
/* Upload the Cairo surface to a GL texture */
texture_id = gsk_gl_driver_create_texture (self->gl_driver,
surface_width,
surface_height);
gsk_gl_driver_bind_source_texture (self->gl_driver, texture_id);
gsk_gl_driver_init_texture_with_surface (self->gl_driver,
texture_id,
surface,
GL_NEAREST, GL_NEAREST);
texture = gdk_texture_new_for_surface (surface);
gsk_gl_driver_init_texture (self->gl_driver,
texture_id,
texture,
GL_NEAREST, GL_NEAREST);
if (gdk_gl_context_has_debug (self->gl_context))
gdk_gl_context_label_object_printf (self->gl_context, GL_TEXTURE, texture_id,
@ -661,6 +665,7 @@ render_fallback_node (GskGLRenderer *self,
g_type_name_from_instance ((GTypeInstance *) node),
texture_id);
g_object_unref (texture);
cairo_surface_destroy (surface);
cairo_surface_destroy (rendered_surface);