[vulkan] Use vkCmdCopyImageToBuffer for screenshot capture

I had missed that vkCmdCopyImage requires the source and destination
images to have exactly the same size, and I guess assumed that the
swapchain images would always be the size they said they were, but this
is not the case for tiled-optimal images. However,
vkCmdCopyImageToBuffer does the right thing regardless of the source
image size.

This fixes the skewed screenshots when the window size is not a multiple
of 8 (for me, might differ for others).
This commit is contained in:
Bill Currie 2022-09-26 15:54:20 +09:00
parent 950d6d1472
commit 71e07e6454
3 changed files with 46 additions and 92 deletions

View file

@ -10,9 +10,8 @@
#include "QF/qtypes.h" #include "QF/qtypes.h"
typedef struct qfv_capture_image_s { typedef struct qfv_capture_image_s {
VkImage image;
VkImageLayout layout;
VkCommandBuffer cmd; VkCommandBuffer cmd;
VkBuffer buffer;
byte *data; byte *data;
} qfv_capture_image_t; } qfv_capture_image_t;
@ -25,7 +24,6 @@ typedef struct qfv_capture_image_set_s
typedef struct qfv_capture_s { typedef struct qfv_capture_s {
struct qfv_device_s *device; struct qfv_device_s *device;
int canBlit;
VkExtent2D extent; VkExtent2D extent;
qfv_capture_image_set_t *image_set; qfv_capture_image_set_t *image_set;
size_t imgsize; size_t imgsize;

View file

@ -558,8 +558,7 @@ capture_screenshot (const byte *data, int width, int height)
tex->flagbits = 0; tex->flagbits = 0;
tex->loaded = 1; tex->loaded = 1;
if (!vulkan_ctx->capture->canBlit || if (is_bgr (vulkan_ctx->swapchain->format)) {
is_bgr (vulkan_ctx->swapchain->format)) {
tex->bgr = 1; tex->bgr = 1;
} }
const byte *src = data; const byte *src = data;

View file

@ -28,6 +28,7 @@
# include "config.h" # include "config.h"
#endif #endif
#include "QF/Vulkan/buffer.h"
#include "QF/Vulkan/capture.h" #include "QF/Vulkan/capture.h"
#include "QF/Vulkan/command.h" #include "QF/Vulkan/command.h"
#include "QF/Vulkan/device.h" #include "QF/Vulkan/device.h"
@ -42,7 +43,6 @@ QFV_CreateCapture (qfv_device_t *device, int numframes,
qfv_instfuncs_t *ifunc = device->physDev->instance->funcs; qfv_instfuncs_t *ifunc = device->physDev->instance->funcs;
qfv_devfuncs_t *dfunc = device->funcs; qfv_devfuncs_t *dfunc = device->funcs;
VkFormat format = VK_FORMAT_R8G8B8A8_UNORM; VkFormat format = VK_FORMAT_R8G8B8A8_UNORM;
int canBlit = 1;
VkFormatProperties format_props; VkFormatProperties format_props;
ifunc->vkGetPhysicalDeviceFormatProperties (device->physDev->dev, ifunc->vkGetPhysicalDeviceFormatProperties (device->physDev->dev,
@ -52,66 +52,38 @@ QFV_CreateCapture (qfv_device_t *device, int numframes,
Sys_Printf ("Swapchain does not support reading. FIXME\n"); Sys_Printf ("Swapchain does not support reading. FIXME\n");
return 0; return 0;
} }
if (!(format_props.optimalTilingFeatures
& VK_FORMAT_FEATURE_BLIT_SRC_BIT)) {
Sys_MaskPrintf (SYS_vulkan,
"Device does not support blitting from optimal tiled "
"images.\n");
canBlit = 0;
}
ifunc->vkGetPhysicalDeviceFormatProperties (device->physDev->dev, format, ifunc->vkGetPhysicalDeviceFormatProperties (device->physDev->dev, format,
&format_props); &format_props);
if (!(format_props.linearTilingFeatures
& VK_FORMAT_FEATURE_BLIT_DST_BIT)) {
Sys_MaskPrintf (SYS_vulkan,
"Device does not support blitting from optimal tiled "
"images.\n");
canBlit = 0;
}
qfv_capture_t *capture = malloc (sizeof (qfv_capture_t)); qfv_capture_t *capture = malloc (sizeof (qfv_capture_t));
capture->device = device; capture->device = device;
capture->canBlit = canBlit;
capture->extent = swapchain->extent; capture->extent = swapchain->extent;
capture->image_set = QFV_AllocCaptureImageSet (numframes, malloc); capture->image_set = QFV_AllocCaptureImageSet (numframes, malloc);
__auto_type cmdset = QFV_AllocCommandBufferSet (numframes, alloca); __auto_type cmdset = QFV_AllocCommandBufferSet (numframes, alloca);
QFV_AllocateCommandBuffers (device, cmdPool, 1, cmdset); QFV_AllocateCommandBuffers (device, cmdPool, 1, cmdset);
VkImageCreateInfo createInfo = { capture->imgsize = QFV_GetImageSize (device,
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, swapchain->images->a[0]);
.imageType = VK_IMAGE_TYPE_2D,
.format = format,
.extent = { swapchain->extent.width, swapchain->extent.height, 1 },
.arrayLayers = 1,
.mipLevels = 1,
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
.samples = VK_SAMPLE_COUNT_1_BIT,
.tiling = VK_IMAGE_TILING_LINEAR,
.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT,
};
for (int i = 0; i < numframes; i++) { for (int i = 0; i < numframes; i++) {
__auto_type image = &capture->image_set->a[i]; __auto_type image = &capture->image_set->a[i];
dfunc->vkCreateImage (device->dev, &createInfo, 0, &image->image); image->buffer = QFV_CreateBuffer (device, capture->imgsize,
image->layout = VK_IMAGE_LAYOUT_UNDEFINED; VK_BUFFER_USAGE_TRANSFER_DST_BIT);
image->cmd = cmdset->a[i];
} }
capture->imgsize = QFV_GetImageSize (device,
capture->image_set->a[0].image);
capture->memsize = numframes * capture->imgsize; capture->memsize = numframes * capture->imgsize;
capture->memory = QFV_AllocImageMemory (device, capture->memory = QFV_AllocBufferMemory (device,
capture->image_set->a[0].image, capture->image_set->a[0].buffer,
VK_MEMORY_PROPERTY_HOST_CACHED_BIT, VK_MEMORY_PROPERTY_HOST_CACHED_BIT,
capture->memsize, 0); capture->memsize, 0);
dfunc->vkMapMemory (device->dev, capture->memory, 0, capture->memsize, 0, dfunc->vkMapMemory (device->dev, capture->memory, 0, capture->memsize, 0,
(void **) &capture->data); (void **) &capture->data);
for (int i = 0; i < numframes; i++) { for (int i = 0; i < numframes; i++) {
__auto_type image = &capture->image_set->a[i]; __auto_type image = &capture->image_set->a[i];
image->cmd = cmdset->a[i];
image->data = capture->data + i * capture->imgsize; image->data = capture->data + i * capture->imgsize;
dfunc->vkBindImageMemory (device->dev, image->image, capture->memory, QFV_BindBufferMemory (device, image->buffer, capture->memory,
image->data - capture->data); i * capture->imgsize);
} }
return capture; return capture;
} }
@ -124,7 +96,7 @@ QFV_DestroyCapture (qfv_capture_t *capture)
for (size_t i = 0; i < capture->image_set->size; i++) { for (size_t i = 0; i < capture->image_set->size; i++) {
__auto_type image = &capture->image_set->a[i]; __auto_type image = &capture->image_set->a[i];
dfunc->vkDestroyImage (device->dev, image->image, 0); dfunc->vkDestroyBuffer (device->dev, image->buffer, 0);
} }
dfunc->vkUnmapMemory (device->dev, capture->memory); dfunc->vkUnmapMemory (device->dev, capture->memory);
dfunc->vkFreeMemory (device->dev, capture->memory, 0); dfunc->vkFreeMemory (device->dev, capture->memory, 0);
@ -132,35 +104,21 @@ QFV_DestroyCapture (qfv_capture_t *capture)
free (capture); free (capture);
} }
static void
blit_image (qfv_capture_t *capture, qfv_devfuncs_t *dfunc,
VkImage scImage, qfv_capture_image_t *image)
{
VkImageBlit blit = {
{ VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 },
{ { }, { capture->extent.width, capture->extent.height, 1 } },
{ VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 },
{ { }, { capture->extent.width, capture->extent.height, 1 } },
};
dfunc->vkCmdBlitImage (image->cmd,
scImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
image->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
1, &blit, VK_FILTER_NEAREST);
}
static void static void
copy_image (qfv_capture_t *capture, qfv_devfuncs_t *dfunc, copy_image (qfv_capture_t *capture, qfv_devfuncs_t *dfunc,
VkImage scImage, qfv_capture_image_t *image) VkImage scImage, qfv_capture_image_t *image)
{ {
VkImageCopy copy = { VkBufferImageCopy copy = {
{ VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 }, { }, .bufferOffset = 0,
{ VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 }, { }, .bufferRowLength = 0,
{ capture->extent.width, capture->extent.height, 1 }, .bufferImageHeight = 0,
.imageSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 },
.imageOffset = { },
.imageExtent = { capture->extent.width, capture->extent.height, 1 },
}; };
dfunc->vkCmdCopyImage (image->cmd, dfunc->vkCmdCopyImageToBuffer (image->cmd, scImage,
scImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
image->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, image->buffer, 1, &copy);
1, &copy);
} }
VkCommandBuffer VkCommandBuffer
@ -181,16 +139,17 @@ QFV_CaptureImage (qfv_capture_t *capture, VkImage scImage, int frame)
}; };
dfunc->vkBeginCommandBuffer (image->cmd, &beginInfo); dfunc->vkBeginCommandBuffer (image->cmd, &beginInfo);
VkImageMemoryBarrier start_barriers[] = { VkBufferMemoryBarrier start_buffer_barriers[] = {
{ {
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
.srcAccessMask = 0, .srcAccessMask = 0,
.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.oldLayout = image->layout, .buffer = image->buffer,
.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, .offset = 0,
.image = image->image, .size = VK_WHOLE_SIZE,
.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 },
}, },
};
VkImageMemoryBarrier start_image_barriers[] = {
{ {
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.srcAccessMask = VK_ACCESS_MEMORY_READ_BIT, .srcAccessMask = VK_ACCESS_MEMORY_READ_BIT,
@ -201,16 +160,17 @@ QFV_CaptureImage (qfv_capture_t *capture, VkImage scImage, int frame)
.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }, .subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 },
}, },
}; };
VkImageMemoryBarrier end_barriers[] = { VkBufferMemoryBarrier end_buffer_barriers[] = {
{ {
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT, .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT,
.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, .buffer = image->buffer,
.newLayout = VK_IMAGE_LAYOUT_GENERAL, .offset = 0,
.image = image->image, .size = VK_WHOLE_SIZE,
.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 },
}, },
};
VkImageMemoryBarrier end_image_barriers[] = {
{ {
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT, .srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
@ -225,21 +185,18 @@ QFV_CaptureImage (qfv_capture_t *capture, VkImage scImage, int frame)
dfunc->vkCmdPipelineBarrier (image->cmd, dfunc->vkCmdPipelineBarrier (image->cmd,
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
0, 0, 0, 0, 0, 0, 0, 0,
2, start_barriers); 1, start_buffer_barriers,
1, start_image_barriers);
if (capture->canBlit) { copy_image (capture, dfunc, scImage, image);
blit_image (capture, dfunc, scImage, image);
} else {
copy_image (capture, dfunc, scImage, image);
}
dfunc->vkCmdPipelineBarrier (image->cmd, dfunc->vkCmdPipelineBarrier (image->cmd,
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
0, 0, 0, 0, 0, 0, 0, 0,
2, end_barriers); 1, end_buffer_barriers,
image->layout = VK_IMAGE_LAYOUT_GENERAL; 1, end_image_barriers);
dfunc->vkEndCommandBuffer (image->cmd); dfunc->vkEndCommandBuffer (image->cmd);