From d1da47dbf0d0475a50cd706f4e28d30415524df1 Mon Sep 17 00:00:00 2001 From: niki Date: Wed, 14 Sep 2022 17:31:53 +0200 Subject: [PATCH] vulkan: implement screenshots --- src/modules/graphics/vulkan/Graphics.cpp | 191 +++++++++++++++++++++-- src/modules/graphics/vulkan/Graphics.h | 14 +- src/modules/graphics/vulkan/Vulkan.cpp | 48 ++++++ 3 files changed, 241 insertions(+), 12 deletions(-) diff --git a/src/modules/graphics/vulkan/Graphics.cpp b/src/modules/graphics/vulkan/Graphics.cpp index 4d699f16b..0f79eab11 100644 --- a/src/modules/graphics/vulkan/Graphics.cpp +++ b/src/modules/graphics/vulkan/Graphics.cpp @@ -226,10 +226,121 @@ void Graphics::discard(const std::vector& 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(swapChainExtent.width), + static_cast(swapChainExtent.height), + 1 + }; + blit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + blit.dstSubresource.layerCount = 1; + blit.dstOffsets[1] = { + static_cast(swapChainExtent.width), + static_cast(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, ®ion); + + addReadbackCallback([ + w = swapChainExtent.width, + h = swapChainExtent.height, + pendingScreenshotCallbacks = pendingScreenshotCallbacks, + screenShotReadbackBuffer = screenshotReadbackBuffers.at(currentFrame), + screenshotCallbackData = screenshotCallbackData]() { + auto imageModule = Module::getInstance(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(); diff --git a/src/modules/graphics/vulkan/Graphics.h b/src/modules/graphics/vulkan/Graphics.h index d6fafd34e..936205a84 100644 --- a/src/modules/graphics/vulkan/Graphics.h +++ b/src/modules/graphics/vulkan/Graphics.h @@ -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 cleanUp); void addReadbackCallback(std::function 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>> cleanUpFunctions; std::vector>> readbackCallbacks; + std::vector screenshotReadbackBuffers; std::set usedShadersInFrame; RenderpassState renderPassState; }; diff --git a/src/modules/graphics/vulkan/Vulkan.cpp b/src/modules/graphics/vulkan/Vulkan.cpp index 624e095c4..d080fdcb4 100644 --- a/src/modules/graphics/vulkan/Vulkan.cpp +++ b/src/modules/graphics/vulkan/Vulkan.cpp @@ -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!");