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.