mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-19 17:29:06 +02:00
Windows/Vulkan: implement screenshots. Something is iffy with the red/blue swap though...
This commit is contained in:
1 parent
3594623a8d
commit
10792cc8e7
10 files changed
+118
-27
No files matched your search
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in new issue
Block a user