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/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/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/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 c101c563bf..f4028a0bfa 100644 --- a/ext/native/thin3d/VulkanQueueRunner.cpp +++ b/ext/native/thin3d/VulkanQueueRunner.cpp @@ -1,13 +1,41 @@ +#include "DataFormat.h" #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 +44,6 @@ void VulkanQueueRunner::DestroyDeviceObjects() { vkDestroyRenderPass(device, backbufferRenderPass_, nullptr); } - void VulkanQueueRunner::InitBackbufferRenderPass() { VkResult U_ASSERT_ONLY res; @@ -167,7 +194,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); + } + + 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 = 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(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. + + 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 0b2a38064d..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; @@ -127,8 +127,8 @@ struct VKRStep { VkFilter filter; } blit; struct { + int aspectMask; VKRFramebuffer *src; - void *destPtr; VkRect2D srcRect; } readback; }; @@ -156,6 +156,8 @@ public: return (int)depth * 3 + (int)color; } + void CopyReadbackBuffer(int width, int height, Draw::DataFormat destFormat, int pixelStride, uint8_t *pixels); + private: void InitBackbufferRenderPass(); void InitRenderpasses(); @@ -164,6 +166,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 +180,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 77b9f5f473..e74eb71853 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) { @@ -287,9 +289,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 +301,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 +327,22 @@ 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) { + VKRStep *step = new VKRStep{ VKRStepType::READBACK }; + step->readback.aspectMask = aspectBits; + 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 FlushSyfnc so the pixels are guaranteed to be ready in CPU-accessible VRAM. + 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. @@ -509,13 +525,10 @@ 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]; - if (frameData.hasInitCommands) { - vkEndCommandBuffer(frameData.initCmd); - } if (!useThread) { frameData.steps = std::move(steps_); Run(curFrame); @@ -529,48 +542,45 @@ 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::BeginSubmitFrame(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); + VkCommandBufferBeginInfo begin{ VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }; + begin.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + res = vkBeginCommandBuffer(frameData.mainCmd, &begin); + + assert(res == VK_SUCCESS); + + // 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); + + queueRunner_.SetBackbuffer(framebuffers_[frameData.curSwapchainImage]); + + frameData.hasBegun = true; + } +} + +void VulkanRenderManager::Submit(int frame, bool triggerFence) { + FrameData &frameData = frameData_[frame]; + if (frameData.hasInitCommands) { + VkResult res = vkEndCommandBuffer(frameData.initCmd); + assert(res == VK_SUCCESS); + } + + VkResult res = vkEndCommandBuffer(frameData.mainCmd); assert(res == VK_SUCCESS); - VkCommandBuffer cmd = frameData.mainCmd; - - VkCommandBufferBeginInfo begin{ VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }; - begin.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; - res = vkBeginCommandBuffer(cmd, &begin); - - assert(res == VK_SUCCESS); - - // 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(); - - insideFrame_ = false; - - TransitionToPresent(frameData.mainCmd, swapchainImages_[curSwapchainImage].image); - - 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; + cmdBufs.reserve(2); if (frameData.hasInitCommands) { cmdBufs.push_back(frameData.initCmd); frameData.hasInitCommands = false; @@ -579,16 +589,20 @@ void VulkanRenderManager::Run(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) { @@ -597,16 +611,27 @@ void VulkanRenderManager::Run(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, true); VkSwapchainKHR swapchain = vulkan_->GetSwapchain(); 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; - 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) { @@ -614,6 +639,49 @@ void VulkanRenderManager::Run(int frame) { } else { assert(res == VK_SUCCESS); } +} + +void VulkanRenderManager::Run(int frame) { + VkDevice device = vulkan_->GetDevice(); + + BeginSubmitFrame(frame); + + FrameData &frameData = frameData_[frame]; + auto &stepsOnThread = frameData_[frame].steps; + VkCommandBuffer cmd = frameData.mainCmd; + queueRunner_.RunSteps(cmd, stepsOnThread); + stepsOnThread.clear(); + + EndSubmitFrame(frame); + VLOG("PULL: Finished running frame %d", frame); } + +void VulkanRenderManager::FlushSync() { + int frame = vulkan_->GetCurFrame(); + 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, 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{ + VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + nullptr, + VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT + }; + 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 f0c1a06c04..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 @@ -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); @@ -78,15 +79,13 @@ public: // Makes sure that the GPU has caught up enough that we can start writing buffers of this frame again. void BeginFrame(); - // Can run on a different thread! Just make sure to use BeginFrameWrites. - void Flush(); + // 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 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); + 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); @@ -186,6 +185,13 @@ public: private: void InitBackbufferFramebuffers(int width, int height); void InitDepthStencilBuffer(VkCommandBuffer cmd); // Used for non-buffered rendering. + void BeginSubmitFrame(int frame); + void EndSubmitFrame(int frame); + void Submit(int frame, bool triggerFence); + + // Bad for performance but sometimes necessary for synchronous CPU readbacks (screenshots and whatnot). + void FlushSync(); + // Permanent objects VkSemaphore acquireSemaphore_; VkSemaphore renderingCompleteSemaphore_; @@ -209,6 +215,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.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 b0f0d08a9b..9ea34596c7 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; @@ -757,7 +758,7 @@ void VKContext::EndFrame() { // Stop collecting data in the frame's data pushbuffer. push_->End(); - renderManager_.Flush(); + renderManager_.Finish(); frameNum_++; if (frameNum_ >= vulkan_->GetInflightFrames()) @@ -1284,6 +1285,21 @@ 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, format, (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.