vulkan: Upload image data directly

Instead of using a staging iamge, we require the final image to be
linearly allocated and have host-visible memory.

This improves performance quite a bit.

The old code is still there and can be enabled with a simple change
to a #define in gskvulkanimage.h
This commit is contained in:
Benjamin Otte 2016-12-06 23:29:38 +01:00
parent 3c4b952256
commit 13b53656ea
2 changed files with 104 additions and 28 deletions

View File

@ -65,27 +65,19 @@ gsk_vulkan_image_new (GdkVulkanContext *context,
return self;
}
static GskVulkanImage *
gsk_vulkan_image_new_staging (GdkVulkanContext *context,
guchar *data,
gsize width,
gsize height,
gsize stride)
static void
gsk_vulkan_image_upload_data (GskVulkanImage *self,
guchar *data,
gsize width,
gsize height,
gsize data_stride)
{
VkMemoryRequirements requirements;
GskVulkanImage *self;
guchar *mem;
gsize mem_stride;
self = gsk_vulkan_image_new (context,
width,
height,
VK_IMAGE_TILING_LINEAR,
VK_IMAGE_USAGE_TRANSFER_SRC_BIT,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
guchar *mem;
mem_stride = width * 4;
vkGetImageMemoryRequirements (gdk_vulkan_context_get_device (context),
vkGetImageMemoryRequirements (gdk_vulkan_context_get_device (self->vulkan),
self->vk_image,
&requirements);
if (mem_stride % requirements.alignment != 0)
@ -93,21 +85,19 @@ gsk_vulkan_image_new_staging (GdkVulkanContext *context,
mem = gsk_vulkan_memory_map (self->memory);
if (stride == width * 4 && stride == mem_stride)
if (data_stride == width * 4 && data_stride == mem_stride)
{
memcpy (mem, data, width * height * 4);
memcpy (mem, data, data_stride * height);
}
else
{
for (gsize i = 0; i < height; i++)
{
memcpy (mem + i * mem_stride, data + i * stride, width * 4);
memcpy (mem + i * mem_stride, data + i * data_stride, width * 4);
}
}
gsk_vulkan_memory_unmap (self->memory);
return self;
}
static void
@ -138,16 +128,23 @@ gsk_vulkan_image_ensure_view (GskVulkanImage *self)
}
GskVulkanImage *
gsk_vulkan_image_new_from_data (GdkVulkanContext *context,
VkCommandBuffer command_buffer,
guchar *data,
gsize width,
gsize height,
gsize stride)
gsk_vulkan_image_new_from_data_via_staging_image (GdkVulkanContext *context,
VkCommandBuffer command_buffer,
guchar *data,
gsize width,
gsize height,
gsize stride)
{
GskVulkanImage *self, *staging;
staging = gsk_vulkan_image_new_staging (context, data, width, height, stride);
staging = gsk_vulkan_image_new (context,
width,
height,
VK_IMAGE_TILING_LINEAR,
VK_IMAGE_USAGE_TRANSFER_SRC_BIT,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT);
gsk_vulkan_image_upload_data (staging, data, width, height, stride);
self = gsk_vulkan_image_new (context,
width,
@ -253,6 +250,7 @@ gsk_vulkan_image_new_from_data (GdkVulkanContext *context,
}
});
/* XXX: Is this okay or do we need to keep the staging image around until the commands execute */
gsk_vulkan_image_free (staging);
gsk_vulkan_image_ensure_view (self);
@ -260,6 +258,76 @@ gsk_vulkan_image_new_from_data (GdkVulkanContext *context,
return self;
}
GskVulkanImage *
gsk_vulkan_image_new_from_data_directly (GdkVulkanContext *context,
VkCommandBuffer command_buffer,
guchar *data,
gsize width,
gsize height,
gsize stride)
{
GskVulkanImage *self;
self = gsk_vulkan_image_new (context,
width,
height,
VK_IMAGE_TILING_LINEAR,
VK_IMAGE_USAGE_SAMPLED_BIT,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT);
gsk_vulkan_image_upload_data (self, data, width, height, stride);
vkCmdPipelineBarrier (command_buffer,
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
0,
0, NULL,
0, NULL,
1, (VkImageMemoryBarrier[1]) {
{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT,
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
.oldLayout = VK_IMAGE_LAYOUT_PREINITIALIZED,
.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = self->vk_image,
.subresourceRange = {
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = 1
}
}
});
gsk_vulkan_image_ensure_view (self);
return self;
}
GskVulkanImage *
gsk_vulkan_image_new_from_data (GdkVulkanContext *context,
VkCommandBuffer command_buffer,
guchar *data,
gsize width,
gsize height,
gsize stride)
{
switch (GSK_VULKAN_UPLOAD_IMAGE_DEFAULT)
{
default:
g_assert_not_reached ();
/* fall through */
case GSK_VULKAN_UPLOAD_DIRECTLY:
return gsk_vulkan_image_new_from_data_directly (context, command_buffer, data, width, height, stride);
case GSK_VULKAN_UPLOAD_VIA_STAGING_IMAGE:
return gsk_vulkan_image_new_from_data_via_staging_image (context, command_buffer, data, width, height, stride);
}
}
void
gsk_vulkan_image_free (GskVulkanImage *self)
{

View File

@ -5,6 +5,14 @@
G_BEGIN_DECLS
/* Modify here for benchmarking */
#define GSK_VULKAN_UPLOAD_IMAGE_DEFAULT GSK_VULKAN_UPLOAD_DIRECTLY
typedef enum {
GSK_VULKAN_UPLOAD_DIRECTLY,
GSK_VULKAN_UPLOAD_VIA_STAGING_IMAGE
} GstkVulkanImageUpload;
typedef struct _GskVulkanImage GskVulkanImage;
GskVulkanImage * gsk_vulkan_image_new_from_data (GdkVulkanContext *context,