Vulkan maintenance fixes.

- Use VkBuffer instead of linear VkImage for staging uploads.
  Not guaranteed to be supported, and desktop IHVs recommend using
  buffers.

- Fix validation error where swapchain recreation with reuse of the old
  VkSwapchainKHR does not create a fence properly, leading to validation
  error next time that swapchain index is observed.
This commit is contained in:
Themaister 2018-09-02 16:05:45 +02:00
parent 1155e11cfc
commit af534f0fa8
3 changed files with 108 additions and 77 deletions

View File

@ -184,13 +184,12 @@ void vulkan_copy_staging_to_dynamic(vk_t *vk, VkCommandBuffer cmd,
struct vk_texture *dynamic,
struct vk_texture *staging)
{
VkImageCopy region;
VkBufferImageCopy region;
retro_assert(dynamic->type == VULKAN_TEXTURE_DYNAMIC);
retro_assert(staging->type == VULKAN_TEXTURE_STAGING);
vulkan_sync_texture_to_gpu(vk, staging);
vulkan_transition_texture(vk, cmd, staging);
/* We don't have to sync against previous TRANSFER,
* since we observed the completion by fences.
@ -208,15 +207,14 @@ void vulkan_copy_staging_to_dynamic(vk_t *vk, VkCommandBuffer cmd,
VK_PIPELINE_STAGE_TRANSFER_BIT);
memset(&region, 0, sizeof(region));
region.extent.width = dynamic->width;
region.extent.height = dynamic->height;
region.extent.depth = 1;
region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
region.srcSubresource.layerCount = 1;
region.dstSubresource = region.srcSubresource;
region.imageExtent.width = dynamic->width;
region.imageExtent.height = dynamic->height;
region.imageExtent.depth = 1;
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
region.imageSubresource.layerCount = 1;
vkCmdCopyImage(cmd,
staging->image, VK_IMAGE_LAYOUT_GENERAL,
vkCmdCopyBufferToImage(cmd,
staging->buffer,
dynamic->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
1, &region);
@ -323,6 +321,7 @@ struct vk_texture vulkan_create_texture(vk_t *vk,
VkSubresourceLayout layout;
VkDevice device = vk->context->device;
VkImageCreateInfo info = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
VkBufferCreateInfo buffer_info = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
VkImageViewCreateInfo view = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO };
VkMemoryAllocateInfo alloc = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO };
VkImageSubresource subresource = { VK_IMAGE_ASPECT_COLOR_BIT };
@ -338,6 +337,10 @@ struct vk_texture vulkan_create_texture(vk_t *vk,
info.extent.height = height;
info.extent.depth = 1;
info.arrayLayers = 1;
info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
buffer_info.size = width * height * vulkan_format_to_bpp(format);
buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
/* For simplicity, always build mipmaps for
* static textures, samplers can be used to enable it dynamically.
@ -355,7 +358,7 @@ struct vk_texture vulkan_create_texture(vk_t *vk,
if (type == VULKAN_TEXTURE_STREAMED)
{
VkFormatProperties format_properties;
VkFormatFeatureFlags required = VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT |
const VkFormatFeatureFlags required = VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT |
VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT;
vkGetPhysicalDeviceFormatProperties(
@ -396,23 +399,33 @@ struct vk_texture vulkan_create_texture(vk_t *vk,
break;
case VULKAN_TEXTURE_STAGING:
info.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
buffer_info.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
info.initialLayout = VK_IMAGE_LAYOUT_GENERAL;
info.tiling = VK_IMAGE_TILING_LINEAR;
info.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;
break;
case VULKAN_TEXTURE_READBACK:
info.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT;
buffer_info.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT;
info.initialLayout = VK_IMAGE_LAYOUT_GENERAL;
info.tiling = VK_IMAGE_TILING_LINEAR;
info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
break;
}
if (type != VULKAN_TEXTURE_STAGING && type != VULKAN_TEXTURE_READBACK)
{
vkCreateImage(device, &info, NULL, &tex.image);
#if 0
vulkan_track_alloc(tex.image);
#endif
vkGetImageMemoryRequirements(device, tex.image, &mem_reqs);
}
else
{
/* Linear staging textures are not guaranteed to be supported,
* use buffers instead. */
vkCreateBuffer(device, &buffer_info, NULL, &tex.buffer);
vkGetBufferMemoryRequirements(device, tex.buffer, &mem_reqs);
}
alloc.allocationSize = mem_reqs.size;
switch (type)
@ -451,11 +464,12 @@ struct vk_texture vulkan_create_texture(vk_t *vk,
RARCH_LOG("[Vulkan]: GPU supports linear images as textures, but not DEVICE_LOCAL. Falling back to copy path.\n");
type = VULKAN_TEXTURE_STAGING;
vkDestroyImage(device, tex.image, NULL);
tex.image = NULL;
info.initialLayout = VK_IMAGE_LAYOUT_GENERAL;
info.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
vkCreateImage(device, &info, NULL, &tex.image);
vkGetImageMemoryRequirements(device, tex.image, &mem_reqs);
buffer_info.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
vkCreateBuffer(device, &buffer_info, NULL, &tex.buffer);
vkGetBufferMemoryRequirements(device, tex.buffer, &mem_reqs);
alloc.allocationSize = mem_reqs.size;
alloc.memoryTypeIndex = vulkan_find_memory_type_fallback(
@ -478,6 +492,8 @@ struct vk_texture vulkan_create_texture(vk_t *vk,
vulkan_track_dealloc(old->image);
#endif
}
if (old && old->buffer != VK_NULL_HANDLE)
vkDestroyBuffer(vk->context->device, old->buffer, NULL);
/* We can pilfer the old memory and move it over to the new texture. */
if (old &&
@ -507,7 +523,10 @@ struct vk_texture vulkan_create_texture(vk_t *vk,
memset(old, 0, sizeof(*old));
}
if (tex.image)
vkBindImageMemory(device, tex.image, tex.memory, 0);
if (tex.buffer)
vkBindBufferMemory(device, tex.buffer, tex.memory, 0);
if (type != VULKAN_TEXTURE_STAGING && type != VULKAN_TEXTURE_READBACK)
{
@ -532,8 +551,14 @@ struct vk_texture vulkan_create_texture(vk_t *vk,
else
tex.view = VK_NULL_HANDLE;
if (info.tiling == VK_IMAGE_TILING_LINEAR)
if (tex.image && info.tiling == VK_IMAGE_TILING_LINEAR)
vkGetImageSubresourceLayout(device, tex.image, &subresource, &layout);
else if (tex.buffer)
{
layout.offset = 0;
layout.size = buffer_info.size;
layout.rowPitch = width * vulkan_format_to_bpp(format);
}
else
memset(&layout, 0, sizeof(layout));
@ -568,7 +593,7 @@ struct vk_texture vulkan_create_texture(vk_t *vk,
}
else if (initial && type == VULKAN_TEXTURE_STATIC)
{
VkImageCopy region;
VkBufferImageCopy region;
VkCommandBuffer staging;
struct vk_texture tmp = vulkan_create_texture(vk, NULL,
width, height, format, initial, NULL, VULKAN_TEXTURE_STAGING);
@ -583,12 +608,6 @@ struct vk_texture vulkan_create_texture(vk_t *vk,
vkBeginCommandBuffer(staging, &begin_info);
vulkan_image_layout_transition(vk, staging, tmp.image,
VK_IMAGE_LAYOUT_PREINITIALIZED, VK_IMAGE_LAYOUT_GENERAL,
VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT,
VK_PIPELINE_STAGE_HOST_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT);
/* If doing mipmapping on upload, keep in general so we can easily do transfers to
* and transfers from the images without having to
* mess around with lots of extra transitions at per-level granularity.
@ -603,16 +622,14 @@ struct vk_texture vulkan_create_texture(vk_t *vk,
VK_PIPELINE_STAGE_TRANSFER_BIT);
memset(&region, 0, sizeof(region));
region.extent.width = width;
region.extent.height = height;
region.extent.depth = 1;
region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
region.srcSubresource.layerCount = 1;
region.dstSubresource = region.srcSubresource;
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
region.imageSubresource.layerCount = 1;
region.imageExtent.width = width;
region.imageExtent.height = height;
region.imageExtent.depth = 1;
vkCmdCopyImage(staging,
tmp.image,
VK_IMAGE_LAYOUT_GENERAL,
vkCmdCopyBufferToImage(staging,
tmp.buffer,
tex.image,
tex.mipmap ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
1, &region);
@ -710,11 +727,17 @@ void vulkan_destroy_texture(
{
if (tex->mapped)
vkUnmapMemory(device, tex->memory);
vkFreeMemory(device, tex->memory, NULL);
if (tex->view)
vkDestroyImageView(device, tex->view, NULL);
if (tex->image)
vkDestroyImage(device, tex->image, NULL);
if (tex->buffer)
vkDestroyBuffer(device, tex->buffer, NULL);
if (tex->memory)
vkFreeMemory(device, tex->memory, NULL);
#ifdef VULKAN_DEBUG_TEXTURE_ALLOC
if (tex->image)
vulkan_track_dealloc(tex->image);
#endif
memset(tex, 0, sizeof(*tex));
@ -762,6 +785,9 @@ static void vulkan_write_quad_descriptors(
void vulkan_transition_texture(vk_t *vk, VkCommandBuffer cmd, struct vk_texture *texture)
{
if (!texture->image)
return;
/* Transition to GENERAL layout for linear streamed textures.
* We're using linear textures here, so only
* GENERAL layout is supported.
@ -782,14 +808,6 @@ void vulkan_transition_texture(vk_t *vk, VkCommandBuffer cmd, struct vk_texture
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT);
break;
case VULKAN_TEXTURE_STAGING:
vulkan_image_layout_transition(vk, cmd, texture->image,
texture->layout, VK_IMAGE_LAYOUT_GENERAL,
VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT,
VK_PIPELINE_STAGE_HOST_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT);
break;
default:
retro_assert(0 && "Attempting to transition invalid texture type.\n");
break;
@ -2455,6 +2473,22 @@ static void vulkan_acquire_wait_fences(gfx_ctx_vulkan_data_t *vk)
vkCreateFence(vk->context.device, &fence_info, NULL, next_fence);
}
static void vulkan_create_wait_fences(gfx_ctx_vulkan_data_t *vk)
{
VkFenceCreateInfo fence_info =
{ VK_STRUCTURE_TYPE_FENCE_CREATE_INFO };
unsigned i;
for (i = 0; i < vk->context.num_swapchain_images; i++)
{
if (!vk->context.swapchain_fences[i])
{
vkCreateFence(vk->context.device, &fence_info, NULL,
&vk->context.swapchain_fences[i]);
}
}
}
void vulkan_acquire_next_image(gfx_ctx_vulkan_data_t *vk)
{
unsigned index;
@ -2577,6 +2611,7 @@ bool vulkan_create_swapchain(gfx_ctx_vulkan_data_t *vk,
/* Do not bother creating a swapchain redundantly. */
RARCH_LOG("[Vulkan]: Do not need to re-create swapchain.\n");
vk->created_new_swapchain = false;
vulkan_create_wait_fences(vk);
return true;
}
@ -2814,5 +2849,6 @@ bool vulkan_create_swapchain(gfx_ctx_vulkan_data_t *vk,
/* Force driver to reset swapchain image handles. */
vk->context.invalid_swapchain = true;
vulkan_create_wait_fences(vk);
return true;
}

View File

@ -179,6 +179,7 @@ struct vk_texture
VkImage image;
VkImageView view;
VkDeviceMemory memory;
VkBuffer buffer;
VkFormat format;

View File

@ -854,6 +854,8 @@ static void vulkan_init_static_resources(vk_t *vk)
uint32_t blank[4 * 4];
VkCommandPoolCreateInfo pool_info = {
VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO };
pool_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
/* Create the pipeline cache. */
VkPipelineCacheCreateInfo cache = {
VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO };
@ -1509,23 +1511,22 @@ static void vulkan_set_viewport(void *data, unsigned viewport_width,
static void vulkan_readback(vk_t *vk)
{
VkImageCopy region;
VkBufferImageCopy region;
struct vk_texture *staging;
struct video_viewport vp;
VkMemoryBarrier barrier = { VK_STRUCTURE_TYPE_MEMORY_BARRIER };
vulkan_viewport_info(vk, &vp);
memset(&region, 0, sizeof(region));
region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
region.srcSubresource.layerCount = 1;
region.dstSubresource = region.srcSubresource;
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
region.imageSubresource.layerCount = 1;
region.imageOffset.x = vp.x;
region.imageOffset.y = vp.y;
region.imageExtent.width = vp.width;
region.imageExtent.height = vp.height;
region.imageExtent.depth = 1;
region.srcOffset.x = vp.x;
region.srcOffset.y = vp.y;
region.extent.width = vp.width;
region.extent.height = vp.height;
region.extent.depth = 1;
/* FIXME: We won't actually get format conversion with vkCmdCopyImage, so have to check
/* FIXME: We won't actually get format conversion with vkCmdCopyImageToBuffer, so have to check
* properly for this. BGRA seems to be the default for all swapchains. */
if (vk->context->swapchain_format != VK_FORMAT_B8G8R8A8_UNORM)
RARCH_WARN("[Vulkan]: Backbuffer is not BGRA8888, readbacks might not work properly.\n");
@ -1537,25 +1538,18 @@ static void vulkan_readback(vk_t *vk)
VK_FORMAT_B8G8R8A8_UNORM,
NULL, NULL, VULKAN_TEXTURE_READBACK);
/* Go through the long-winded dance of remapping image layouts. */
vulkan_image_layout_transition(vk, vk->cmd, staging->image,
VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL,
0, VK_ACCESS_TRANSFER_WRITE_BIT,
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT);
vkCmdCopyImage(vk->cmd, vk->chain->backbuffer.image,
vkCmdCopyImageToBuffer(vk->cmd, vk->chain->backbuffer.image,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
staging->image,
VK_IMAGE_LAYOUT_GENERAL,
staging->buffer,
1, &region);
/* Make the data visible to host. */
vulkan_image_layout_transition(vk, vk->cmd, staging->image,
VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL,
VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_HOST_READ_BIT,
barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
barrier.dstAccessMask = VK_ACCESS_HOST_READ_BIT;
vkCmdPipelineBarrier(vk->cmd,
VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_HOST_BIT);
VK_PIPELINE_STAGE_HOST_BIT, 0,
1, &barrier, 0, NULL, 0, NULL);
}
static void vulkan_inject_black_frame(vk_t *vk, video_frame_info_t *video_info)