gtk/gsk/gpu/gskgpuuploadop.c
Christian Hergert a7c5b53f46 gsk/gpu: avoid unnecessary buffer uploads
Some maps are used for read only and do not require uploading contents
back to the GPU afterwards. In other cases, we can often upload less than
the fully allocated buffer size.
2024-03-08 13:55:57 -08:00

639 lines
22 KiB
C

#include "config.h"
#include "gskgpuuploadopprivate.h"
#include "gskgpuframeprivate.h"
#include "gskgpuimageprivate.h"
#include "gskgpuprintprivate.h"
#include "gskgldeviceprivate.h"
#include "gskglimageprivate.h"
#ifdef GDK_RENDERING_VULKAN
#include "gskvulkanbufferprivate.h"
#include "gskvulkanimageprivate.h"
#endif
#include "gdk/gdkglcontextprivate.h"
#include "gsk/gskdebugprivate.h"
static GskGpuOp *
gsk_gpu_upload_op_gl_command_with_area (GskGpuOp *op,
GskGpuFrame *frame,
GskGpuImage *image,
const cairo_rectangle_int_t *area,
void (* draw_func) (GskGpuOp *, guchar *, gsize))
{
GskGLImage *gl_image = GSK_GL_IMAGE (image);
GdkMemoryFormat format;
GdkGLContext *context;
gsize stride, bpp;
guchar *data;
guint gl_format, gl_type;
context = GDK_GL_CONTEXT (gsk_gpu_frame_get_context (frame));
format = gsk_gpu_image_get_format (image);
bpp = gdk_memory_format_bytes_per_pixel (format);
stride = area->width * bpp;
data = g_malloc (area->height * stride);
draw_func (op, data, stride);
gl_format = gsk_gl_image_get_gl_format (gl_image);
gl_type = gsk_gl_image_get_gl_type (gl_image);
glActiveTexture (GL_TEXTURE0);
gsk_gl_image_bind_texture (gl_image);
glPixelStorei (GL_UNPACK_ALIGNMENT, gdk_memory_format_alignment (format));
/* GL_UNPACK_ROW_LENGTH is available on desktop GL, OpenGL ES >= 3.0, or if
* the GL_EXT_unpack_subimage extension for OpenGL ES 2.0 is available
*/
if (stride == gsk_gpu_image_get_width (image) * bpp)
{
glTexSubImage2D (GL_TEXTURE_2D, 0, area->x, area->y, area->width, area->height, gl_format, gl_type, data);
}
else if (stride % bpp == 0 && gdk_gl_context_has_unpack_subimage (context))
{
glPixelStorei (GL_UNPACK_ROW_LENGTH, stride / bpp);
glTexSubImage2D (GL_TEXTURE_2D, 0, area->x, area->y, area->width, area->height, gl_format, gl_type, data);
glPixelStorei (GL_UNPACK_ROW_LENGTH, 0);
}
else
{
gsize i;
for (i = 0; i < area->height; i++)
glTexSubImage2D (GL_TEXTURE_2D, 0, area->x, area->y + i, area->width, 1, gl_format, gl_type, data + (i * stride));
}
glPixelStorei (GL_UNPACK_ALIGNMENT, 4);
g_free (data);
return op->next;
}
static GskGpuOp *
gsk_gpu_upload_op_gl_command (GskGpuOp *op,
GskGpuFrame *frame,
GskGpuImage *image,
void (* draw_func) (GskGpuOp *, guchar *, gsize))
{
return gsk_gpu_upload_op_gl_command_with_area (op,
frame,
image,
&(cairo_rectangle_int_t) {
0, 0,
gsk_gpu_image_get_width (image),
gsk_gpu_image_get_height (image)
},
draw_func);
}
#ifdef GDK_RENDERING_VULKAN
static GskGpuOp *
gsk_gpu_upload_op_vk_command_with_area (GskGpuOp *op,
GskGpuFrame *frame,
GskVulkanCommandState *state,
GskVulkanImage *image,
const cairo_rectangle_int_t *area,
void (* draw_func) (GskGpuOp *, guchar *, gsize),
GskGpuBuffer **buffer)
{
gsize stride;
guchar *data;
stride = area->width * gdk_memory_format_bytes_per_pixel (gsk_gpu_image_get_format (GSK_GPU_IMAGE (image)));
*buffer = gsk_vulkan_buffer_new_write (GSK_VULKAN_DEVICE (gsk_gpu_frame_get_device (frame)),
area->height * stride);
data = gsk_gpu_buffer_map (*buffer);
draw_func (op, data, stride);
gsk_gpu_buffer_unmap (*buffer, area->height * stride);
vkCmdPipelineBarrier (state->vk_command_buffer,
VK_PIPELINE_STAGE_HOST_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT,
0,
0, NULL,
1, &(VkBufferMemoryBarrier) {
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT,
.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.buffer = gsk_vulkan_buffer_get_vk_buffer (GSK_VULKAN_BUFFER (*buffer)),
.offset = 0,
.size = VK_WHOLE_SIZE,
},
0, NULL);
gsk_vulkan_image_transition (image,
state->semaphores,
state->vk_command_buffer,
VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
VK_ACCESS_TRANSFER_WRITE_BIT);
vkCmdCopyBufferToImage (state->vk_command_buffer,
gsk_vulkan_buffer_get_vk_buffer (GSK_VULKAN_BUFFER (*buffer)),
gsk_vulkan_image_get_vk_image (image),
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
1,
(VkBufferImageCopy[1]) {
{
.bufferOffset = 0,
.bufferRowLength = area->width,
.bufferImageHeight = area->height,
.imageSubresource = {
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.mipLevel = 0,
.baseArrayLayer = 0,
.layerCount = 1
},
.imageOffset = {
.x = area->x,
.y = area->y,
.z = 0
},
.imageExtent = {
.width = area->width,
.height = area->height,
.depth = 1
}
}
});
return op->next;
}
static GskGpuOp *
gsk_gpu_upload_op_vk_command (GskGpuOp *op,
GskGpuFrame *frame,
GskVulkanCommandState *state,
GskVulkanImage *image,
void (* draw_func) (GskGpuOp *, guchar *, gsize),
GskGpuBuffer **buffer)
{
gsize stride;
guchar *data;
data = gsk_vulkan_image_get_data (image, &stride);
if (data)
{
draw_func (op, data, stride);
*buffer = NULL;
return op->next;
}
return gsk_gpu_upload_op_vk_command_with_area (op,
frame,
state,
image,
&(cairo_rectangle_int_t) {
0, 0,
gsk_gpu_image_get_width (GSK_GPU_IMAGE (image)),
gsk_gpu_image_get_height (GSK_GPU_IMAGE (image)),
},
draw_func,
buffer);
}
#endif
typedef struct _GskGpuUploadTextureOp GskGpuUploadTextureOp;
struct _GskGpuUploadTextureOp
{
GskGpuOp op;
GskGpuImage *image;
GskGpuBuffer *buffer;
GdkTexture *texture;
};
static void
gsk_gpu_upload_texture_op_finish (GskGpuOp *op)
{
GskGpuUploadTextureOp *self = (GskGpuUploadTextureOp *) op;
g_object_unref (self->image);
g_clear_object (&self->buffer);
g_object_unref (self->texture);
}
static void
gsk_gpu_upload_texture_op_print (GskGpuOp *op,
GskGpuFrame *frame,
GString *string,
guint indent)
{
GskGpuUploadTextureOp *self = (GskGpuUploadTextureOp *) op;
gsk_gpu_print_op (string, indent, "upload-texture");
gsk_gpu_print_image (string, self->image);
gsk_gpu_print_newline (string);
}
static void
gsk_gpu_upload_texture_op_draw (GskGpuOp *op,
guchar *data,
gsize stride)
{
GskGpuUploadTextureOp *self = (GskGpuUploadTextureOp *) op;
GdkTextureDownloader *downloader;
downloader = gdk_texture_downloader_new (self->texture);
gdk_texture_downloader_set_format (downloader, gsk_gpu_image_get_format (self->image));
gdk_texture_downloader_download_into (downloader, data, stride);
gdk_texture_downloader_free (downloader);
}
#ifdef GDK_RENDERING_VULKAN
static GskGpuOp *
gsk_gpu_upload_texture_op_vk_command (GskGpuOp *op,
GskGpuFrame *frame,
GskVulkanCommandState *state)
{
GskGpuUploadTextureOp *self = (GskGpuUploadTextureOp *) op;
return gsk_gpu_upload_op_vk_command (op,
frame,
state,
GSK_VULKAN_IMAGE (self->image),
gsk_gpu_upload_texture_op_draw,
&self->buffer);
}
#endif
static GskGpuOp *
gsk_gpu_upload_texture_op_gl_command (GskGpuOp *op,
GskGpuFrame *frame,
GskGLCommandState *state)
{
GskGpuUploadTextureOp *self = (GskGpuUploadTextureOp *) op;
return gsk_gpu_upload_op_gl_command (op,
frame,
self->image,
gsk_gpu_upload_texture_op_draw);
}
static const GskGpuOpClass GSK_GPU_UPLOAD_TEXTURE_OP_CLASS = {
GSK_GPU_OP_SIZE (GskGpuUploadTextureOp),
GSK_GPU_STAGE_UPLOAD,
gsk_gpu_upload_texture_op_finish,
gsk_gpu_upload_texture_op_print,
#ifdef GDK_RENDERING_VULKAN
gsk_gpu_upload_texture_op_vk_command,
#endif
gsk_gpu_upload_texture_op_gl_command
};
GskGpuImage *
gsk_gpu_upload_texture_op_try (GskGpuFrame *frame,
gboolean with_mipmap,
GdkTexture *texture)
{
GskGpuUploadTextureOp *self;
GskGpuImage *image;
image = gsk_gpu_device_create_upload_image (gsk_gpu_frame_get_device (frame),
with_mipmap,
gdk_texture_get_format (texture),
gdk_texture_get_width (texture),
gdk_texture_get_height (texture));
if (image == NULL)
return NULL;
if (GSK_DEBUG_CHECK (FALLBACK))
{
GEnumClass *enum_class = g_type_class_ref (GDK_TYPE_MEMORY_FORMAT);
if (gdk_texture_get_format (texture) != gsk_gpu_image_get_format (image))
{
gdk_debug_message ("Unsupported format %s, converting on CPU to %s",
g_enum_get_value (enum_class, gdk_texture_get_format (texture))->value_nick,
g_enum_get_value (enum_class, gsk_gpu_image_get_format (image))->value_nick);
}
if (with_mipmap && !(gsk_gpu_image_get_flags (image) & GSK_GPU_IMAGE_CAN_MIPMAP))
{
gdk_debug_message ("Format %s does not support mipmaps",
g_enum_get_value (enum_class, gsk_gpu_image_get_format (image))->value_nick);
}
g_type_class_unref (enum_class);
}
self = (GskGpuUploadTextureOp *) gsk_gpu_op_alloc (frame, &GSK_GPU_UPLOAD_TEXTURE_OP_CLASS);
self->texture = g_object_ref (texture);
self->image = image;
return self->image;
}
typedef struct _GskGpuUploadCairoOp GskGpuUploadCairoOp;
struct _GskGpuUploadCairoOp
{
GskGpuOp op;
GskGpuImage *image;
graphene_rect_t viewport;
GskGpuCairoFunc func;
gpointer user_data;
GDestroyNotify user_destroy;
GskGpuBuffer *buffer;
};
static void
gsk_gpu_upload_cairo_op_finish (GskGpuOp *op)
{
GskGpuUploadCairoOp *self = (GskGpuUploadCairoOp *) op;
g_object_unref (self->image);
if (self->user_destroy)
self->user_destroy (self->user_data);
g_clear_object (&self->buffer);
}
static void
gsk_gpu_upload_cairo_op_print (GskGpuOp *op,
GskGpuFrame *frame,
GString *string,
guint indent)
{
GskGpuUploadCairoOp *self = (GskGpuUploadCairoOp *) op;
gsk_gpu_print_op (string, indent, "upload-cairo");
gsk_gpu_print_image (string, self->image);
gsk_gpu_print_newline (string);
}
static void
gsk_gpu_upload_cairo_op_draw (GskGpuOp *op,
guchar *data,
gsize stride)
{
GskGpuUploadCairoOp *self = (GskGpuUploadCairoOp *) op;
cairo_surface_t *surface;
cairo_t *cr;
int width, height;
width = gsk_gpu_image_get_width (self->image);
height = gsk_gpu_image_get_height (self->image);
surface = cairo_image_surface_create_for_data (data,
CAIRO_FORMAT_ARGB32,
width, height,
stride);
cairo_surface_set_device_scale (surface,
width / self->viewport.size.width,
height / self->viewport.size.height);
cr = cairo_create (surface);
cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
cairo_paint (cr);
cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
cairo_translate (cr, -self->viewport.origin.x, -self->viewport.origin.y);
self->func (self->user_data, cr);
cairo_destroy (cr);
cairo_surface_finish (surface);
cairo_surface_destroy (surface);
}
#ifdef GDK_RENDERING_VULKAN
static GskGpuOp *
gsk_gpu_upload_cairo_op_vk_command (GskGpuOp *op,
GskGpuFrame *frame,
GskVulkanCommandState *state)
{
GskGpuUploadCairoOp *self = (GskGpuUploadCairoOp *) op;
return gsk_gpu_upload_op_vk_command (op,
frame,
state,
GSK_VULKAN_IMAGE (self->image),
gsk_gpu_upload_cairo_op_draw,
&self->buffer);
}
#endif
static GskGpuOp *
gsk_gpu_upload_cairo_op_gl_command (GskGpuOp *op,
GskGpuFrame *frame,
GskGLCommandState *state)
{
GskGpuUploadCairoOp *self = (GskGpuUploadCairoOp *) op;
return gsk_gpu_upload_op_gl_command (op,
frame,
self->image,
gsk_gpu_upload_cairo_op_draw);
}
static const GskGpuOpClass GSK_GPU_UPLOAD_CAIRO_OP_CLASS = {
GSK_GPU_OP_SIZE (GskGpuUploadCairoOp),
GSK_GPU_STAGE_UPLOAD,
gsk_gpu_upload_cairo_op_finish,
gsk_gpu_upload_cairo_op_print,
#ifdef GDK_RENDERING_VULKAN
gsk_gpu_upload_cairo_op_vk_command,
#endif
gsk_gpu_upload_cairo_op_gl_command
};
GskGpuImage *
gsk_gpu_upload_cairo_op (GskGpuFrame *frame,
const graphene_vec2_t *scale,
const graphene_rect_t *viewport,
GskGpuCairoFunc func,
gpointer user_data,
GDestroyNotify user_destroy)
{
GskGpuUploadCairoOp *self;
self = (GskGpuUploadCairoOp *) gsk_gpu_op_alloc (frame, &GSK_GPU_UPLOAD_CAIRO_OP_CLASS);
self->image = gsk_gpu_device_create_upload_image (gsk_gpu_frame_get_device (frame),
FALSE,
GDK_MEMORY_DEFAULT,
ceil (graphene_vec2_get_x (scale) * viewport->size.width),
ceil (graphene_vec2_get_y (scale) * viewport->size.height));
self->viewport = *viewport;
self->func = func;
self->user_data = user_data;
self->user_destroy = user_destroy;
return self->image;
}
typedef struct _GskGpuUploadGlyphOp GskGpuUploadGlyphOp;
struct _GskGpuUploadGlyphOp
{
GskGpuOp op;
GskGpuImage *image;
cairo_rectangle_int_t area;
PangoFont *font;
PangoGlyph glyph;
graphene_point_t origin;
GskGpuBuffer *buffer;
};
static void
gsk_gpu_upload_glyph_op_finish (GskGpuOp *op)
{
GskGpuUploadGlyphOp *self = (GskGpuUploadGlyphOp *) op;
g_object_unref (self->image);
g_object_unref (self->font);
g_clear_object (&self->buffer);
}
static void
gsk_gpu_upload_glyph_op_print (GskGpuOp *op,
GskGpuFrame *frame,
GString *string,
guint indent)
{
GskGpuUploadGlyphOp *self = (GskGpuUploadGlyphOp *) op;
PangoFontDescription *desc;
char *str;
desc = pango_font_describe_with_absolute_size (self->font);
str = pango_font_description_to_string (desc);
gsk_gpu_print_op (string, indent, "upload-glyph");
gsk_gpu_print_int_rect (string, &self->area);
g_string_append_printf (string, "glyph %u font %s ", self->glyph, str);
gsk_gpu_print_newline (string);
g_free (str);
pango_font_description_free (desc);
}
static void
gsk_gpu_upload_glyph_op_draw (GskGpuOp *op,
guchar *data,
gsize stride)
{
GskGpuUploadGlyphOp *self = (GskGpuUploadGlyphOp *) op;
cairo_surface_t *surface;
cairo_t *cr;
PangoRectangle ink_rect = { 0, };
surface = cairo_image_surface_create_for_data (data,
CAIRO_FORMAT_ARGB32,
self->area.width,
self->area.height,
stride);
cairo_surface_set_device_offset (surface, self->origin.x, self->origin.y);
cr = cairo_create (surface);
cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
cairo_paint (cr);
cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
/* Make sure the entire surface is initialized to black */
cairo_set_source_rgba (cr, 0, 0, 0, 0);
cairo_rectangle (cr, 0.0, 0.0, self->area.width, self->area.height);
cairo_fill (cr);
/* Draw glyph */
cairo_set_source_rgba (cr, 1, 1, 1, 1);
/* The pango code for drawing hex boxes uses the glyph width */
if (self->glyph & PANGO_GLYPH_UNKNOWN_FLAG)
pango_font_get_glyph_extents (self->font, self->glyph, &ink_rect, NULL);
pango_cairo_show_glyph_string (cr,
self->font,
&(PangoGlyphString) {
.num_glyphs = 1,
.glyphs = (PangoGlyphInfo[1]) { {
.glyph = self->glyph,
.geometry = {
.width = ink_rect.width,
}
} }
});
cairo_destroy (cr);
cairo_surface_finish (surface);
cairo_surface_destroy (surface);
}
#ifdef GDK_RENDERING_VULKAN
static GskGpuOp *
gsk_gpu_upload_glyph_op_vk_command (GskGpuOp *op,
GskGpuFrame *frame,
GskVulkanCommandState *state)
{
GskGpuUploadGlyphOp *self = (GskGpuUploadGlyphOp *) op;
return gsk_gpu_upload_op_vk_command_with_area (op,
frame,
state,
GSK_VULKAN_IMAGE (self->image),
&self->area,
gsk_gpu_upload_glyph_op_draw,
&self->buffer);
}
#endif
static GskGpuOp *
gsk_gpu_upload_glyph_op_gl_command (GskGpuOp *op,
GskGpuFrame *frame,
GskGLCommandState *state)
{
GskGpuUploadGlyphOp *self = (GskGpuUploadGlyphOp *) op;
return gsk_gpu_upload_op_gl_command_with_area (op,
frame,
self->image,
&self->area,
gsk_gpu_upload_glyph_op_draw);
}
static const GskGpuOpClass GSK_GPU_UPLOAD_GLYPH_OP_CLASS = {
GSK_GPU_OP_SIZE (GskGpuUploadGlyphOp),
GSK_GPU_STAGE_UPLOAD,
gsk_gpu_upload_glyph_op_finish,
gsk_gpu_upload_glyph_op_print,
#ifdef GDK_RENDERING_VULKAN
gsk_gpu_upload_glyph_op_vk_command,
#endif
gsk_gpu_upload_glyph_op_gl_command,
};
void
gsk_gpu_upload_glyph_op (GskGpuFrame *frame,
GskGpuImage *image,
PangoFont *font,
const PangoGlyph glyph,
const cairo_rectangle_int_t *area,
const graphene_point_t *origin)
{
GskGpuUploadGlyphOp *self;
self = (GskGpuUploadGlyphOp *) gsk_gpu_op_alloc (frame, &GSK_GPU_UPLOAD_GLYPH_OP_CLASS);
self->image = g_object_ref (image);
self->area = *area;
self->font = g_object_ref (font);
self->glyph = glyph;
self->origin = *origin;
}