From 10792cc8e7c1ddf550f63e8cf09ed5dc02aaef7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 15 Nov 2017 13:18:29 +0100 Subject: [PATCH] Windows/Vulkan: implement screenshots. Something is iffy with the red/blue swap though... --- Common/Vulkan/VulkanContext.cpp | 5 ++ GPU/Vulkan/FramebufferVulkan.cpp | 12 ++++- GPU/Vulkan/FramebufferVulkan.h | 3 ++ ext/native/thin3d/DataFormat.h | 1 + ext/native/thin3d/VulkanQueueRunner.cpp | 66 +++++++++++++++++------ ext/native/thin3d/VulkanQueueRunner.h | 4 +- ext/native/thin3d/VulkanRenderManager.cpp | 24 +++++++-- ext/native/thin3d/VulkanRenderManager.h | 2 +- ext/native/thin3d/thin3d.cpp | 25 +++++++++ ext/native/thin3d/thin3d_vulkan.cpp | 3 +- 10 files changed, 118 insertions(+), 27 deletions(-) diff --git a/Common/Vulkan/VulkanContext.cpp b/Common/Vulkan/VulkanContext.cpp index 31abd4215e..cc774f4b91 100644 --- a/Common/Vulkan/VulkanContext.cpp +++ b/Common/Vulkan/VulkanContext.cpp @@ -800,6 +800,11 @@ bool VulkanContext::InitSwapchain() { swap_chain_info.oldSwapchain = VK_NULL_HANDLE; swap_chain_info.clipped = true; swap_chain_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; +#ifndef ANDROID + // We don't support screenshots on Android + swap_chain_info.imageUsage = swap_chain_info.imageUsage | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; +#endif + swap_chain_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; swap_chain_info.queueFamilyIndexCount = 0; swap_chain_info.pQueueFamilyIndices = NULL; diff --git a/GPU/Vulkan/FramebufferVulkan.cpp b/GPU/Vulkan/FramebufferVulkan.cpp index 1684e70638..f5ab503601 100644 --- a/GPU/Vulkan/FramebufferVulkan.cpp +++ b/GPU/Vulkan/FramebufferVulkan.cpp @@ -699,4 +699,14 @@ void FramebufferManagerVulkan::CompilePostShader() { usePostShader_ = true; -} \ No newline at end of file +} + +bool FramebufferManagerVulkan::GetOutputFramebuffer(GPUDebugBuffer &buffer) { + int w, h; + draw_->GetFramebufferDimensions(nullptr, &w, &h); + // I'm really not sure why I have to pass the wrong format here, it seems all the other color conversion are correct. + // But I get R/B swapped if I do what seems right... Maybe driver bug for copies from the backbuffer, who knows. + buffer.Allocate(w, h, GPU_DBG_FORMAT_8888_BGRA, false); + draw_->CopyFramebufferToMemorySync(nullptr, Draw::FB_COLOR_BIT, 0, 0, w, h, Draw::DataFormat::R8G8B8A8_UNORM, buffer.GetData(), w); + return true; +} diff --git a/GPU/Vulkan/FramebufferVulkan.h b/GPU/Vulkan/FramebufferVulkan.h index 49538dfa99..5979201536 100644 --- a/GPU/Vulkan/FramebufferVulkan.h +++ b/GPU/Vulkan/FramebufferVulkan.h @@ -97,6 +97,9 @@ protected: bool CreateDownloadTempBuffer(VirtualFramebuffer *nvfb) override; void UpdateDownloadTempBuffer(VirtualFramebuffer *nvfb) override; + // Small tweak to the Common one. + bool GetOutputFramebuffer(GPUDebugBuffer &buffer); + private: // The returned texture does not need to be free'd, might be returned from a pool (currently single entry) void MakePixelTexture(const u8 *srcPixels, GEBufferFormat srcPixelFormat, int srcStride, int width, int height, float &u1, float &v1) override; diff --git a/ext/native/thin3d/DataFormat.h b/ext/native/thin3d/DataFormat.h index 831b34d626..22cd4d3c07 100644 --- a/ext/native/thin3d/DataFormat.h +++ b/ext/native/thin3d/DataFormat.h @@ -73,6 +73,7 @@ inline bool DataFormatIsColor(DataFormat fmt) { } void ConvertFromRGBA8888(uint8_t *dst, const uint8_t *src, uint32_t dstStride, uint32_t srcStride, uint32_t width, uint32_t height, DataFormat format); +void ConvertFromBGRA8888(uint8_t *dst, const uint8_t *src, uint32_t dstStride, uint32_t srcStride, uint32_t width, uint32_t height, DataFormat format); void ConvertToD32F(uint8_t *dst, const uint8_t *src, uint32_t dstStride, uint32_t srcStride, uint32_t width, uint32_t height, DataFormat format); } // namespace diff --git a/ext/native/thin3d/VulkanQueueRunner.cpp b/ext/native/thin3d/VulkanQueueRunner.cpp index a2e56e483d..29170b8004 100644 --- a/ext/native/thin3d/VulkanQueueRunner.cpp +++ b/ext/native/thin3d/VulkanQueueRunner.cpp @@ -837,22 +837,6 @@ void VulkanQueueRunner::SetupTransitionToTransferDst(VKRImage &img, VkImageMemor } void VulkanQueueRunner::PerformReadback(const VKRStep &step, VkCommandBuffer cmd) { - VKRImage *srcImage; - if (step.readback.aspectMask & VK_IMAGE_ASPECT_COLOR_BIT) { - srcImage = &step.readback.src->color; - } else if (step.readback.aspectMask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) { - srcImage = &step.readback.src->depth; - } else { - assert(false); - } - - VkImageMemoryBarrier barrier{ VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER }; - VkPipelineStageFlags stage = 0; - if (srcImage->layout != VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL) { - SetupTransitionToTransferSrc(*srcImage, barrier, stage, step.readback.aspectMask); - vkCmdPipelineBarrier(cmd, stage, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier); - } - ResizeReadbackBuffer(sizeof(uint32_t) * step.readback.srcRect.extent.width * step.readback.srcRect.extent.height); VkBufferImageCopy region{}; @@ -863,9 +847,55 @@ void VulkanQueueRunner::PerformReadback(const VKRStep &step, VkCommandBuffer cmd region.bufferOffset = 0; region.bufferRowLength = step.readback.srcRect.extent.width; region.bufferImageHeight = step.readback.srcRect.extent.height; - vkCmdCopyImageToBuffer(cmd, srcImage->image, srcImage->layout, readbackBuffer_, 1, ®ion); + + VkImage image; + VkImageLayout copyLayout; + // Special case for backbuffer readbacks. + if (step.readback.src == nullptr) { + // We only take screenshots after the main render pass (anything else would be stupid) so we need to transition out of PRESENT, + // and then back into it. + TransitionImageLayout2(cmd, backbufferImage_, VK_IMAGE_ASPECT_COLOR_BIT, + VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, VK_ACCESS_TRANSFER_READ_BIT); + copyLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + image = backbufferImage_; + } else { + VKRImage *srcImage; + if (step.readback.aspectMask & VK_IMAGE_ASPECT_COLOR_BIT) { + srcImage = &step.readback.src->color; + } + else if (step.readback.aspectMask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) { + srcImage = &step.readback.src->depth; + } + else { + assert(false); + } + + VkImageMemoryBarrier barrier{ VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER }; + VkPipelineStageFlags stage = 0; + if (srcImage->layout != VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL) { + SetupTransitionToTransferSrc(*srcImage, barrier, stage, step.readback.aspectMask); + vkCmdPipelineBarrier(cmd, stage, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier); + } + image = srcImage->image; + copyLayout = srcImage->layout; + } + + vkCmdCopyImageToBuffer(cmd, image, copyLayout, readbackBuffer_, 1, ®ion); // NOTE: Can't read the buffer using the CPU here - need to sync first. + + // If we copied from the backbuffer, transition it back. + if (step.readback.src == nullptr) { + // We only take screenshots after the main render pass (anything else would be stupid) so we need to transition out of PRESENT, + // and then back into it. + TransitionImageLayout2(cmd, backbufferImage_, VK_IMAGE_ASPECT_COLOR_BIT, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + VK_ACCESS_TRANSFER_READ_BIT, 0); + copyLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + } } void VulkanQueueRunner::PerformReadbackImage(const VKRStep &step, VkCommandBuffer cmd) { @@ -911,6 +941,8 @@ void VulkanQueueRunner::CopyReadbackBuffer(int width, int height, Draw::DataForm assert(res == VK_SUCCESS); if (srcFormat == Draw::DataFormat::R8G8B8A8_UNORM) { ConvertFromRGBA8888(pixels, (const uint8_t *)mappedData, pixelStride, width, width, height, destFormat); + } else if (srcFormat == Draw::DataFormat::B8G8R8A8_UNORM) { + ConvertFromBGRA8888(pixels, (const uint8_t *)mappedData, pixelStride, width, width, height, destFormat); } else if (srcFormat == destFormat) { uint8_t *dst = pixels; const uint8_t *src = (const uint8_t *)mappedData; diff --git a/ext/native/thin3d/VulkanQueueRunner.h b/ext/native/thin3d/VulkanQueueRunner.h index c49bb207ff..f2bafcff40 100644 --- a/ext/native/thin3d/VulkanQueueRunner.h +++ b/ext/native/thin3d/VulkanQueueRunner.h @@ -145,8 +145,9 @@ struct VKRStep { class VulkanQueueRunner { public: VulkanQueueRunner(VulkanContext *vulkan) : vulkan_(vulkan) {} - void SetBackbuffer(VkFramebuffer fb) { + void SetBackbuffer(VkFramebuffer fb, VkImage img) { backbuffer_ = fb; + backbufferImage_ = img; } void RunSteps(VkCommandBuffer cmd, const std::vector &steps); void LogSteps(const std::vector &steps); @@ -192,6 +193,7 @@ private: VulkanContext *vulkan_; VkFramebuffer backbuffer_; + VkImage backbufferImage_; VkFramebuffer curFramebuffer_ = VK_NULL_HANDLE; VkRenderPass backbufferRenderPass_ = VK_NULL_HANDLE; diff --git a/ext/native/thin3d/VulkanRenderManager.cpp b/ext/native/thin3d/VulkanRenderManager.cpp index 36ca452f21..a6182e48b6 100644 --- a/ext/native/thin3d/VulkanRenderManager.cpp +++ b/ext/native/thin3d/VulkanRenderManager.cpp @@ -413,7 +413,7 @@ void VulkanRenderManager::BindFramebufferAsRenderTarget(VKRFramebuffer *fb, VKRR curHeight_ = fb ? fb->height : vulkan_->GetBackbufferHeight(); } -void VulkanRenderManager::CopyFramebufferToMemorySync(VKRFramebuffer *src, int aspectBits, int x, int y, int w, int h, Draw::DataFormat destFormat, uint8_t *pixels, int pixelStride) { +bool VulkanRenderManager::CopyFramebufferToMemorySync(VKRFramebuffer *src, int aspectBits, int x, int y, int w, int h, Draw::DataFormat destFormat, uint8_t *pixels, int pixelStride) { VKRStep *step = new VKRStep{ VKRStepType::READBACK }; step->readback.aspectMask = aspectBits; step->readback.src = src; @@ -427,9 +427,22 @@ void VulkanRenderManager::CopyFramebufferToMemorySync(VKRFramebuffer *src, int a Draw::DataFormat srcFormat; if (aspectBits & VK_IMAGE_ASPECT_COLOR_BIT) { - switch (src->color.format) { - case VK_FORMAT_R8G8B8A8_UNORM: srcFormat = Draw::DataFormat::R8G8B8A8_UNORM; break; - default: assert(false); + if (src) { + switch (src->color.format) { + case VK_FORMAT_R8G8B8A8_UNORM: srcFormat = Draw::DataFormat::R8G8B8A8_UNORM; break; + default: assert(false); + } + } + else { + // Backbuffer. + switch (vulkan_->GetSwapchainFormat()) { + case VK_FORMAT_B8G8R8A8_UNORM: srcFormat = Draw::DataFormat::B8G8R8A8_UNORM; break; + case VK_FORMAT_R8G8B8A8_UNORM: srcFormat = Draw::DataFormat::R8G8B8A8_UNORM; break; + // NOTE: If you add supported formats here, make sure to also support them in VulkanQueueRunner::CopyReadbackBuffer. + default: + ELOG("Unsupported backbuffer format for screenshots"); + return false; + } } } else if (aspectBits & VK_IMAGE_ASPECT_STENCIL_BIT) { // Copies from stencil are always S8. @@ -446,6 +459,7 @@ void VulkanRenderManager::CopyFramebufferToMemorySync(VKRFramebuffer *src, int a } // Need to call this after FlushSync so the pixels are guaranteed to be ready in CPU-accessible VRAM. queueRunner_.CopyReadbackBuffer(w, h, srcFormat, destFormat, pixelStride, pixels); + return true; } void VulkanRenderManager::CopyImageToMemorySync(VkImage image, int mipLevel, int x, int y, int w, int h, Draw::DataFormat destFormat, uint8_t *pixels, int pixelStride) { @@ -724,7 +738,7 @@ void VulkanRenderManager::BeginSubmitFrame(int frame) { assert(res == VK_SUCCESS); - queueRunner_.SetBackbuffer(framebuffers_[frameData.curSwapchainImage]); + queueRunner_.SetBackbuffer(framebuffers_[frameData.curSwapchainImage], swapchainImages_[frameData.curSwapchainImage].image); frameData.hasBegun = true; } diff --git a/ext/native/thin3d/VulkanRenderManager.h b/ext/native/thin3d/VulkanRenderManager.h index 0f9995771c..6a7f4ffbec 100644 --- a/ext/native/thin3d/VulkanRenderManager.h +++ b/ext/native/thin3d/VulkanRenderManager.h @@ -94,7 +94,7 @@ public: void BindFramebufferAsRenderTarget(VKRFramebuffer *fb, VKRRenderPassAction color, VKRRenderPassAction depth, uint32_t clearColor, float clearDepth, uint8_t clearStencil); VkImageView BindFramebufferAsTexture(VKRFramebuffer *fb, int binding, int aspectBit, int attachment); - void CopyFramebufferToMemorySync(VKRFramebuffer *src, int aspectBits, int x, int y, int w, int h, Draw::DataFormat destFormat, uint8_t *pixels, int pixelStride); + bool CopyFramebufferToMemorySync(VKRFramebuffer *src, int aspectBits, int x, int y, int w, int h, Draw::DataFormat destFormat, uint8_t *pixels, int pixelStride); void CopyImageToMemorySync(VkImage image, int mipLevel, int x, int y, int w, int h, Draw::DataFormat destFormat, uint8_t *pixels, int pixelStride); void CopyFramebuffer(VKRFramebuffer *src, VkRect2D srcRect, VKRFramebuffer *dst, VkOffset2D dstPos, int aspectMask); diff --git a/ext/native/thin3d/thin3d.cpp b/ext/native/thin3d/thin3d.cpp index 70a7fb0feb..fa21453ded 100644 --- a/ext/native/thin3d/thin3d.cpp +++ b/ext/native/thin3d/thin3d.cpp @@ -381,6 +381,31 @@ void ConvertFromRGBA8888(uint8_t *dst, const uint8_t *src, uint32_t dstStride, u } } +// TODO: SSE/NEON +// Could also make C fake-simd for 64-bit, two 8888 pixels fit in a register :) +void ConvertFromBGRA8888(uint8_t *dst, const uint8_t *src, uint32_t dstStride, uint32_t srcStride, uint32_t width, uint32_t height, DataFormat format) { + // Must skip stride in the cases below. Some games pack data into the cracks, like MotoGP. + const uint32_t *src32 = (const uint32_t *)src; + + if (format == Draw::DataFormat::R8G8B8A8_UNORM) { + uint32_t *dst32 = (uint32_t *)dst; + if (src == dst) { + return; + } + else { + for (uint32_t y = 0; y < height; ++y) { + ConvertBGRA8888ToRGBA8888(dst32, src32, width); + memcpy(dst32, src32, width * 4); + src32 += srcStride; + dst32 += dstStride; + } + } + } + else { + // Don't even bother with these, this path only happens in screenshots and we don't save those to 16-bit. + assert(false); + } +} void ConvertToD32F(uint8_t *dst, const uint8_t *src, uint32_t dstStride, uint32_t srcStride, uint32_t width, uint32_t height, DataFormat format) { if (format == Draw::DataFormat::D32F) { const float *src32 = (const float *)src; diff --git a/ext/native/thin3d/thin3d_vulkan.cpp b/ext/native/thin3d/thin3d_vulkan.cpp index fcb7b73cf8..504f900275 100644 --- a/ext/native/thin3d/thin3d_vulkan.cpp +++ b/ext/native/thin3d/thin3d_vulkan.cpp @@ -1337,8 +1337,7 @@ bool VKContext::CopyFramebufferToMemorySync(Framebuffer *srcfb, int channelBits, if (channelBits & FBChannel::FB_DEPTH_BIT) aspectMask |= VK_IMAGE_ASPECT_DEPTH_BIT; if (channelBits & FBChannel::FB_STENCIL_BIT) aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT; - renderManager_.CopyFramebufferToMemorySync(src->GetFB(), aspectMask, x, y, w, h, format, (uint8_t *)pixels, pixelStride); - return true; + return renderManager_.CopyFramebufferToMemorySync(src ? src->GetFB() : nullptr, aspectMask, x, y, w, h, format, (uint8_t *)pixels, pixelStride); } void VKContext::BindFramebufferAsRenderTarget(Framebuffer *fbo, const RenderPassInfo &rp) {