vulkan: implement screenshots

This commit is contained in:
niki
2022-09-14 17:31:53 +02:00
parent 4e59b563a4
commit d1da47dbf0
3 changed files with 241 additions and 12 deletions
+180 -11
View File
@@ -226,10 +226,121 @@ void Graphics::discard(const std::vector<bool>& colorbuffers, bool depthstencil)
startRenderPass();
}
void Graphics::submitGpuCommands(bool present)
void Graphics::submitGpuCommands(bool present, void* screenshotCallbackData)
{
flushBatchedDraws();
if (renderPassState.active)
endRenderPass();
if (present)
{
if (pendingScreenshotCallbacks.empty())
Vulkan::cmdTransitionImageLayout(
commandBuffers.at(currentFrame),
swapChainImages.at(imageIndex),
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
else
{
Vulkan::cmdTransitionImageLayout(
commandBuffers.at(currentFrame),
swapChainImages.at(imageIndex),
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
Vulkan::cmdTransitionImageLayout(
commandBuffers.at(currentFrame),
screenshotReadbackBuffers.at(currentFrame).image,
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
VkImageCopy imageCopy{};
imageCopy.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
imageCopy.srcSubresource.layerCount = 1;
imageCopy.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
imageCopy.dstSubresource.layerCount = 1;
imageCopy.extent = {
swapChainExtent.width,
swapChainExtent.height,
1
};
VkImageBlit blit{};
blit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
blit.srcSubresource.layerCount = 1;
blit.srcOffsets[1] = {
static_cast<int>(swapChainExtent.width),
static_cast<int>(swapChainExtent.height),
1
};
blit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
blit.dstSubresource.layerCount = 1;
blit.dstOffsets[1] = {
static_cast<int>(swapChainExtent.width),
static_cast<int>(swapChainExtent.height),
1
};
vkCmdBlitImage(
commandBuffers.at(currentFrame),
swapChainImages.at(imageIndex), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
screenshotReadbackBuffers.at(currentFrame).image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
1, &blit,
VK_FILTER_NEAREST);
Vulkan::cmdTransitionImageLayout(
commandBuffers.at(currentFrame),
swapChainImages.at(imageIndex),
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
Vulkan::cmdTransitionImageLayout(
commandBuffers.at(currentFrame),
screenshotReadbackBuffers.at(currentFrame).image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
VkBufferImageCopy region{};
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
region.imageSubresource.layerCount = 1;
region.imageExtent = {
swapChainExtent.width,
swapChainExtent.height,
1
};
vkCmdCopyImageToBuffer(
commandBuffers.at(currentFrame),
screenshotReadbackBuffers.at(currentFrame).image,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
screenshotReadbackBuffers.at(currentFrame).buffer,
1, &region);
addReadbackCallback([
w = swapChainExtent.width,
h = swapChainExtent.height,
pendingScreenshotCallbacks = pendingScreenshotCallbacks,
screenShotReadbackBuffer = screenshotReadbackBuffers.at(currentFrame),
screenshotCallbackData = screenshotCallbackData]() {
auto imageModule = Module::getInstance<love::image::Image>(M_IMAGE);
for (const auto &info : pendingScreenshotCallbacks)
{
image::ImageData *img = imageModule->newImageData(
w,
h,
PIXELFORMAT_RGBA8_UNORM,
screenShotReadbackBuffer.allocationInfo.pMappedData);
info.callback(&info, img, screenshotCallbackData);
img->release();
}
});
pendingScreenshotCallbacks.clear();
}
}
endRecordingGraphicsCommands(present);
if (imagesInFlight[imageIndex] != VK_NULL_HANDLE)
@@ -358,6 +469,7 @@ bool Graphics::setMode(void *context, int width, int height, int pixelwidth, int
initCapabilities();
createSwapChain();
createImageViews();
createScreenshotCallbackBuffers();
createSyncObjects();
createColorResources();
createDepthResources();
@@ -955,13 +1067,6 @@ void Graphics::endRecordingGraphicsCommands(bool present) {
if (renderPassState.active)
endRenderPass();
if (present)
Vulkan::cmdTransitionImageLayout(
commandBuffers.at(currentFrame),
swapChainImages[imageIndex],
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
if (vkEndCommandBuffer(commandBuffers.at(currentFrame)) != VK_SUCCESS)
throw love::Exception("failed to record command buffer");
}
@@ -1575,7 +1680,7 @@ void Graphics::createSwapChain()
createInfo.imageColorSpace = surfaceFormat.colorSpace;
createInfo.imageExtent = extent;
createInfo.imageArrayLayers = 1;
createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
QueueFamilyIndices indices = findQueueFamilies(physicalDevice);
uint32_t queueFamilyIndices[] = { indices.graphicsFamily.value(), indices.presentFamily.value() };
@@ -1713,6 +1818,65 @@ void Graphics::createImageViews()
}
}
void Graphics::createScreenshotCallbackBuffers()
{
screenshotReadbackBuffers.resize(MAX_FRAMES_IN_FLIGHT);
for (uint32_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++)
{
VkBufferCreateInfo bufferInfo{};
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
bufferInfo.size = 4ll * swapChainExtent.width * swapChainExtent.height;
bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT;
bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
VmaAllocationCreateInfo allocCreateInfo{};
allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO;
allocCreateInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT;
auto result = vmaCreateBuffer(
vmaAllocator,
&bufferInfo,
&allocCreateInfo,
&screenshotReadbackBuffers.at(i).buffer,
&screenshotReadbackBuffers.at(i).allocation,
&screenshotReadbackBuffers.at(i).allocationInfo);
if (result != VK_SUCCESS)
throw love::Exception("failed to create screenshot readback buffer");
VkImageCreateInfo imageInfo{};
imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
imageInfo.imageType = VK_IMAGE_TYPE_2D;
imageInfo.format = VK_FORMAT_R8G8B8A8_SRGB;
imageInfo.extent = {
swapChainExtent.width,
swapChainExtent.height,
1
};
imageInfo.mipLevels = 1;
imageInfo.arrayLayers = 1;
imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
VmaAllocationCreateInfo imageAllocCreateInfo{};
result = vmaCreateImage(
vmaAllocator,
&imageInfo,
&imageAllocCreateInfo,
&screenshotReadbackBuffers.at(i).image,
&screenshotReadbackBuffers.at(i).imageAllocation,
nullptr);
if (result != VK_SUCCESS)
throw love::Exception("failed to create screenshot readback image");
}
}
void Graphics::createDefaultRenderPass()
{
RenderPassConfiguration renderPassConfiguration{};
@@ -1890,7 +2054,7 @@ VkRenderPass Graphics::createRenderPass(RenderPassConfiguration &configuration)
VkSubpassDependency readbackDependency{};
readbackDependency.srcSubpass = 0;
readbackDependency.dstSubpass = VK_SUBPASS_EXTERNAL;
readbackDependency.srcStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
readbackDependency.srcStageMask = VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
readbackDependency.srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
readbackDependency.dstStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT;
readbackDependency.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
@@ -2092,7 +2256,6 @@ void Graphics::setRenderPass(const RenderTargets &rts, int pixelw, int pixelh, b
auto currentCommandBuffer = commandBuffers.at(currentFrame);
// fixme: hasSRGBtexture
// fixme: msaaSamples
RenderPassConfiguration renderPassConfiguration{};
for (const auto& color : rts.colors)
renderPassConfiguration.colorAttachments.push_back({
@@ -2650,6 +2813,11 @@ void Graphics::cleanup()
void Graphics::cleanupSwapChain()
{
for (const auto& readbackBuffer : screenshotReadbackBuffers)
{
vmaDestroyBuffer(vmaAllocator, readbackBuffer.buffer, readbackBuffer.allocation);
vmaDestroyImage(vmaAllocator, readbackBuffer.image, readbackBuffer.imageAllocation);
}
for (const auto &framebuffer : defaultFramebuffers)
vkDestroyFramebuffer(device, framebuffer, nullptr);
vkDestroyRenderPass(device, defaultRenderPass, nullptr);
@@ -2674,6 +2842,7 @@ void Graphics::recreateSwapChain()
createSwapChain();
createImageViews();
createScreenshotCallbackBuffers();
createColorResources();
createDepthResources();
createDefaultRenderPass();
+13 -1
View File
@@ -231,6 +231,16 @@ struct RenderpassState
VkSampleCountFlagBits msaa = VK_SAMPLE_COUNT_1_BIT;
};
struct ScreenshotReadbackBuffer
{
VkBuffer buffer;
VmaAllocation allocation;
VmaAllocationInfo allocationInfo;
VkImage image;
VmaAllocation imageAllocation;
};
class Graphics final : public love::graphics::Graphics
{
public:
@@ -282,7 +292,7 @@ public:
VkCommandBuffer getCommandBufferForDataTransfer();
void queueCleanUp(std::function<void()> cleanUp);
void addReadbackCallback(std::function<void()> callback);
void submitGpuCommands(bool present);
void submitGpuCommands(bool present, void *screenshotCallbackData = nullptr);
uint32_t getNumImagesInFlight() const;
uint32_t getFrameIndex() const;
const VkDeviceSize getMinUniformBufferOffsetAlignment() const;
@@ -320,6 +330,7 @@ private:
VkCompositeAlphaFlagBitsKHR chooseCompositeAlpha(const VkSurfaceCapabilitiesKHR &capabilities);
void createSwapChain();
void createImageViews();
void createScreenshotCallbackBuffers();
void createDefaultRenderPass();
void createDefaultFramebuffers();
VkFramebuffer createFramebuffer(FramebufferConfiguration &configuration);
@@ -411,6 +422,7 @@ private:
// just like batchedDrawBuffers we need a vector for each frame in flight.
std::vector<std::vector<std::function<void()>>> cleanUpFunctions;
std::vector<std::vector<std::function<void()>>> readbackCallbacks;
std::vector<ScreenshotReadbackBuffer> screenshotReadbackBuffers;
std::set<Shader*> usedShadersInFrame;
RenderpassState renderPassState;
};
+48
View File
@@ -822,6 +822,54 @@ void Vulkan::cmdTransitionImageLayout(VkCommandBuffer commandBuffer, VkImage ima
sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
destinationStage = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
}
else if (oldLayout == VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL)
{
barrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
sourceStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
}
else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_PRESENT_SRC_KHR)
{
barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
barrier.dstAccessMask = 0;
sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
destinationStage = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
}
else if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL)
{
barrier.srcAccessMask = 0;
barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
}
else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL)
{
barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
}
else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL)
{
barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
}
else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_UNDEFINED)
{
barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
barrier.dstAccessMask = 0;
sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
destinationStage = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
}
else
throw std::invalid_argument("unsupported layout transition!");