Windows/Vulkan: implement screenshots. Something is iffy with the red/blue swap though...

This commit is contained in:
Henrik Rydgård committed 2017-11-15 13:18:29 +01:00
1 parent 3594623a8d
commit 10792cc8e7
10 files changed
+118 -27

No files matched your search

+5
View File
@@ -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;
+11 -1
View File
@@ -699,4 +699,14 @@ void FramebufferManagerVulkan::CompilePostShader() {
usePostShader_ = true;
}
}
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;
}
+3
View File
@@ -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;
+1
View File
@@ -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
+49 -17
View File
@@ -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, &region);
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, &region);
// 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;
+3 -1
View File
@@ -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<VKRStep *> &steps);
void LogSteps(const std::vector<VKRStep *> &steps);
@@ -192,6 +193,7 @@ private:
VulkanContext *vulkan_;
VkFramebuffer backbuffer_;
VkImage backbufferImage_;
VkFramebuffer curFramebuffer_ = VK_NULL_HANDLE;
VkRenderPass backbufferRenderPass_ = VK_NULL_HANDLE;
+19 -5
View File
@@ -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;
}
+1 -1
View File
@@ -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);
+25
View File
@@ -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;
+1 -2
View File
@@ -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) {