From 717ec2387b39293063324cfa7b025819af19104f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 28 Oct 2017 16:47:08 +0200 Subject: [PATCH 1/7] VulkanRenderManager: Split out BeginFrame and EndFrame from Run --- ext/native/thin3d/VulkanRenderManager.cpp | 71 +++++++++++++---------- ext/native/thin3d/VulkanRenderManager.h | 12 +++- ext/native/thin3d/thin3d_vulkan.cpp | 2 +- 3 files changed, 52 insertions(+), 33 deletions(-) diff --git a/ext/native/thin3d/VulkanRenderManager.cpp b/ext/native/thin3d/VulkanRenderManager.cpp index 77b9f5f473..6c9c18ffc1 100644 --- a/ext/native/thin3d/VulkanRenderManager.cpp +++ b/ext/native/thin3d/VulkanRenderManager.cpp @@ -509,7 +509,7 @@ VkImageView VulkanRenderManager::BindFramebufferAsTexture(VKRFramebuffer *fb, in return fb->color.imageView; } -void VulkanRenderManager::Flush() { +void VulkanRenderManager::Finish() { curRenderStep_ = nullptr; int curFrame = vulkan_->GetCurFrame(); FrameData &frameData = frameData_[curFrame]; @@ -529,46 +529,42 @@ void VulkanRenderManager::Flush() { vulkan_->EndFrame(); } -void VulkanRenderManager::Run(int frame) { +// Can be called multiple times with no bad side effects. This is so that we can either begin a frame the normal way, +void VulkanRenderManager::BeginFrame(int frame) { FrameData &frameData = frameData_[frame]; - auto &stepsOnThread = frameData_[frame].steps; - VkDevice device = vulkan_->GetDevice(); + if (!frameData.hasBegun) { + // Get the index of the next available swapchain image, and a semaphore to block command buffer execution on. + // Now, I wonder if we should do this early in the frame or late? Right now we do it early, which should be fine. + VkResult res = vkAcquireNextImageKHR(vulkan_->GetDevice(), vulkan_->GetSwapchain(), UINT64_MAX, acquireSemaphore_, (VkFence)VK_NULL_HANDLE, &frameData.curSwapchainImage); + assert(res == VK_SUCCESS); + // TODO: Deal with the VK_SUBOPTIMAL_KHR and VK_ERROR_OUT_OF_DATE_KHR + // return codes - uint32_t curSwapchainImage = 0; - // Get the index of the next available swapchain image, and a semaphore to block command buffer execution on. - // Now, I wonder if we should do this early in the frame or late? Right now we do it early, which should be fine. - VkResult res = vkAcquireNextImageKHR(device, vulkan_->GetSwapchain(), UINT64_MAX, acquireSemaphore_, (VkFence)VK_NULL_HANDLE, &curSwapchainImage); - assert(res == VK_SUCCESS); + VkCommandBufferBeginInfo begin{ VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }; + begin.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + res = vkBeginCommandBuffer(frameData.mainCmd, &begin); - VkCommandBuffer cmd = frameData.mainCmd; + assert(res == VK_SUCCESS); - VkCommandBufferBeginInfo begin{ VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }; - begin.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; - res = vkBeginCommandBuffer(cmd, &begin); + // TODO: Is it best to do this here, or combine with some other transition, or just do it right before the backbuffer bind-for-render? + TransitionFromPresent(frameData.mainCmd, swapchainImages_[frameData.curSwapchainImage].image); - assert(res == VK_SUCCESS); + queueRunner_.SetBackbuffer(framebuffers_[frameData.curSwapchainImage]); - // TODO: Deal with the VK_SUBOPTIMAL_KHR and VK_ERROR_OUT_OF_DATE_KHR - // return codes - // TODO: Is it best to do this here, or combine with some other transition, or just do it right before the backbuffer bind-for-render? - assert(res == VK_SUCCESS); - TransitionFromPresent(cmd, swapchainImages_[curSwapchainImage].image); - - queueRunner_.SetBackbuffer(framebuffers_[curSwapchainImage]); - queueRunner_.RunSteps(cmd, stepsOnThread); - stepsOnThread.clear(); + frameData.hasBegun = true; + } +} +void VulkanRenderManager::EndFrame(int frame) { + FrameData &frameData = frameData_[frame]; + frameData.hasBegun = false; insideFrame_ = false; - TransitionToPresent(frameData.mainCmd, swapchainImages_[curSwapchainImage].image); + TransitionToPresent(frameData.mainCmd, swapchainImages_[frameData.curSwapchainImage].image); - res = vkEndCommandBuffer(frameData.mainCmd); + VkResult res = vkEndCommandBuffer(frameData.mainCmd); assert(res == VK_SUCCESS); - // So the sequence will be, cmdInit, [cmdQueue_], frame->cmdBuf. - // This way we bunch up all the initialization needed for the frame, we render to - // other buffers before the back buffer, and then last we render to the backbuffer. - int numCmdBufs = 0; std::vector cmdBufs; if (frameData.hasInitCommands) { @@ -602,7 +598,7 @@ void VulkanRenderManager::Run(int frame) { VkPresentInfoKHR present = { VK_STRUCTURE_TYPE_PRESENT_INFO_KHR }; present.swapchainCount = 1; present.pSwapchains = &swapchain; - present.pImageIndices = &curSwapchainImage; + present.pImageIndices = &frameData.curSwapchainImage; present.pWaitSemaphores = &renderingCompleteSemaphore_; present.waitSemaphoreCount = 1; present.pResults = nullptr; @@ -614,6 +610,21 @@ void VulkanRenderManager::Run(int frame) { } else { assert(res == VK_SUCCESS); } +} + +void VulkanRenderManager::Run(int frame) { + VkDevice device = vulkan_->GetDevice(); + + BeginFrame(frame); + + FrameData &frameData = frameData_[frame]; + auto &stepsOnThread = frameData_[frame].steps; + VkCommandBuffer cmd = frameData.mainCmd; + queueRunner_.RunSteps(cmd, stepsOnThread); + stepsOnThread.clear(); + + EndFrame(frame); + VLOG("PULL: Finished running frame %d", frame); } diff --git a/ext/native/thin3d/VulkanRenderManager.h b/ext/native/thin3d/VulkanRenderManager.h index f0c1a06c04..85c04264db 100644 --- a/ext/native/thin3d/VulkanRenderManager.h +++ b/ext/native/thin3d/VulkanRenderManager.h @@ -78,12 +78,13 @@ public: // Makes sure that the GPU has caught up enough that we can start writing buffers of this frame again. void BeginFrame(); + void EndFrame(); // Can run on a different thread! Just make sure to use BeginFrameWrites. - void Flush(); + void Finish(); void Run(int frame); + // Bad for performance but sometimes necessary for synchronous CPU readbacks (screenshots and whatnot). void Sync(); - void RunSteps(VkCommandBuffer cmd, const std::vector &steps, int curSwapChainImage); 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); @@ -186,6 +187,9 @@ public: private: void InitBackbufferFramebuffers(int width, int height); void InitDepthStencilBuffer(VkCommandBuffer cmd); // Used for non-buffered rendering. + void BeginFrame(int frame); + void EndFrame(int frame); + // Permanent objects VkSemaphore acquireSemaphore_; VkSemaphore renderingCompleteSemaphore_; @@ -209,6 +213,10 @@ private: VkCommandBuffer mainCmd; bool hasInitCommands = false; std::vector steps; + + // Swapchain. + bool hasBegun = false; + uint32_t curSwapchainImage; }; FrameData frameData_[VulkanContext::MAX_INFLIGHT_FRAMES]; diff --git a/ext/native/thin3d/thin3d_vulkan.cpp b/ext/native/thin3d/thin3d_vulkan.cpp index b0f0d08a9b..7aba729f65 100644 --- a/ext/native/thin3d/thin3d_vulkan.cpp +++ b/ext/native/thin3d/thin3d_vulkan.cpp @@ -757,7 +757,7 @@ void VKContext::EndFrame() { // Stop collecting data in the frame's data pushbuffer. push_->End(); - renderManager_.Flush(); + renderManager_.Finish(); frameNum_++; if (frameNum_ >= vulkan_->GetInflightFrames()) From b98d4e5c9d8cec48f96189edf364ad3d71c127ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 28 Oct 2017 18:03:27 +0200 Subject: [PATCH 2/7] Vulkan: Rough untested implementation of synchronous readbacks. --- Windows/GEDebugger/GEDebugger.cpp | 3 +- Windows/GEDebugger/GEDebugger.h | 36 ++++----- ext/native/thin3d/VulkanQueueRunner.cpp | 66 ++++++++++++++++- ext/native/thin3d/VulkanQueueRunner.h | 14 +++- ext/native/thin3d/VulkanRenderManager.cpp | 89 ++++++++++++++++++----- ext/native/thin3d/VulkanRenderManager.h | 15 ++-- ext/native/thin3d/thin3d_vulkan.cpp | 14 ++++ 7 files changed, 187 insertions(+), 50 deletions(-) diff --git a/Windows/GEDebugger/GEDebugger.cpp b/Windows/GEDebugger/GEDebugger.cpp index 3e0a2cd5af..cb68ae915a 100644 --- a/Windows/GEDebugger/GEDebugger.cpp +++ b/Windows/GEDebugger/GEDebugger.cpp @@ -61,8 +61,7 @@ void CGEDebugger::Init() { } CGEDebugger::CGEDebugger(HINSTANCE _hInstance, HWND _hParent) - : Dialog((LPCSTR)IDD_GEDEBUGGER, _hInstance, _hParent), primaryWindow(nullptr), secondWindow(nullptr), - textureLevel_(0), showClut_(false), primaryBuffer_(nullptr), secondBuffer_(nullptr) { + : Dialog((LPCSTR)IDD_GEDEBUGGER, _hInstance, _hParent) { GPUBreakpoints::Init(); Core_ListenShutdown(ForceUnpause); diff --git a/Windows/GEDebugger/GEDebugger.h b/Windows/GEDebugger/GEDebugger.h index 10ca029945..f4f6aa3db5 100644 --- a/Windows/GEDebugger/GEDebugger.h +++ b/Windows/GEDebugger/GEDebugger.h @@ -85,25 +85,25 @@ private: u32 TexturePreviewFlags(const GPUgstate &state); - CtrlDisplayListView *displayList; - TabDisplayLists *lists; - TabStateFlags *flags; - TabStateLighting *lighting; - TabStateTexture *textureState; - TabStateSettings *settings; - TabVertices *vertices; - TabMatrices *matrices; - SimpleGLWindow *primaryWindow; - SimpleGLWindow *secondWindow; - TabStateWatch *watch; - TabControl *tabs; - TabControl *fbTabs; - int textureLevel_; - bool showClut_; - bool forceOpaque_; + CtrlDisplayListView *displayList = nullptr; + TabDisplayLists *lists = nullptr; + TabStateFlags *flags = nullptr; + TabStateLighting *lighting = nullptr; + TabStateTexture *textureState = nullptr; + TabStateSettings *settings = nullptr; + TabVertices *vertices = nullptr; + TabMatrices *matrices = nullptr; + SimpleGLWindow *primaryWindow = nullptr; + SimpleGLWindow *secondWindow = nullptr; + TabStateWatch *watch = nullptr; + TabControl *tabs = nullptr; + TabControl *fbTabs = nullptr; + int textureLevel_ = 0; + bool showClut_ = false; + bool forceOpaque_ = false; // The most recent primary/framebuffer and texture buffers. - const GPUDebugBuffer *primaryBuffer_; - const GPUDebugBuffer *secondBuffer_; + const GPUDebugBuffer *primaryBuffer_ = nullptr; + const GPUDebugBuffer *secondBuffer_ = nullptr; int minWidth_; int minHeight_; diff --git a/ext/native/thin3d/VulkanQueueRunner.cpp b/ext/native/thin3d/VulkanQueueRunner.cpp index c101c563bf..ff70dc1d4e 100644 --- a/ext/native/thin3d/VulkanQueueRunner.cpp +++ b/ext/native/thin3d/VulkanQueueRunner.cpp @@ -1,13 +1,40 @@ #include "VulkanQueueRunner.h" #include "VulkanRenderManager.h" +const uint32_t readbackBufferSize = 2048 * 2048 * 4; + void VulkanQueueRunner::CreateDeviceObjects() { InitBackbufferRenderPass(); InitRenderpasses(); + + VkDevice device = vulkan_->GetDevice(); + + VkBufferCreateInfo buf{ VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + buf.size = readbackBufferSize; + buf.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT; + + vkCreateBuffer(device, &buf, nullptr, &readbackBuffer_); + + VkMemoryRequirements reqs{}; + vkGetBufferMemoryRequirements(device, readbackBuffer_, &reqs); + + VkMemoryAllocateInfo alloc{ VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO }; + alloc.allocationSize = reqs.size; + + VkFlags typeReqs = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + bool success = vulkan_->MemoryTypeFromProperties(reqs.memoryTypeBits, typeReqs, &alloc.memoryTypeIndex); + assert(success); + vkAllocateMemory(device, &alloc, nullptr, &readbackMemory_); + + uint32_t offset = 0; + vkBindBufferMemory(device, readbackBuffer_, readbackMemory_, offset); } void VulkanQueueRunner::DestroyDeviceObjects() { VkDevice device = vulkan_->GetDevice(); + vkFreeMemory(device, readbackMemory_, nullptr); + vulkan_->Delete().QueueDeleteBuffer(readbackBuffer_); + for (int i = 0; i < ARRAY_SIZE(renderPasses_); i++) { assert(renderPasses_[i] != VK_NULL_HANDLE); vkDestroyRenderPass(device, renderPasses_[i], nullptr); @@ -16,7 +43,6 @@ void VulkanQueueRunner::DestroyDeviceObjects() { vkDestroyRenderPass(device, backbufferRenderPass_, nullptr); } - void VulkanQueueRunner::InitBackbufferRenderPass() { VkResult U_ASSERT_ONLY res; @@ -167,7 +193,7 @@ void VulkanQueueRunner::RunSteps(VkCommandBuffer cmd, const std::vectorcolor; + } else if (step.readback.aspectMask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) { + srcImage = &step.readback.src->depth; + } else { + assert(false); + } + + VkBufferImageCopy region{}; + region.imageOffset = { step.readback.srcRect.offset.x, step.readback.srcRect.offset.y, 0 }; + region.imageExtent = { step.readback.srcRect.extent.width, step.readback.srcRect.extent.height, 1 }; + region.imageSubresource.aspectMask = step.readback.aspectMask; + region.bufferOffset = 0; + region.bufferRowLength = 0; + region.bufferImageHeight = step.readback.srcRect.extent.height; + vkCmdCopyImageToBuffer(cmd, srcImage->image, srcImage->layout, readbackBuffer_, 1, ®ion); + + // NOTE: Can't read the buffer using the CPU here - need to sync first. +} + +void VulkanQueueRunner::CopyReadbackBuffer(const VKRStep &step) { + // Read back to the requested address in ram from buffer. + void *mappedData; + VkResult res = vkMapMemory(vulkan_->GetDevice(), readbackMemory_, 0, step.readback.srcRect.extent.width * step.readback.srcRect.extent.height * 4, 0, &mappedData); + assert(res == VK_SUCCESS); + + const int pixelSize = 4; // TODO: Fix. + for (int y = 0; y < step.readback.srcRect.extent.height; y++) { + const uint8_t *src = (const uint8_t *)mappedData + step.readback.srcRect.extent.width * y; + uint8_t *dst = (uint8_t *)step.readback.destPtr + step.readback.pixelStride * pixelSize * y; + } + vkUnmapMemory(vulkan_->GetDevice(), readbackMemory_); +} diff --git a/ext/native/thin3d/VulkanQueueRunner.h b/ext/native/thin3d/VulkanQueueRunner.h index 0b2a38064d..c8d84fd151 100644 --- a/ext/native/thin3d/VulkanQueueRunner.h +++ b/ext/native/thin3d/VulkanQueueRunner.h @@ -127,8 +127,10 @@ struct VKRStep { VkFilter filter; } blit; struct { + int aspectMask; VKRFramebuffer *src; - void *destPtr; + uint8_t *destPtr; + int pixelStride; VkRect2D srcRect; } readback; }; @@ -156,6 +158,8 @@ public: return (int)depth * 3 + (int)color; } + void CopyReadbackBuffer(const VKRStep &step); + private: void InitBackbufferRenderPass(); void InitRenderpasses(); @@ -164,6 +168,7 @@ private: void PerformRenderPass(const VKRStep &pass, VkCommandBuffer cmd); void PerformCopy(const VKRStep &pass, VkCommandBuffer cmd); void PerformBlit(const VKRStep &pass, VkCommandBuffer cmd); + void PerformReadback(const VKRStep &pass, VkCommandBuffer cmd); static void SetupTransitionToTransferSrc(VKRImage &img, VkImageMemoryBarrier &barrier, VkPipelineStageFlags &stage, VkImageAspectFlags aspect); static void SetupTransitionToTransferDst(VKRImage &img, VkImageMemoryBarrier &barrier, VkPipelineStageFlags &stage, VkImageAspectFlags aspect); @@ -177,4 +182,9 @@ private: // Renderpasses, all combinations of preserving or clearing or dont-care-ing fb contents. // TODO: Create these on demand. VkRenderPass renderPasses_[9]{}; -}; \ No newline at end of file + + // Readback buffer. Currently we only support synchronous readback, so we only really need one. + // We size it generously. + VkDeviceMemory readbackMemory_; + VkBuffer readbackBuffer_; +}; diff --git a/ext/native/thin3d/VulkanRenderManager.cpp b/ext/native/thin3d/VulkanRenderManager.cpp index 6c9c18ffc1..17cf3afd3f 100644 --- a/ext/native/thin3d/VulkanRenderManager.cpp +++ b/ext/native/thin3d/VulkanRenderManager.cpp @@ -287,9 +287,11 @@ VkCommandBuffer VulkanRenderManager::GetInitCmd() { int curFrame = vulkan_->GetCurFrame(); FrameData &frameData = frameData_[curFrame]; if (!frameData.hasInitCommands) { - VkCommandBufferBeginInfo begin = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }; - begin.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; - begin.pInheritanceInfo = nullptr; + VkCommandBufferBeginInfo begin = { + VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + nullptr, + VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT + }; VkResult res = vkBeginCommandBuffer(frameData.initCmd, &begin); assert(res == VK_SUCCESS); frameData.hasInitCommands = true; @@ -297,10 +299,6 @@ VkCommandBuffer VulkanRenderManager::GetInitCmd() { return frameData_[curFrame].initCmd; } -void VulkanRenderManager::Sync() { - -} - void VulkanRenderManager::BindFramebufferAsRenderTarget(VKRFramebuffer *fb, VKRRenderPassAction color, VKRRenderPassAction depth, uint32_t clearColor, float clearDepth, uint8_t clearStencil) { // Eliminate dupes. if (steps_.size() && steps_.back()->stepType == VKRStepType::RENDER && steps_.back()->render.framebuffer == fb) { @@ -327,6 +325,25 @@ 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, uint8_t *pixels, int pixelStride) { + VKRStep *step = new VKRStep{ VKRStepType::READBACK }; + step->readback.aspectMask = aspectBits; + step->readback.destPtr = (uint8_t *)pixels; + step->readback.pixelStride = pixelStride; + step->readback.src = src; + step->readback.srcRect.offset = { x, y }; + step->readback.srcRect.extent = { (uint32_t)w, (uint32_t)h }; + steps_.push_back(step); + + curRenderStep_ = nullptr; + + FlushSync(); + + // Need to call this after FlushSync so the pixels are guaranteed to be ready in CPU-accessible VRAM. + queueRunner_.CopyReadbackBuffer(*step); +} + + void VulkanRenderManager::InitBackbufferFramebuffers(int width, int height) { VkResult U_ASSERT_ONLY res; // We share the same depth buffer but have multiple color buffers, see the loop below. @@ -513,9 +530,6 @@ void VulkanRenderManager::Finish() { curRenderStep_ = nullptr; int curFrame = vulkan_->GetCurFrame(); FrameData &frameData = frameData_[curFrame]; - if (frameData.hasInitCommands) { - vkEndCommandBuffer(frameData.initCmd); - } if (!useThread) { frameData.steps = std::move(steps_); Run(curFrame); @@ -530,7 +544,7 @@ void VulkanRenderManager::Finish() { } // Can be called multiple times with no bad side effects. This is so that we can either begin a frame the normal way, -void VulkanRenderManager::BeginFrame(int frame) { +void VulkanRenderManager::BeginSubmitFrame(int frame) { FrameData &frameData = frameData_[frame]; if (!frameData.hasBegun) { // Get the index of the next available swapchain image, and a semaphore to block command buffer execution on. @@ -555,18 +569,19 @@ void VulkanRenderManager::BeginFrame(int frame) { } } -void VulkanRenderManager::EndFrame(int frame) { +void VulkanRenderManager::Submit(int frame) { FrameData &frameData = frameData_[frame]; - frameData.hasBegun = false; - insideFrame_ = false; - - TransitionToPresent(frameData.mainCmd, swapchainImages_[frameData.curSwapchainImage].image); + if (frameData.hasInitCommands) { + VkResult res = vkEndCommandBuffer(frameData.initCmd); + assert(res == VK_SUCCESS); + } VkResult res = vkEndCommandBuffer(frameData.mainCmd); assert(res == VK_SUCCESS); int numCmdBufs = 0; std::vector cmdBufs; + cmdBufs.reserve(2); if (frameData.hasInitCommands) { cmdBufs.push_back(frameData.initCmd); frameData.hasInitCommands = false; @@ -593,6 +608,16 @@ void VulkanRenderManager::EndFrame(int frame) { frameData.readyForFence = true; frameData.push_condVar.notify_all(); } +} + +void VulkanRenderManager::EndSubmitFrame(int frame) { + FrameData &frameData = frameData_[frame]; + frameData.hasBegun = false; + insideFrame_ = false; + + TransitionToPresent(frameData.mainCmd, swapchainImages_[frameData.curSwapchainImage].image); + + Submit(frame); VkSwapchainKHR swapchain = vulkan_->GetSwapchain(); VkPresentInfoKHR present = { VK_STRUCTURE_TYPE_PRESENT_INFO_KHR }; @@ -602,7 +627,8 @@ void VulkanRenderManager::EndFrame(int frame) { present.pWaitSemaphores = &renderingCompleteSemaphore_; present.waitSemaphoreCount = 1; present.pResults = nullptr; - res = vkQueuePresentKHR(vulkan_->GetGraphicsQueue(), &present); + + VkResult res = vkQueuePresentKHR(vulkan_->GetGraphicsQueue(), &present); // TODO: Deal with the VK_SUBOPTIMAL_WSI and VK_ERROR_OUT_OF_DATE_WSI // return codes if (res == VK_ERROR_OUT_OF_DATE_KHR) { @@ -615,7 +641,7 @@ void VulkanRenderManager::EndFrame(int frame) { void VulkanRenderManager::Run(int frame) { VkDevice device = vulkan_->GetDevice(); - BeginFrame(frame); + BeginSubmitFrame(frame); FrameData &frameData = frameData_[frame]; auto &stepsOnThread = frameData_[frame].steps; @@ -623,8 +649,33 @@ void VulkanRenderManager::Run(int frame) { queueRunner_.RunSteps(cmd, stepsOnThread); stepsOnThread.clear(); - EndFrame(frame); + EndSubmitFrame(frame); VLOG("PULL: Finished running frame %d", frame); } + +void VulkanRenderManager::FlushSync() { + int frame = vulkan_->GetCurFrame(); + BeginSubmitFrame(frame); + + FrameData &frameData = frameData_[frame]; + auto &stepsOnThread = frameData_[frame].steps; + VkCommandBuffer cmd = frameData.mainCmd; + queueRunner_.RunSteps(cmd, stepsOnThread); + stepsOnThread.clear(); + + Submit(frame); + + vkDeviceWaitIdle(vulkan_->GetDevice()); + + // At this point we can resume filling the command buffers for the current frame since + // we know the device is idle - and thus all previously enqueued command buffers have been processed. + // No need to switch to the next frame number. + VkCommandBufferBeginInfo begin = { + VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + nullptr, + VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT + }; + vkBeginCommandBuffer(frameData_->mainCmd, &begin); +} diff --git a/ext/native/thin3d/VulkanRenderManager.h b/ext/native/thin3d/VulkanRenderManager.h index 85c04264db..ff7849941b 100644 --- a/ext/native/thin3d/VulkanRenderManager.h +++ b/ext/native/thin3d/VulkanRenderManager.h @@ -78,16 +78,13 @@ public: // Makes sure that the GPU has caught up enough that we can start writing buffers of this frame again. void BeginFrame(); - void EndFrame(); - // Can run on a different thread! Just make sure to use BeginFrameWrites. + // Can run on a different thread! void Finish(); void Run(int frame); - // Bad for performance but sometimes necessary for synchronous CPU readbacks (screenshots and whatnot). - void Sync(); - 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, uint8_t *pixels, int pixelStride); void CopyFramebuffer(VKRFramebuffer *src, VkRect2D srcRect, VKRFramebuffer *dst, VkOffset2D dstPos, int aspectMask); void BlitFramebuffer(VKRFramebuffer *src, VkRect2D srcRect, VKRFramebuffer *dst, VkRect2D dstRect, int aspectMask, VkFilter filter); @@ -187,8 +184,12 @@ public: private: void InitBackbufferFramebuffers(int width, int height); void InitDepthStencilBuffer(VkCommandBuffer cmd); // Used for non-buffered rendering. - void BeginFrame(int frame); - void EndFrame(int frame); + void BeginSubmitFrame(int frame); + void EndSubmitFrame(int frame); + void Submit(int frame); + + // Bad for performance but sometimes necessary for synchronous CPU readbacks (screenshots and whatnot). + void FlushSync(); // Permanent objects VkSemaphore acquireSemaphore_; diff --git a/ext/native/thin3d/thin3d_vulkan.cpp b/ext/native/thin3d/thin3d_vulkan.cpp index 7aba729f65..16d2c953c7 100644 --- a/ext/native/thin3d/thin3d_vulkan.cpp +++ b/ext/native/thin3d/thin3d_vulkan.cpp @@ -372,6 +372,7 @@ public: void CopyFramebufferImage(Framebuffer *src, int level, int x, int y, int z, Framebuffer *dst, int dstLevel, int dstX, int dstY, int dstZ, int width, int height, int depth, int channelBits) override; bool BlitFramebuffer(Framebuffer *src, int srcX1, int srcY1, int srcX2, int srcY2, Framebuffer *dst, int dstX1, int dstY1, int dstX2, int dstY2, int channelBits, FBBlitFilter filter) override; + bool CopyFramebufferToMemorySync(Framebuffer *src, int channelBits, int x, int y, int w, int h, Draw::DataFormat format, void *pixels, int pixelStride); // These functions should be self explanatory. void BindFramebufferAsRenderTarget(Framebuffer *fbo, const RenderPassInfo &rp) override; @@ -1284,6 +1285,19 @@ bool VKContext::BlitFramebuffer(Framebuffer *srcfb, int srcX1, int srcY1, int sr return true; } +bool VKContext::CopyFramebufferToMemorySync(Framebuffer *srcfb, int channelBits, int x, int y, int w, int h, Draw::DataFormat format, void *pixels, int pixelStride) { + VKFramebuffer *src = (VKFramebuffer *)srcfb; + + int aspectMask = 0; + if (channelBits & FBChannel::FB_COLOR_BIT) aspectMask |= VK_IMAGE_ASPECT_COLOR_BIT; + 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, (uint8_t *)pixels, pixelStride); + return true; +} + + void VKContext::BindFramebufferAsRenderTarget(Framebuffer *fbo, const RenderPassInfo &rp) { VKFramebuffer *fb = (VKFramebuffer *)fbo; VKRRenderPassAction color = (VKRRenderPassAction)rp.color; // same values. From 34b65c0ca907fef92f315ff39114237c94918423 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 28 Oct 2017 18:41:54 +0200 Subject: [PATCH 3/7] Fix Vulkan framebuffer readbacks, as long as they're in R8G8B8A8. --- ext/native/thin3d/VulkanQueueRunner.cpp | 23 +++++++++----- ext/native/thin3d/VulkanQueueRunner.h | 4 +-- ext/native/thin3d/VulkanRenderManager.cpp | 37 ++++++++++++++--------- ext/native/thin3d/VulkanRenderManager.h | 3 +- ext/native/thin3d/thin3d_vulkan.cpp | 7 ++++- 5 files changed, 47 insertions(+), 27 deletions(-) diff --git a/ext/native/thin3d/VulkanQueueRunner.cpp b/ext/native/thin3d/VulkanQueueRunner.cpp index ff70dc1d4e..ae4f668199 100644 --- a/ext/native/thin3d/VulkanQueueRunner.cpp +++ b/ext/native/thin3d/VulkanQueueRunner.cpp @@ -725,28 +725,37 @@ void VulkanQueueRunner::PerformReadback(const VKRStep &step, VkCommandBuffer cmd assert(false); } + assert(srcImage->format == VK_FORMAT_R8G8B8A8_UNORM); + + VkImageMemoryBarrier barrier{ VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER }; + VkPipelineStageFlags stage = 0; + SetupTransitionToTransferSrc(*srcImage, barrier, stage, step.readback.aspectMask); + vkCmdPipelineBarrier(cmd, stage, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier); + VkBufferImageCopy region{}; region.imageOffset = { step.readback.srcRect.offset.x, step.readback.srcRect.offset.y, 0 }; region.imageExtent = { step.readback.srcRect.extent.width, step.readback.srcRect.extent.height, 1 }; region.imageSubresource.aspectMask = step.readback.aspectMask; + region.imageSubresource.layerCount = 1; region.bufferOffset = 0; - region.bufferRowLength = 0; + region.bufferRowLength = step.readback.srcRect.extent.width; region.bufferImageHeight = step.readback.srcRect.extent.height; vkCmdCopyImageToBuffer(cmd, srcImage->image, srcImage->layout, readbackBuffer_, 1, ®ion); // NOTE: Can't read the buffer using the CPU here - need to sync first. } -void VulkanQueueRunner::CopyReadbackBuffer(const VKRStep &step) { +void VulkanQueueRunner::CopyReadbackBuffer(int width, int height, int pixelStride, uint8_t *pixels) { // Read back to the requested address in ram from buffer. void *mappedData; - VkResult res = vkMapMemory(vulkan_->GetDevice(), readbackMemory_, 0, step.readback.srcRect.extent.width * step.readback.srcRect.extent.height * 4, 0, &mappedData); + const int pixelSize = 4; // TODO: Fix. + VkResult res = vkMapMemory(vulkan_->GetDevice(), readbackMemory_, 0, width * height * pixelSize, 0, &mappedData); assert(res == VK_SUCCESS); - const int pixelSize = 4; // TODO: Fix. - for (int y = 0; y < step.readback.srcRect.extent.height; y++) { - const uint8_t *src = (const uint8_t *)mappedData + step.readback.srcRect.extent.width * y; - uint8_t *dst = (uint8_t *)step.readback.destPtr + step.readback.pixelStride * pixelSize * y; + for (int y = 0; y < height; y++) { + const uint8_t *src = (const uint8_t *)mappedData + width * y; + uint8_t *dst = pixels + pixelStride * pixelSize * y; + memcpy(dst, src, width * pixelSize); } vkUnmapMemory(vulkan_->GetDevice(), readbackMemory_); } diff --git a/ext/native/thin3d/VulkanQueueRunner.h b/ext/native/thin3d/VulkanQueueRunner.h index c8d84fd151..1db5e1abea 100644 --- a/ext/native/thin3d/VulkanQueueRunner.h +++ b/ext/native/thin3d/VulkanQueueRunner.h @@ -129,8 +129,6 @@ struct VKRStep { struct { int aspectMask; VKRFramebuffer *src; - uint8_t *destPtr; - int pixelStride; VkRect2D srcRect; } readback; }; @@ -158,7 +156,7 @@ public: return (int)depth * 3 + (int)color; } - void CopyReadbackBuffer(const VKRStep &step); + void CopyReadbackBuffer(int width, int height, int pixelStride, uint8_t *pixels); private: void InitBackbufferRenderPass(); diff --git a/ext/native/thin3d/VulkanRenderManager.cpp b/ext/native/thin3d/VulkanRenderManager.cpp index 17cf3afd3f..9728bcb8e6 100644 --- a/ext/native/thin3d/VulkanRenderManager.cpp +++ b/ext/native/thin3d/VulkanRenderManager.cpp @@ -5,7 +5,7 @@ #include "thin3d/VulkanRenderManager.h" #include "thread/threadutil.h" -#ifdef _DEBUG +#if 0 // def _DEBUG #define VLOG ILOG #else #define VLOG(...) @@ -92,6 +92,8 @@ void CreateImage(VulkanContext *vulkan, VkCommandBuffer cmd, VKRImage &img, int VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, dstStage, 0, dstAccessMask); img.layout = initialLayout; + + img.format = format; } VulkanRenderManager::VulkanRenderManager(VulkanContext *vulkan) : vulkan_(vulkan), queueRunner_(vulkan) { @@ -328,8 +330,6 @@ void VulkanRenderManager::BindFramebufferAsRenderTarget(VKRFramebuffer *fb, VKRR void VulkanRenderManager::CopyFramebufferToMemorySync(VKRFramebuffer *src, int aspectBits, int x, int y, int w, int h, uint8_t *pixels, int pixelStride) { VKRStep *step = new VKRStep{ VKRStepType::READBACK }; step->readback.aspectMask = aspectBits; - step->readback.destPtr = (uint8_t *)pixels; - step->readback.pixelStride = pixelStride; step->readback.src = src; step->readback.srcRect.offset = { x, y }; step->readback.srcRect.extent = { (uint32_t)w, (uint32_t)h }; @@ -339,8 +339,8 @@ void VulkanRenderManager::CopyFramebufferToMemorySync(VKRFramebuffer *src, int a FlushSync(); - // Need to call this after FlushSync so the pixels are guaranteed to be ready in CPU-accessible VRAM. - queueRunner_.CopyReadbackBuffer(*step); + // Need to call this after FlushSyfnc so the pixels are guaranteed to be ready in CPU-accessible VRAM. + queueRunner_.CopyReadbackBuffer(w, h, pixelStride, pixels); } @@ -569,7 +569,7 @@ void VulkanRenderManager::BeginSubmitFrame(int frame) { } } -void VulkanRenderManager::Submit(int frame) { +void VulkanRenderManager::Submit(int frame, bool triggerFence) { FrameData &frameData = frameData_[frame]; if (frameData.hasInitCommands) { VkResult res = vkEndCommandBuffer(frameData.initCmd); @@ -590,16 +590,20 @@ void VulkanRenderManager::Submit(int frame) { cmdBufs.push_back(frameData.mainCmd); VkSubmitInfo submit_info = { VK_STRUCTURE_TYPE_SUBMIT_INFO }; - submit_info.waitSemaphoreCount = 1; - submit_info.pWaitSemaphores = &acquireSemaphore_; + if (triggerFence) { + submit_info.waitSemaphoreCount = 1; + submit_info.pWaitSemaphores = &acquireSemaphore_; + } VkPipelineStageFlags waitStage[1] = { VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT }; submit_info.pWaitDstStageMask = waitStage; submit_info.commandBufferCount = (uint32_t)cmdBufs.size(); submit_info.pCommandBuffers = cmdBufs.data(); - submit_info.signalSemaphoreCount = 1; - submit_info.pSignalSemaphores = &renderingCompleteSemaphore_; - res = vkQueueSubmit(vulkan_->GetGraphicsQueue(), 1, &submit_info, frameData.fence); + if (triggerFence) { + submit_info.signalSemaphoreCount = 1; + submit_info.pSignalSemaphores = &renderingCompleteSemaphore_; + } + res = vkQueueSubmit(vulkan_->GetGraphicsQueue(), 1, &submit_info, triggerFence ? frameData.fence : VK_NULL_HANDLE); assert(res == VK_SUCCESS); if (useThread) { @@ -617,7 +621,7 @@ void VulkanRenderManager::EndSubmitFrame(int frame) { TransitionToPresent(frameData.mainCmd, swapchainImages_[frameData.curSwapchainImage].image); - Submit(frame); + Submit(frame, true); VkSwapchainKHR swapchain = vulkan_->GetSwapchain(); VkPresentInfoKHR present = { VK_STRUCTURE_TYPE_PRESENT_INFO_KHR }; @@ -660,22 +664,25 @@ void VulkanRenderManager::FlushSync() { BeginSubmitFrame(frame); FrameData &frameData = frameData_[frame]; + frameData.steps = std::move(steps_); + auto &stepsOnThread = frameData_[frame].steps; VkCommandBuffer cmd = frameData.mainCmd; queueRunner_.RunSteps(cmd, stepsOnThread); stepsOnThread.clear(); - Submit(frame); + Submit(frame, false); vkDeviceWaitIdle(vulkan_->GetDevice()); // At this point we can resume filling the command buffers for the current frame since // we know the device is idle - and thus all previously enqueued command buffers have been processed. // No need to switch to the next frame number. - VkCommandBufferBeginInfo begin = { + VkCommandBufferBeginInfo begin{ VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, nullptr, VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT }; - vkBeginCommandBuffer(frameData_->mainCmd, &begin); + VkResult res = vkBeginCommandBuffer(frameData.mainCmd, &begin); + assert(res == VK_SUCCESS); } diff --git a/ext/native/thin3d/VulkanRenderManager.h b/ext/native/thin3d/VulkanRenderManager.h index ff7849941b..3a795f4b0c 100644 --- a/ext/native/thin3d/VulkanRenderManager.h +++ b/ext/native/thin3d/VulkanRenderManager.h @@ -21,6 +21,7 @@ struct VKRImage { VkImageView imageView; VkDeviceMemory memory; VkImageLayout layout; + VkFormat format; }; void CreateImage(VulkanContext *vulkan, VkCommandBuffer cmd, VKRImage &img, int width, int height, VkFormat format, VkImageLayout initialLayout, bool color); @@ -186,7 +187,7 @@ private: void InitDepthStencilBuffer(VkCommandBuffer cmd); // Used for non-buffered rendering. void BeginSubmitFrame(int frame); void EndSubmitFrame(int frame); - void Submit(int frame); + void Submit(int frame, bool triggerFence); // Bad for performance but sometimes necessary for synchronous CPU readbacks (screenshots and whatnot). void FlushSync(); diff --git a/ext/native/thin3d/thin3d_vulkan.cpp b/ext/native/thin3d/thin3d_vulkan.cpp index 16d2c953c7..e09503e043 100644 --- a/ext/native/thin3d/thin3d_vulkan.cpp +++ b/ext/native/thin3d/thin3d_vulkan.cpp @@ -1288,8 +1288,13 @@ bool VKContext::BlitFramebuffer(Framebuffer *srcfb, int srcX1, int srcY1, int sr bool VKContext::CopyFramebufferToMemorySync(Framebuffer *srcfb, int channelBits, int x, int y, int w, int h, Draw::DataFormat format, void *pixels, int pixelStride) { VKFramebuffer *src = (VKFramebuffer *)srcfb; + VkFormat vkFormat = DataFormatToVulkan(format); + int aspectMask = 0; - if (channelBits & FBChannel::FB_COLOR_BIT) aspectMask |= VK_IMAGE_ASPECT_COLOR_BIT; + if (channelBits & FBChannel::FB_COLOR_BIT) { + assert(vkFormat == src->GetFB()->color.format); + aspectMask |= VK_IMAGE_ASPECT_COLOR_BIT; + } if (channelBits & FBChannel::FB_DEPTH_BIT) aspectMask |= VK_IMAGE_ASPECT_DEPTH_BIT; if (channelBits & FBChannel::FB_STENCIL_BIT) aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT; From a9f01c45e07d730adfadf6636da1c5851135c5ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 28 Oct 2017 19:20:46 +0200 Subject: [PATCH 4/7] Use the CPU color conversion from GLES for readbacks in Vulkan (and D3D11). Fix bug. --- GPU/Common/FramebufferCommon.cpp | 99 ++++++++++++++++++++--- GPU/Common/FramebufferCommon.h | 4 + GPU/GLES/FramebufferManagerGLES.cpp | 101 +----------------------- GPU/GLES/FramebufferManagerGLES.h | 2 - ext/native/thin3d/VulkanQueueRunner.cpp | 2 +- 5 files changed, 96 insertions(+), 112 deletions(-) diff --git a/GPU/Common/FramebufferCommon.cpp b/GPU/Common/FramebufferCommon.cpp index 3291f94754..b06a5840b9 100644 --- a/GPU/Common/FramebufferCommon.cpp +++ b/GPU/Common/FramebufferCommon.cpp @@ -1974,6 +1974,63 @@ bool FramebufferManagerCommon::GetOutputFramebuffer(GPUDebugBuffer &buffer) { return true; } +// TODO: SSE/NEON +// Could also make C fake-simd for 64-bit, two 8888 pixels fit in a register :) +void ConvertFromRGBA8888(u8 *dst, const u8 *src, u32 dstStride, u32 srcStride, u32 width, u32 height, GEBufferFormat format) { + // Must skip stride in the cases below. Some games pack data into the cracks, like MotoGP. + const u32 *src32 = (const u32 *)src; + + if (format == GE_FORMAT_8888) { + u32 *dst32 = (u32 *)dst; + if (src == dst) { + return; + } else { + // Here let's assume they don't intersect + for (u32 y = 0; y < height; ++y) { + memcpy(dst32, src32, width * 4); + src32 += srcStride; + dst32 += dstStride; + } + } + } else { + // But here it shouldn't matter if they do intersect + u16 *dst16 = (u16 *)dst; + switch (format) { + case GE_FORMAT_565: // BGR 565 + { + for (u32 y = 0; y < height; ++y) { + ConvertRGBA8888ToRGB565(dst16, src32, width); + src32 += srcStride; + dst16 += dstStride; + } + } + break; + case GE_FORMAT_5551: // ABGR 1555 + { + for (u32 y = 0; y < height; ++y) { + ConvertRGBA8888ToRGBA5551(dst16, src32, width); + src32 += srcStride; + dst16 += dstStride; + } + } + break; + case GE_FORMAT_4444: // ABGR 4444 + { + for (u32 y = 0; y < height; ++y) { + ConvertRGBA8888ToRGBA4444(dst16, src32, width); + src32 += srcStride; + dst16 += dstStride; + } + } + break; + case GE_FORMAT_8888: + case GE_FORMAT_INVALID: + // Not possible. + break; + } + } +} + // This function takes an already correctly-sized framebuffer and packs it into RAM. // Does not need to account for scaling. // Color conversion is currently done on CPU but should theoretically be done on GPU. @@ -1986,19 +2043,43 @@ void FramebufferManagerCommon::PackFramebufferSync_(VirtualFramebuffer *vfb, int return; } - const u32 fb_address = (0x04000000) | vfb->fb_address; + int possibleH = std::max(vfb->height - y, 0); + if (h > possibleH) { + h = possibleH; + } - Draw::DataFormat destFormat = GEFormatToThin3D(vfb->format); - const int dstBpp = (int)DataFormatSizeInBytes(destFormat); + // Pixel size always 4 here because we always request RGBA8888 + u32 bufSize = vfb->fb_stride * h * 4; + u32 fb_address = 0x04000000 | vfb->fb_address; - const int dstByteOffset = (y * vfb->fb_stride + x) * dstBpp; - u8 *destPtr = Memory::GetPointer(fb_address + dstByteOffset); + bool convert = vfb->format != GE_FORMAT_8888; + const int dstBpp = vfb->format == GE_FORMAT_8888 ? 4 : 2; + const int packWidth = std::min(vfb->fb_stride, std::min(x + w, (int)vfb->width)); - // We always need to convert from the framebuffer native format. - // Right now that's always 8888. - DEBUG_LOG(G3D, "Reading framebuffer to mem, fb_address = %08x", fb_address); + int dstByteOffset = y * vfb->fb_stride * dstBpp; + u8 *dst = Memory::GetPointer(fb_address + dstByteOffset); - draw_->CopyFramebufferToMemorySync(vfb->fbo, Draw::FB_COLOR_BIT, x, y, w, h, destFormat, destPtr, vfb->fb_stride); + u8 *packed = nullptr; + if (!convert) { + packed = (u8 *)dst; + } else { + // End result may be 16-bit but we are reading 32-bit, so there may not be enough space at fb_address + if (!convBuf_ || convBufSize_ < bufSize) { + delete[] convBuf_; + convBuf_ = new u8[bufSize]; + convBufSize_ = bufSize; + } + packed = convBuf_; + } + + if (packed) { + DEBUG_LOG(FRAMEBUF, "Reading framebuffer to mem, bufSize = %u, fb_address = %08x", bufSize, fb_address); + int packW = h == 1 ? packWidth : vfb->fb_stride; // TODO: What's this about? + draw_->CopyFramebufferToMemorySync(vfb->fbo, Draw::FB_COLOR_BIT, 0, y, w, h, Draw::DataFormat::R8G8B8A8_UNORM, packed, packW); + if (convert) { + ConvertFromRGBA8888(dst, packed, vfb->fb_stride, vfb->fb_stride, packWidth, h, vfb->format); + } + } } void FramebufferManagerCommon::ReadFramebufferToMemory(VirtualFramebuffer *vfb, bool sync, int x, int y, int w, int h) { diff --git a/GPU/Common/FramebufferCommon.h b/GPU/Common/FramebufferCommon.h index 62befe6913..552ae1cee1 100644 --- a/GPU/Common/FramebufferCommon.h +++ b/GPU/Common/FramebufferCommon.h @@ -402,6 +402,10 @@ protected: int bloomHack_ = 0; bool trueColor_ = false; + // Used to convert readbacks. + u8 *convBuf_ = nullptr; + u32 convBufSize_ = 0; + // Used by post-processing shaders std::vector extraFBOs_; diff --git a/GPU/GLES/FramebufferManagerGLES.cpp b/GPU/GLES/FramebufferManagerGLES.cpp index 3eb62e0bcd..376870a7a2 100644 --- a/GPU/GLES/FramebufferManagerGLES.cpp +++ b/GPU/GLES/FramebufferManagerGLES.cpp @@ -204,7 +204,6 @@ FramebufferManagerGLES::FramebufferManagerGLES(Draw::DrawContext *draw) : FramebufferManagerCommon(draw), drawPixelsTex_(0), drawPixelsTexFormat_(GE_FORMAT_INVALID), - convBuf_(nullptr), draw2dprogram_(nullptr), postShaderProgram_(nullptr), stencilUploadProgram_(nullptr), @@ -721,63 +720,6 @@ void FramebufferManagerGLES::BlitFramebuffer(VirtualFramebuffer *dst, int dstX, CHECK_GL_ERROR_IF_DEBUG(); } -// TODO: SSE/NEON -// Could also make C fake-simd for 64-bit, two 8888 pixels fit in a register :) -void ConvertFromRGBA8888(u8 *dst, const u8 *src, u32 dstStride, u32 srcStride, u32 width, u32 height, GEBufferFormat format) { - // Must skip stride in the cases below. Some games pack data into the cracks, like MotoGP. - const u32 *src32 = (const u32 *)src; - - if (format == GE_FORMAT_8888) { - u32 *dst32 = (u32 *)dst; - if (src == dst) { - return; - } else { - // Here let's assume they don't intersect - for (u32 y = 0; y < height; ++y) { - memcpy(dst32, src32, width * 4); - src32 += srcStride; - dst32 += dstStride; - } - } - } else { - // But here it shouldn't matter if they do intersect - u16 *dst16 = (u16 *)dst; - switch (format) { - case GE_FORMAT_565: // BGR 565 - { - for (u32 y = 0; y < height; ++y) { - ConvertRGBA8888ToRGB565(dst16, src32, width); - src32 += srcStride; - dst16 += dstStride; - } - } - break; - case GE_FORMAT_5551: // ABGR 1555 - { - for (u32 y = 0; y < height; ++y) { - ConvertRGBA8888ToRGBA5551(dst16, src32, width); - src32 += srcStride; - dst16 += dstStride; - } - } - break; - case GE_FORMAT_4444: // ABGR 4444 - { - for (u32 y = 0; y < height; ++y) { - ConvertRGBA8888ToRGBA4444(dst16, src32, width); - src32 += srcStride; - dst16 += dstStride; - } - } - break; - case GE_FORMAT_8888: - case GE_FORMAT_INVALID: - // Not possible. - break; - } - } -} - void FramebufferManagerGLES::PackFramebufferAsync_(VirtualFramebuffer *vfb) { CHECK_GL_ERROR_IF_DEBUG(); const int MAX_PBO = 2; @@ -900,48 +842,7 @@ void FramebufferManagerGLES::PackFramebufferAsync_(VirtualFramebuffer *vfb) { } void FramebufferManagerGLES::PackFramebufferSync_(VirtualFramebuffer *vfb, int x, int y, int w, int h) { - if (!vfb->fbo) { - ERROR_LOG_REPORT_ONCE(vfbfbozero, SCEGE, "PackFramebufferSync_: vfb->fbo == 0"); - return; - } - - int possibleH = std::max(vfb->height - y, 0); - if (h > possibleH) { - h = possibleH; - } - - // Pixel size always 4 here because we always request RGBA8888 - u32 bufSize = vfb->fb_stride * h * 4; - u32 fb_address = 0x04000000 | vfb->fb_address; - - bool convert = vfb->format != GE_FORMAT_8888; - const int dstBpp = vfb->format == GE_FORMAT_8888 ? 4 : 2; - const int packWidth = std::min(vfb->fb_stride, std::min(x + w, (int)vfb->width)); - - int dstByteOffset = y * vfb->fb_stride * dstBpp; - u8 *dst = Memory::GetPointer(fb_address + dstByteOffset); - - u8 *packed = nullptr; - if (!convert) { - packed = (u8 *)dst; - } else { - // End result may be 16-bit but we are reading 32-bit, so there may not be enough space at fb_address - if (!convBuf_ || convBufSize_ < bufSize) { - delete [] convBuf_; - convBuf_ = new u8[bufSize]; - convBufSize_ = bufSize; - } - packed = convBuf_; - } - - if (packed) { - DEBUG_LOG(FRAMEBUF, "Reading framebuffer to mem, bufSize = %u, fb_address = %08x", bufSize, fb_address); - int packW = h == 1 ? packWidth : vfb->fb_stride; // TODO: What's this about? - draw_->CopyFramebufferToMemorySync(vfb->fbo, Draw::FB_COLOR_BIT, 0, y, packW, h, Draw::DataFormat::R8G8B8A8_UNORM, packed, packW); - if (convert) { - ConvertFromRGBA8888(dst, packed, vfb->fb_stride, vfb->fb_stride, packWidth, h, vfb->format); - } - } + FramebufferManagerCommon::PackFramebufferSync_(vfb, x, y, w, h); // TODO: Move this into Thin3d. if (gl_extensions.GLES3 && glInvalidateFramebuffer != nullptr) { diff --git a/GPU/GLES/FramebufferManagerGLES.h b/GPU/GLES/FramebufferManagerGLES.h index f867f833d6..d5f922c852 100644 --- a/GPU/GLES/FramebufferManagerGLES.h +++ b/GPU/GLES/FramebufferManagerGLES.h @@ -112,8 +112,6 @@ private: int drawPixelsTexW_; int drawPixelsTexH_; - u8 *convBuf_; - u32 convBufSize_; GLSLProgram *draw2dprogram_; GLSLProgram *plainColorProgram_; GLSLProgram *postShaderProgram_; diff --git a/ext/native/thin3d/VulkanQueueRunner.cpp b/ext/native/thin3d/VulkanQueueRunner.cpp index ae4f668199..cadc947f5c 100644 --- a/ext/native/thin3d/VulkanQueueRunner.cpp +++ b/ext/native/thin3d/VulkanQueueRunner.cpp @@ -753,7 +753,7 @@ void VulkanQueueRunner::CopyReadbackBuffer(int width, int height, int pixelStrid assert(res == VK_SUCCESS); for (int y = 0; y < height; y++) { - const uint8_t *src = (const uint8_t *)mappedData + width * y; + const uint8_t *src = (const uint8_t *)mappedData + width * pixelSize * y; uint8_t *dst = pixels + pixelStride * pixelSize * y; memcpy(dst, src, width * pixelSize); } From 9c254ea1c34da1aa021a81b57978b064fbfcb16d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 29 Oct 2017 09:31:28 +0100 Subject: [PATCH 5/7] Revert most of the last commit - will do it differently. --- GPU/Common/FramebufferCommon.cpp | 99 +++------------------------ GPU/Common/FramebufferCommon.h | 4 -- GPU/GLES/FramebufferManagerGLES.cpp | 101 +++++++++++++++++++++++++++- GPU/GLES/FramebufferManagerGLES.h | 2 + 4 files changed, 111 insertions(+), 95 deletions(-) diff --git a/GPU/Common/FramebufferCommon.cpp b/GPU/Common/FramebufferCommon.cpp index b06a5840b9..3291f94754 100644 --- a/GPU/Common/FramebufferCommon.cpp +++ b/GPU/Common/FramebufferCommon.cpp @@ -1974,63 +1974,6 @@ bool FramebufferManagerCommon::GetOutputFramebuffer(GPUDebugBuffer &buffer) { return true; } -// TODO: SSE/NEON -// Could also make C fake-simd for 64-bit, two 8888 pixels fit in a register :) -void ConvertFromRGBA8888(u8 *dst, const u8 *src, u32 dstStride, u32 srcStride, u32 width, u32 height, GEBufferFormat format) { - // Must skip stride in the cases below. Some games pack data into the cracks, like MotoGP. - const u32 *src32 = (const u32 *)src; - - if (format == GE_FORMAT_8888) { - u32 *dst32 = (u32 *)dst; - if (src == dst) { - return; - } else { - // Here let's assume they don't intersect - for (u32 y = 0; y < height; ++y) { - memcpy(dst32, src32, width * 4); - src32 += srcStride; - dst32 += dstStride; - } - } - } else { - // But here it shouldn't matter if they do intersect - u16 *dst16 = (u16 *)dst; - switch (format) { - case GE_FORMAT_565: // BGR 565 - { - for (u32 y = 0; y < height; ++y) { - ConvertRGBA8888ToRGB565(dst16, src32, width); - src32 += srcStride; - dst16 += dstStride; - } - } - break; - case GE_FORMAT_5551: // ABGR 1555 - { - for (u32 y = 0; y < height; ++y) { - ConvertRGBA8888ToRGBA5551(dst16, src32, width); - src32 += srcStride; - dst16 += dstStride; - } - } - break; - case GE_FORMAT_4444: // ABGR 4444 - { - for (u32 y = 0; y < height; ++y) { - ConvertRGBA8888ToRGBA4444(dst16, src32, width); - src32 += srcStride; - dst16 += dstStride; - } - } - break; - case GE_FORMAT_8888: - case GE_FORMAT_INVALID: - // Not possible. - break; - } - } -} - // This function takes an already correctly-sized framebuffer and packs it into RAM. // Does not need to account for scaling. // Color conversion is currently done on CPU but should theoretically be done on GPU. @@ -2043,43 +1986,19 @@ void FramebufferManagerCommon::PackFramebufferSync_(VirtualFramebuffer *vfb, int return; } - int possibleH = std::max(vfb->height - y, 0); - if (h > possibleH) { - h = possibleH; - } + const u32 fb_address = (0x04000000) | vfb->fb_address; - // Pixel size always 4 here because we always request RGBA8888 - u32 bufSize = vfb->fb_stride * h * 4; - u32 fb_address = 0x04000000 | vfb->fb_address; + Draw::DataFormat destFormat = GEFormatToThin3D(vfb->format); + const int dstBpp = (int)DataFormatSizeInBytes(destFormat); - bool convert = vfb->format != GE_FORMAT_8888; - const int dstBpp = vfb->format == GE_FORMAT_8888 ? 4 : 2; - const int packWidth = std::min(vfb->fb_stride, std::min(x + w, (int)vfb->width)); + const int dstByteOffset = (y * vfb->fb_stride + x) * dstBpp; + u8 *destPtr = Memory::GetPointer(fb_address + dstByteOffset); - int dstByteOffset = y * vfb->fb_stride * dstBpp; - u8 *dst = Memory::GetPointer(fb_address + dstByteOffset); + // We always need to convert from the framebuffer native format. + // Right now that's always 8888. + DEBUG_LOG(G3D, "Reading framebuffer to mem, fb_address = %08x", fb_address); - u8 *packed = nullptr; - if (!convert) { - packed = (u8 *)dst; - } else { - // End result may be 16-bit but we are reading 32-bit, so there may not be enough space at fb_address - if (!convBuf_ || convBufSize_ < bufSize) { - delete[] convBuf_; - convBuf_ = new u8[bufSize]; - convBufSize_ = bufSize; - } - packed = convBuf_; - } - - if (packed) { - DEBUG_LOG(FRAMEBUF, "Reading framebuffer to mem, bufSize = %u, fb_address = %08x", bufSize, fb_address); - int packW = h == 1 ? packWidth : vfb->fb_stride; // TODO: What's this about? - draw_->CopyFramebufferToMemorySync(vfb->fbo, Draw::FB_COLOR_BIT, 0, y, w, h, Draw::DataFormat::R8G8B8A8_UNORM, packed, packW); - if (convert) { - ConvertFromRGBA8888(dst, packed, vfb->fb_stride, vfb->fb_stride, packWidth, h, vfb->format); - } - } + draw_->CopyFramebufferToMemorySync(vfb->fbo, Draw::FB_COLOR_BIT, x, y, w, h, destFormat, destPtr, vfb->fb_stride); } void FramebufferManagerCommon::ReadFramebufferToMemory(VirtualFramebuffer *vfb, bool sync, int x, int y, int w, int h) { diff --git a/GPU/Common/FramebufferCommon.h b/GPU/Common/FramebufferCommon.h index 552ae1cee1..62befe6913 100644 --- a/GPU/Common/FramebufferCommon.h +++ b/GPU/Common/FramebufferCommon.h @@ -402,10 +402,6 @@ protected: int bloomHack_ = 0; bool trueColor_ = false; - // Used to convert readbacks. - u8 *convBuf_ = nullptr; - u32 convBufSize_ = 0; - // Used by post-processing shaders std::vector extraFBOs_; diff --git a/GPU/GLES/FramebufferManagerGLES.cpp b/GPU/GLES/FramebufferManagerGLES.cpp index 376870a7a2..3eb62e0bcd 100644 --- a/GPU/GLES/FramebufferManagerGLES.cpp +++ b/GPU/GLES/FramebufferManagerGLES.cpp @@ -204,6 +204,7 @@ FramebufferManagerGLES::FramebufferManagerGLES(Draw::DrawContext *draw) : FramebufferManagerCommon(draw), drawPixelsTex_(0), drawPixelsTexFormat_(GE_FORMAT_INVALID), + convBuf_(nullptr), draw2dprogram_(nullptr), postShaderProgram_(nullptr), stencilUploadProgram_(nullptr), @@ -720,6 +721,63 @@ void FramebufferManagerGLES::BlitFramebuffer(VirtualFramebuffer *dst, int dstX, CHECK_GL_ERROR_IF_DEBUG(); } +// TODO: SSE/NEON +// Could also make C fake-simd for 64-bit, two 8888 pixels fit in a register :) +void ConvertFromRGBA8888(u8 *dst, const u8 *src, u32 dstStride, u32 srcStride, u32 width, u32 height, GEBufferFormat format) { + // Must skip stride in the cases below. Some games pack data into the cracks, like MotoGP. + const u32 *src32 = (const u32 *)src; + + if (format == GE_FORMAT_8888) { + u32 *dst32 = (u32 *)dst; + if (src == dst) { + return; + } else { + // Here let's assume they don't intersect + for (u32 y = 0; y < height; ++y) { + memcpy(dst32, src32, width * 4); + src32 += srcStride; + dst32 += dstStride; + } + } + } else { + // But here it shouldn't matter if they do intersect + u16 *dst16 = (u16 *)dst; + switch (format) { + case GE_FORMAT_565: // BGR 565 + { + for (u32 y = 0; y < height; ++y) { + ConvertRGBA8888ToRGB565(dst16, src32, width); + src32 += srcStride; + dst16 += dstStride; + } + } + break; + case GE_FORMAT_5551: // ABGR 1555 + { + for (u32 y = 0; y < height; ++y) { + ConvertRGBA8888ToRGBA5551(dst16, src32, width); + src32 += srcStride; + dst16 += dstStride; + } + } + break; + case GE_FORMAT_4444: // ABGR 4444 + { + for (u32 y = 0; y < height; ++y) { + ConvertRGBA8888ToRGBA4444(dst16, src32, width); + src32 += srcStride; + dst16 += dstStride; + } + } + break; + case GE_FORMAT_8888: + case GE_FORMAT_INVALID: + // Not possible. + break; + } + } +} + void FramebufferManagerGLES::PackFramebufferAsync_(VirtualFramebuffer *vfb) { CHECK_GL_ERROR_IF_DEBUG(); const int MAX_PBO = 2; @@ -842,7 +900,48 @@ void FramebufferManagerGLES::PackFramebufferAsync_(VirtualFramebuffer *vfb) { } void FramebufferManagerGLES::PackFramebufferSync_(VirtualFramebuffer *vfb, int x, int y, int w, int h) { - FramebufferManagerCommon::PackFramebufferSync_(vfb, x, y, w, h); + if (!vfb->fbo) { + ERROR_LOG_REPORT_ONCE(vfbfbozero, SCEGE, "PackFramebufferSync_: vfb->fbo == 0"); + return; + } + + int possibleH = std::max(vfb->height - y, 0); + if (h > possibleH) { + h = possibleH; + } + + // Pixel size always 4 here because we always request RGBA8888 + u32 bufSize = vfb->fb_stride * h * 4; + u32 fb_address = 0x04000000 | vfb->fb_address; + + bool convert = vfb->format != GE_FORMAT_8888; + const int dstBpp = vfb->format == GE_FORMAT_8888 ? 4 : 2; + const int packWidth = std::min(vfb->fb_stride, std::min(x + w, (int)vfb->width)); + + int dstByteOffset = y * vfb->fb_stride * dstBpp; + u8 *dst = Memory::GetPointer(fb_address + dstByteOffset); + + u8 *packed = nullptr; + if (!convert) { + packed = (u8 *)dst; + } else { + // End result may be 16-bit but we are reading 32-bit, so there may not be enough space at fb_address + if (!convBuf_ || convBufSize_ < bufSize) { + delete [] convBuf_; + convBuf_ = new u8[bufSize]; + convBufSize_ = bufSize; + } + packed = convBuf_; + } + + if (packed) { + DEBUG_LOG(FRAMEBUF, "Reading framebuffer to mem, bufSize = %u, fb_address = %08x", bufSize, fb_address); + int packW = h == 1 ? packWidth : vfb->fb_stride; // TODO: What's this about? + draw_->CopyFramebufferToMemorySync(vfb->fbo, Draw::FB_COLOR_BIT, 0, y, packW, h, Draw::DataFormat::R8G8B8A8_UNORM, packed, packW); + if (convert) { + ConvertFromRGBA8888(dst, packed, vfb->fb_stride, vfb->fb_stride, packWidth, h, vfb->format); + } + } // TODO: Move this into Thin3d. if (gl_extensions.GLES3 && glInvalidateFramebuffer != nullptr) { diff --git a/GPU/GLES/FramebufferManagerGLES.h b/GPU/GLES/FramebufferManagerGLES.h index d5f922c852..f867f833d6 100644 --- a/GPU/GLES/FramebufferManagerGLES.h +++ b/GPU/GLES/FramebufferManagerGLES.h @@ -112,6 +112,8 @@ private: int drawPixelsTexW_; int drawPixelsTexH_; + u8 *convBuf_; + u32 convBufSize_; GLSLProgram *draw2dprogram_; GLSLProgram *plainColorProgram_; GLSLProgram *postShaderProgram_; From 93c785b76d41d838efabfaa5858e0128d28080cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 29 Oct 2017 10:56:02 +0100 Subject: [PATCH 6/7] Color-convert directly during the readback, saves a copy. Like we already do in D3D11. --- GPU/Vulkan/FramebufferVulkan.cpp | 1 + ext/native/thin3d/VulkanQueueRunner.cpp | 69 ++++++++++++++++++++--- ext/native/thin3d/VulkanQueueRunner.h | 2 +- ext/native/thin3d/VulkanRenderManager.cpp | 5 +- ext/native/thin3d/VulkanRenderManager.h | 2 +- ext/native/thin3d/thin3d_vulkan.cpp | 3 +- 6 files changed, 66 insertions(+), 16 deletions(-) diff --git a/GPU/Vulkan/FramebufferVulkan.cpp b/GPU/Vulkan/FramebufferVulkan.cpp index d6124cc467..84bb6faa80 100644 --- a/GPU/Vulkan/FramebufferVulkan.cpp +++ b/GPU/Vulkan/FramebufferVulkan.cpp @@ -387,6 +387,7 @@ void FramebufferManagerVulkan::RebindFramebuffer() { // Should this even happen? draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::KEEP, Draw::RPAction::KEEP }); } + gstate_c.Dirty(DIRTY_VIEWPORTSCISSOR_STATE); } bool FramebufferManagerVulkan::NotifyStencilUpload(u32 addr, int size, bool skipZero) { diff --git a/ext/native/thin3d/VulkanQueueRunner.cpp b/ext/native/thin3d/VulkanQueueRunner.cpp index cadc947f5c..734daa3ddd 100644 --- a/ext/native/thin3d/VulkanQueueRunner.cpp +++ b/ext/native/thin3d/VulkanQueueRunner.cpp @@ -1,3 +1,5 @@ +#include "Common/ColorConv.h" + #include "VulkanQueueRunner.h" #include "VulkanRenderManager.h" @@ -745,17 +747,66 @@ void VulkanQueueRunner::PerformReadback(const VKRStep &step, VkCommandBuffer cmd // NOTE: Can't read the buffer using the CPU here - need to sync first. } -void VulkanQueueRunner::CopyReadbackBuffer(int width, int height, int pixelStride, uint8_t *pixels) { +// Would love to share this with D3D11 but don't have a shared format enum we can use... as we don't want +// to depend on thin3d here. +// TODO: SSE/NEON +// Could also make C fake-simd for 64-bit, two 8888 pixels fit in a register :) +// Strides are in pixels. +void ConvertFromRGBA8888(u8 *dst, const u8 *src, u32 dstStride, u32 srcStride, u32 width, u32 height, VkFormat format) { + // Must skip stride in the cases below. Some games pack data into the cracks, like MotoGP. + const u32 *src32 = (const u32 *)src; + + if (format == VK_FORMAT_R8G8B8A8_UNORM) { + u32 *dst32 = (u32 *)dst; + if (src == dst) { + return; + } else { + for (u32 y = 0; y < height; ++y) { + memcpy(dst32, src32, width * 4); + src32 += srcStride; + dst32 += dstStride; + } + } + } else { + // But here it shouldn't matter if they do intersect + u16 *dst16 = (u16 *)dst; + switch (format) { + case VK_FORMAT_B5G6R5_UNORM_PACK16: // BGR 565 + for (u32 y = 0; y < height; ++y) { + ConvertRGBA8888ToRGB565(dst16, src32, width); + src32 += srcStride; + dst16 += dstStride; + } + break; + case VK_FORMAT_A1R5G5B5_UNORM_PACK16: // ABGR 1555 + for (u32 y = 0; y < height; ++y) { + ConvertRGBA8888ToRGBA5551(dst16, src32, width); + src32 += srcStride; + dst16 += dstStride; + } + break; + case VK_FORMAT_R4G4B4A4_UNORM_PACK16: // ABGR 4444 + for (u32 y = 0; y < height; ++y) { + ConvertRGBA8888ToRGBA4444(dst16, src32, width); + src32 += srcStride; + dst16 += dstStride; + } + break; + default: + Crash(); + // Not possible. + break; + } + } +} + +void VulkanQueueRunner::CopyReadbackBuffer(int width, int height, VkFormat destFormat, int pixelStride, uint8_t *pixels) { // Read back to the requested address in ram from buffer. void *mappedData; - const int pixelSize = 4; // TODO: Fix. - VkResult res = vkMapMemory(vulkan_->GetDevice(), readbackMemory_, 0, width * height * pixelSize, 0, &mappedData); - assert(res == VK_SUCCESS); + const int srcPixelSize = 4; // TODO: Fix. - for (int y = 0; y < height; y++) { - const uint8_t *src = (const uint8_t *)mappedData + width * pixelSize * y; - uint8_t *dst = pixels + pixelStride * pixelSize * y; - memcpy(dst, src, width * pixelSize); - } + VkResult res = vkMapMemory(vulkan_->GetDevice(), readbackMemory_, 0, width * height * srcPixelSize, 0, &mappedData); + assert(res == VK_SUCCESS); + ConvertFromRGBA8888(pixels, (const uint8_t *)mappedData, pixelStride, width, width, height, destFormat); vkUnmapMemory(vulkan_->GetDevice(), readbackMemory_); } diff --git a/ext/native/thin3d/VulkanQueueRunner.h b/ext/native/thin3d/VulkanQueueRunner.h index 1db5e1abea..477d361421 100644 --- a/ext/native/thin3d/VulkanQueueRunner.h +++ b/ext/native/thin3d/VulkanQueueRunner.h @@ -156,7 +156,7 @@ public: return (int)depth * 3 + (int)color; } - void CopyReadbackBuffer(int width, int height, int pixelStride, uint8_t *pixels); + void CopyReadbackBuffer(int width, int height, VkFormat destFormat, int pixelStride, uint8_t *pixels); private: void InitBackbufferRenderPass(); diff --git a/ext/native/thin3d/VulkanRenderManager.cpp b/ext/native/thin3d/VulkanRenderManager.cpp index 9728bcb8e6..7cf2c4bd11 100644 --- a/ext/native/thin3d/VulkanRenderManager.cpp +++ b/ext/native/thin3d/VulkanRenderManager.cpp @@ -327,7 +327,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, uint8_t *pixels, int pixelStride) { +void VulkanRenderManager::CopyFramebufferToMemorySync(VKRFramebuffer *src, int aspectBits, int x, int y, int w, int h, VkFormat destFormat, uint8_t *pixels, int pixelStride) { VKRStep *step = new VKRStep{ VKRStepType::READBACK }; step->readback.aspectMask = aspectBits; step->readback.src = src; @@ -340,10 +340,9 @@ void VulkanRenderManager::CopyFramebufferToMemorySync(VKRFramebuffer *src, int a FlushSync(); // Need to call this after FlushSyfnc so the pixels are guaranteed to be ready in CPU-accessible VRAM. - queueRunner_.CopyReadbackBuffer(w, h, pixelStride, pixels); + queueRunner_.CopyReadbackBuffer(w, h, destFormat, pixelStride, pixels); } - void VulkanRenderManager::InitBackbufferFramebuffers(int width, int height) { VkResult U_ASSERT_ONLY res; // We share the same depth buffer but have multiple color buffers, see the loop below. diff --git a/ext/native/thin3d/VulkanRenderManager.h b/ext/native/thin3d/VulkanRenderManager.h index 3a795f4b0c..063c37a1c0 100644 --- a/ext/native/thin3d/VulkanRenderManager.h +++ b/ext/native/thin3d/VulkanRenderManager.h @@ -85,7 +85,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, uint8_t *pixels, int pixelStride); + void CopyFramebufferToMemorySync(VKRFramebuffer *src, int aspectBits, int x, int y, int w, int h, VkFormat destFormat, uint8_t *pixels, int pixelStride); void CopyFramebuffer(VKRFramebuffer *src, VkRect2D srcRect, VKRFramebuffer *dst, VkOffset2D dstPos, int aspectMask); void BlitFramebuffer(VKRFramebuffer *src, VkRect2D srcRect, VKRFramebuffer *dst, VkRect2D dstRect, int aspectMask, VkFilter filter); diff --git a/ext/native/thin3d/thin3d_vulkan.cpp b/ext/native/thin3d/thin3d_vulkan.cpp index e09503e043..b6a8161baa 100644 --- a/ext/native/thin3d/thin3d_vulkan.cpp +++ b/ext/native/thin3d/thin3d_vulkan.cpp @@ -1292,13 +1292,12 @@ bool VKContext::CopyFramebufferToMemorySync(Framebuffer *srcfb, int channelBits, int aspectMask = 0; if (channelBits & FBChannel::FB_COLOR_BIT) { - assert(vkFormat == src->GetFB()->color.format); aspectMask |= VK_IMAGE_ASPECT_COLOR_BIT; } 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, (uint8_t *)pixels, pixelStride); + renderManager_.CopyFramebufferToMemorySync(src->GetFB(), aspectMask, x, y, w, h, vkFormat, (uint8_t *)pixels, pixelStride); return true; } From 97dced543e182ca3abdba8fc84bf36b12bf5044e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 29 Oct 2017 14:42:51 +0100 Subject: [PATCH 7/7] Share the conversion code between vulkan and D3D, fixing the missing format thing. --- CMakeLists.txt | 3 +- ext/native/native.vcxproj | 3 +- ext/native/native.vcxproj.filters | 5 +- ext/native/thin3d/DataFormat.h | 71 +++++++++++++++++++++++ ext/native/thin3d/VulkanQueueRunner.cpp | 58 +----------------- ext/native/thin3d/VulkanQueueRunner.h | 4 +- ext/native/thin3d/VulkanRenderManager.cpp | 2 +- ext/native/thin3d/VulkanRenderManager.h | 4 +- ext/native/thin3d/thin3d.cpp | 55 +++++++++++++++++- ext/native/thin3d/thin3d.h | 63 +------------------- ext/native/thin3d/thin3d_d3d11.cpp | 50 ---------------- ext/native/thin3d/thin3d_vulkan.cpp | 4 +- 12 files changed, 142 insertions(+), 180 deletions(-) create mode 100644 ext/native/thin3d/DataFormat.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 30acdec62a..2135eb8ba2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -808,7 +808,8 @@ if(VULKAN) ext/native/thin3d/VulkanRenderManager.cpp ext/native/thin3d/VulkanRenderManager.h ext/native/thin3d/VulkanQueueRunner.cpp - ext/native/thin3d/VulkanQueueRunner.h) + ext/native/thin3d/VulkanQueueRunner.h + ext/native/thin3d/DataFormat.h) endif() if(WIN32) set(THIN3D_PLATFORMS ${THIN3D_PLATFORMS} diff --git a/ext/native/native.vcxproj b/ext/native/native.vcxproj index 681a99b453..aa3b268a19 100644 --- a/ext/native/native.vcxproj +++ b/ext/native/native.vcxproj @@ -240,6 +240,7 @@ + @@ -760,4 +761,4 @@ - + \ No newline at end of file diff --git a/ext/native/native.vcxproj.filters b/ext/native/native.vcxproj.filters index 57aee62de4..6996479a53 100644 --- a/ext/native/native.vcxproj.filters +++ b/ext/native/native.vcxproj.filters @@ -326,6 +326,9 @@ thin3d + + thin3d + @@ -865,4 +868,4 @@ {06c6305a-a646-485b-85b9-645a24dd6553} - + \ No newline at end of file diff --git a/ext/native/thin3d/DataFormat.h b/ext/native/thin3d/DataFormat.h new file mode 100644 index 0000000000..fa9a8325be --- /dev/null +++ b/ext/native/thin3d/DataFormat.h @@ -0,0 +1,71 @@ +#pragma once + +#include + +namespace Draw { + +enum class DataFormat : uint8_t { + UNDEFINED, + + R8_UNORM, + R8G8_UNORM, + R8G8B8_UNORM, + + R8G8B8A8_UNORM, + R8G8B8A8_UNORM_SRGB, + B8G8R8A8_UNORM, // D3D style + B8G8R8A8_UNORM_SRGB, // D3D style + + R8G8B8A8_SNORM, + R8G8B8A8_UINT, + R8G8B8A8_SINT, + + R4G4_UNORM_PACK8, + A4R4G4B4_UNORM_PACK16, // A4 in the UPPER bit + B4G4R4A4_UNORM_PACK16, + R4G4B4A4_UNORM_PACK16, + R5G6B5_UNORM_PACK16, + B5G6R5_UNORM_PACK16, + R5G5B5A1_UNORM_PACK16, // A1 in the LOWER bit + B5G5R5A1_UNORM_PACK16, // A1 in the LOWER bit + A1R5G5B5_UNORM_PACK16, // A1 in the UPPER bit. + + R16_FLOAT, + R16G16_FLOAT, + R16G16B16A16_FLOAT, + + R32_FLOAT, + R32G32_FLOAT, + R32G32B32_FLOAT, + R32G32B32A32_FLOAT, + + // Block compression formats. + // These are modern names for DXT and friends, now patent free. + // https://msdn.microsoft.com/en-us/library/bb694531.aspx + BC1_RGBA_UNORM_BLOCK, + BC1_RGBA_SRGB_BLOCK, + BC2_UNORM_BLOCK, // 4-bit straight alpha + DXT1 color. Usually not worth using + BC2_SRGB_BLOCK, + BC3_UNORM_BLOCK, // 3-bit alpha with 2 ref values (+ magic) + DXT1 color + BC3_SRGB_BLOCK, + BC4_UNORM_BLOCK, // 1-channel, same storage as BC3 alpha + BC4_SNORM_BLOCK, + BC5_UNORM_BLOCK, // 2-channel RG, each has same storage as BC3 alpha + BC5_SNORM_BLOCK, + BC6H_UFLOAT_BLOCK, // TODO + BC6H_SFLOAT_BLOCK, + BC7_UNORM_BLOCK, // Highly advanced, very expensive to compress, very good quality. + BC7_SRGB_BLOCK, + + ETC1, + + S8, + D16, + D24_S8, + D32F, + D32F_S8, +}; + +void ConvertFromRGBA8888(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 734daa3ddd..f4028a0bfa 100644 --- a/ext/native/thin3d/VulkanQueueRunner.cpp +++ b/ext/native/thin3d/VulkanQueueRunner.cpp @@ -1,5 +1,4 @@ -#include "Common/ColorConv.h" - +#include "DataFormat.h" #include "VulkanQueueRunner.h" #include "VulkanRenderManager.h" @@ -747,60 +746,7 @@ void VulkanQueueRunner::PerformReadback(const VKRStep &step, VkCommandBuffer cmd // NOTE: Can't read the buffer using the CPU here - need to sync first. } -// Would love to share this with D3D11 but don't have a shared format enum we can use... as we don't want -// to depend on thin3d here. -// TODO: SSE/NEON -// Could also make C fake-simd for 64-bit, two 8888 pixels fit in a register :) -// Strides are in pixels. -void ConvertFromRGBA8888(u8 *dst, const u8 *src, u32 dstStride, u32 srcStride, u32 width, u32 height, VkFormat format) { - // Must skip stride in the cases below. Some games pack data into the cracks, like MotoGP. - const u32 *src32 = (const u32 *)src; - - if (format == VK_FORMAT_R8G8B8A8_UNORM) { - u32 *dst32 = (u32 *)dst; - if (src == dst) { - return; - } else { - for (u32 y = 0; y < height; ++y) { - memcpy(dst32, src32, width * 4); - src32 += srcStride; - dst32 += dstStride; - } - } - } else { - // But here it shouldn't matter if they do intersect - u16 *dst16 = (u16 *)dst; - switch (format) { - case VK_FORMAT_B5G6R5_UNORM_PACK16: // BGR 565 - for (u32 y = 0; y < height; ++y) { - ConvertRGBA8888ToRGB565(dst16, src32, width); - src32 += srcStride; - dst16 += dstStride; - } - break; - case VK_FORMAT_A1R5G5B5_UNORM_PACK16: // ABGR 1555 - for (u32 y = 0; y < height; ++y) { - ConvertRGBA8888ToRGBA5551(dst16, src32, width); - src32 += srcStride; - dst16 += dstStride; - } - break; - case VK_FORMAT_R4G4B4A4_UNORM_PACK16: // ABGR 4444 - for (u32 y = 0; y < height; ++y) { - ConvertRGBA8888ToRGBA4444(dst16, src32, width); - src32 += srcStride; - dst16 += dstStride; - } - break; - default: - Crash(); - // Not possible. - break; - } - } -} - -void VulkanQueueRunner::CopyReadbackBuffer(int width, int height, VkFormat destFormat, int pixelStride, uint8_t *pixels) { +void VulkanQueueRunner::CopyReadbackBuffer(int width, int height, Draw::DataFormat destFormat, int pixelStride, uint8_t *pixels) { // Read back to the requested address in ram from buffer. void *mappedData; const int srcPixelSize = 4; // TODO: Fix. diff --git a/ext/native/thin3d/VulkanQueueRunner.h b/ext/native/thin3d/VulkanQueueRunner.h index 477d361421..05accdf6a5 100644 --- a/ext/native/thin3d/VulkanQueueRunner.h +++ b/ext/native/thin3d/VulkanQueueRunner.h @@ -4,7 +4,7 @@ #include "Common/Vulkan/VulkanContext.h" #include "math/dataconv.h" -#include "thin3d/thin3d.h" +#include "thin3d/DataFormat.h" class VKRFramebuffer; struct VKRImage; @@ -156,7 +156,7 @@ public: return (int)depth * 3 + (int)color; } - void CopyReadbackBuffer(int width, int height, VkFormat destFormat, int pixelStride, uint8_t *pixels); + void CopyReadbackBuffer(int width, int height, Draw::DataFormat destFormat, int pixelStride, uint8_t *pixels); private: void InitBackbufferRenderPass(); diff --git a/ext/native/thin3d/VulkanRenderManager.cpp b/ext/native/thin3d/VulkanRenderManager.cpp index 7cf2c4bd11..e74eb71853 100644 --- a/ext/native/thin3d/VulkanRenderManager.cpp +++ b/ext/native/thin3d/VulkanRenderManager.cpp @@ -327,7 +327,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, VkFormat destFormat, uint8_t *pixels, int pixelStride) { +void 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; diff --git a/ext/native/thin3d/VulkanRenderManager.h b/ext/native/thin3d/VulkanRenderManager.h index 063c37a1c0..717539cee0 100644 --- a/ext/native/thin3d/VulkanRenderManager.h +++ b/ext/native/thin3d/VulkanRenderManager.h @@ -11,7 +11,7 @@ #include "Common/Vulkan/VulkanContext.h" #include "math/dataconv.h" -#include "thin3d/thin3d.h" +#include "thin3d/DataFormat.h" #include "thin3d/VulkanQueueRunner.h" // Simple independent framebuffer image. Gets its own allocation, we don't have that many framebuffers so it's fine @@ -85,7 +85,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, VkFormat destFormat, uint8_t *pixels, int pixelStride); + void CopyFramebufferToMemorySync(VKRFramebuffer *src, int aspectBits, 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); void BlitFramebuffer(VKRFramebuffer *src, VkRect2D srcRect, VKRFramebuffer *dst, VkRect2D dstRect, int aspectMask, VkFilter filter); diff --git a/ext/native/thin3d/thin3d.cpp b/ext/native/thin3d/thin3d.cpp index 1de3bbba4d..39bdc447bc 100644 --- a/ext/native/thin3d/thin3d.cpp +++ b/ext/native/thin3d/thin3d.cpp @@ -1,7 +1,9 @@ #include -#include +#include #include "base/logging.h" +#include "thin3d/thin3d.h" +#include "Common/ColorConv.h" namespace Draw { @@ -320,4 +322,55 @@ DrawContext::~DrawContext() { } } +// TODO: SSE/NEON +// Could also make C fake-simd for 64-bit, two 8888 pixels fit in a register :) +void ConvertFromRGBA8888(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) { + memcpy(dst32, src32, width * 4); + src32 += srcStride; + dst32 += dstStride; + } + } + } else { + // But here it shouldn't matter if they do intersect + uint16_t *dst16 = (uint16_t *)dst; + switch (format) { + case Draw::DataFormat::R5G6B5_UNORM_PACK16: // BGR 565 + for (uint32_t y = 0; y < height; ++y) { + ConvertRGBA8888ToRGB565(dst16, src32, width); + src32 += srcStride; + dst16 += dstStride; + } + break; + case Draw::DataFormat::A1R5G5B5_UNORM_PACK16: // ABGR 1555 + for (uint32_t y = 0; y < height; ++y) { + ConvertRGBA8888ToRGBA5551(dst16, src32, width); + src32 += srcStride; + dst16 += dstStride; + } + break; + case Draw::DataFormat::A4R4G4B4_UNORM_PACK16: // ABGR 4444 + for (uint32_t y = 0; y < height; ++y) { + ConvertRGBA8888ToRGBA4444(dst16, src32, width); + src32 += srcStride; + dst16 += dstStride; + } + break; + case Draw::DataFormat::R8G8B8A8_UNORM: + case Draw::DataFormat::UNDEFINED: + // Not possible. + break; + } + } +} + + } // namespace Draw diff --git a/ext/native/thin3d/thin3d.h b/ext/native/thin3d/thin3d.h index 6b3a6dfac9..0dead1552a 100644 --- a/ext/native/thin3d/thin3d.h +++ b/ext/native/thin3d/thin3d.h @@ -12,6 +12,7 @@ #include #include "base/logging.h" +#include "DataFormat.h" class Matrix4x4; @@ -176,68 +177,6 @@ enum class TextureType : uint8_t { ARRAY2D, }; -enum class DataFormat : uint8_t { - UNDEFINED, - - R8_UNORM, - R8G8_UNORM, - R8G8B8_UNORM, - - R8G8B8A8_UNORM, - R8G8B8A8_UNORM_SRGB, - B8G8R8A8_UNORM, // D3D style - B8G8R8A8_UNORM_SRGB, // D3D style - - R8G8B8A8_SNORM, - R8G8B8A8_UINT, - R8G8B8A8_SINT, - - R4G4_UNORM_PACK8, - A4R4G4B4_UNORM_PACK16, // A4 in the UPPER bit - B4G4R4A4_UNORM_PACK16, - R4G4B4A4_UNORM_PACK16, - R5G6B5_UNORM_PACK16, - B5G6R5_UNORM_PACK16, - R5G5B5A1_UNORM_PACK16, // A1 in the LOWER bit - B5G5R5A1_UNORM_PACK16, // A1 in the LOWER bit - A1R5G5B5_UNORM_PACK16, // A1 in the UPPER bit. - - R16_FLOAT, - R16G16_FLOAT, - R16G16B16A16_FLOAT, - - R32_FLOAT, - R32G32_FLOAT, - R32G32B32_FLOAT, - R32G32B32A32_FLOAT, - - // Block compression formats. - // These are modern names for DXT and friends, now patent free. - // https://msdn.microsoft.com/en-us/library/bb694531.aspx - BC1_RGBA_UNORM_BLOCK, - BC1_RGBA_SRGB_BLOCK, - BC2_UNORM_BLOCK, // 4-bit straight alpha + DXT1 color. Usually not worth using - BC2_SRGB_BLOCK, - BC3_UNORM_BLOCK, // 3-bit alpha with 2 ref values (+ magic) + DXT1 color - BC3_SRGB_BLOCK, - BC4_UNORM_BLOCK, // 1-channel, same storage as BC3 alpha - BC4_SNORM_BLOCK, - BC5_UNORM_BLOCK, // 2-channel RG, each has same storage as BC3 alpha - BC5_SNORM_BLOCK, - BC6H_UFLOAT_BLOCK, // TODO - BC6H_SFLOAT_BLOCK, - BC7_UNORM_BLOCK, // Highly advanced, very expensive to compress, very good quality. - BC7_SRGB_BLOCK, - - ETC1, - - S8, - D16, - D24_S8, - D32F, - D32F_S8, -}; - enum class ShaderStage { VERTEX, FRAGMENT, diff --git a/ext/native/thin3d/thin3d_d3d11.cpp b/ext/native/thin3d/thin3d_d3d11.cpp index a40d4c4bfc..72e73fe951 100644 --- a/ext/native/thin3d/thin3d_d3d11.cpp +++ b/ext/native/thin3d/thin3d_d3d11.cpp @@ -1310,56 +1310,6 @@ bool D3D11DrawContext::BlitFramebuffer(Framebuffer *srcfb, int srcX1, int srcY1, return false; } -// TODO: SSE/NEON -// Could also make C fake-simd for 64-bit, two 8888 pixels fit in a register :) -void ConvertFromRGBA8888(u8 *dst, u8 *src, u32 dstStride, u32 srcStride, u32 width, u32 height, Draw::DataFormat format) { - // Must skip stride in the cases below. Some games pack data into the cracks, like MotoGP. - const u32 *src32 = (const u32 *)src; - - if (format == Draw::DataFormat::R8G8B8A8_UNORM) { - u32 *dst32 = (u32 *)dst; - if (src == dst) { - return; - } else { - for (u32 y = 0; y < height; ++y) { - memcpy(dst32, src32, width * 4); - src32 += srcStride; - dst32 += dstStride; - } - } - } else { - // But here it shouldn't matter if they do intersect - u16 *dst16 = (u16 *)dst; - switch (format) { - case Draw::DataFormat::R5G6B5_UNORM_PACK16: // BGR 565 - for (u32 y = 0; y < height; ++y) { - ConvertRGBA8888ToRGB565(dst16, src32, width); - src32 += srcStride; - dst16 += dstStride; - } - break; - case Draw::DataFormat::A1R5G5B5_UNORM_PACK16: // ABGR 1555 - for (u32 y = 0; y < height; ++y) { - ConvertRGBA8888ToRGBA5551(dst16, src32, width); - src32 += srcStride; - dst16 += dstStride; - } - break; - case Draw::DataFormat::A4R4G4B4_UNORM_PACK16: // ABGR 4444 - for (u32 y = 0; y < height; ++y) { - ConvertRGBA8888ToRGBA4444(dst16, src32, width); - src32 += srcStride; - dst16 += dstStride; - } - break; - case Draw::DataFormat::R8G8B8A8_UNORM: - case Draw::DataFormat::UNDEFINED: - // Not possible. - break; - } - } -} - bool D3D11DrawContext::CopyFramebufferToMemorySync(Framebuffer *src, int channelBits, int bx, int by, int bw, int bh, Draw::DataFormat format, void *pixels, int pixelStride) { D3D11Framebuffer *fb = (D3D11Framebuffer *)src; diff --git a/ext/native/thin3d/thin3d_vulkan.cpp b/ext/native/thin3d/thin3d_vulkan.cpp index b6a8161baa..9ea34596c7 100644 --- a/ext/native/thin3d/thin3d_vulkan.cpp +++ b/ext/native/thin3d/thin3d_vulkan.cpp @@ -1288,8 +1288,6 @@ bool VKContext::BlitFramebuffer(Framebuffer *srcfb, int srcX1, int srcY1, int sr bool VKContext::CopyFramebufferToMemorySync(Framebuffer *srcfb, int channelBits, int x, int y, int w, int h, Draw::DataFormat format, void *pixels, int pixelStride) { VKFramebuffer *src = (VKFramebuffer *)srcfb; - VkFormat vkFormat = DataFormatToVulkan(format); - int aspectMask = 0; if (channelBits & FBChannel::FB_COLOR_BIT) { aspectMask |= VK_IMAGE_ASPECT_COLOR_BIT; @@ -1297,7 +1295,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, vkFormat, (uint8_t *)pixels, pixelStride); + renderManager_.CopyFramebufferToMemorySync(src->GetFB(), aspectMask, x, y, w, h, format, (uint8_t *)pixels, pixelStride); return true; }