diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt
index a2e9516f44..5989d47399 100644
--- a/Source/Core/Core/CMakeLists.txt
+++ b/Source/Core/Core/CMakeLists.txt
@@ -233,6 +233,7 @@ set(LIBS
sfml-network
sfml-system
videonull
+ videovulkan
videoogl
videosoftware
z
diff --git a/Source/Core/DolphinQt2/DolphinQt2.vcxproj b/Source/Core/DolphinQt2/DolphinQt2.vcxproj
index 5605bd5ce3..a754927eba 100644
--- a/Source/Core/DolphinQt2/DolphinQt2.vcxproj
+++ b/Source/Core/DolphinQt2/DolphinQt2.vcxproj
@@ -207,6 +207,9 @@
{570215b7-e32f-4438-95ae-c8d955f9fca3}
+
+ {29f29a19-f141-45ad-9679-5a2923b49da3}
+
diff --git a/Source/Core/DolphinWX/DolphinWX.vcxproj b/Source/Core/DolphinWX/DolphinWX.vcxproj
index 56b5ed630a..992ab457ea 100644
--- a/Source/Core/DolphinWX/DolphinWX.vcxproj
+++ b/Source/Core/DolphinWX/DolphinWX.vcxproj
@@ -238,6 +238,9 @@
{53A5391B-737E-49A8-BC8F-312ADA00736F}
+
+ {29F29A19-F141-45AD-9679-5A2923B49DA3}
+
{3de9ee35-3e91-4f27-a014-2866ad8c3fe3}
diff --git a/Source/Core/VideoBackends/CMakeLists.txt b/Source/Core/VideoBackends/CMakeLists.txt
index cb41b64b82..64344e43f8 100644
--- a/Source/Core/VideoBackends/CMakeLists.txt
+++ b/Source/Core/VideoBackends/CMakeLists.txt
@@ -1,4 +1,5 @@
add_subdirectory(OGL)
add_subdirectory(Null)
add_subdirectory(Software)
+add_subdirectory(Vulkan)
# TODO: Add other backends here!
diff --git a/Source/Core/VideoBackends/Vulkan/BoundingBox.cpp b/Source/Core/VideoBackends/Vulkan/BoundingBox.cpp
new file mode 100644
index 0000000000..eea4b7e673
--- /dev/null
+++ b/Source/Core/VideoBackends/Vulkan/BoundingBox.cpp
@@ -0,0 +1,249 @@
+// Copyright 2016 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#include
+
+#include "Common/Assert.h"
+
+#include "VideoBackends/Vulkan/BoundingBox.h"
+#include "VideoBackends/Vulkan/CommandBufferManager.h"
+#include "VideoBackends/Vulkan/ObjectCache.h"
+#include "VideoBackends/Vulkan/StagingBuffer.h"
+#include "VideoBackends/Vulkan/StateTracker.h"
+#include "VideoBackends/Vulkan/Util.h"
+#include "VideoBackends/Vulkan/VulkanContext.h"
+
+namespace Vulkan
+{
+BoundingBox::BoundingBox()
+{
+}
+
+BoundingBox::~BoundingBox()
+{
+ if (m_gpu_buffer != VK_NULL_HANDLE)
+ {
+ vkDestroyBuffer(g_vulkan_context->GetDevice(), m_gpu_buffer, nullptr);
+ vkFreeMemory(g_vulkan_context->GetDevice(), m_gpu_memory, nullptr);
+ }
+}
+
+bool BoundingBox::Initialize()
+{
+ if (!g_vulkan_context->SupportsBoundingBox())
+ {
+ WARN_LOG(VIDEO, "Vulkan: Bounding box is unsupported by your device.");
+ return true;
+ }
+
+ if (!CreateGPUBuffer())
+ return false;
+
+ if (!CreateReadbackBuffer())
+ return false;
+
+ return true;
+}
+
+void BoundingBox::Flush(StateTracker* state_tracker)
+{
+ if (m_gpu_buffer == VK_NULL_HANDLE)
+ return;
+
+ // Combine updates together, chances are the game would have written all 4.
+ bool updated_buffer = false;
+ for (size_t start = 0; start < 4; start++)
+ {
+ if (!m_values_dirty[start])
+ continue;
+
+ size_t count = 0;
+ std::array write_values;
+ for (; (start + count) < 4; count++)
+ {
+ if (!m_values_dirty[start + count])
+ break;
+
+ m_readback_buffer->Read((start + count) * sizeof(s32), &write_values[count], sizeof(s32),
+ false);
+ m_values_dirty[start + count] = false;
+ }
+
+ // We can't issue vkCmdUpdateBuffer within a render pass.
+ // However, the writes must be serialized, so we can't put it in the init buffer.
+ if (!updated_buffer)
+ {
+ state_tracker->EndRenderPass();
+
+ // Ensure GPU buffer is in a state where it can be transferred to.
+ Util::BufferMemoryBarrier(
+ g_command_buffer_mgr->GetCurrentCommandBuffer(), m_gpu_buffer,
+ VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, 0,
+ BUFFER_SIZE, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
+
+ updated_buffer = true;
+ }
+
+ vkCmdUpdateBuffer(g_command_buffer_mgr->GetCurrentCommandBuffer(), m_gpu_buffer,
+ start * sizeof(s32), count * sizeof(s32),
+ reinterpret_cast(write_values.data()));
+ }
+
+ // Restore fragment shader access to the buffer.
+ if (updated_buffer)
+ {
+ Util::BufferMemoryBarrier(
+ g_command_buffer_mgr->GetCurrentCommandBuffer(), m_gpu_buffer, VK_ACCESS_TRANSFER_WRITE_BIT,
+ VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, 0, BUFFER_SIZE,
+ VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT);
+ }
+
+ // We're now up-to-date.
+ m_valid = true;
+}
+
+void BoundingBox::Invalidate(StateTracker* state_tracker)
+{
+ if (m_gpu_buffer == VK_NULL_HANDLE)
+ return;
+
+ m_valid = false;
+}
+
+s32 BoundingBox::Get(StateTracker* state_tracker, size_t index)
+{
+ _assert_(index < NUM_VALUES);
+
+ if (!m_valid)
+ Readback(state_tracker);
+
+ s32 value;
+ m_readback_buffer->Read(index * sizeof(s32), &value, sizeof(value), false);
+ return value;
+}
+
+void BoundingBox::Set(StateTracker* state_tracker, size_t index, s32 value)
+{
+ _assert_(index < NUM_VALUES);
+
+ // If we're currently valid, update the stored value in both our cache and the GPU buffer.
+ if (m_valid)
+ {
+ // Skip when it hasn't changed.
+ s32 current_value;
+ m_readback_buffer->Read(index * sizeof(s32), ¤t_value, sizeof(current_value), false);
+ if (current_value == value)
+ return;
+ }
+
+ // Flag as dirty, and update values.
+ m_readback_buffer->Write(index * sizeof(s32), &value, sizeof(value), true);
+ m_values_dirty[index] = true;
+}
+
+bool BoundingBox::CreateGPUBuffer()
+{
+ VkBufferUsageFlags buffer_usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT |
+ VK_BUFFER_USAGE_TRANSFER_SRC_BIT |
+ VK_BUFFER_USAGE_TRANSFER_DST_BIT;
+ VkBufferCreateInfo info = {
+ VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, // VkStructureType sType
+ nullptr, // const void* pNext
+ 0, // VkBufferCreateFlags flags
+ BUFFER_SIZE, // VkDeviceSize size
+ buffer_usage, // VkBufferUsageFlags usage
+ VK_SHARING_MODE_EXCLUSIVE, // VkSharingMode sharingMode
+ 0, // uint32_t queueFamilyIndexCount
+ nullptr // const uint32_t* pQueueFamilyIndices
+ };
+
+ VkBuffer buffer;
+ VkResult res = vkCreateBuffer(g_vulkan_context->GetDevice(), &info, nullptr, &buffer);
+ if (res != VK_SUCCESS)
+ {
+ LOG_VULKAN_ERROR(res, "vkCreateBuffer failed: ");
+ return false;
+ }
+
+ VkMemoryRequirements memory_requirements;
+ vkGetBufferMemoryRequirements(g_vulkan_context->GetDevice(), buffer, &memory_requirements);
+
+ uint32_t memory_type_index = g_vulkan_context->GetMemoryType(memory_requirements.memoryTypeBits,
+ VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+ VkMemoryAllocateInfo memory_allocate_info = {
+ VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, // VkStructureType sType
+ nullptr, // const void* pNext
+ memory_requirements.size, // VkDeviceSize allocationSize
+ memory_type_index // uint32_t memoryTypeIndex
+ };
+ VkDeviceMemory memory;
+ res = vkAllocateMemory(g_vulkan_context->GetDevice(), &memory_allocate_info, nullptr, &memory);
+ if (res != VK_SUCCESS)
+ {
+ LOG_VULKAN_ERROR(res, "vkAllocateMemory failed: ");
+ vkDestroyBuffer(g_vulkan_context->GetDevice(), buffer, nullptr);
+ return false;
+ }
+
+ res = vkBindBufferMemory(g_vulkan_context->GetDevice(), buffer, memory, 0);
+ if (res != VK_SUCCESS)
+ {
+ LOG_VULKAN_ERROR(res, "vkBindBufferMemory failed: ");
+ vkDestroyBuffer(g_vulkan_context->GetDevice(), buffer, nullptr);
+ vkFreeMemory(g_vulkan_context->GetDevice(), memory, nullptr);
+ return false;
+ }
+
+ m_gpu_buffer = buffer;
+ m_gpu_memory = memory;
+ return true;
+}
+
+bool BoundingBox::CreateReadbackBuffer()
+{
+ m_readback_buffer = StagingBuffer::Create(STAGING_BUFFER_TYPE_READBACK, BUFFER_SIZE,
+ VK_BUFFER_USAGE_TRANSFER_DST_BIT);
+
+ if (!m_readback_buffer || !m_readback_buffer->Map())
+ return false;
+
+ return true;
+}
+
+void BoundingBox::Readback(StateTracker* state_tracker)
+{
+ // Can't be done within a render pass.
+ state_tracker->EndRenderPass();
+
+ // Ensure all writes are completed to the GPU buffer prior to the transfer.
+ Util::BufferMemoryBarrier(
+ g_command_buffer_mgr->GetCurrentCommandBuffer(), m_gpu_buffer,
+ VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT, 0,
+ BUFFER_SIZE, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
+ m_readback_buffer->PrepareForGPUWrite(g_command_buffer_mgr->GetCurrentCommandBuffer(),
+ VK_ACCESS_TRANSFER_WRITE_BIT,
+ VK_PIPELINE_STAGE_TRANSFER_BIT);
+
+ // Copy from GPU -> readback buffer.
+ VkBufferCopy region = {0, 0, BUFFER_SIZE};
+ vkCmdCopyBuffer(g_command_buffer_mgr->GetCurrentCommandBuffer(), m_gpu_buffer,
+ m_readback_buffer->GetBuffer(), 1, ®ion);
+
+ // Restore GPU buffer access.
+ Util::BufferMemoryBarrier(g_command_buffer_mgr->GetCurrentCommandBuffer(), m_gpu_buffer,
+ VK_ACCESS_TRANSFER_READ_BIT,
+ VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, 0, BUFFER_SIZE,
+ VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT);
+ m_readback_buffer->FlushGPUCache(g_command_buffer_mgr->GetCurrentCommandBuffer(),
+ VK_ACCESS_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
+
+ // Wait until these commands complete.
+ Util::ExecuteCurrentCommandsAndRestoreState(state_tracker, false, true);
+
+ // Cache is now valid.
+ m_readback_buffer->InvalidateCPUCache();
+ m_valid = true;
+}
+
+} // namespace Vulkan
diff --git a/Source/Core/VideoBackends/Vulkan/BoundingBox.h b/Source/Core/VideoBackends/Vulkan/BoundingBox.h
new file mode 100644
index 0000000000..fd6d0fcdd0
--- /dev/null
+++ b/Source/Core/VideoBackends/Vulkan/BoundingBox.h
@@ -0,0 +1,52 @@
+// Copyright 2016 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include
+#include
+
+#include "Common/CommonTypes.h"
+
+#include "VideoBackends/Vulkan/VulkanLoader.h"
+
+namespace Vulkan
+{
+class StagingBuffer;
+class StateTracker;
+
+class BoundingBox
+{
+public:
+ BoundingBox();
+ ~BoundingBox();
+
+ bool Initialize();
+
+ VkBuffer GetGPUBuffer() const { return m_gpu_buffer; }
+ VkDeviceSize GetGPUBufferOffset() const { return 0; }
+ VkDeviceSize GetGPUBufferSize() const { return BUFFER_SIZE; }
+ s32 Get(StateTracker* state_tracker, size_t index);
+ void Set(StateTracker* state_tracker, size_t index, s32 value);
+
+ void Invalidate(StateTracker* state_tracker);
+ void Flush(StateTracker* state_tracker);
+
+private:
+ bool CreateGPUBuffer();
+ bool CreateReadbackBuffer();
+ void Readback(StateTracker* state_tracker);
+
+ VkBuffer m_gpu_buffer = VK_NULL_HANDLE;
+ VkDeviceMemory m_gpu_memory = nullptr;
+
+ static const size_t NUM_VALUES = 4;
+ static const size_t BUFFER_SIZE = sizeof(u32) * NUM_VALUES;
+
+ std::unique_ptr m_readback_buffer;
+ std::array m_values_dirty = {};
+ bool m_valid = true;
+};
+
+} // namespace Vulkan
diff --git a/Source/Core/VideoBackends/Vulkan/CMakeLists.txt b/Source/Core/VideoBackends/Vulkan/CMakeLists.txt
new file mode 100644
index 0000000000..82d471ba54
--- /dev/null
+++ b/Source/Core/VideoBackends/Vulkan/CMakeLists.txt
@@ -0,0 +1,42 @@
+set(SRCS
+ BoundingBox.cpp
+ CommandBufferManager.cpp
+ FramebufferManager.cpp
+ ObjectCache.cpp
+ PaletteTextureConverter.cpp
+ PerfQuery.cpp
+ RasterFont.cpp
+ Renderer.cpp
+ ShaderCompiler.cpp
+ StateTracker.cpp
+ StagingBuffer.cpp
+ StagingTexture2D.cpp
+ StreamBuffer.cpp
+ SwapChain.cpp
+ Texture2D.cpp
+ TextureCache.cpp
+ TextureEncoder.cpp
+ Util.cpp
+ VertexFormat.cpp
+ VertexManager.cpp
+ VulkanContext.cpp
+ VulkanLoader.cpp
+ main.cpp
+)
+
+set(LIBS
+ videocommon
+ common
+)
+
+# Only include the Vulkan headers when building the Vulkan backend
+include_directories(${CMAKE_SOURCE_DIR}/Externals/Vulkan/Include)
+
+# Silence warnings on glslang by flagging it as a system include
+include_directories(SYSTEM ${CMAKE_SOURCE_DIR}/Externals/glslang/glslang/Public)
+include_directories(SYSTEM ${CMAKE_SOURCE_DIR}/Externals/glslang/SPIRV)
+
+# Link against glslang, the other necessary libraries are referenced by the executable.
+add_dolphin_library(videovulkan "${SRCS}" "${LIBS}")
+target_link_libraries(videovulkan glslang)
+
diff --git a/Source/Core/VideoBackends/Vulkan/CommandBufferManager.cpp b/Source/Core/VideoBackends/Vulkan/CommandBufferManager.cpp
new file mode 100644
index 0000000000..d3d84a3a04
--- /dev/null
+++ b/Source/Core/VideoBackends/Vulkan/CommandBufferManager.cpp
@@ -0,0 +1,457 @@
+// Copyright 2016 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#include
+#include
+
+#include "Common/Assert.h"
+#include "Common/CommonFuncs.h"
+#include "Common/MsgHandler.h"
+
+#include "VideoBackends/Vulkan/CommandBufferManager.h"
+#include "VideoBackends/Vulkan/VulkanContext.h"
+
+namespace Vulkan
+{
+CommandBufferManager::CommandBufferManager(bool use_threaded_submission)
+ : m_submit_semaphore(1, 1), m_use_threaded_submission(use_threaded_submission)
+{
+}
+
+CommandBufferManager::~CommandBufferManager()
+{
+ // If the worker thread is enabled, wait for it to exit.
+ if (m_use_threaded_submission)
+ {
+ // Wait for all command buffers to be consumed by the worker thread.
+ m_submit_semaphore.Wait();
+ m_submit_loop->Stop();
+ m_submit_thread.join();
+ }
+
+ vkDeviceWaitIdle(g_vulkan_context->GetDevice());
+
+ DestroyCommandBuffers();
+ DestroyCommandPool();
+}
+
+bool CommandBufferManager::Initialize()
+{
+ if (!CreateCommandPool())
+ return false;
+
+ if (!CreateCommandBuffers())
+ return false;
+
+ if (m_use_threaded_submission && !CreateSubmitThread())
+ return false;
+
+ return true;
+}
+
+bool CommandBufferManager::CreateCommandPool()
+{
+ VkCommandPoolCreateInfo info = {VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, nullptr,
+ VK_COMMAND_POOL_CREATE_TRANSIENT_BIT |
+ VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
+ g_vulkan_context->GetGraphicsQueueFamilyIndex()};
+
+ VkResult res =
+ vkCreateCommandPool(g_vulkan_context->GetDevice(), &info, nullptr, &m_command_pool);
+ if (res != VK_SUCCESS)
+ {
+ LOG_VULKAN_ERROR(res, "vkCreateCommandPool failed: ");
+ return false;
+ }
+
+ return true;
+}
+
+void CommandBufferManager::DestroyCommandPool()
+{
+ if (m_command_pool)
+ {
+ vkDestroyCommandPool(g_vulkan_context->GetDevice(), m_command_pool, nullptr);
+ m_command_pool = nullptr;
+ }
+}
+
+bool CommandBufferManager::CreateCommandBuffers()
+{
+ VkDevice device = g_vulkan_context->GetDevice();
+
+ for (FrameResources& resources : m_frame_resources)
+ {
+ resources.needs_fence_wait = false;
+
+ VkCommandBufferAllocateInfo allocate_info = {
+ VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, nullptr, m_command_pool,
+ VK_COMMAND_BUFFER_LEVEL_PRIMARY, static_cast(resources.command_buffers.size())};
+
+ VkResult res =
+ vkAllocateCommandBuffers(device, &allocate_info, resources.command_buffers.data());
+ if (res != VK_SUCCESS)
+ {
+ LOG_VULKAN_ERROR(res, "vkAllocateCommandBuffers failed: ");
+ return false;
+ }
+
+ VkFenceCreateInfo fence_info = {VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, nullptr,
+ VK_FENCE_CREATE_SIGNALED_BIT};
+
+ res = vkCreateFence(device, &fence_info, nullptr, &resources.fence);
+ if (res != VK_SUCCESS)
+ {
+ LOG_VULKAN_ERROR(res, "vkCreateFence failed: ");
+ return false;
+ }
+
+ // TODO: A better way to choose the number of descriptors.
+ VkDescriptorPoolSize pool_sizes[] = {{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 500000},
+ {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 500000},
+ {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 16},
+ {VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1024}};
+
+ VkDescriptorPoolCreateInfo pool_create_info = {VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
+ nullptr,
+ 0,
+ 100000, // tweak this
+ static_cast(ArraySize(pool_sizes)),
+ pool_sizes};
+
+ res = vkCreateDescriptorPool(device, &pool_create_info, nullptr, &resources.descriptor_pool);
+ if (res != VK_SUCCESS)
+ {
+ LOG_VULKAN_ERROR(res, "vkCreateDescriptorPool failed: ");
+ return false;
+ }
+ }
+
+ // Activate the first command buffer. ActivateCommandBuffer moves forward, so start with the last
+ m_current_frame = m_frame_resources.size() - 1;
+ ActivateCommandBuffer();
+ return true;
+}
+
+void CommandBufferManager::DestroyCommandBuffers()
+{
+ VkDevice device = g_vulkan_context->GetDevice();
+
+ for (FrameResources& resources : m_frame_resources)
+ {
+ for (const auto& it : resources.cleanup_resources)
+ it.destroy_callback(device, it.object);
+ resources.cleanup_resources.clear();
+
+ if (resources.fence != VK_NULL_HANDLE)
+ {
+ vkDestroyFence(device, resources.fence, nullptr);
+ resources.fence = VK_NULL_HANDLE;
+ }
+ if (resources.descriptor_pool != VK_NULL_HANDLE)
+ {
+ vkDestroyDescriptorPool(device, resources.descriptor_pool, nullptr);
+ resources.descriptor_pool = VK_NULL_HANDLE;
+ }
+ if (resources.command_buffers[0] != VK_NULL_HANDLE)
+ {
+ vkFreeCommandBuffers(device, m_command_pool,
+ static_cast(resources.command_buffers.size()),
+ resources.command_buffers.data());
+
+ resources.command_buffers.fill(VK_NULL_HANDLE);
+ }
+ }
+}
+
+VkDescriptorSet CommandBufferManager::AllocateDescriptorSet(VkDescriptorSetLayout set_layout)
+{
+ VkDescriptorSetAllocateInfo allocate_info = {
+ VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, nullptr,
+ m_frame_resources[m_current_frame].descriptor_pool, 1, &set_layout};
+
+ VkDescriptorSet descriptor_set;
+ VkResult res =
+ vkAllocateDescriptorSets(g_vulkan_context->GetDevice(), &allocate_info, &descriptor_set);
+ if (res != VK_SUCCESS)
+ {
+ // Failing to allocate a descriptor set is not a fatal error, we can
+ // recover by moving to the next command buffer.
+ return VK_NULL_HANDLE;
+ }
+
+ return descriptor_set;
+}
+
+bool CommandBufferManager::CreateSubmitThread()
+{
+ m_submit_loop = std::make_unique();
+ m_submit_thread = std::thread([this]() {
+ m_submit_loop->Run([this]() {
+ PendingCommandBufferSubmit submit;
+ {
+ std::lock_guard guard(m_pending_submit_lock);
+ if (m_pending_submits.empty())
+ {
+ m_submit_loop->AllowSleep();
+ return;
+ }
+
+ submit = m_pending_submits.front();
+ m_pending_submits.pop_front();
+ }
+
+ SubmitCommandBuffer(submit.index, submit.wait_semaphore, submit.signal_semaphore,
+ submit.present_swap_chain, submit.present_image_index);
+ });
+ });
+
+ return true;
+}
+
+void CommandBufferManager::PrepareToSubmitCommandBuffer()
+{
+ // Grab the semaphore before submitting command buffer either on-thread or off-thread.
+ // This prevents a race from occurring where a second command buffer is executed
+ // before the worker thread has woken and executed the first one yet.
+ m_submit_semaphore.Wait();
+}
+
+void CommandBufferManager::WaitForWorkerThreadIdle()
+{
+ // Drain the semaphore, then allow another request in the future.
+ m_submit_semaphore.Wait();
+ m_submit_semaphore.Post();
+}
+
+void CommandBufferManager::WaitForGPUIdle()
+{
+ WaitForWorkerThreadIdle();
+ vkDeviceWaitIdle(g_vulkan_context->GetDevice());
+}
+
+void CommandBufferManager::WaitForFence(VkFence fence)
+{
+ // Find the command buffer that this fence corresponds to.
+ size_t command_buffer_index = 0;
+ for (; command_buffer_index < m_frame_resources.size(); command_buffer_index++)
+ {
+ if (m_frame_resources[command_buffer_index].fence == fence)
+ break;
+ }
+ _assert_(command_buffer_index < m_frame_resources.size());
+
+ // Has this command buffer already been waited for?
+ if (!m_frame_resources[command_buffer_index].needs_fence_wait)
+ return;
+
+ // Wait for this command buffer to be completed.
+ VkResult res =
+ vkWaitForFences(g_vulkan_context->GetDevice(), 1,
+ &m_frame_resources[command_buffer_index].fence, VK_TRUE, UINT64_MAX);
+ if (res != VK_SUCCESS)
+ LOG_VULKAN_ERROR(res, "vkWaitForFences failed: ");
+
+ // Immediately fire callbacks and cleanups, since the commands has been completed.
+ m_frame_resources[command_buffer_index].needs_fence_wait = false;
+ OnCommandBufferExecuted(command_buffer_index);
+}
+
+void CommandBufferManager::SubmitCommandBuffer(bool submit_on_worker_thread,
+ VkSemaphore wait_semaphore,
+ VkSemaphore signal_semaphore,
+ VkSwapchainKHR present_swap_chain,
+ uint32_t present_image_index)
+{
+ FrameResources& resources = m_frame_resources[m_current_frame];
+
+ // Fire fence tracking callbacks. This can't happen on the worker thread.
+ // We invoke these before submitting so that any last-minute commands can be added.
+ for (const auto& iter : m_fence_point_callbacks)
+ iter.second.first(resources.command_buffers[1], resources.fence);
+
+ // End the current command buffer.
+ for (VkCommandBuffer command_buffer : resources.command_buffers)
+ {
+ VkResult res = vkEndCommandBuffer(command_buffer);
+ if (res != VK_SUCCESS)
+ {
+ LOG_VULKAN_ERROR(res, "vkEndCommandBuffer failed: ");
+ PanicAlert("Failed to end command buffer");
+ }
+ }
+
+ // This command buffer now has commands, so can't be re-used without waiting.
+ resources.needs_fence_wait = true;
+
+ // Submitting off-thread?
+ if (m_use_threaded_submission && submit_on_worker_thread)
+ {
+ // Push to the pending submit queue.
+ {
+ std::lock_guard guard(m_pending_submit_lock);
+ m_pending_submits.push_back({m_current_frame, wait_semaphore, signal_semaphore,
+ present_swap_chain, present_image_index});
+ }
+
+ // Wake up the worker thread for a single iteration.
+ m_submit_loop->Wakeup();
+ }
+ else
+ {
+ // Pass through to normal submission path.
+ SubmitCommandBuffer(m_current_frame, wait_semaphore, signal_semaphore, present_swap_chain,
+ present_image_index);
+ }
+}
+
+void CommandBufferManager::SubmitCommandBuffer(size_t index, VkSemaphore wait_semaphore,
+ VkSemaphore signal_semaphore,
+ VkSwapchainKHR present_swap_chain,
+ uint32_t present_image_index)
+{
+ FrameResources& resources = m_frame_resources[index];
+
+ // This may be executed on the worker thread, so don't modify any state of the manager class.
+ uint32_t wait_bits = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+ VkSubmitInfo submit_info = {VK_STRUCTURE_TYPE_SUBMIT_INFO,
+ nullptr,
+ 0,
+ nullptr,
+ &wait_bits,
+ static_cast(resources.command_buffers.size()),
+ resources.command_buffers.data(),
+ 0,
+ nullptr};
+
+ if (wait_semaphore != VK_NULL_HANDLE)
+ {
+ submit_info.pWaitSemaphores = &wait_semaphore;
+ submit_info.waitSemaphoreCount = 1;
+ }
+
+ if (signal_semaphore != VK_NULL_HANDLE)
+ {
+ submit_info.signalSemaphoreCount = 1;
+ submit_info.pSignalSemaphores = &signal_semaphore;
+ }
+
+ VkResult res =
+ vkQueueSubmit(g_vulkan_context->GetGraphicsQueue(), 1, &submit_info, resources.fence);
+ if (res != VK_SUCCESS)
+ {
+ LOG_VULKAN_ERROR(res, "vkQueueSubmit failed: ");
+ PanicAlert("Failed to submit command buffer.");
+ }
+
+ // Do we have a swap chain to present?
+ if (present_swap_chain != VK_NULL_HANDLE)
+ {
+ // Should have a signal semaphore.
+ _assert_(signal_semaphore != VK_NULL_HANDLE);
+ VkPresentInfoKHR present_info = {VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
+ nullptr,
+ 1,
+ &signal_semaphore,
+ 1,
+ &present_swap_chain,
+ &present_image_index,
+ nullptr};
+
+ res = vkQueuePresentKHR(g_vulkan_context->GetGraphicsQueue(), &present_info);
+ if (res != VK_SUCCESS && res != VK_ERROR_OUT_OF_DATE_KHR && res != VK_SUBOPTIMAL_KHR)
+ LOG_VULKAN_ERROR(res, "vkQueuePresentKHR failed: ");
+ }
+
+ // Command buffer has been queued, so permit the next one.
+ m_submit_semaphore.Post();
+}
+
+void CommandBufferManager::OnCommandBufferExecuted(size_t index)
+{
+ FrameResources& resources = m_frame_resources[index];
+
+ // Fire fence tracking callbacks.
+ for (const auto& iter : m_fence_point_callbacks)
+ iter.second.second(resources.fence);
+
+ // Clean up all objects pending destruction on this command buffer
+ for (const auto& it : resources.cleanup_resources)
+ it.destroy_callback(g_vulkan_context->GetDevice(), it.object);
+ resources.cleanup_resources.clear();
+}
+
+void CommandBufferManager::ActivateCommandBuffer()
+{
+ // Move to the next command buffer.
+ m_current_frame = (m_current_frame + 1) % NUM_COMMAND_BUFFERS;
+ FrameResources& resources = m_frame_resources[m_current_frame];
+
+ // Wait for the GPU to finish with all resources for this command buffer.
+ if (resources.needs_fence_wait)
+ {
+ VkResult res =
+ vkWaitForFences(g_vulkan_context->GetDevice(), 1, &resources.fence, true, UINT64_MAX);
+ if (res != VK_SUCCESS)
+ LOG_VULKAN_ERROR(res, "vkWaitForFences failed: ");
+
+ OnCommandBufferExecuted(m_current_frame);
+ }
+
+ // Reset fence to unsignaled before starting.
+ VkResult res = vkResetFences(g_vulkan_context->GetDevice(), 1, &resources.fence);
+ if (res != VK_SUCCESS)
+ LOG_VULKAN_ERROR(res, "vkResetFences failed: ");
+
+ // Reset command buffer to beginning since we can re-use the memory now
+ VkCommandBufferBeginInfo begin_info = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, nullptr,
+ VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, nullptr};
+ for (VkCommandBuffer command_buffer : resources.command_buffers)
+ {
+ res = vkResetCommandBuffer(command_buffer, 0);
+ if (res != VK_SUCCESS)
+ LOG_VULKAN_ERROR(res, "vkResetCommandBuffer failed: ");
+
+ res = vkBeginCommandBuffer(command_buffer, &begin_info);
+ if (res != VK_SUCCESS)
+ LOG_VULKAN_ERROR(res, "vkBeginCommandBuffer failed: ");
+ }
+
+ // Also can do the same for the descriptor pools
+ res = vkResetDescriptorPool(g_vulkan_context->GetDevice(), resources.descriptor_pool, 0);
+ if (res != VK_SUCCESS)
+ LOG_VULKAN_ERROR(res, "vkResetDescriptorPool failed: ");
+}
+
+void CommandBufferManager::ExecuteCommandBuffer(bool submit_off_thread, bool wait_for_completion)
+{
+ VkFence pending_fence = GetCurrentCommandBufferFence();
+
+ // If we're waiting for completion, don't bother waking the worker thread.
+ PrepareToSubmitCommandBuffer();
+ SubmitCommandBuffer((submit_off_thread && wait_for_completion));
+ ActivateCommandBuffer();
+
+ if (wait_for_completion)
+ WaitForFence(pending_fence);
+}
+
+void CommandBufferManager::AddFencePointCallback(
+ const void* key, const CommandBufferQueuedCallback& queued_callback,
+ const CommandBufferExecutedCallback& executed_callback)
+{
+ // Shouldn't be adding twice.
+ _assert_(m_fence_point_callbacks.find(key) == m_fence_point_callbacks.end());
+ m_fence_point_callbacks.emplace(key, std::make_pair(queued_callback, executed_callback));
+}
+
+void CommandBufferManager::RemoveFencePointCallback(const void* key)
+{
+ auto iter = m_fence_point_callbacks.find(key);
+ _assert_(iter != m_fence_point_callbacks.end());
+ m_fence_point_callbacks.erase(iter);
+}
+
+std::unique_ptr g_command_buffer_mgr;
+}
diff --git a/Source/Core/VideoBackends/Vulkan/CommandBufferManager.h b/Source/Core/VideoBackends/Vulkan/CommandBufferManager.h
new file mode 100644
index 0000000000..73ab126f97
--- /dev/null
+++ b/Source/Core/VideoBackends/Vulkan/CommandBufferManager.h
@@ -0,0 +1,154 @@
+// Copyright 2016 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include
+#include
+#include
+#include