From d7fd892fdec4261d4f7c24545bd448e757ca3b0f Mon Sep 17 00:00:00 2001 From: Shawn Hoffman Date: Tue, 15 Sep 2020 05:43:41 -0700 Subject: normalize common filenames in VideoBackends/Vulkan --- Source/Core/VideoBackends/Vulkan/BoundingBox.cpp | 254 -------- Source/Core/VideoBackends/Vulkan/BoundingBox.h | 49 -- Source/Core/VideoBackends/Vulkan/CMakeLists.txt | 34 +- Source/Core/VideoBackends/Vulkan/ObjectCache.cpp | 4 +- Source/Core/VideoBackends/Vulkan/PerfQuery.cpp | 217 ------- Source/Core/VideoBackends/Vulkan/PerfQuery.h | 60 -- Source/Core/VideoBackends/Vulkan/Renderer.cpp | 652 --------------------- Source/Core/VideoBackends/Vulkan/Renderer.h | 108 ---- Source/Core/VideoBackends/Vulkan/StateTracker.cpp | 4 +- Source/Core/VideoBackends/Vulkan/StreamBuffer.cpp | 328 ----------- Source/Core/VideoBackends/Vulkan/StreamBuffer.h | 58 -- Source/Core/VideoBackends/Vulkan/SwapChain.cpp | 583 ------------------ Source/Core/VideoBackends/Vulkan/SwapChain.h | 114 ---- Source/Core/VideoBackends/Vulkan/VKBoundingBox.cpp | 254 ++++++++ Source/Core/VideoBackends/Vulkan/VKBoundingBox.h | 49 ++ Source/Core/VideoBackends/Vulkan/VKMain.cpp | 361 ++++++++++++ Source/Core/VideoBackends/Vulkan/VKPerfQuery.cpp | 217 +++++++ Source/Core/VideoBackends/Vulkan/VKPerfQuery.h | 60 ++ Source/Core/VideoBackends/Vulkan/VKPipeline.cpp | 2 +- Source/Core/VideoBackends/Vulkan/VKRenderer.cpp | 652 +++++++++++++++++++++ Source/Core/VideoBackends/Vulkan/VKRenderer.h | 108 ++++ .../Core/VideoBackends/Vulkan/VKStreamBuffer.cpp | 328 +++++++++++ Source/Core/VideoBackends/Vulkan/VKStreamBuffer.h | 58 ++ Source/Core/VideoBackends/Vulkan/VKSwapChain.cpp | 583 ++++++++++++++++++ Source/Core/VideoBackends/Vulkan/VKSwapChain.h | 114 ++++ Source/Core/VideoBackends/Vulkan/VKTexture.cpp | 4 +- .../Core/VideoBackends/Vulkan/VKVertexFormat.cpp | 129 ++++ Source/Core/VideoBackends/Vulkan/VKVertexFormat.h | 38 ++ .../Core/VideoBackends/Vulkan/VKVertexManager.cpp | 397 +++++++++++++ Source/Core/VideoBackends/Vulkan/VKVertexManager.h | 58 ++ Source/Core/VideoBackends/Vulkan/VertexFormat.cpp | 129 ---- Source/Core/VideoBackends/Vulkan/VertexFormat.h | 38 -- Source/Core/VideoBackends/Vulkan/VertexManager.cpp | 397 ------------- Source/Core/VideoBackends/Vulkan/VertexManager.h | 58 -- Source/Core/VideoBackends/Vulkan/Vulkan.vcxproj | 34 +- Source/Core/VideoBackends/Vulkan/main.cpp | 361 ------------ 36 files changed, 3447 insertions(+), 3447 deletions(-) delete mode 100644 Source/Core/VideoBackends/Vulkan/BoundingBox.cpp delete mode 100644 Source/Core/VideoBackends/Vulkan/BoundingBox.h delete mode 100644 Source/Core/VideoBackends/Vulkan/PerfQuery.cpp delete mode 100644 Source/Core/VideoBackends/Vulkan/PerfQuery.h delete mode 100644 Source/Core/VideoBackends/Vulkan/Renderer.cpp delete mode 100644 Source/Core/VideoBackends/Vulkan/Renderer.h delete mode 100644 Source/Core/VideoBackends/Vulkan/StreamBuffer.cpp delete mode 100644 Source/Core/VideoBackends/Vulkan/StreamBuffer.h delete mode 100644 Source/Core/VideoBackends/Vulkan/SwapChain.cpp delete mode 100644 Source/Core/VideoBackends/Vulkan/SwapChain.h create mode 100644 Source/Core/VideoBackends/Vulkan/VKBoundingBox.cpp create mode 100644 Source/Core/VideoBackends/Vulkan/VKBoundingBox.h create mode 100644 Source/Core/VideoBackends/Vulkan/VKMain.cpp create mode 100644 Source/Core/VideoBackends/Vulkan/VKPerfQuery.cpp create mode 100644 Source/Core/VideoBackends/Vulkan/VKPerfQuery.h create mode 100644 Source/Core/VideoBackends/Vulkan/VKRenderer.cpp create mode 100644 Source/Core/VideoBackends/Vulkan/VKRenderer.h create mode 100644 Source/Core/VideoBackends/Vulkan/VKStreamBuffer.cpp create mode 100644 Source/Core/VideoBackends/Vulkan/VKStreamBuffer.h create mode 100644 Source/Core/VideoBackends/Vulkan/VKSwapChain.cpp create mode 100644 Source/Core/VideoBackends/Vulkan/VKSwapChain.h create mode 100644 Source/Core/VideoBackends/Vulkan/VKVertexFormat.cpp create mode 100644 Source/Core/VideoBackends/Vulkan/VKVertexFormat.h create mode 100644 Source/Core/VideoBackends/Vulkan/VKVertexManager.cpp create mode 100644 Source/Core/VideoBackends/Vulkan/VKVertexManager.h delete mode 100644 Source/Core/VideoBackends/Vulkan/VertexFormat.cpp delete mode 100644 Source/Core/VideoBackends/Vulkan/VertexFormat.h delete mode 100644 Source/Core/VideoBackends/Vulkan/VertexManager.cpp delete mode 100644 Source/Core/VideoBackends/Vulkan/VertexManager.h delete mode 100644 Source/Core/VideoBackends/Vulkan/main.cpp (limited to 'Source') diff --git a/Source/Core/VideoBackends/Vulkan/BoundingBox.cpp b/Source/Core/VideoBackends/Vulkan/BoundingBox.cpp deleted file mode 100644 index d16cabfd30..0000000000 --- a/Source/Core/VideoBackends/Vulkan/BoundingBox.cpp +++ /dev/null @@ -1,254 +0,0 @@ -// Copyright 2016 Dolphin Emulator Project -// Licensed under GPLv2+ -// Refer to the license.txt file included. - -#include - -#include "Common/Assert.h" -#include "Common/Logging/Log.h" - -#include "VideoBackends/Vulkan/BoundingBox.h" -#include "VideoBackends/Vulkan/CommandBufferManager.h" -#include "VideoBackends/Vulkan/ObjectCache.h" -#include "VideoBackends/Vulkan/Renderer.h" -#include "VideoBackends/Vulkan/StagingBuffer.h" -#include "VideoBackends/Vulkan/StateTracker.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_ActiveConfig.backend_info.bSupportsBBox) - { - WARN_LOG_FMT(VIDEO, "Vulkan: Bounding box is unsupported by your device."); - return true; - } - - if (!CreateGPUBuffer()) - return false; - - if (!CreateReadbackBuffer()) - return false; - - // Bind bounding box to state tracker - StateTracker::GetInstance()->SetSSBO(m_gpu_buffer, 0, BUFFER_SIZE); - return true; -} - -void BoundingBox::Flush() -{ - 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) - { - StateTracker::GetInstance()->EndRenderPass(); - - // Ensure GPU buffer is in a state where it can be transferred to. - StagingBuffer::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) - { - StagingBuffer::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_FRAGMENT_SHADER_BIT); - } - - // We're now up-to-date. - m_valid = true; -} - -void BoundingBox::Invalidate() -{ - if (m_gpu_buffer == VK_NULL_HANDLE) - return; - - m_valid = false; -} - -s32 BoundingBox::Get(size_t index) -{ - ASSERT(index < NUM_VALUES); - - if (!m_valid) - Readback(); - - s32 value; - m_readback_buffer->Read(index * sizeof(s32), &value, sizeof(value), false); - return value; -} - -void BoundingBox::Set(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, false) - .value_or(0); - 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() -{ - // Can't be done within a render pass. - StateTracker::GetInstance()->EndRenderPass(); - - // Ensure all writes are completed to the GPU buffer prior to the transfer. - StagingBuffer::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_FRAGMENT_SHADER_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. - StagingBuffer::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_FRAGMENT_SHADER_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. - Renderer::GetInstance()->ExecuteCommandBuffer(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 deleted file mode 100644 index e5fda1066a..0000000000 --- a/Source/Core/VideoBackends/Vulkan/BoundingBox.h +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2016 Dolphin Emulator Project -// Licensed under GPLv2+ -// Refer to the license.txt file included. - -#pragma once - -#include -#include -#include - -#include "Common/CommonTypes.h" - -#include "VideoBackends/Vulkan/VulkanLoader.h" - -namespace Vulkan -{ -class StagingBuffer; - -class BoundingBox -{ -public: - BoundingBox(); - ~BoundingBox(); - - bool Initialize(); - - s32 Get(size_t index); - void Set(size_t index, s32 value); - - void Invalidate(); - void Flush(); - -private: - bool CreateGPUBuffer(); - bool CreateReadbackBuffer(); - void Readback(); - - VkBuffer m_gpu_buffer = VK_NULL_HANDLE; - VkDeviceMemory m_gpu_memory = VK_NULL_HANDLE; - - 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 index 40966f191d..74127f3aba 100644 --- a/Source/Core/VideoBackends/Vulkan/CMakeLists.txt +++ b/Source/Core/VideoBackends/Vulkan/CMakeLists.txt @@ -1,36 +1,36 @@ add_library(videovulkan - BoundingBox.cpp - BoundingBox.h CommandBufferManager.cpp CommandBufferManager.h Constants.h - main.cpp ObjectCache.cpp ObjectCache.h - PerfQuery.cpp - PerfQuery.h - Renderer.cpp - Renderer.h ShaderCompiler.cpp ShaderCompiler.h - StateTracker.cpp - StateTracker.h StagingBuffer.cpp StagingBuffer.h - StreamBuffer.cpp - StreamBuffer.h - SwapChain.cpp - SwapChain.h - VertexFormat.cpp - VertexFormat.h - VertexManager.cpp - VertexManager.h + StateTracker.cpp + StateTracker.h + VKBoundingBox.cpp + VKBoundingBox.h + VKMain.cpp + VKPerfQuery.cpp + VKPerfQuery.h VKPipeline.cpp VKPipeline.h + VKRenderer.cpp + VKRenderer.h VKShader.cpp VKShader.h + VKStreamBuffer.cpp + VKStreamBuffer.h + VKSwapChain.cpp + VKSwapChain.h VKTexture.cpp VKTexture.h + VKVertexFormat.cpp + VKVertexFormat.h + VKVertexManager.cpp + VKVertexManager.h VulkanContext.cpp VulkanContext.h VulkanLoader.cpp diff --git a/Source/Core/VideoBackends/Vulkan/ObjectCache.cpp b/Source/Core/VideoBackends/Vulkan/ObjectCache.cpp index 8a532e1f24..645a57b7e6 100644 --- a/Source/Core/VideoBackends/Vulkan/ObjectCache.cpp +++ b/Source/Core/VideoBackends/Vulkan/ObjectCache.cpp @@ -18,9 +18,9 @@ #include "VideoBackends/Vulkan/CommandBufferManager.h" #include "VideoBackends/Vulkan/ShaderCompiler.h" -#include "VideoBackends/Vulkan/StreamBuffer.h" +#include "VideoBackends/Vulkan/VKStreamBuffer.h" #include "VideoBackends/Vulkan/VKTexture.h" -#include "VideoBackends/Vulkan/VertexFormat.h" +#include "VideoBackends/Vulkan/VKVertexFormat.h" #include "VideoBackends/Vulkan/VulkanContext.h" #include "VideoCommon/VideoCommon.h" diff --git a/Source/Core/VideoBackends/Vulkan/PerfQuery.cpp b/Source/Core/VideoBackends/Vulkan/PerfQuery.cpp deleted file mode 100644 index 4b449fdf7d..0000000000 --- a/Source/Core/VideoBackends/Vulkan/PerfQuery.cpp +++ /dev/null @@ -1,217 +0,0 @@ -// Copyright 2016 Dolphin Emulator Project -// Licensed under GPLv2+ -// Refer to the license.txt file included. - -#include "VideoBackends/Vulkan/PerfQuery.h" - -#include -#include -#include - -#include "Common/Assert.h" -#include "Common/Logging/Log.h" -#include "Common/MsgHandler.h" - -#include "VideoBackends/Vulkan/CommandBufferManager.h" -#include "VideoBackends/Vulkan/Renderer.h" -#include "VideoBackends/Vulkan/StateTracker.h" -#include "VideoBackends/Vulkan/VulkanContext.h" -#include "VideoCommon/VideoCommon.h" - -namespace Vulkan -{ -PerfQuery::PerfQuery() = default; - -PerfQuery::~PerfQuery() -{ - if (m_query_pool != VK_NULL_HANDLE) - vkDestroyQueryPool(g_vulkan_context->GetDevice(), m_query_pool, nullptr); -} - -bool PerfQuery::Initialize() -{ - if (!CreateQueryPool()) - { - PanicAlertFmt("Failed to create query pool"); - return false; - } - - return true; -} - -void PerfQuery::EnableQuery(PerfQueryGroup type) -{ - // Block if there are no free slots. - // Otherwise, try to keep half of them available. - if (m_query_count > m_query_buffer.size() / 2) - PartialFlush(m_query_count == PERF_QUERY_BUFFER_SIZE); - - // Ensure command buffer is ready to go before beginning the query, that way we don't submit - // a buffer with open queries. - StateTracker::GetInstance()->Bind(); - - if (type == PQG_ZCOMP_ZCOMPLOC || type == PQG_ZCOMP) - { - ActiveQuery& entry = m_query_buffer[m_query_next_pos]; - DEBUG_ASSERT(!entry.has_value); - entry.has_value = true; - - // Use precise queries if supported, otherwise boolean (which will be incorrect). - VkQueryControlFlags flags = - g_vulkan_context->SupportsPreciseOcclusionQueries() ? VK_QUERY_CONTROL_PRECISE_BIT : 0; - - // Ensure the query starts within a render pass. - StateTracker::GetInstance()->BeginRenderPass(); - vkCmdBeginQuery(g_command_buffer_mgr->GetCurrentCommandBuffer(), m_query_pool, m_query_next_pos, - flags); - } -} - -void PerfQuery::DisableQuery(PerfQueryGroup type) -{ - if (type == PQG_ZCOMP_ZCOMPLOC || type == PQG_ZCOMP) - { - vkCmdEndQuery(g_command_buffer_mgr->GetCurrentCommandBuffer(), m_query_pool, m_query_next_pos); - m_query_next_pos = (m_query_next_pos + 1) % PERF_QUERY_BUFFER_SIZE; - m_query_count++; - } -} - -void PerfQuery::ResetQuery() -{ - m_query_count = 0; - m_query_readback_pos = 0; - m_query_next_pos = 0; - std::fill(std::begin(m_results), std::end(m_results), 0); - - // Reset entire query pool, ensuring all queries are ready to write to. - StateTracker::GetInstance()->EndRenderPass(); - vkCmdResetQueryPool(g_command_buffer_mgr->GetCurrentCommandBuffer(), m_query_pool, 0, - PERF_QUERY_BUFFER_SIZE); - - std::memset(m_query_buffer.data(), 0, sizeof(ActiveQuery) * m_query_buffer.size()); -} - -u32 PerfQuery::GetQueryResult(PerfQueryType type) -{ - u32 result = 0; - if (type == PQ_ZCOMP_INPUT_ZCOMPLOC || type == PQ_ZCOMP_OUTPUT_ZCOMPLOC) - result = m_results[PQG_ZCOMP_ZCOMPLOC]; - else if (type == PQ_ZCOMP_INPUT || type == PQ_ZCOMP_OUTPUT) - result = m_results[PQG_ZCOMP]; - else if (type == PQ_BLEND_INPUT) - result = m_results[PQG_ZCOMP] + m_results[PQG_ZCOMP_ZCOMPLOC]; - else if (type == PQ_EFB_COPY_CLOCKS) - result = m_results[PQG_EFB_COPY_CLOCKS]; - - return result / 4; -} - -void PerfQuery::FlushResults() -{ - while (!IsFlushed()) - PartialFlush(true); -} - -bool PerfQuery::IsFlushed() const -{ - return m_query_count == 0; -} - -bool PerfQuery::CreateQueryPool() -{ - VkQueryPoolCreateInfo info = { - VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO, // VkStructureType sType - nullptr, // const void* pNext - 0, // VkQueryPoolCreateFlags flags - VK_QUERY_TYPE_OCCLUSION, // VkQueryType queryType - PERF_QUERY_BUFFER_SIZE, // uint32_t queryCount - 0 // VkQueryPipelineStatisticFlags pipelineStatistics; - }; - - VkResult res = vkCreateQueryPool(g_vulkan_context->GetDevice(), &info, nullptr, &m_query_pool); - if (res != VK_SUCCESS) - { - LOG_VULKAN_ERROR(res, "vkCreateQueryPool failed: "); - return false; - } - - return true; -} - -void PerfQuery::ReadbackQueries() -{ - const u64 completed_fence_counter = g_command_buffer_mgr->GetCompletedFenceCounter(); - - // Need to save these since ProcessResults will modify them. - const u32 outstanding_queries = m_query_count; - u32 readback_count = 0; - for (u32 i = 0; i < outstanding_queries; i++) - { - u32 index = (m_query_readback_pos + readback_count) % PERF_QUERY_BUFFER_SIZE; - const ActiveQuery& entry = m_query_buffer[index]; - if (entry.fence_counter > completed_fence_counter) - break; - - // If this wrapped around, we need to flush the entries before the end of the buffer. - if (index < m_query_readback_pos) - { - ReadbackQueries(readback_count); - DEBUG_ASSERT(m_query_readback_pos == 0); - readback_count = 0; - } - - readback_count++; - } - - if (readback_count > 0) - ReadbackQueries(readback_count); -} - -void PerfQuery::ReadbackQueries(u32 query_count) -{ - // Should be at maximum query_count queries pending. - ASSERT(query_count <= m_query_count && - (m_query_readback_pos + query_count) <= PERF_QUERY_BUFFER_SIZE); - - // Read back from the GPU. - VkResult res = - vkGetQueryPoolResults(g_vulkan_context->GetDevice(), m_query_pool, m_query_readback_pos, - query_count, query_count * sizeof(PerfQueryDataType), - m_query_result_buffer.data(), sizeof(PerfQueryDataType), 0); - if (res != VK_SUCCESS) - LOG_VULKAN_ERROR(res, "vkGetQueryPoolResults failed: "); - - // Remove pending queries. - for (u32 i = 0; i < query_count; i++) - { - u32 index = (m_query_readback_pos + i) % PERF_QUERY_BUFFER_SIZE; - ActiveQuery& entry = m_query_buffer[index]; - - // Should have a fence associated with it (waiting for a result). - DEBUG_ASSERT(entry.fence_counter != 0); - entry.fence_counter = 0; - entry.has_value = false; - - // NOTE: Reported pixel metrics should be referenced to native resolution - m_results[entry.query_type] += - static_cast(static_cast(m_query_result_buffer[i]) * EFB_WIDTH / - g_renderer->GetTargetWidth() * EFB_HEIGHT / g_renderer->GetTargetHeight()); - } - - m_query_readback_pos = (m_query_readback_pos + query_count) % PERF_QUERY_BUFFER_SIZE; - m_query_count -= query_count; -} - -void PerfQuery::PartialFlush(bool blocking) -{ - // Submit a command buffer in the background if the front query is not bound to one. - if (blocking || m_query_buffer[m_query_readback_pos].fence_counter == - g_command_buffer_mgr->GetCurrentFenceCounter()) - { - Renderer::GetInstance()->ExecuteCommandBuffer(true, blocking); - } - - ReadbackQueries(); -} -} // namespace Vulkan diff --git a/Source/Core/VideoBackends/Vulkan/PerfQuery.h b/Source/Core/VideoBackends/Vulkan/PerfQuery.h deleted file mode 100644 index 8ca91ac658..0000000000 --- a/Source/Core/VideoBackends/Vulkan/PerfQuery.h +++ /dev/null @@ -1,60 +0,0 @@ -// 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/Constants.h" -#include "VideoCommon/PerfQueryBase.h" - -namespace Vulkan -{ -class PerfQuery : public PerfQueryBase -{ -public: - PerfQuery(); - ~PerfQuery(); - - static PerfQuery* GetInstance() { return static_cast(g_perf_query.get()); } - - bool Initialize(); - - void EnableQuery(PerfQueryGroup type) override; - void DisableQuery(PerfQueryGroup type) override; - void ResetQuery() override; - u32 GetQueryResult(PerfQueryType type) override; - void FlushResults() override; - bool IsFlushed() const override; - -private: - // u32 is used for the sample counts. - using PerfQueryDataType = u32; - - // when testing in SMS: 64 was too small, 128 was ok - // TODO: This should be size_t, but the base class uses u32s - static const u32 PERF_QUERY_BUFFER_SIZE = 512; - - struct ActiveQuery - { - u64 fence_counter; - PerfQueryType query_type; - bool has_value; - }; - - bool CreateQueryPool(); - void ReadbackQueries(); - void ReadbackQueries(u32 query_count); - void PartialFlush(bool blocking); - - VkQueryPool m_query_pool = VK_NULL_HANDLE; - u32 m_query_readback_pos = 0; - u32 m_query_next_pos = 0; - std::array m_query_buffer = {}; - std::array m_query_result_buffer = {}; -}; - -} // namespace Vulkan diff --git a/Source/Core/VideoBackends/Vulkan/Renderer.cpp b/Source/Core/VideoBackends/Vulkan/Renderer.cpp deleted file mode 100644 index 68c6777599..0000000000 --- a/Source/Core/VideoBackends/Vulkan/Renderer.cpp +++ /dev/null @@ -1,652 +0,0 @@ -// Copyright 2016 Dolphin Emulator Project -// Licensed under GPLv2+ -// Refer to the license.txt file included. - -#include -#include -#include -#include -#include -#include - -#include "Common/Assert.h" -#include "Common/CommonTypes.h" -#include "Common/Logging/Log.h" -#include "Common/MsgHandler.h" - -#include "Core/Core.h" - -#include "VideoBackends/Vulkan/BoundingBox.h" -#include "VideoBackends/Vulkan/CommandBufferManager.h" -#include "VideoBackends/Vulkan/ObjectCache.h" -#include "VideoBackends/Vulkan/PerfQuery.h" -#include "VideoBackends/Vulkan/Renderer.h" -#include "VideoBackends/Vulkan/StateTracker.h" -#include "VideoBackends/Vulkan/StreamBuffer.h" -#include "VideoBackends/Vulkan/SwapChain.h" -#include "VideoBackends/Vulkan/VKPipeline.h" -#include "VideoBackends/Vulkan/VKShader.h" -#include "VideoBackends/Vulkan/VKTexture.h" -#include "VideoBackends/Vulkan/VertexFormat.h" -#include "VideoBackends/Vulkan/VulkanContext.h" - -#include "VideoCommon/DriverDetails.h" -#include "VideoCommon/FramebufferManager.h" -#include "VideoCommon/RenderState.h" -#include "VideoCommon/VertexManagerBase.h" -#include "VideoCommon/VideoBackendBase.h" -#include "VideoCommon/VideoCommon.h" -#include "VideoCommon/VideoConfig.h" -#include "VideoCommon/XFMemory.h" - -namespace Vulkan -{ -Renderer::Renderer(std::unique_ptr swap_chain, float backbuffer_scale) - : ::Renderer(swap_chain ? static_cast(swap_chain->GetWidth()) : 1, - swap_chain ? static_cast(swap_chain->GetHeight()) : 0, backbuffer_scale, - swap_chain ? swap_chain->GetTextureFormat() : AbstractTextureFormat::Undefined), - m_swap_chain(std::move(swap_chain)) -{ - UpdateActiveConfig(); - for (SamplerState& m_sampler_state : m_sampler_states) - m_sampler_state.hex = RenderState::GetPointSamplerState().hex; -} - -Renderer::~Renderer() = default; - -bool Renderer::IsHeadless() const -{ - return m_swap_chain == nullptr; -} - -bool Renderer::Initialize() -{ - if (!::Renderer::Initialize()) - return false; - - m_bounding_box = std::make_unique(); - if (!m_bounding_box->Initialize()) - { - PanicAlertFmt("Failed to initialize bounding box."); - return false; - } - - // Various initialization routines will have executed commands on the command buffer. - // Execute what we have done before beginning the first frame. - ExecuteCommandBuffer(true, false); - return true; -} - -void Renderer::Shutdown() -{ - ::Renderer::Shutdown(); - m_swap_chain.reset(); -} - -std::unique_ptr Renderer::CreateTexture(const TextureConfig& config) -{ - return VKTexture::Create(config); -} - -std::unique_ptr Renderer::CreateStagingTexture(StagingTextureType type, - const TextureConfig& config) -{ - return VKStagingTexture::Create(type, config); -} - -std::unique_ptr Renderer::CreateShaderFromSource(ShaderStage stage, - std::string_view source) -{ - return VKShader::CreateFromSource(stage, source); -} - -std::unique_ptr Renderer::CreateShaderFromBinary(ShaderStage stage, - const void* data, size_t length) -{ - return VKShader::CreateFromBinary(stage, data, length); -} - -std::unique_ptr -Renderer::CreateNativeVertexFormat(const PortableVertexDeclaration& vtx_decl) -{ - return std::make_unique(vtx_decl); -} - -std::unique_ptr Renderer::CreatePipeline(const AbstractPipelineConfig& config, - const void* cache_data, - size_t cache_data_length) -{ - return VKPipeline::Create(config); -} - -std::unique_ptr Renderer::CreateFramebuffer(AbstractTexture* color_attachment, - AbstractTexture* depth_attachment) -{ - return VKFramebuffer::Create(static_cast(color_attachment), - static_cast(depth_attachment)); -} - -void Renderer::SetPipeline(const AbstractPipeline* pipeline) -{ - StateTracker::GetInstance()->SetPipeline(static_cast(pipeline)); -} - -u16 Renderer::BBoxRead(int index) -{ - return static_cast(m_bounding_box->Get(index)); -} - -void Renderer::BBoxWrite(int index, u16 value) -{ - m_bounding_box->Set(index, value); -} - -void Renderer::BBoxFlush() -{ - m_bounding_box->Flush(); - m_bounding_box->Invalidate(); -} - -void Renderer::ClearScreen(const MathUtil::Rectangle& rc, bool color_enable, bool alpha_enable, - bool z_enable, u32 color, u32 z) -{ - g_framebuffer_manager->FlushEFBPokes(); - g_framebuffer_manager->FlagPeekCacheAsOutOfDate(); - - // Native -> EFB coordinates - MathUtil::Rectangle target_rc = Renderer::ConvertEFBRectangle(rc); - - // Size we pass this size to vkBeginRenderPass, it has to be clamped to the framebuffer - // dimensions. The other backends just silently ignore this case. - target_rc.ClampUL(0, 0, m_target_width, m_target_height); - - VkRect2D target_vk_rc = { - {target_rc.left, target_rc.top}, - {static_cast(target_rc.GetWidth()), static_cast(target_rc.GetHeight())}}; - - // Determine whether the EFB has an alpha channel. If it doesn't, we can clear the alpha - // channel to 0xFF. This hopefully allows us to use the fast path in most cases. - if (bpmem.zcontrol.pixel_format == PEControl::RGB565_Z16 || - bpmem.zcontrol.pixel_format == PEControl::RGB8_Z24 || - bpmem.zcontrol.pixel_format == PEControl::Z24) - { - // Force alpha writes, and clear the alpha channel. This is different to the other backends, - // where the existing values of the alpha channel are preserved. - alpha_enable = true; - color &= 0x00FFFFFF; - } - - // Convert RGBA8 -> floating-point values. - VkClearValue clear_color_value = {}; - VkClearValue clear_depth_value = {}; - clear_color_value.color.float32[0] = static_cast((color >> 16) & 0xFF) / 255.0f; - clear_color_value.color.float32[1] = static_cast((color >> 8) & 0xFF) / 255.0f; - clear_color_value.color.float32[2] = static_cast((color >> 0) & 0xFF) / 255.0f; - clear_color_value.color.float32[3] = static_cast((color >> 24) & 0xFF) / 255.0f; - clear_depth_value.depthStencil.depth = static_cast(z & 0xFFFFFF) / 16777216.0f; - if (!g_ActiveConfig.backend_info.bSupportsReversedDepthRange) - clear_depth_value.depthStencil.depth = 1.0f - clear_depth_value.depthStencil.depth; - - // If we're not in a render pass (start of the frame), we can use a clear render pass - // to discard the data, rather than loading and then clearing. - bool use_clear_attachments = (color_enable && alpha_enable) || z_enable; - bool use_clear_render_pass = - !StateTracker::GetInstance()->InRenderPass() && color_enable && alpha_enable && z_enable; - - // The NVIDIA Vulkan driver causes the GPU to lock up, or throw exceptions if MSAA is enabled, - // a non-full clear rect is specified, and a clear loadop or vkCmdClearAttachments is used. - if (g_ActiveConfig.iMultisamples > 1 && - DriverDetails::HasBug(DriverDetails::BUG_BROKEN_MSAA_CLEAR)) - { - use_clear_render_pass = false; - use_clear_attachments = false; - } - - // This path cannot be used if the driver implementation doesn't guarantee pixels with no drawn - // geometry in "this" renderpass won't be cleared - if (DriverDetails::HasBug(DriverDetails::BUG_BROKEN_CLEAR_LOADOP_RENDERPASS)) - use_clear_render_pass = false; - - // Fastest path: Use a render pass to clear the buffers. - if (use_clear_render_pass) - { - const std::array clear_values = {{clear_color_value, clear_depth_value}}; - StateTracker::GetInstance()->BeginClearRenderPass(target_vk_rc, clear_values.data(), - static_cast(clear_values.size())); - return; - } - - // Fast path: Use vkCmdClearAttachments to clear the buffers within a render path - // We can't use this when preserving alpha but clearing color. - if (use_clear_attachments) - { - VkClearAttachment clear_attachments[2]; - uint32_t num_clear_attachments = 0; - if (color_enable && alpha_enable) - { - clear_attachments[num_clear_attachments].aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - clear_attachments[num_clear_attachments].colorAttachment = 0; - clear_attachments[num_clear_attachments].clearValue = clear_color_value; - num_clear_attachments++; - color_enable = false; - alpha_enable = false; - } - if (z_enable) - { - clear_attachments[num_clear_attachments].aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; - clear_attachments[num_clear_attachments].colorAttachment = 0; - clear_attachments[num_clear_attachments].clearValue = clear_depth_value; - num_clear_attachments++; - z_enable = false; - } - if (num_clear_attachments > 0) - { - VkClearRect vk_rect = {target_vk_rc, 0, g_framebuffer_manager->GetEFBLayers()}; - if (!StateTracker::GetInstance()->IsWithinRenderArea( - target_vk_rc.offset.x, target_vk_rc.offset.y, target_vk_rc.extent.width, - target_vk_rc.extent.height)) - { - StateTracker::GetInstance()->EndClearRenderPass(); - } - StateTracker::GetInstance()->BeginRenderPass(); - - vkCmdClearAttachments(g_command_buffer_mgr->GetCurrentCommandBuffer(), num_clear_attachments, - clear_attachments, 1, &vk_rect); - } - } - - // Anything left over for the slow path? - if (!color_enable && !alpha_enable && !z_enable) - return; - - g_framebuffer_manager->ClearEFB(rc, color_enable, alpha_enable, z_enable, color, z); -} - -void Renderer::Flush() -{ - ExecuteCommandBuffer(true, false); -} - -void Renderer::WaitForGPUIdle() -{ - ExecuteCommandBuffer(false, true); -} - -void Renderer::BindBackbuffer(const ClearColor& clear_color) -{ - StateTracker::GetInstance()->EndRenderPass(); - - // Handle host window resizes. - CheckForSurfaceChange(); - CheckForSurfaceResize(); - - // Check for exclusive fullscreen request. - if (m_swap_chain->GetCurrentFullscreenState() != m_swap_chain->GetNextFullscreenState() && - !m_swap_chain->SetFullscreenState(m_swap_chain->GetNextFullscreenState())) - { - // if it fails, don't keep trying - m_swap_chain->SetNextFullscreenState(m_swap_chain->GetCurrentFullscreenState()); - } - - VkResult res = g_command_buffer_mgr->CheckLastPresentFail() ? - g_command_buffer_mgr->GetLastPresentResult() : - m_swap_chain->AcquireNextImage(); - if (res != VK_SUCCESS) - { - // Execute cmdbuffer before resizing, as the last frame could still be presenting. - ExecuteCommandBuffer(false, true); - - // Was this a lost exclusive fullscreen? - if (res == VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT) - { - // The present keeps returning exclusive mode lost unless we re-create the swap chain. - INFO_LOG_FMT(VIDEO, "Lost exclusive fullscreen."); - m_swap_chain->RecreateSwapChain(); - } - else if (res == VK_SUBOPTIMAL_KHR || res == VK_ERROR_OUT_OF_DATE_KHR) - { - INFO_LOG_FMT(VIDEO, "Resizing swap chain due to suboptimal/out-of-date"); - m_swap_chain->ResizeSwapChain(); - } - else - { - ERROR_LOG_FMT(VIDEO, "Unknown present error {:#010X}, please report.", res); - m_swap_chain->RecreateSwapChain(); - } - - res = m_swap_chain->AcquireNextImage(); - } - if (res != VK_SUCCESS) - PanicAlertFmt("Failed to grab image from swap chain"); - - // Transition from undefined (or present src, but it can be substituted) to - // color attachment ready for writing. These transitions must occur outside - // a render pass, unless the render pass declares a self-dependency. - m_swap_chain->GetCurrentTexture()->OverrideImageLayout(VK_IMAGE_LAYOUT_UNDEFINED); - m_swap_chain->GetCurrentTexture()->TransitionToLayout( - g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); - SetAndClearFramebuffer(m_swap_chain->GetCurrentFramebuffer(), - ClearColor{{0.0f, 0.0f, 0.0f, 1.0f}}); -} - -void Renderer::PresentBackbuffer() -{ - // End drawing to backbuffer - StateTracker::GetInstance()->EndRenderPass(); - - // Transition the backbuffer to PRESENT_SRC to ensure all commands drawing - // to it have finished before present. - m_swap_chain->GetCurrentTexture()->TransitionToLayout( - g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_PRESENT_SRC_KHR); - - // Submit the current command buffer, signaling rendering finished semaphore when it's done - // Because this final command buffer is rendering to the swap chain, we need to wait for - // the available semaphore to be signaled before executing the buffer. This final submission - // can happen off-thread in the background while we're preparing the next frame. - g_command_buffer_mgr->SubmitCommandBuffer(true, false, m_swap_chain->GetSwapChain(), - m_swap_chain->GetCurrentImageIndex()); - - // New cmdbuffer, so invalidate state. - StateTracker::GetInstance()->InvalidateCachedState(); -} - -void Renderer::SetFullscreen(bool enable_fullscreen) -{ - if (!m_swap_chain->IsFullscreenSupported()) - return; - - m_swap_chain->SetNextFullscreenState(enable_fullscreen); -} - -bool Renderer::IsFullscreen() const -{ - return m_swap_chain && m_swap_chain->GetCurrentFullscreenState(); -} - -void Renderer::ExecuteCommandBuffer(bool submit_off_thread, bool wait_for_completion) -{ - StateTracker::GetInstance()->EndRenderPass(); - - g_command_buffer_mgr->SubmitCommandBuffer(submit_off_thread, wait_for_completion); - - StateTracker::GetInstance()->InvalidateCachedState(); -} - -void Renderer::CheckForSurfaceChange() -{ - if (!m_surface_changed.TestAndClear() || !m_swap_chain) - return; - - // Submit the current draws up until rendering the XFB. - ExecuteCommandBuffer(false, true); - - // Clear the present failed flag, since we don't want to resize after recreating. - g_command_buffer_mgr->CheckLastPresentFail(); - - // Recreate the surface. If this fails we're in trouble. - if (!m_swap_chain->RecreateSurface(m_new_surface_handle)) - PanicAlertFmt("Failed to recreate Vulkan surface. Cannot continue."); - m_new_surface_handle = nullptr; - - // Handle case where the dimensions are now different. - OnSwapChainResized(); -} - -void Renderer::CheckForSurfaceResize() -{ - if (!m_surface_resized.TestAndClear()) - return; - - // If we don't have a surface, how can we resize the swap chain? - // CheckForSurfaceChange should handle this case. - if (!m_swap_chain) - { - WARN_LOG_FMT(VIDEO, "Surface resize event received without active surface, ignoring"); - return; - } - - // Wait for the GPU to catch up since we're going to destroy the swap chain. - ExecuteCommandBuffer(false, true); - - // Clear the present failed flag, since we don't want to resize after recreating. - g_command_buffer_mgr->CheckLastPresentFail(); - - // Resize the swap chain. - m_swap_chain->RecreateSwapChain(); - OnSwapChainResized(); -} - -void Renderer::OnConfigChanged(u32 bits) -{ - if (bits & CONFIG_CHANGE_BIT_HOST_CONFIG) - g_object_cache->ReloadPipelineCache(); - - // For vsync, we need to change the present mode, which means recreating the swap chain. - if (m_swap_chain && bits & CONFIG_CHANGE_BIT_VSYNC) - { - ExecuteCommandBuffer(false, true); - m_swap_chain->SetVSync(g_ActiveConfig.bVSyncActive); - } - - // For quad-buffered stereo we need to change the layer count, so recreate the swap chain. - if (m_swap_chain && bits & CONFIG_CHANGE_BIT_STEREO_MODE) - { - ExecuteCommandBuffer(false, true); - m_swap_chain->RecreateSwapChain(); - } - - // Wipe sampler cache if force texture filtering or anisotropy changes. - if (bits & (CONFIG_CHANGE_BIT_ANISOTROPY | CONFIG_CHANGE_BIT_FORCE_TEXTURE_FILTERING)) - { - ExecuteCommandBuffer(false, true); - ResetSamplerStates(); - } -} - -void Renderer::OnSwapChainResized() -{ - m_backbuffer_width = m_swap_chain->GetWidth(); - m_backbuffer_height = m_swap_chain->GetHeight(); -} - -void Renderer::BindFramebuffer(VKFramebuffer* fb) -{ - StateTracker::GetInstance()->EndRenderPass(); - - // Shouldn't be bound as a texture. - if (fb->GetColorAttachment()) - { - StateTracker::GetInstance()->UnbindTexture( - static_cast(fb->GetColorAttachment())->GetView()); - } - if (fb->GetDepthAttachment()) - { - StateTracker::GetInstance()->UnbindTexture( - static_cast(fb->GetDepthAttachment())->GetView()); - } - - fb->TransitionForRender(); - StateTracker::GetInstance()->SetFramebuffer(fb); - m_current_framebuffer = fb; -} - -void Renderer::SetFramebuffer(AbstractFramebuffer* framebuffer) -{ - if (m_current_framebuffer == framebuffer) - return; - - VKFramebuffer* vkfb = static_cast(framebuffer); - BindFramebuffer(vkfb); -} - -void Renderer::SetAndDiscardFramebuffer(AbstractFramebuffer* framebuffer) -{ - if (m_current_framebuffer == framebuffer) - return; - - VKFramebuffer* vkfb = static_cast(framebuffer); - BindFramebuffer(vkfb); - - // If we're discarding, begin the discard pass, then switch to a load pass. - // This way if the command buffer is flushed, we don't start another discard pass. - StateTracker::GetInstance()->BeginDiscardRenderPass(); -} - -void Renderer::SetAndClearFramebuffer(AbstractFramebuffer* framebuffer, - const ClearColor& color_value, float depth_value) -{ - VKFramebuffer* vkfb = static_cast(framebuffer); - BindFramebuffer(vkfb); - - std::array clear_values; - u32 num_clear_values = 0; - if (vkfb->GetColorFormat() != AbstractTextureFormat::Undefined) - { - std::memcpy(clear_values[num_clear_values].color.float32, color_value.data(), - sizeof(clear_values[num_clear_values].color.float32)); - num_clear_values++; - } - if (vkfb->GetDepthFormat() != AbstractTextureFormat::Undefined) - { - clear_values[num_clear_values].depthStencil.depth = depth_value; - clear_values[num_clear_values].depthStencil.stencil = 0; - num_clear_values++; - } - StateTracker::GetInstance()->BeginClearRenderPass(vkfb->GetRect(), clear_values.data(), - num_clear_values); -} - -void Renderer::SetTexture(u32 index, const AbstractTexture* texture) -{ - // Texture should always be in SHADER_READ_ONLY layout prior to use. - // This is so we don't need to transition during render passes. - const VKTexture* tex = static_cast(texture); - if (tex) - { - if (tex->GetLayout() != VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) - { - if (StateTracker::GetInstance()->InRenderPass()) - { - WARN_LOG_FMT(VIDEO, "Transitioning image in render pass in Renderer::SetTexture()"); - StateTracker::GetInstance()->EndRenderPass(); - } - - tex->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), - VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); - } - - StateTracker::GetInstance()->SetTexture(index, tex->GetView()); - } - else - { - StateTracker::GetInstance()->SetTexture(0, VK_NULL_HANDLE); - } -} - -void Renderer::SetSamplerState(u32 index, const SamplerState& state) -{ - // Skip lookup if the state hasn't changed. - if (m_sampler_states[index].hex == state.hex) - return; - - // Look up new state and replace in state tracker. - VkSampler sampler = g_object_cache->GetSampler(state); - if (sampler == VK_NULL_HANDLE) - { - ERROR_LOG_FMT(VIDEO, "Failed to create sampler"); - sampler = g_object_cache->GetPointSampler(); - } - - StateTracker::GetInstance()->SetSampler(index, sampler); - m_sampler_states[index].hex = state.hex; -} - -void Renderer::SetComputeImageTexture(AbstractTexture* texture, bool read, bool write) -{ - VKTexture* vk_texture = static_cast(texture); - if (vk_texture) - { - StateTracker::GetInstance()->EndRenderPass(); - StateTracker::GetInstance()->SetImageTexture(vk_texture->GetView()); - vk_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), - read ? (write ? VKTexture::ComputeImageLayout::ReadWrite : - VKTexture::ComputeImageLayout::ReadOnly) : - VKTexture::ComputeImageLayout::WriteOnly); - } - else - { - StateTracker::GetInstance()->SetImageTexture(VK_NULL_HANDLE); - } -} - -void Renderer::UnbindTexture(const AbstractTexture* texture) -{ - StateTracker::GetInstance()->UnbindTexture(static_cast(texture)->GetView()); -} - -void Renderer::ResetSamplerStates() -{ - // Invalidate all sampler states, next draw will re-initialize them. - for (u32 i = 0; i < m_sampler_states.size(); i++) - { - m_sampler_states[i].hex = RenderState::GetPointSamplerState().hex; - StateTracker::GetInstance()->SetSampler(i, g_object_cache->GetPointSampler()); - } - - // Invalidate all sampler objects (some will be unused now). - g_object_cache->ClearSamplerCache(); -} - -void Renderer::SetScissorRect(const MathUtil::Rectangle& rc) -{ - VkRect2D scissor = {{rc.left, rc.top}, - {static_cast(rc.GetWidth()), static_cast(rc.GetHeight())}}; - - // See Vulkan spec for vkCmdSetScissor: - // The x and y members of offset must be greater than or equal to 0. - if (scissor.offset.x < 0) - { - scissor.extent.width -= -scissor.offset.x; - scissor.offset.x = 0; - } - if (scissor.offset.y < 0) - { - scissor.extent.height -= -scissor.offset.y; - scissor.offset.y = 0; - } - StateTracker::GetInstance()->SetScissor(scissor); -} - -void Renderer::SetViewport(float x, float y, float width, float height, float near_depth, - float far_depth) -{ - VkViewport viewport = {x, y, width, height, near_depth, far_depth}; - StateTracker::GetInstance()->SetViewport(viewport); -} - -void Renderer::Draw(u32 base_vertex, u32 num_vertices) -{ - if (!StateTracker::GetInstance()->Bind()) - return; - - vkCmdDraw(g_command_buffer_mgr->GetCurrentCommandBuffer(), num_vertices, 1, base_vertex, 0); -} - -void Renderer::DrawIndexed(u32 base_index, u32 num_indices, u32 base_vertex) -{ - if (!StateTracker::GetInstance()->Bind()) - return; - - vkCmdDrawIndexed(g_command_buffer_mgr->GetCurrentCommandBuffer(), num_indices, 1, base_index, - base_vertex, 0); -} - -void Renderer::DispatchComputeShader(const AbstractShader* shader, u32 groups_x, u32 groups_y, - u32 groups_z) -{ - StateTracker::GetInstance()->SetComputeShader(static_cast(shader)); - if (StateTracker::GetInstance()->BindCompute()) - vkCmdDispatch(g_command_buffer_mgr->GetCurrentCommandBuffer(), groups_x, groups_y, groups_z); -} - -} // namespace Vulkan diff --git a/Source/Core/VideoBackends/Vulkan/Renderer.h b/Source/Core/VideoBackends/Vulkan/Renderer.h deleted file mode 100644 index 43ed3bd45b..0000000000 --- a/Source/Core/VideoBackends/Vulkan/Renderer.h +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright 2016 Dolphin Emulator Project -// Licensed under GPLv2+ -// Refer to the license.txt file included. - -#pragma once - -#include -#include -#include - -#include "Common/CommonTypes.h" -#include "VideoBackends/Vulkan/Constants.h" -#include "VideoCommon/RenderBase.h" - -struct XFBSourceBase; - -namespace Vulkan -{ -class BoundingBox; -class SwapChain; -class StagingTexture2D; -class VKFramebuffer; -class VKPipeline; -class VKTexture; - -class Renderer : public ::Renderer -{ -public: - Renderer(std::unique_ptr swap_chain, float backbuffer_scale); - ~Renderer() override; - - static Renderer* GetInstance() { return static_cast(g_renderer.get()); } - - bool IsHeadless() const override; - - bool Initialize() override; - void Shutdown() override; - - std::unique_ptr CreateTexture(const TextureConfig& config) override; - std::unique_ptr - CreateStagingTexture(StagingTextureType type, const TextureConfig& config) override; - std::unique_ptr - CreateFramebuffer(AbstractTexture* color_attachment, AbstractTexture* depth_attachment) override; - - std::unique_ptr CreateShaderFromSource(ShaderStage stage, - std::string_view source) override; - std::unique_ptr CreateShaderFromBinary(ShaderStage stage, const void* data, - size_t length) override; - std::unique_ptr - CreateNativeVertexFormat(const PortableVertexDeclaration& vtx_decl) override; - std::unique_ptr CreatePipeline(const AbstractPipelineConfig& config, - const void* cache_data = nullptr, - size_t cache_data_length = 0) override; - - SwapChain* GetSwapChain() const { return m_swap_chain.get(); } - BoundingBox* GetBoundingBox() const { return m_bounding_box.get(); } - u16 BBoxRead(int index) override; - void BBoxWrite(int index, u16 value) override; - void BBoxFlush() override; - - void Flush() override; - void WaitForGPUIdle() override; - void OnConfigChanged(u32 bits) override; - - void ClearScreen(const MathUtil::Rectangle& rc, bool color_enable, bool alpha_enable, - bool z_enable, u32 color, u32 z) override; - - void SetPipeline(const AbstractPipeline* pipeline) override; - void SetFramebuffer(AbstractFramebuffer* framebuffer) override; - void SetAndDiscardFramebuffer(AbstractFramebuffer* framebuffer) override; - void SetAndClearFramebuffer(AbstractFramebuffer* framebuffer, const ClearColor& color_value = {}, - float depth_value = 0.0f) override; - void SetScissorRect(const MathUtil::Rectangle& rc) override; - void SetTexture(u32 index, const AbstractTexture* texture) override; - void SetSamplerState(u32 index, const SamplerState& state) override; - void SetComputeImageTexture(AbstractTexture* texture, bool read, bool write) override; - void UnbindTexture(const AbstractTexture* texture) override; - void SetViewport(float x, float y, float width, float height, float near_depth, - float far_depth) override; - void Draw(u32 base_vertex, u32 num_vertices) override; - void DrawIndexed(u32 base_index, u32 num_indices, u32 base_vertex) override; - void DispatchComputeShader(const AbstractShader* shader, u32 groups_x, u32 groups_y, - u32 groups_z) override; - void BindBackbuffer(const ClearColor& clear_color = {}) override; - void PresentBackbuffer() override; - void SetFullscreen(bool enable_fullscreen) override; - bool IsFullscreen() const override; - - // Completes the current render pass, executes the command buffer, and restores state ready for - // next render. Use when you want to kick the current buffer to make room for new data. - void ExecuteCommandBuffer(bool execute_off_thread, bool wait_for_completion = false); - -private: - void CheckForSurfaceChange(); - void CheckForSurfaceResize(); - - void ResetSamplerStates(); - - void OnSwapChainResized(); - void BindFramebuffer(VKFramebuffer* fb); - - std::unique_ptr m_swap_chain; - std::unique_ptr m_bounding_box; - - // Keep a copy of sampler states to avoid cache lookups every draw - std::array m_sampler_states = {}; -}; -} // namespace Vulkan diff --git a/Source/Core/VideoBackends/Vulkan/StateTracker.cpp b/Source/Core/VideoBackends/Vulkan/StateTracker.cpp index 53081c4be2..dc898413a3 100644 --- a/Source/Core/VideoBackends/Vulkan/StateTracker.cpp +++ b/Source/Core/VideoBackends/Vulkan/StateTracker.cpp @@ -8,11 +8,11 @@ #include "VideoBackends/Vulkan/CommandBufferManager.h" #include "VideoBackends/Vulkan/ObjectCache.h" -#include "VideoBackends/Vulkan/Renderer.h" #include "VideoBackends/Vulkan/VKPipeline.h" +#include "VideoBackends/Vulkan/VKRenderer.h" #include "VideoBackends/Vulkan/VKShader.h" #include "VideoBackends/Vulkan/VKTexture.h" -#include "VideoBackends/Vulkan/VertexFormat.h" +#include "VideoBackends/Vulkan/VKVertexFormat.h" #include "VideoBackends/Vulkan/VulkanContext.h" namespace Vulkan diff --git a/Source/Core/VideoBackends/Vulkan/StreamBuffer.cpp b/Source/Core/VideoBackends/Vulkan/StreamBuffer.cpp deleted file mode 100644 index ee86615090..0000000000 --- a/Source/Core/VideoBackends/Vulkan/StreamBuffer.cpp +++ /dev/null @@ -1,328 +0,0 @@ -// Copyright 2016 Dolphin Emulator Project -// Licensed under GPLv2+ -// Refer to the license.txt file included. - -#include "VideoBackends/Vulkan/StreamBuffer.h" - -#include -#include -#include - -#include "Common/Align.h" -#include "Common/Assert.h" -#include "Common/MsgHandler.h" - -#include "VideoBackends/Vulkan/CommandBufferManager.h" -#include "VideoBackends/Vulkan/VulkanContext.h" - -namespace Vulkan -{ -StreamBuffer::StreamBuffer(VkBufferUsageFlags usage, u32 size) : m_usage(usage), m_size(size) -{ -} - -StreamBuffer::~StreamBuffer() -{ - if (m_host_pointer) - vkUnmapMemory(g_vulkan_context->GetDevice(), m_memory); - - if (m_buffer != VK_NULL_HANDLE) - g_command_buffer_mgr->DeferBufferDestruction(m_buffer); - if (m_memory != VK_NULL_HANDLE) - g_command_buffer_mgr->DeferDeviceMemoryDestruction(m_memory); -} - -std::unique_ptr StreamBuffer::Create(VkBufferUsageFlags usage, u32 size) -{ - std::unique_ptr buffer = std::make_unique(usage, size); - if (!buffer->AllocateBuffer()) - return nullptr; - - return buffer; -} - -bool StreamBuffer::AllocateBuffer() -{ - // Create the buffer descriptor - VkBufferCreateInfo buffer_create_info = { - VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, // VkStructureType sType - nullptr, // const void* pNext - 0, // VkBufferCreateFlags flags - static_cast(m_size), // VkDeviceSize size - m_usage, // VkBufferUsageFlags usage - VK_SHARING_MODE_EXCLUSIVE, // VkSharingMode sharingMode - 0, // uint32_t queueFamilyIndexCount - nullptr // const uint32_t* pQueueFamilyIndices - }; - - VkBuffer buffer = VK_NULL_HANDLE; - VkResult res = - vkCreateBuffer(g_vulkan_context->GetDevice(), &buffer_create_info, nullptr, &buffer); - if (res != VK_SUCCESS) - { - LOG_VULKAN_ERROR(res, "vkCreateBuffer failed: "); - return false; - } - - // Get memory requirements (types etc) for this buffer - VkMemoryRequirements memory_requirements; - vkGetBufferMemoryRequirements(g_vulkan_context->GetDevice(), buffer, &memory_requirements); - - // Aim for a coherent mapping if possible. - u32 memory_type_index = g_vulkan_context->GetUploadMemoryType(memory_requirements.memoryTypeBits, - &m_coherent_mapping); - - // Allocate memory for backing this buffer - 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 = VK_NULL_HANDLE; - 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; - } - - // Bind memory to buffer - 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; - } - - // Map this buffer into user-space - void* mapped_ptr = nullptr; - res = vkMapMemory(g_vulkan_context->GetDevice(), memory, 0, m_size, 0, &mapped_ptr); - if (res != VK_SUCCESS) - { - LOG_VULKAN_ERROR(res, "vkMapMemory failed: "); - vkDestroyBuffer(g_vulkan_context->GetDevice(), buffer, nullptr); - vkFreeMemory(g_vulkan_context->GetDevice(), memory, nullptr); - return false; - } - - // Unmap current host pointer (if there was a previous buffer) - if (m_host_pointer) - vkUnmapMemory(g_vulkan_context->GetDevice(), m_memory); - - // Destroy the backings for the buffer after the command buffer executes - if (m_buffer != VK_NULL_HANDLE) - g_command_buffer_mgr->DeferBufferDestruction(m_buffer); - if (m_memory != VK_NULL_HANDLE) - g_command_buffer_mgr->DeferDeviceMemoryDestruction(m_memory); - - // Replace with the new buffer - m_buffer = buffer; - m_memory = memory; - m_host_pointer = reinterpret_cast(mapped_ptr); - m_current_offset = 0; - m_current_gpu_position = 0; - m_tracked_fences.clear(); - return true; -} - -bool StreamBuffer::ReserveMemory(u32 num_bytes, u32 alignment) -{ - const u32 required_bytes = num_bytes + alignment; - - // Check for sane allocations - if (required_bytes > m_size) - { - PanicAlertFmt("Attempting to allocate {} bytes from a {} byte stream buffer", num_bytes, - m_size); - - return false; - } - - // Is the GPU behind or up to date with our current offset? - UpdateCurrentFencePosition(); - if (m_current_offset >= m_current_gpu_position) - { - const u32 remaining_bytes = m_size - m_current_offset; - if (required_bytes <= remaining_bytes) - { - // Place at the current position, after the GPU position. - m_current_offset = Common::AlignUp(m_current_offset, alignment); - m_last_allocation_size = num_bytes; - return true; - } - - // Check for space at the start of the buffer - // We use < here because we don't want to have the case of m_current_offset == - // m_current_gpu_position. That would mean the code above would assume the - // GPU has caught up to us, which it hasn't. - if (required_bytes < m_current_gpu_position) - { - // Reset offset to zero, since we're allocating behind the gpu now - m_current_offset = 0; - m_last_allocation_size = num_bytes; - return true; - } - } - - // Is the GPU ahead of our current offset? - if (m_current_offset < m_current_gpu_position) - { - // We have from m_current_offset..m_current_gpu_position space to use. - const u32 remaining_bytes = m_current_gpu_position - m_current_offset; - if (required_bytes < remaining_bytes) - { - // Place at the current position, since this is still behind the GPU. - m_current_offset = Common::AlignUp(m_current_offset, alignment); - m_last_allocation_size = num_bytes; - return true; - } - } - - // Can we find a fence to wait on that will give us enough memory? - if (WaitForClearSpace(required_bytes)) - { - m_current_offset = Common::AlignUp(m_current_offset, alignment); - m_last_allocation_size = num_bytes; - return true; - } - - // We tried everything we could, and still couldn't get anything. This means that too much space - // in the buffer is being used by the command buffer currently being recorded. Therefore, the - // only option is to execute it, and wait until it's done. - return false; -} - -void StreamBuffer::CommitMemory(u32 final_num_bytes) -{ - ASSERT((m_current_offset + final_num_bytes) <= m_size); - ASSERT(final_num_bytes <= m_last_allocation_size); - - // For non-coherent mappings, flush the memory range - if (!m_coherent_mapping) - { - VkMappedMemoryRange range = {VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE, nullptr, m_memory, - m_current_offset, final_num_bytes}; - vkFlushMappedMemoryRanges(g_vulkan_context->GetDevice(), 1, &range); - } - - m_current_offset += final_num_bytes; -} - -void StreamBuffer::UpdateCurrentFencePosition() -{ - // Don't create a tracking entry if the GPU is caught up with the buffer. - if (m_current_offset == m_current_gpu_position) - return; - - // Has the offset changed since the last fence? - const u64 counter = g_command_buffer_mgr->GetCurrentFenceCounter(); - if (!m_tracked_fences.empty() && m_tracked_fences.back().first == counter) - { - // Still haven't executed a command buffer, so just update the offset. - m_tracked_fences.back().second = m_current_offset; - return; - } - - // New buffer, so update the GPU position while we're at it. - UpdateGPUPosition(); - m_tracked_fences.emplace_back(counter, m_current_offset); -} - -void StreamBuffer::UpdateGPUPosition() -{ - auto start = m_tracked_fences.begin(); - auto end = start; - - const u64 completed_counter = g_command_buffer_mgr->GetCompletedFenceCounter(); - while (end != m_tracked_fences.end() && completed_counter >= end->first) - { - m_current_gpu_position = end->second; - ++end; - } - - if (start != end) - m_tracked_fences.erase(start, end); -} - -bool StreamBuffer::WaitForClearSpace(u32 num_bytes) -{ - u32 new_offset = 0; - u32 new_gpu_position = 0; - - auto iter = m_tracked_fences.begin(); - for (; iter != m_tracked_fences.end(); ++iter) - { - // Would this fence bring us in line with the GPU? - // This is the "last resort" case, where a command buffer execution has been forced - // after no additional data has been written to it, so we can assume that after the - // fence has been signaled the entire buffer is now consumed. - u32 gpu_position = iter->second; - if (m_current_offset == gpu_position) - { - new_offset = 0; - new_gpu_position = 0; - break; - } - - // Assuming that we wait for this fence, are we allocating in front of the GPU? - if (m_current_offset > gpu_position) - { - // This would suggest the GPU has now followed us and wrapped around, so we have from - // m_current_position..m_size free, as well as and 0..gpu_position. - const u32 remaining_space_after_offset = m_size - m_current_offset; - if (remaining_space_after_offset >= num_bytes) - { - // Switch to allocating in front of the GPU, using the remainder of the buffer. - new_offset = m_current_offset; - new_gpu_position = gpu_position; - break; - } - - // We can wrap around to the start, behind the GPU, if there is enough space. - // We use > here because otherwise we'd end up lining up with the GPU, and then the - // allocator would assume that the GPU has consumed what we just wrote. - if (gpu_position > num_bytes) - { - new_offset = 0; - new_gpu_position = gpu_position; - break; - } - } - else - { - // We're currently allocating behind the GPU. This would give us between the current - // offset and the GPU position worth of space to work with. Again, > because we can't - // align the GPU position with the buffer offset. - u32 available_space_inbetween = gpu_position - m_current_offset; - if (available_space_inbetween > num_bytes) - { - // Leave the offset as-is, but update the GPU position. - new_offset = m_current_offset; - new_gpu_position = gpu_position; - break; - } - } - } - - // Did any fences satisfy this condition? - // Has the command buffer been executed yet? If not, the caller should execute it. - if (iter == m_tracked_fences.end() || - iter->first == g_command_buffer_mgr->GetCurrentFenceCounter()) - { - return false; - } - - // Wait until this fence is signaled. This will fire the callback, updating the GPU position. - g_command_buffer_mgr->WaitForFenceCounter(iter->first); - m_tracked_fences.erase(m_tracked_fences.begin(), - m_current_offset == iter->second ? m_tracked_fences.end() : ++iter); - m_current_offset = new_offset; - m_current_gpu_position = new_gpu_position; - return true; -} - -} // namespace Vulkan diff --git a/Source/Core/VideoBackends/Vulkan/StreamBuffer.h b/Source/Core/VideoBackends/Vulkan/StreamBuffer.h deleted file mode 100644 index 677313939a..0000000000 --- a/Source/Core/VideoBackends/Vulkan/StreamBuffer.h +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2016 Dolphin Emulator Project -// Licensed under GPLv2+ -// Refer to the license.txt file included. - -#pragma once - -#include -#include -#include -#include - -#include "Common/CommonTypes.h" -#include "VideoBackends/Vulkan/Constants.h" - -namespace Vulkan -{ -class StreamBuffer -{ -public: - StreamBuffer(VkBufferUsageFlags usage, u32 size); - ~StreamBuffer(); - - VkBuffer GetBuffer() const { return m_buffer; } - VkDeviceMemory GetDeviceMemory() const { return m_memory; } - u8* GetHostPointer() const { return m_host_pointer; } - u8* GetCurrentHostPointer() const { return m_host_pointer + m_current_offset; } - u32 GetCurrentSize() const { return m_size; } - u32 GetCurrentOffset() const { return m_current_offset; } - bool ReserveMemory(u32 num_bytes, u32 alignment); - void CommitMemory(u32 final_num_bytes); - - static std::unique_ptr Create(VkBufferUsageFlags usage, u32 size); - -private: - bool AllocateBuffer(); - void UpdateCurrentFencePosition(); - void UpdateGPUPosition(); - - // Waits for as many fences as needed to allocate num_bytes bytes from the buffer. - bool WaitForClearSpace(u32 num_bytes); - - VkBufferUsageFlags m_usage; - u32 m_size; - u32 m_current_offset = 0; - u32 m_current_gpu_position = 0; - u32 m_last_allocation_size = 0; - - VkBuffer m_buffer = VK_NULL_HANDLE; - VkDeviceMemory m_memory = VK_NULL_HANDLE; - u8* m_host_pointer = nullptr; - - // List of fences and the corresponding positions in the buffer - std::deque> m_tracked_fences; - - bool m_coherent_mapping = false; -}; - -} // namespace Vulkan diff --git a/Source/Core/VideoBackends/Vulkan/SwapChain.cpp b/Source/Core/VideoBackends/Vulkan/SwapChain.cpp deleted file mode 100644 index f16a7c3bd0..0000000000 --- a/Source/Core/VideoBackends/Vulkan/SwapChain.cpp +++ /dev/null @@ -1,583 +0,0 @@ -// Copyright 2016 Dolphin Emulator Project -// Licensed under GPLv2+ -// Refer to the license.txt file included. - -#include "VideoBackends/Vulkan/SwapChain.h" - -#include -#include - -#include "Common/Assert.h" -#include "Common/CommonFuncs.h" -#include "Common/Logging/Log.h" -#include "Common/MsgHandler.h" - -#include "VideoBackends/Vulkan/CommandBufferManager.h" -#include "VideoBackends/Vulkan/ObjectCache.h" -#include "VideoBackends/Vulkan/VKTexture.h" -#include "VideoBackends/Vulkan/VulkanContext.h" -#include "VideoCommon/RenderBase.h" - -#if defined(VK_USE_PLATFORM_XLIB_KHR) -#include -#endif - -namespace Vulkan -{ -SwapChain::SwapChain(const WindowSystemInfo& wsi, VkSurfaceKHR surface, bool vsync) - : m_wsi(wsi), m_surface(surface), m_vsync_enabled(vsync), - m_fullscreen_supported(g_vulkan_context->SupportsExclusiveFullscreen(wsi, surface)) -{ -} - -SwapChain::~SwapChain() -{ - DestroySwapChainImages(); - DestroySwapChain(); - DestroySurface(); -} - -VkSurfaceKHR SwapChain::CreateVulkanSurface(VkInstance instance, const WindowSystemInfo& wsi) -{ -#if defined(VK_USE_PLATFORM_WIN32_KHR) - if (wsi.type == WindowSystemType::Windows) - { - VkWin32SurfaceCreateInfoKHR surface_create_info = { - VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR, // VkStructureType sType - nullptr, // const void* pNext - 0, // VkWin32SurfaceCreateFlagsKHR flags - nullptr, // HINSTANCE hinstance - reinterpret_cast(wsi.render_surface) // HWND hwnd - }; - - VkSurfaceKHR surface; - VkResult res = vkCreateWin32SurfaceKHR(instance, &surface_create_info, nullptr, &surface); - if (res != VK_SUCCESS) - { - LOG_VULKAN_ERROR(res, "vkCreateWin32SurfaceKHR failed: "); - return VK_NULL_HANDLE; - } - - return surface; - } -#endif - -#if defined(VK_USE_PLATFORM_XLIB_KHR) - if (wsi.type == WindowSystemType::X11) - { - VkXlibSurfaceCreateInfoKHR surface_create_info = { - VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR, // VkStructureType sType - nullptr, // const void* pNext - 0, // VkXlibSurfaceCreateFlagsKHR flags - static_cast(wsi.display_connection), // Display* dpy - reinterpret_cast(wsi.render_surface) // Window window - }; - - VkSurfaceKHR surface; - VkResult res = vkCreateXlibSurfaceKHR(instance, &surface_create_info, nullptr, &surface); - if (res != VK_SUCCESS) - { - LOG_VULKAN_ERROR(res, "vkCreateXlibSurfaceKHR failed: "); - return VK_NULL_HANDLE; - } - - return surface; - } -#endif - -#if defined(VK_USE_PLATFORM_ANDROID_KHR) - if (wsi.type == WindowSystemType::Android) - { - VkAndroidSurfaceCreateInfoKHR surface_create_info = { - VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR, // VkStructureType sType - nullptr, // const void* pNext - 0, // VkAndroidSurfaceCreateFlagsKHR flags - reinterpret_cast(wsi.render_surface) // ANativeWindow* window - }; - - VkSurfaceKHR surface; - VkResult res = vkCreateAndroidSurfaceKHR(instance, &surface_create_info, nullptr, &surface); - if (res != VK_SUCCESS) - { - LOG_VULKAN_ERROR(res, "vkCreateAndroidSurfaceKHR failed: "); - return VK_NULL_HANDLE; - } - - return surface; - } -#endif - -#if defined(VK_USE_PLATFORM_METAL_EXT) - if (wsi.type == WindowSystemType::MacOS) - { - VkMetalSurfaceCreateInfoEXT surface_create_info = { - VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT, nullptr, 0, - static_cast(wsi.render_surface)}; - - VkSurfaceKHR surface; - VkResult res = vkCreateMetalSurfaceEXT(instance, &surface_create_info, nullptr, &surface); - if (res != VK_SUCCESS) - { - LOG_VULKAN_ERROR(res, "vkCreateMetalSurfaceEXT failed: "); - return VK_NULL_HANDLE; - } - - return surface; - } -#endif - - return VK_NULL_HANDLE; -} - -std::unique_ptr SwapChain::Create(const WindowSystemInfo& wsi, VkSurfaceKHR surface, - bool vsync) -{ - std::unique_ptr swap_chain = std::make_unique(wsi, surface, vsync); - if (!swap_chain->CreateSwapChain() || !swap_chain->SetupSwapChainImages()) - return nullptr; - - return swap_chain; -} - -bool SwapChain::SelectSurfaceFormat() -{ - u32 format_count; - VkResult res = vkGetPhysicalDeviceSurfaceFormatsKHR(g_vulkan_context->GetPhysicalDevice(), - m_surface, &format_count, nullptr); - if (res != VK_SUCCESS || format_count == 0) - { - LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceFormatsKHR failed: "); - return false; - } - - std::vector surface_formats(format_count); - res = vkGetPhysicalDeviceSurfaceFormatsKHR(g_vulkan_context->GetPhysicalDevice(), m_surface, - &format_count, surface_formats.data()); - ASSERT(res == VK_SUCCESS); - - // If there is a single undefined surface format, the device doesn't care, so we'll just use RGBA - if (surface_formats[0].format == VK_FORMAT_UNDEFINED) - { - m_surface_format.format = VK_FORMAT_R8G8B8A8_UNORM; - m_surface_format.colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; - return true; - } - - // Try to find a suitable format. - for (const VkSurfaceFormatKHR& surface_format : surface_formats) - { - // Some drivers seem to return a SRGB format here (Intel Mesa). - // This results in gamma correction when presenting to the screen, which we don't want. - // Use a linear format instead, if this is the case. - VkFormat format = VKTexture::GetLinearFormat(surface_format.format); - if (format == VK_FORMAT_R8G8B8A8_UNORM) - m_texture_format = AbstractTextureFormat::RGBA8; - else if (format == VK_FORMAT_B8G8R8A8_UNORM) - m_texture_format = AbstractTextureFormat::BGRA8; - else - continue; - - m_surface_format.format = format; - m_surface_format.colorSpace = surface_format.colorSpace; - return true; - } - - PanicAlertFmt("Failed to find a suitable format for swap chain buffers."); - return false; -} - -bool SwapChain::SelectPresentMode() -{ - VkResult res; - u32 mode_count; - res = vkGetPhysicalDeviceSurfacePresentModesKHR(g_vulkan_context->GetPhysicalDevice(), m_surface, - &mode_count, nullptr); - if (res != VK_SUCCESS || mode_count == 0) - { - LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceFormatsKHR failed: "); - return false; - } - - std::vector present_modes(mode_count); - res = vkGetPhysicalDeviceSurfacePresentModesKHR(g_vulkan_context->GetPhysicalDevice(), m_surface, - &mode_count, present_modes.data()); - ASSERT(res == VK_SUCCESS); - - // Checks if a particular mode is supported, if it is, returns that mode. - auto CheckForMode = [&present_modes](VkPresentModeKHR check_mode) { - auto it = std::find_if(present_modes.begin(), present_modes.end(), - [check_mode](VkPresentModeKHR mode) { return check_mode == mode; }); - return it != present_modes.end(); - }; - - // If vsync is enabled, use VK_PRESENT_MODE_FIFO_KHR. - // This check should not fail with conforming drivers, as the FIFO present mode is mandated by - // the specification (VK_KHR_swapchain). In case it isn't though, fall through to any other mode. - if (m_vsync_enabled && CheckForMode(VK_PRESENT_MODE_FIFO_KHR)) - { - m_present_mode = VK_PRESENT_MODE_FIFO_KHR; - return true; - } - - // Prefer screen-tearing, if possible, for lowest latency. - if (CheckForMode(VK_PRESENT_MODE_IMMEDIATE_KHR)) - { - m_present_mode = VK_PRESENT_MODE_IMMEDIATE_KHR; - return true; - } - - // Use optimized-vsync above vsync. - if (CheckForMode(VK_PRESENT_MODE_MAILBOX_KHR)) - { - m_present_mode = VK_PRESENT_MODE_MAILBOX_KHR; - return true; - } - - // Fall back to whatever is available. - m_present_mode = present_modes[0]; - return true; -} - -bool SwapChain::CreateSwapChain() -{ - // Look up surface properties to determine image count and dimensions - VkSurfaceCapabilitiesKHR surface_capabilities; - VkResult res = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(g_vulkan_context->GetPhysicalDevice(), - m_surface, &surface_capabilities); - if (res != VK_SUCCESS) - { - LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceCapabilitiesKHR failed: "); - return false; - } - - // Select swap chain format and present mode - if (!SelectSurfaceFormat() || !SelectPresentMode()) - return false; - - // Select number of images in swap chain, we prefer one buffer in the background to work on - uint32_t image_count = surface_capabilities.minImageCount + 1; - - // maxImageCount can be zero, in which case there isn't an upper limit on the number of buffers. - if (surface_capabilities.maxImageCount > 0) - image_count = std::min(image_count, surface_capabilities.maxImageCount); - - // Determine the dimensions of the swap chain. Values of -1 indicate the size we specify here - // determines window size? - VkExtent2D size = surface_capabilities.currentExtent; - if (size.width == UINT32_MAX) - { - size.width = std::max(g_renderer->GetBackbufferWidth(), 1); - size.height = std::max(g_renderer->GetBackbufferHeight(), 1); - } - size.width = std::clamp(size.width, surface_capabilities.minImageExtent.width, - surface_capabilities.maxImageExtent.width); - size.height = std::clamp(size.height, surface_capabilities.minImageExtent.height, - surface_capabilities.maxImageExtent.height); - - // Prefer identity transform if possible - VkSurfaceTransformFlagBitsKHR transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; - if (!(surface_capabilities.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR)) - transform = surface_capabilities.currentTransform; - - // Select swap chain flags, we only need a colour attachment - VkImageUsageFlags image_usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; - if (!(surface_capabilities.supportedUsageFlags & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT)) - { - ERROR_LOG_FMT(VIDEO, "Vulkan: Swap chain does not support usage as color attachment"); - return false; - } - - // Select the number of image layers for Quad-Buffered stereoscopy - uint32_t image_layers = g_ActiveConfig.stereo_mode == StereoMode::QuadBuffer ? 2 : 1; - - // Store the old/current swap chain when recreating for resize - VkSwapchainKHR old_swap_chain = m_swap_chain; - m_swap_chain = VK_NULL_HANDLE; - - // Now we can actually create the swap chain - VkSwapchainCreateInfoKHR swap_chain_info = {VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, - nullptr, - 0, - m_surface, - image_count, - m_surface_format.format, - m_surface_format.colorSpace, - size, - image_layers, - image_usage, - VK_SHARING_MODE_EXCLUSIVE, - 0, - nullptr, - transform, - VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, - m_present_mode, - VK_TRUE, - old_swap_chain}; - std::array indices = {{ - g_vulkan_context->GetGraphicsQueueFamilyIndex(), - g_vulkan_context->GetPresentQueueFamilyIndex(), - }}; - if (g_vulkan_context->GetGraphicsQueueFamilyIndex() != - g_vulkan_context->GetPresentQueueFamilyIndex()) - { - swap_chain_info.imageSharingMode = VK_SHARING_MODE_CONCURRENT; - swap_chain_info.queueFamilyIndexCount = 2; - swap_chain_info.pQueueFamilyIndices = indices.data(); - } - -#ifdef SUPPORTS_VULKAN_EXCLUSIVE_FULLSCREEN - if (m_fullscreen_supported) - { - VkSurfaceFullScreenExclusiveInfoEXT fullscreen_support = {}; - swap_chain_info.pNext = &fullscreen_support; - fullscreen_support.sType = VK_STRUCTURE_TYPE_SURFACE_FULL_SCREEN_EXCLUSIVE_INFO_EXT; - fullscreen_support.fullScreenExclusive = VK_FULL_SCREEN_EXCLUSIVE_APPLICATION_CONTROLLED_EXT; - - auto platform_info = g_vulkan_context->GetPlatformExclusiveFullscreenInfo(m_wsi); - fullscreen_support.pNext = &platform_info; - - res = vkCreateSwapchainKHR(g_vulkan_context->GetDevice(), &swap_chain_info, nullptr, - &m_swap_chain); - if (res != VK_SUCCESS) - { - // Try without exclusive fullscreen. - WARN_LOG_FMT(VIDEO, "Failed to create exclusive fullscreen swapchain, trying without."); - swap_chain_info.pNext = nullptr; - g_Config.backend_info.bSupportsExclusiveFullscreen = false; - g_ActiveConfig.backend_info.bSupportsExclusiveFullscreen = false; - m_fullscreen_supported = false; - } - } -#endif - - if (m_swap_chain == VK_NULL_HANDLE) - { - res = vkCreateSwapchainKHR(g_vulkan_context->GetDevice(), &swap_chain_info, nullptr, - &m_swap_chain); - } - if (res != VK_SUCCESS) - { - LOG_VULKAN_ERROR(res, "vkCreateSwapchainKHR failed: "); - return false; - } - - // Now destroy the old swap chain, since it's been recreated. - // We can do this immediately since all work should have been completed before calling resize. - if (old_swap_chain != VK_NULL_HANDLE) - vkDestroySwapchainKHR(g_vulkan_context->GetDevice(), old_swap_chain, nullptr); - - m_width = size.width; - m_height = size.height; - m_layers = image_layers; - return true; -} - -bool SwapChain::SetupSwapChainImages() -{ - ASSERT(m_swap_chain_images.empty()); - - uint32_t image_count; - VkResult res = - vkGetSwapchainImagesKHR(g_vulkan_context->GetDevice(), m_swap_chain, &image_count, nullptr); - if (res != VK_SUCCESS) - { - LOG_VULKAN_ERROR(res, "vkGetSwapchainImagesKHR failed: "); - return false; - } - - std::vector images(image_count); - res = vkGetSwapchainImagesKHR(g_vulkan_context->GetDevice(), m_swap_chain, &image_count, - images.data()); - ASSERT(res == VK_SUCCESS); - - const TextureConfig texture_config(TextureConfig( - m_width, m_height, 1, m_layers, 1, m_texture_format, AbstractTextureFlag_RenderTarget)); - const VkRenderPass load_render_pass = g_object_cache->GetRenderPass( - m_surface_format.format, VK_FORMAT_UNDEFINED, 1, VK_ATTACHMENT_LOAD_OP_LOAD); - const VkRenderPass clear_render_pass = g_object_cache->GetRenderPass( - m_surface_format.format, VK_FORMAT_UNDEFINED, 1, VK_ATTACHMENT_LOAD_OP_CLEAR); - if (load_render_pass == VK_NULL_HANDLE || clear_render_pass == VK_NULL_HANDLE) - { - PanicAlertFmt("Failed to get swap chain render passes."); - return false; - } - - m_swap_chain_images.reserve(image_count); - for (uint32_t i = 0; i < image_count; i++) - { - SwapChainImage image; - image.image = images[i]; - - // Create texture object, which creates a view of the backbuffer - image.texture = - VKTexture::CreateAdopted(texture_config, image.image, - m_layers > 1 ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D, - VK_IMAGE_LAYOUT_UNDEFINED); - if (!image.texture) - return false; - - image.framebuffer = VKFramebuffer::Create(image.texture.get(), nullptr); - if (!image.framebuffer) - { - image.texture.reset(); - return false; - } - - m_swap_chain_images.emplace_back(std::move(image)); - } - - return true; -} - -void SwapChain::DestroySwapChainImages() -{ - for (auto& it : m_swap_chain_images) - { - // Images themselves are cleaned up by the swap chain object - it.framebuffer.reset(); - it.texture.reset(); - } - m_swap_chain_images.clear(); -} - -void SwapChain::DestroySwapChain() -{ - if (m_swap_chain == VK_NULL_HANDLE) - return; - - // Release exclusive fullscreen before destroying. - if (m_current_fullscreen_state) - SetFullscreenState(false); - - vkDestroySwapchainKHR(g_vulkan_context->GetDevice(), m_swap_chain, nullptr); - m_swap_chain = VK_NULL_HANDLE; -} - -VkResult SwapChain::AcquireNextImage() -{ - VkResult res = vkAcquireNextImageKHR(g_vulkan_context->GetDevice(), m_swap_chain, UINT64_MAX, - g_command_buffer_mgr->GetCurrentCommandBufferSemaphore(), - VK_NULL_HANDLE, &m_current_swap_chain_image_index); - if (res != VK_SUCCESS && res != VK_ERROR_OUT_OF_DATE_KHR && res != VK_SUBOPTIMAL_KHR) - LOG_VULKAN_ERROR(res, "vkAcquireNextImageKHR failed: "); - - return res; -} - -bool SwapChain::ResizeSwapChain() -{ - DestroySwapChainImages(); - if (!CreateSwapChain() || !SetupSwapChainImages()) - { - PanicAlertFmt("Failed to re-configure swap chain images, this is fatal (for now)"); - return false; - } - - return true; -} - -bool SwapChain::RecreateSwapChain() -{ - DestroySwapChainImages(); - DestroySwapChain(); - if (!CreateSwapChain() || !SetupSwapChainImages()) - { - PanicAlertFmt("Failed to re-configure swap chain images, this is fatal (for now)"); - return false; - } - - return true; -} - -bool SwapChain::SetVSync(bool enabled) -{ - if (m_vsync_enabled == enabled) - return true; - - // Recreate the swap chain with the new present mode. - m_vsync_enabled = enabled; - return RecreateSwapChain(); -} - -bool SwapChain::SetFullscreenState(bool state) -{ -#ifdef SUPPORTS_VULKAN_EXCLUSIVE_FULLSCREEN - if (m_current_fullscreen_state == state) - return true; - - if (state) - { - VkResult res = vkAcquireFullScreenExclusiveModeEXT(g_vulkan_context->GetDevice(), m_swap_chain); - if (res != VK_SUCCESS) - { - LOG_VULKAN_ERROR(res, "vkAcquireFullScreenExclusiveModeEXT failed:"); - return false; - } - - INFO_LOG_FMT(VIDEO, "Exclusive fullscreen acquired."); - } - else - { - VkResult res = vkReleaseFullScreenExclusiveModeEXT(g_vulkan_context->GetDevice(), m_swap_chain); - if (res != VK_SUCCESS) - LOG_VULKAN_ERROR(res, "vkReleaseFullScreenExclusiveModeEXT failed:"); - - INFO_LOG_FMT(VIDEO, "Exclusive fullscreen released."); - } - - m_current_fullscreen_state = state; - return true; -#else - return false; -#endif -} - -bool SwapChain::RecreateSurface(void* native_handle) -{ - // Destroy the old swap chain, images, and surface. - DestroySwapChainImages(); - DestroySwapChain(); - DestroySurface(); - - // Re-create the surface with the new native handle - m_wsi.render_surface = native_handle; - m_surface = CreateVulkanSurface(g_vulkan_context->GetVulkanInstance(), m_wsi); - if (m_surface == VK_NULL_HANDLE) - return false; - - // The validation layers get angry at us if we don't call this before creating the swapchain. - VkBool32 present_supported = VK_TRUE; - VkResult res = vkGetPhysicalDeviceSurfaceSupportKHR( - g_vulkan_context->GetPhysicalDevice(), g_vulkan_context->GetPresentQueueFamilyIndex(), - m_surface, &present_supported); - if (res != VK_SUCCESS) - { - LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceSupportKHR failed: "); - return false; - } - if (!present_supported) - { - PanicAlertFmt("Recreated surface does not support presenting."); - return false; - } - - // Update exclusive fullscreen support (unlikely to change). - m_fullscreen_supported = g_vulkan_context->SupportsExclusiveFullscreen(m_wsi, m_surface); - g_Config.backend_info.bSupportsExclusiveFullscreen = m_fullscreen_supported; - g_ActiveConfig.backend_info.bSupportsExclusiveFullscreen = m_fullscreen_supported; - m_current_fullscreen_state = false; - m_next_fullscreen_state = false; - - // Finally re-create the swap chain - if (!CreateSwapChain() || !SetupSwapChainImages()) - return false; - - return true; -} - -void SwapChain::DestroySurface() -{ - vkDestroySurfaceKHR(g_vulkan_context->GetVulkanInstance(), m_surface, nullptr); - m_surface = VK_NULL_HANDLE; -} -} // namespace Vulkan diff --git a/Source/Core/VideoBackends/Vulkan/SwapChain.h b/Source/Core/VideoBackends/Vulkan/SwapChain.h deleted file mode 100644 index a6ee28d965..0000000000 --- a/Source/Core/VideoBackends/Vulkan/SwapChain.h +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright 2016 Dolphin Emulator Project -// Licensed under GPLv2+ -// Refer to the license.txt file included. - -#pragma once - -#include -#include - -#include "Common/CommonTypes.h" -#include "Common/WindowSystemInfo.h" -#include "VideoBackends/Vulkan/Constants.h" -#include "VideoCommon/TextureConfig.h" - -namespace Vulkan -{ -class CommandBufferManager; -class ObjectCache; -class VKTexture; -class VKFramebuffer; - -class SwapChain -{ -public: - SwapChain(const WindowSystemInfo& wsi, VkSurfaceKHR surface, bool vsync); - ~SwapChain(); - - // Creates a vulkan-renderable surface for the specified window handle. - static VkSurfaceKHR CreateVulkanSurface(VkInstance instance, const WindowSystemInfo& wsi); - - // Create a new swap chain from a pre-existing surface. - static std::unique_ptr Create(const WindowSystemInfo& wsi, VkSurfaceKHR surface, - bool vsync); - - VkSurfaceKHR GetSurface() const { return m_surface; } - VkSurfaceFormatKHR GetSurfaceFormat() const { return m_surface_format; } - AbstractTextureFormat GetTextureFormat() const { return m_texture_format; } - bool IsVSyncEnabled() const { return m_vsync_enabled; } - bool IsStereoEnabled() const { return m_layers == 2; } - VkSwapchainKHR GetSwapChain() const { return m_swap_chain; } - u32 GetWidth() const { return m_width; } - u32 GetHeight() const { return m_height; } - u32 GetCurrentImageIndex() const { return m_current_swap_chain_image_index; } - VkImage GetCurrentImage() const - { - return m_swap_chain_images[m_current_swap_chain_image_index].image; - } - VKTexture* GetCurrentTexture() const - { - return m_swap_chain_images[m_current_swap_chain_image_index].texture.get(); - } - VKFramebuffer* GetCurrentFramebuffer() const - { - return m_swap_chain_images[m_current_swap_chain_image_index].framebuffer.get(); - } - VkResult AcquireNextImage(); - - bool RecreateSurface(void* native_handle); - bool ResizeSwapChain(); - bool RecreateSwapChain(); - - // Change vsync enabled state. This may fail as it causes a swapchain recreation. - bool SetVSync(bool enabled); - - // Is exclusive fullscreen supported? - bool IsFullscreenSupported() const { return m_fullscreen_supported; } - - // Retrieves the "next" fullscreen state. Safe to call off-thread. - bool GetCurrentFullscreenState() const { return m_current_fullscreen_state; } - bool GetNextFullscreenState() const { return m_next_fullscreen_state; } - void SetNextFullscreenState(bool state) { m_next_fullscreen_state = state; } - - // Updates the fullscreen state. Must call on-thread. - bool SetFullscreenState(bool state); - -private: - bool SelectSurfaceFormat(); - bool SelectPresentMode(); - - bool CreateSwapChain(); - void DestroySwapChain(); - - bool SetupSwapChainImages(); - void DestroySwapChainImages(); - - void DestroySurface(); - - struct SwapChainImage - { - VkImage image; - std::unique_ptr texture; - std::unique_ptr framebuffer; - }; - - WindowSystemInfo m_wsi; - VkSurfaceKHR m_surface = VK_NULL_HANDLE; - VkSurfaceFormatKHR m_surface_format = {}; - VkPresentModeKHR m_present_mode = VK_PRESENT_MODE_RANGE_SIZE_KHR; - AbstractTextureFormat m_texture_format = AbstractTextureFormat::Undefined; - bool m_vsync_enabled = false; - bool m_fullscreen_supported = false; - bool m_current_fullscreen_state = false; - bool m_next_fullscreen_state = false; - - VkSwapchainKHR m_swap_chain = VK_NULL_HANDLE; - std::vector m_swap_chain_images; - u32 m_current_swap_chain_image_index = 0; - - u32 m_width = 0; - u32 m_height = 0; - u32 m_layers = 0; -}; - -} // namespace Vulkan diff --git a/Source/Core/VideoBackends/Vulkan/VKBoundingBox.cpp b/Source/Core/VideoBackends/Vulkan/VKBoundingBox.cpp new file mode 100644 index 0000000000..5412d5cb06 --- /dev/null +++ b/Source/Core/VideoBackends/Vulkan/VKBoundingBox.cpp @@ -0,0 +1,254 @@ +// Copyright 2016 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include + +#include "Common/Assert.h" +#include "Common/Logging/Log.h" + +#include "VideoBackends/Vulkan/CommandBufferManager.h" +#include "VideoBackends/Vulkan/ObjectCache.h" +#include "VideoBackends/Vulkan/StagingBuffer.h" +#include "VideoBackends/Vulkan/StateTracker.h" +#include "VideoBackends/Vulkan/VKBoundingBox.h" +#include "VideoBackends/Vulkan/VKRenderer.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_ActiveConfig.backend_info.bSupportsBBox) + { + WARN_LOG_FMT(VIDEO, "Vulkan: Bounding box is unsupported by your device."); + return true; + } + + if (!CreateGPUBuffer()) + return false; + + if (!CreateReadbackBuffer()) + return false; + + // Bind bounding box to state tracker + StateTracker::GetInstance()->SetSSBO(m_gpu_buffer, 0, BUFFER_SIZE); + return true; +} + +void BoundingBox::Flush() +{ + 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) + { + StateTracker::GetInstance()->EndRenderPass(); + + // Ensure GPU buffer is in a state where it can be transferred to. + StagingBuffer::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) + { + StagingBuffer::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_FRAGMENT_SHADER_BIT); + } + + // We're now up-to-date. + m_valid = true; +} + +void BoundingBox::Invalidate() +{ + if (m_gpu_buffer == VK_NULL_HANDLE) + return; + + m_valid = false; +} + +s32 BoundingBox::Get(size_t index) +{ + ASSERT(index < NUM_VALUES); + + if (!m_valid) + Readback(); + + s32 value; + m_readback_buffer->Read(index * sizeof(s32), &value, sizeof(value), false); + return value; +} + +void BoundingBox::Set(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, false) + .value_or(0); + 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() +{ + // Can't be done within a render pass. + StateTracker::GetInstance()->EndRenderPass(); + + // Ensure all writes are completed to the GPU buffer prior to the transfer. + StagingBuffer::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_FRAGMENT_SHADER_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. + StagingBuffer::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_FRAGMENT_SHADER_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. + Renderer::GetInstance()->ExecuteCommandBuffer(false, true); + + // Cache is now valid. + m_readback_buffer->InvalidateCPUCache(); + m_valid = true; +} + +} // namespace Vulkan diff --git a/Source/Core/VideoBackends/Vulkan/VKBoundingBox.h b/Source/Core/VideoBackends/Vulkan/VKBoundingBox.h new file mode 100644 index 0000000000..e5fda1066a --- /dev/null +++ b/Source/Core/VideoBackends/Vulkan/VKBoundingBox.h @@ -0,0 +1,49 @@ +// Copyright 2016 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include + +#include "Common/CommonTypes.h" + +#include "VideoBackends/Vulkan/VulkanLoader.h" + +namespace Vulkan +{ +class StagingBuffer; + +class BoundingBox +{ +public: + BoundingBox(); + ~BoundingBox(); + + bool Initialize(); + + s32 Get(size_t index); + void Set(size_t index, s32 value); + + void Invalidate(); + void Flush(); + +private: + bool CreateGPUBuffer(); + bool CreateReadbackBuffer(); + void Readback(); + + VkBuffer m_gpu_buffer = VK_NULL_HANDLE; + VkDeviceMemory m_gpu_memory = VK_NULL_HANDLE; + + 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/VKMain.cpp b/Source/Core/VideoBackends/Vulkan/VKMain.cpp new file mode 100644 index 0000000000..37cc754449 --- /dev/null +++ b/Source/Core/VideoBackends/Vulkan/VKMain.cpp @@ -0,0 +1,361 @@ +// Copyright 2016 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include + +#include "Common/Logging/LogManager.h" +#include "Common/MsgHandler.h" + +#include "VideoBackends/Vulkan/CommandBufferManager.h" +#include "VideoBackends/Vulkan/Constants.h" +#include "VideoBackends/Vulkan/ObjectCache.h" +#include "VideoBackends/Vulkan/StateTracker.h" +#include "VideoBackends/Vulkan/VKPerfQuery.h" +#include "VideoBackends/Vulkan/VKRenderer.h" +#include "VideoBackends/Vulkan/VKSwapChain.h" +#include "VideoBackends/Vulkan/VKVertexManager.h" +#include "VideoBackends/Vulkan/VideoBackend.h" +#include "VideoBackends/Vulkan/VulkanContext.h" + +#include "VideoCommon/FramebufferManager.h" +#include "VideoCommon/TextureCacheBase.h" +#include "VideoCommon/VideoBackendBase.h" +#include "VideoCommon/VideoConfig.h" + +#if defined(VK_USE_PLATFORM_METAL_EXT) +#include +#endif + +namespace Vulkan +{ +void VideoBackend::InitBackendInfo() +{ + VulkanContext::PopulateBackendInfo(&g_Config); + + if (LoadVulkanLibrary()) + { + VkInstance temp_instance = + VulkanContext::CreateVulkanInstance(WindowSystemType::Headless, false, false); + if (temp_instance) + { + if (LoadVulkanInstanceFunctions(temp_instance)) + { + VulkanContext::GPUList gpu_list = VulkanContext::EnumerateGPUs(temp_instance); + VulkanContext::PopulateBackendInfoAdapters(&g_Config, gpu_list); + + if (!gpu_list.empty()) + { + // Use the selected adapter, or the first to fill features. + size_t device_index = static_cast(g_Config.iAdapter); + if (device_index >= gpu_list.size()) + device_index = 0; + + VkPhysicalDevice gpu = gpu_list[device_index]; + VkPhysicalDeviceProperties properties; + vkGetPhysicalDeviceProperties(gpu, &properties); + VkPhysicalDeviceFeatures features; + vkGetPhysicalDeviceFeatures(gpu, &features); + VulkanContext::PopulateBackendInfoFeatures(&g_Config, gpu, properties, features); + VulkanContext::PopulateBackendInfoMultisampleModes(&g_Config, gpu, properties); + } + } + + vkDestroyInstance(temp_instance, nullptr); + } + else + { + PanicAlertFmt("Failed to create Vulkan instance."); + } + + UnloadVulkanLibrary(); + } + else + { + PanicAlertFmt("Failed to load Vulkan library."); + } +} + +// Helper method to check whether the Host GPU logging category is enabled. +static bool IsHostGPULoggingEnabled() +{ + return Common::Log::LogManager::GetInstance()->IsEnabled(Common::Log::HOST_GPU, + Common::Log::LERROR); +} + +// Helper method to determine whether to enable the debug report extension. +static bool ShouldEnableDebugReports(bool enable_validation_layers) +{ + // Enable debug reports if the Host GPU log option is checked, or validation layers are enabled. + // The only issue here is that if Host GPU is not checked when the instance is created, the debug + // report extension will not be enabled, requiring the game to be restarted before any reports + // will be logged. Otherwise, we'd have to enable debug reports on every instance, when most + // users will never check the Host GPU logging category. + return enable_validation_layers || IsHostGPULoggingEnabled(); +} + +bool VideoBackend::Initialize(const WindowSystemInfo& wsi) +{ + if (!LoadVulkanLibrary()) + { + PanicAlertFmt("Failed to load Vulkan library."); + return false; + } + + // Check for presence of the validation layers before trying to enable it + bool enable_validation_layer = g_Config.bEnableValidationLayer; + if (enable_validation_layer && !VulkanContext::CheckValidationLayerAvailablility()) + { + WARN_LOG_FMT(VIDEO, "Validation layer requested but not available, disabling."); + enable_validation_layer = false; + } + + // Create Vulkan instance, needed before we can create a surface, or enumerate devices. + // We use this instance to fill in backend info, then re-use it for the actual device. + bool enable_surface = wsi.type != WindowSystemType::Headless; + bool enable_debug_reports = ShouldEnableDebugReports(enable_validation_layer); + VkInstance instance = + VulkanContext::CreateVulkanInstance(wsi.type, enable_debug_reports, enable_validation_layer); + if (instance == VK_NULL_HANDLE) + { + PanicAlertFmt("Failed to create Vulkan instance."); + UnloadVulkanLibrary(); + return false; + } + + // Load instance function pointers. + if (!LoadVulkanInstanceFunctions(instance)) + { + PanicAlertFmt("Failed to load Vulkan instance functions."); + vkDestroyInstance(instance, nullptr); + UnloadVulkanLibrary(); + return false; + } + + // Obtain a list of physical devices (GPUs) from the instance. + // We'll re-use this list later when creating the device. + VulkanContext::GPUList gpu_list = VulkanContext::EnumerateGPUs(instance); + if (gpu_list.empty()) + { + PanicAlertFmt("No Vulkan physical devices available."); + vkDestroyInstance(instance, nullptr); + UnloadVulkanLibrary(); + return false; + } + + // Populate BackendInfo with as much information as we can at this point. + VulkanContext::PopulateBackendInfo(&g_Config); + VulkanContext::PopulateBackendInfoAdapters(&g_Config, gpu_list); + + // We need the surface before we can create a device, as some parameters depend on it. + VkSurfaceKHR surface = VK_NULL_HANDLE; + if (enable_surface) + { + surface = SwapChain::CreateVulkanSurface(instance, wsi); + if (surface == VK_NULL_HANDLE) + { + PanicAlertFmt("Failed to create Vulkan surface."); + vkDestroyInstance(instance, nullptr); + UnloadVulkanLibrary(); + return false; + } + } + + // Since we haven't called InitializeShared yet, iAdapter may be out of range, + // so we have to check it ourselves. + size_t selected_adapter_index = static_cast(g_Config.iAdapter); + if (selected_adapter_index >= gpu_list.size()) + { + WARN_LOG_FMT(VIDEO, "Vulkan adapter index out of range, selecting first adapter."); + selected_adapter_index = 0; + } + + // Now we can create the Vulkan device. VulkanContext takes ownership of the instance and surface. + g_vulkan_context = VulkanContext::Create(instance, gpu_list[selected_adapter_index], surface, + enable_debug_reports, enable_validation_layer); + if (!g_vulkan_context) + { + PanicAlertFmt("Failed to create Vulkan device"); + UnloadVulkanLibrary(); + return false; + } + + // Since VulkanContext maintains a copy of the device features and properties, we can use this + // to initialize the backend information, so that we don't need to enumerate everything again. + VulkanContext::PopulateBackendInfoFeatures(&g_Config, g_vulkan_context->GetPhysicalDevice(), + g_vulkan_context->GetDeviceProperties(), + g_vulkan_context->GetDeviceFeatures()); + VulkanContext::PopulateBackendInfoMultisampleModes( + &g_Config, g_vulkan_context->GetPhysicalDevice(), g_vulkan_context->GetDeviceProperties()); + g_Config.backend_info.bSupportsExclusiveFullscreen = + enable_surface && g_vulkan_context->SupportsExclusiveFullscreen(wsi, surface); + + // With the backend information populated, we can now initialize videocommon. + InitializeShared(); + + // Create command buffers. We do this separately because the other classes depend on it. + g_command_buffer_mgr = std::make_unique(g_Config.bBackendMultithreading); + if (!g_command_buffer_mgr->Initialize()) + { + PanicAlertFmt("Failed to create Vulkan command buffers"); + Shutdown(); + return false; + } + + // Remaining classes are also dependent on object cache. + g_object_cache = std::make_unique(); + if (!g_object_cache->Initialize()) + { + PanicAlertFmt("Failed to initialize Vulkan object cache."); + Shutdown(); + return false; + } + + // Create swap chain. This has to be done early so that the target size is correct for auto-scale. + std::unique_ptr swap_chain; + if (surface != VK_NULL_HANDLE) + { + swap_chain = SwapChain::Create(wsi, surface, g_ActiveConfig.bVSyncActive); + if (!swap_chain) + { + PanicAlertFmt("Failed to create Vulkan swap chain."); + Shutdown(); + return false; + } + } + + if (!StateTracker::CreateInstance()) + { + PanicAlertFmt("Failed to create state tracker"); + Shutdown(); + return false; + } + + // Create main wrapper instances. + g_renderer = std::make_unique(std::move(swap_chain), wsi.render_surface_scale); + g_vertex_manager = std::make_unique(); + g_shader_cache = std::make_unique(); + g_framebuffer_manager = std::make_unique(); + g_texture_cache = std::make_unique(); + g_perf_query = std::make_unique(); + + if (!g_vertex_manager->Initialize() || !g_shader_cache->Initialize() || + !g_renderer->Initialize() || !g_framebuffer_manager->Initialize() || + !g_texture_cache->Initialize() || !PerfQuery::GetInstance()->Initialize()) + { + PanicAlertFmt("Failed to initialize renderer classes"); + Shutdown(); + return false; + } + + g_shader_cache->InitializeShaderCache(); + return true; +} + +void VideoBackend::Shutdown() +{ + if (g_vulkan_context) + vkDeviceWaitIdle(g_vulkan_context->GetDevice()); + + if (g_shader_cache) + g_shader_cache->Shutdown(); + + if (g_object_cache) + g_object_cache->Shutdown(); + + if (g_renderer) + g_renderer->Shutdown(); + + g_perf_query.reset(); + g_texture_cache.reset(); + g_framebuffer_manager.reset(); + g_shader_cache.reset(); + g_vertex_manager.reset(); + g_renderer.reset(); + g_object_cache.reset(); + StateTracker::DestroyInstance(); + g_command_buffer_mgr.reset(); + g_vulkan_context.reset(); + ShutdownShared(); + UnloadVulkanLibrary(); +} + +#if defined(VK_USE_PLATFORM_METAL_EXT) +static bool IsRunningOnMojaveOrHigher() +{ + // id processInfo = [NSProcessInfo processInfo] + id processInfo = reinterpret_cast(objc_msgSend)( + objc_getClass("NSProcessInfo"), sel_getUid("processInfo")); + if (!processInfo) + return false; + + struct OSVersion // NSOperatingSystemVersion + { + size_t major_version; // NSInteger majorVersion + size_t minor_version; // NSInteger minorVersion + size_t patch_version; // NSInteger patchVersion + }; + + // const bool meets_requirement = [processInfo isOperatingSystemAtLeastVersion:required_version]; + constexpr OSVersion required_version = {10, 14, 0}; + const bool meets_requirement = reinterpret_cast(objc_msgSend)( + processInfo, sel_getUid("isOperatingSystemAtLeastVersion:"), required_version); + return meets_requirement; +} +#endif + +void VideoBackend::PrepareWindow(WindowSystemInfo& wsi) +{ +#if defined(VK_USE_PLATFORM_METAL_EXT) + // This is kinda messy, but it avoids having to write Objective C++ just to create a metal layer. + id view = reinterpret_cast(wsi.render_surface); + Class clsCAMetalLayer = objc_getClass("CAMetalLayer"); + if (!clsCAMetalLayer) + { + ERROR_LOG_FMT(VIDEO, "Failed to get CAMetalLayer class."); + return; + } + + // [CAMetalLayer layer] + id layer = reinterpret_cast(objc_msgSend)(objc_getClass("CAMetalLayer"), + sel_getUid("layer")); + if (!layer) + { + ERROR_LOG_FMT(VIDEO, "Failed to create Metal layer."); + return; + } + + // [view setWantsLayer:YES] + reinterpret_cast(objc_msgSend)(view, sel_getUid("setWantsLayer:"), YES); + + // [view setLayer:layer] + reinterpret_cast(objc_msgSend)(view, sel_getUid("setLayer:"), layer); + + // NSScreen* screen = [NSScreen mainScreen] + id screen = reinterpret_cast(objc_msgSend)(objc_getClass("NSScreen"), + sel_getUid("mainScreen")); + + // CGFloat factor = [screen backingScaleFactor] + double factor = + reinterpret_cast(objc_msgSend)(screen, sel_getUid("backingScaleFactor")); + + // layer.contentsScale = factor + reinterpret_cast(objc_msgSend)(layer, sel_getUid("setContentsScale:"), + factor); + + // Store the layer pointer, that way MoltenVK doesn't call [NSView layer] outside the main thread. + wsi.render_surface = layer; + + // The Metal version included with MacOS 10.13 and below does not support several features we + // require. Furthermore, the drivers seem to choke on our shaders (mainly Intel). So, we warn + // the user that this is an unsupported configuration, but permit them to continue. + if (!IsRunningOnMojaveOrHigher()) + { + PanicAlertFmtT( + "You are attempting to use the Vulkan (Metal) backend on an unsupported operating system. " + "For all functionality to be enabled, you must use macOS 10.14 (Mojave) or newer. Please " + "do not report any issues encountered unless they also occur on 10.14+."); + } +#endif +} +} // namespace Vulkan diff --git a/Source/Core/VideoBackends/Vulkan/VKPerfQuery.cpp b/Source/Core/VideoBackends/Vulkan/VKPerfQuery.cpp new file mode 100644 index 0000000000..c8fcd05842 --- /dev/null +++ b/Source/Core/VideoBackends/Vulkan/VKPerfQuery.cpp @@ -0,0 +1,217 @@ +// Copyright 2016 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "VideoBackends/Vulkan/VKPerfQuery.h" + +#include +#include +#include + +#include "Common/Assert.h" +#include "Common/Logging/Log.h" +#include "Common/MsgHandler.h" + +#include "VideoBackends/Vulkan/CommandBufferManager.h" +#include "VideoBackends/Vulkan/StateTracker.h" +#include "VideoBackends/Vulkan/VKRenderer.h" +#include "VideoBackends/Vulkan/VulkanContext.h" +#include "VideoCommon/VideoCommon.h" + +namespace Vulkan +{ +PerfQuery::PerfQuery() = default; + +PerfQuery::~PerfQuery() +{ + if (m_query_pool != VK_NULL_HANDLE) + vkDestroyQueryPool(g_vulkan_context->GetDevice(), m_query_pool, nullptr); +} + +bool PerfQuery::Initialize() +{ + if (!CreateQueryPool()) + { + PanicAlertFmt("Failed to create query pool"); + return false; + } + + return true; +} + +void PerfQuery::EnableQuery(PerfQueryGroup type) +{ + // Block if there are no free slots. + // Otherwise, try to keep half of them available. + if (m_query_count > m_query_buffer.size() / 2) + PartialFlush(m_query_count == PERF_QUERY_BUFFER_SIZE); + + // Ensure command buffer is ready to go before beginning the query, that way we don't submit + // a buffer with open queries. + StateTracker::GetInstance()->Bind(); + + if (type == PQG_ZCOMP_ZCOMPLOC || type == PQG_ZCOMP) + { + ActiveQuery& entry = m_query_buffer[m_query_next_pos]; + DEBUG_ASSERT(!entry.has_value); + entry.has_value = true; + + // Use precise queries if supported, otherwise boolean (which will be incorrect). + VkQueryControlFlags flags = + g_vulkan_context->SupportsPreciseOcclusionQueries() ? VK_QUERY_CONTROL_PRECISE_BIT : 0; + + // Ensure the query starts within a render pass. + StateTracker::GetInstance()->BeginRenderPass(); + vkCmdBeginQuery(g_command_buffer_mgr->GetCurrentCommandBuffer(), m_query_pool, m_query_next_pos, + flags); + } +} + +void PerfQuery::DisableQuery(PerfQueryGroup type) +{ + if (type == PQG_ZCOMP_ZCOMPLOC || type == PQG_ZCOMP) + { + vkCmdEndQuery(g_command_buffer_mgr->GetCurrentCommandBuffer(), m_query_pool, m_query_next_pos); + m_query_next_pos = (m_query_next_pos + 1) % PERF_QUERY_BUFFER_SIZE; + m_query_count++; + } +} + +void PerfQuery::ResetQuery() +{ + m_query_count = 0; + m_query_readback_pos = 0; + m_query_next_pos = 0; + std::fill(std::begin(m_results), std::end(m_results), 0); + + // Reset entire query pool, ensuring all queries are ready to write to. + StateTracker::GetInstance()->EndRenderPass(); + vkCmdResetQueryPool(g_command_buffer_mgr->GetCurrentCommandBuffer(), m_query_pool, 0, + PERF_QUERY_BUFFER_SIZE); + + std::memset(m_query_buffer.data(), 0, sizeof(ActiveQuery) * m_query_buffer.size()); +} + +u32 PerfQuery::GetQueryResult(PerfQueryType type) +{ + u32 result = 0; + if (type == PQ_ZCOMP_INPUT_ZCOMPLOC || type == PQ_ZCOMP_OUTPUT_ZCOMPLOC) + result = m_results[PQG_ZCOMP_ZCOMPLOC]; + else if (type == PQ_ZCOMP_INPUT || type == PQ_ZCOMP_OUTPUT) + result = m_results[PQG_ZCOMP]; + else if (type == PQ_BLEND_INPUT) + result = m_results[PQG_ZCOMP] + m_results[PQG_ZCOMP_ZCOMPLOC]; + else if (type == PQ_EFB_COPY_CLOCKS) + result = m_results[PQG_EFB_COPY_CLOCKS]; + + return result / 4; +} + +void PerfQuery::FlushResults() +{ + while (!IsFlushed()) + PartialFlush(true); +} + +bool PerfQuery::IsFlushed() const +{ + return m_query_count == 0; +} + +bool PerfQuery::CreateQueryPool() +{ + VkQueryPoolCreateInfo info = { + VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO, // VkStructureType sType + nullptr, // const void* pNext + 0, // VkQueryPoolCreateFlags flags + VK_QUERY_TYPE_OCCLUSION, // VkQueryType queryType + PERF_QUERY_BUFFER_SIZE, // uint32_t queryCount + 0 // VkQueryPipelineStatisticFlags pipelineStatistics; + }; + + VkResult res = vkCreateQueryPool(g_vulkan_context->GetDevice(), &info, nullptr, &m_query_pool); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkCreateQueryPool failed: "); + return false; + } + + return true; +} + +void PerfQuery::ReadbackQueries() +{ + const u64 completed_fence_counter = g_command_buffer_mgr->GetCompletedFenceCounter(); + + // Need to save these since ProcessResults will modify them. + const u32 outstanding_queries = m_query_count; + u32 readback_count = 0; + for (u32 i = 0; i < outstanding_queries; i++) + { + u32 index = (m_query_readback_pos + readback_count) % PERF_QUERY_BUFFER_SIZE; + const ActiveQuery& entry = m_query_buffer[index]; + if (entry.fence_counter > completed_fence_counter) + break; + + // If this wrapped around, we need to flush the entries before the end of the buffer. + if (index < m_query_readback_pos) + { + ReadbackQueries(readback_count); + DEBUG_ASSERT(m_query_readback_pos == 0); + readback_count = 0; + } + + readback_count++; + } + + if (readback_count > 0) + ReadbackQueries(readback_count); +} + +void PerfQuery::ReadbackQueries(u32 query_count) +{ + // Should be at maximum query_count queries pending. + ASSERT(query_count <= m_query_count && + (m_query_readback_pos + query_count) <= PERF_QUERY_BUFFER_SIZE); + + // Read back from the GPU. + VkResult res = + vkGetQueryPoolResults(g_vulkan_context->GetDevice(), m_query_pool, m_query_readback_pos, + query_count, query_count * sizeof(PerfQueryDataType), + m_query_result_buffer.data(), sizeof(PerfQueryDataType), 0); + if (res != VK_SUCCESS) + LOG_VULKAN_ERROR(res, "vkGetQueryPoolResults failed: "); + + // Remove pending queries. + for (u32 i = 0; i < query_count; i++) + { + u32 index = (m_query_readback_pos + i) % PERF_QUERY_BUFFER_SIZE; + ActiveQuery& entry = m_query_buffer[index]; + + // Should have a fence associated with it (waiting for a result). + DEBUG_ASSERT(entry.fence_counter != 0); + entry.fence_counter = 0; + entry.has_value = false; + + // NOTE: Reported pixel metrics should be referenced to native resolution + m_results[entry.query_type] += + static_cast(static_cast(m_query_result_buffer[i]) * EFB_WIDTH / + g_renderer->GetTargetWidth() * EFB_HEIGHT / g_renderer->GetTargetHeight()); + } + + m_query_readback_pos = (m_query_readback_pos + query_count) % PERF_QUERY_BUFFER_SIZE; + m_query_count -= query_count; +} + +void PerfQuery::PartialFlush(bool blocking) +{ + // Submit a command buffer in the background if the front query is not bound to one. + if (blocking || m_query_buffer[m_query_readback_pos].fence_counter == + g_command_buffer_mgr->GetCurrentFenceCounter()) + { + Renderer::GetInstance()->ExecuteCommandBuffer(true, blocking); + } + + ReadbackQueries(); +} +} // namespace Vulkan diff --git a/Source/Core/VideoBackends/Vulkan/VKPerfQuery.h b/Source/Core/VideoBackends/Vulkan/VKPerfQuery.h new file mode 100644 index 0000000000..8ca91ac658 --- /dev/null +++ b/Source/Core/VideoBackends/Vulkan/VKPerfQuery.h @@ -0,0 +1,60 @@ +// 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/Constants.h" +#include "VideoCommon/PerfQueryBase.h" + +namespace Vulkan +{ +class PerfQuery : public PerfQueryBase +{ +public: + PerfQuery(); + ~PerfQuery(); + + static PerfQuery* GetInstance() { return static_cast(g_perf_query.get()); } + + bool Initialize(); + + void EnableQuery(PerfQueryGroup type) override; + void DisableQuery(PerfQueryGroup type) override; + void ResetQuery() override; + u32 GetQueryResult(PerfQueryType type) override; + void FlushResults() override; + bool IsFlushed() const override; + +private: + // u32 is used for the sample counts. + using PerfQueryDataType = u32; + + // when testing in SMS: 64 was too small, 128 was ok + // TODO: This should be size_t, but the base class uses u32s + static const u32 PERF_QUERY_BUFFER_SIZE = 512; + + struct ActiveQuery + { + u64 fence_counter; + PerfQueryType query_type; + bool has_value; + }; + + bool CreateQueryPool(); + void ReadbackQueries(); + void ReadbackQueries(u32 query_count); + void PartialFlush(bool blocking); + + VkQueryPool m_query_pool = VK_NULL_HANDLE; + u32 m_query_readback_pos = 0; + u32 m_query_next_pos = 0; + std::array m_query_buffer = {}; + std::array m_query_result_buffer = {}; +}; + +} // namespace Vulkan diff --git a/Source/Core/VideoBackends/Vulkan/VKPipeline.cpp b/Source/Core/VideoBackends/Vulkan/VKPipeline.cpp index 0152b67921..b7c656bec9 100644 --- a/Source/Core/VideoBackends/Vulkan/VKPipeline.cpp +++ b/Source/Core/VideoBackends/Vulkan/VKPipeline.cpp @@ -12,7 +12,7 @@ #include "VideoBackends/Vulkan/ObjectCache.h" #include "VideoBackends/Vulkan/VKShader.h" #include "VideoBackends/Vulkan/VKTexture.h" -#include "VideoBackends/Vulkan/VertexFormat.h" +#include "VideoBackends/Vulkan/VKVertexFormat.h" #include "VideoBackends/Vulkan/VulkanContext.h" namespace Vulkan diff --git a/Source/Core/VideoBackends/Vulkan/VKRenderer.cpp b/Source/Core/VideoBackends/Vulkan/VKRenderer.cpp new file mode 100644 index 0000000000..b2f3f45072 --- /dev/null +++ b/Source/Core/VideoBackends/Vulkan/VKRenderer.cpp @@ -0,0 +1,652 @@ +// Copyright 2016 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include +#include +#include +#include +#include +#include + +#include "Common/Assert.h" +#include "Common/CommonTypes.h" +#include "Common/Logging/Log.h" +#include "Common/MsgHandler.h" + +#include "Core/Core.h" + +#include "VideoBackends/Vulkan/CommandBufferManager.h" +#include "VideoBackends/Vulkan/ObjectCache.h" +#include "VideoBackends/Vulkan/StateTracker.h" +#include "VideoBackends/Vulkan/VKBoundingBox.h" +#include "VideoBackends/Vulkan/VKPerfQuery.h" +#include "VideoBackends/Vulkan/VKPipeline.h" +#include "VideoBackends/Vulkan/VKRenderer.h" +#include "VideoBackends/Vulkan/VKShader.h" +#include "VideoBackends/Vulkan/VKStreamBuffer.h" +#include "VideoBackends/Vulkan/VKSwapChain.h" +#include "VideoBackends/Vulkan/VKTexture.h" +#include "VideoBackends/Vulkan/VKVertexFormat.h" +#include "VideoBackends/Vulkan/VulkanContext.h" + +#include "VideoCommon/DriverDetails.h" +#include "VideoCommon/FramebufferManager.h" +#include "VideoCommon/RenderState.h" +#include "VideoCommon/VertexManagerBase.h" +#include "VideoCommon/VideoBackendBase.h" +#include "VideoCommon/VideoCommon.h" +#include "VideoCommon/VideoConfig.h" +#include "VideoCommon/XFMemory.h" + +namespace Vulkan +{ +Renderer::Renderer(std::unique_ptr swap_chain, float backbuffer_scale) + : ::Renderer(swap_chain ? static_cast(swap_chain->GetWidth()) : 1, + swap_chain ? static_cast(swap_chain->GetHeight()) : 0, backbuffer_scale, + swap_chain ? swap_chain->GetTextureFormat() : AbstractTextureFormat::Undefined), + m_swap_chain(std::move(swap_chain)) +{ + UpdateActiveConfig(); + for (SamplerState& m_sampler_state : m_sampler_states) + m_sampler_state.hex = RenderState::GetPointSamplerState().hex; +} + +Renderer::~Renderer() = default; + +bool Renderer::IsHeadless() const +{ + return m_swap_chain == nullptr; +} + +bool Renderer::Initialize() +{ + if (!::Renderer::Initialize()) + return false; + + m_bounding_box = std::make_unique(); + if (!m_bounding_box->Initialize()) + { + PanicAlertFmt("Failed to initialize bounding box."); + return false; + } + + // Various initialization routines will have executed commands on the command buffer. + // Execute what we have done before beginning the first frame. + ExecuteCommandBuffer(true, false); + return true; +} + +void Renderer::Shutdown() +{ + ::Renderer::Shutdown(); + m_swap_chain.reset(); +} + +std::unique_ptr Renderer::CreateTexture(const TextureConfig& config) +{ + return VKTexture::Create(config); +} + +std::unique_ptr Renderer::CreateStagingTexture(StagingTextureType type, + const TextureConfig& config) +{ + return VKStagingTexture::Create(type, config); +} + +std::unique_ptr Renderer::CreateShaderFromSource(ShaderStage stage, + std::string_view source) +{ + return VKShader::CreateFromSource(stage, source); +} + +std::unique_ptr Renderer::CreateShaderFromBinary(ShaderStage stage, + const void* data, size_t length) +{ + return VKShader::CreateFromBinary(stage, data, length); +} + +std::unique_ptr +Renderer::CreateNativeVertexFormat(const PortableVertexDeclaration& vtx_decl) +{ + return std::make_unique(vtx_decl); +} + +std::unique_ptr Renderer::CreatePipeline(const AbstractPipelineConfig& config, + const void* cache_data, + size_t cache_data_length) +{ + return VKPipeline::Create(config); +} + +std::unique_ptr Renderer::CreateFramebuffer(AbstractTexture* color_attachment, + AbstractTexture* depth_attachment) +{ + return VKFramebuffer::Create(static_cast(color_attachment), + static_cast(depth_attachment)); +} + +void Renderer::SetPipeline(const AbstractPipeline* pipeline) +{ + StateTracker::GetInstance()->SetPipeline(static_cast(pipeline)); +} + +u16 Renderer::BBoxRead(int index) +{ + return static_cast(m_bounding_box->Get(index)); +} + +void Renderer::BBoxWrite(int index, u16 value) +{ + m_bounding_box->Set(index, value); +} + +void Renderer::BBoxFlush() +{ + m_bounding_box->Flush(); + m_bounding_box->Invalidate(); +} + +void Renderer::ClearScreen(const MathUtil::Rectangle& rc, bool color_enable, bool alpha_enable, + bool z_enable, u32 color, u32 z) +{ + g_framebuffer_manager->FlushEFBPokes(); + g_framebuffer_manager->FlagPeekCacheAsOutOfDate(); + + // Native -> EFB coordinates + MathUtil::Rectangle target_rc = Renderer::ConvertEFBRectangle(rc); + + // Size we pass this size to vkBeginRenderPass, it has to be clamped to the framebuffer + // dimensions. The other backends just silently ignore this case. + target_rc.ClampUL(0, 0, m_target_width, m_target_height); + + VkRect2D target_vk_rc = { + {target_rc.left, target_rc.top}, + {static_cast(target_rc.GetWidth()), static_cast(target_rc.GetHeight())}}; + + // Determine whether the EFB has an alpha channel. If it doesn't, we can clear the alpha + // channel to 0xFF. This hopefully allows us to use the fast path in most cases. + if (bpmem.zcontrol.pixel_format == PEControl::RGB565_Z16 || + bpmem.zcontrol.pixel_format == PEControl::RGB8_Z24 || + bpmem.zcontrol.pixel_format == PEControl::Z24) + { + // Force alpha writes, and clear the alpha channel. This is different to the other backends, + // where the existing values of the alpha channel are preserved. + alpha_enable = true; + color &= 0x00FFFFFF; + } + + // Convert RGBA8 -> floating-point values. + VkClearValue clear_color_value = {}; + VkClearValue clear_depth_value = {}; + clear_color_value.color.float32[0] = static_cast((color >> 16) & 0xFF) / 255.0f; + clear_color_value.color.float32[1] = static_cast((color >> 8) & 0xFF) / 255.0f; + clear_color_value.color.float32[2] = static_cast((color >> 0) & 0xFF) / 255.0f; + clear_color_value.color.float32[3] = static_cast((color >> 24) & 0xFF) / 255.0f; + clear_depth_value.depthStencil.depth = static_cast(z & 0xFFFFFF) / 16777216.0f; + if (!g_ActiveConfig.backend_info.bSupportsReversedDepthRange) + clear_depth_value.depthStencil.depth = 1.0f - clear_depth_value.depthStencil.depth; + + // If we're not in a render pass (start of the frame), we can use a clear render pass + // to discard the data, rather than loading and then clearing. + bool use_clear_attachments = (color_enable && alpha_enable) || z_enable; + bool use_clear_render_pass = + !StateTracker::GetInstance()->InRenderPass() && color_enable && alpha_enable && z_enable; + + // The NVIDIA Vulkan driver causes the GPU to lock up, or throw exceptions if MSAA is enabled, + // a non-full clear rect is specified, and a clear loadop or vkCmdClearAttachments is used. + if (g_ActiveConfig.iMultisamples > 1 && + DriverDetails::HasBug(DriverDetails::BUG_BROKEN_MSAA_CLEAR)) + { + use_clear_render_pass = false; + use_clear_attachments = false; + } + + // This path cannot be used if the driver implementation doesn't guarantee pixels with no drawn + // geometry in "this" renderpass won't be cleared + if (DriverDetails::HasBug(DriverDetails::BUG_BROKEN_CLEAR_LOADOP_RENDERPASS)) + use_clear_render_pass = false; + + // Fastest path: Use a render pass to clear the buffers. + if (use_clear_render_pass) + { + const std::array clear_values = {{clear_color_value, clear_depth_value}}; + StateTracker::GetInstance()->BeginClearRenderPass(target_vk_rc, clear_values.data(), + static_cast(clear_values.size())); + return; + } + + // Fast path: Use vkCmdClearAttachments to clear the buffers within a render path + // We can't use this when preserving alpha but clearing color. + if (use_clear_attachments) + { + VkClearAttachment clear_attachments[2]; + uint32_t num_clear_attachments = 0; + if (color_enable && alpha_enable) + { + clear_attachments[num_clear_attachments].aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + clear_attachments[num_clear_attachments].colorAttachment = 0; + clear_attachments[num_clear_attachments].clearValue = clear_color_value; + num_clear_attachments++; + color_enable = false; + alpha_enable = false; + } + if (z_enable) + { + clear_attachments[num_clear_attachments].aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + clear_attachments[num_clear_attachments].colorAttachment = 0; + clear_attachments[num_clear_attachments].clearValue = clear_depth_value; + num_clear_attachments++; + z_enable = false; + } + if (num_clear_attachments > 0) + { + VkClearRect vk_rect = {target_vk_rc, 0, g_framebuffer_manager->GetEFBLayers()}; + if (!StateTracker::GetInstance()->IsWithinRenderArea( + target_vk_rc.offset.x, target_vk_rc.offset.y, target_vk_rc.extent.width, + target_vk_rc.extent.height)) + { + StateTracker::GetInstance()->EndClearRenderPass(); + } + StateTracker::GetInstance()->BeginRenderPass(); + + vkCmdClearAttachments(g_command_buffer_mgr->GetCurrentCommandBuffer(), num_clear_attachments, + clear_attachments, 1, &vk_rect); + } + } + + // Anything left over for the slow path? + if (!color_enable && !alpha_enable && !z_enable) + return; + + g_framebuffer_manager->ClearEFB(rc, color_enable, alpha_enable, z_enable, color, z); +} + +void Renderer::Flush() +{ + ExecuteCommandBuffer(true, false); +} + +void Renderer::WaitForGPUIdle() +{ + ExecuteCommandBuffer(false, true); +} + +void Renderer::BindBackbuffer(const ClearColor& clear_color) +{ + StateTracker::GetInstance()->EndRenderPass(); + + // Handle host window resizes. + CheckForSurfaceChange(); + CheckForSurfaceResize(); + + // Check for exclusive fullscreen request. + if (m_swap_chain->GetCurrentFullscreenState() != m_swap_chain->GetNextFullscreenState() && + !m_swap_chain->SetFullscreenState(m_swap_chain->GetNextFullscreenState())) + { + // if it fails, don't keep trying + m_swap_chain->SetNextFullscreenState(m_swap_chain->GetCurrentFullscreenState()); + } + + VkResult res = g_command_buffer_mgr->CheckLastPresentFail() ? + g_command_buffer_mgr->GetLastPresentResult() : + m_swap_chain->AcquireNextImage(); + if (res != VK_SUCCESS) + { + // Execute cmdbuffer before resizing, as the last frame could still be presenting. + ExecuteCommandBuffer(false, true); + + // Was this a lost exclusive fullscreen? + if (res == VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT) + { + // The present keeps returning exclusive mode lost unless we re-create the swap chain. + INFO_LOG_FMT(VIDEO, "Lost exclusive fullscreen."); + m_swap_chain->RecreateSwapChain(); + } + else if (res == VK_SUBOPTIMAL_KHR || res == VK_ERROR_OUT_OF_DATE_KHR) + { + INFO_LOG_FMT(VIDEO, "Resizing swap chain due to suboptimal/out-of-date"); + m_swap_chain->ResizeSwapChain(); + } + else + { + ERROR_LOG_FMT(VIDEO, "Unknown present error {:#010X}, please report.", res); + m_swap_chain->RecreateSwapChain(); + } + + res = m_swap_chain->AcquireNextImage(); + } + if (res != VK_SUCCESS) + PanicAlertFmt("Failed to grab image from swap chain"); + + // Transition from undefined (or present src, but it can be substituted) to + // color attachment ready for writing. These transitions must occur outside + // a render pass, unless the render pass declares a self-dependency. + m_swap_chain->GetCurrentTexture()->OverrideImageLayout(VK_IMAGE_LAYOUT_UNDEFINED); + m_swap_chain->GetCurrentTexture()->TransitionToLayout( + g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); + SetAndClearFramebuffer(m_swap_chain->GetCurrentFramebuffer(), + ClearColor{{0.0f, 0.0f, 0.0f, 1.0f}}); +} + +void Renderer::PresentBackbuffer() +{ + // End drawing to backbuffer + StateTracker::GetInstance()->EndRenderPass(); + + // Transition the backbuffer to PRESENT_SRC to ensure all commands drawing + // to it have finished before present. + m_swap_chain->GetCurrentTexture()->TransitionToLayout( + g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_PRESENT_SRC_KHR); + + // Submit the current command buffer, signaling rendering finished semaphore when it's done + // Because this final command buffer is rendering to the swap chain, we need to wait for + // the available semaphore to be signaled before executing the buffer. This final submission + // can happen off-thread in the background while we're preparing the next frame. + g_command_buffer_mgr->SubmitCommandBuffer(true, false, m_swap_chain->GetSwapChain(), + m_swap_chain->GetCurrentImageIndex()); + + // New cmdbuffer, so invalidate state. + StateTracker::GetInstance()->InvalidateCachedState(); +} + +void Renderer::SetFullscreen(bool enable_fullscreen) +{ + if (!m_swap_chain->IsFullscreenSupported()) + return; + + m_swap_chain->SetNextFullscreenState(enable_fullscreen); +} + +bool Renderer::IsFullscreen() const +{ + return m_swap_chain && m_swap_chain->GetCurrentFullscreenState(); +} + +void Renderer::ExecuteCommandBuffer(bool submit_off_thread, bool wait_for_completion) +{ + StateTracker::GetInstance()->EndRenderPass(); + + g_command_buffer_mgr->SubmitCommandBuffer(submit_off_thread, wait_for_completion); + + StateTracker::GetInstance()->InvalidateCachedState(); +} + +void Renderer::CheckForSurfaceChange() +{ + if (!m_surface_changed.TestAndClear() || !m_swap_chain) + return; + + // Submit the current draws up until rendering the XFB. + ExecuteCommandBuffer(false, true); + + // Clear the present failed flag, since we don't want to resize after recreating. + g_command_buffer_mgr->CheckLastPresentFail(); + + // Recreate the surface. If this fails we're in trouble. + if (!m_swap_chain->RecreateSurface(m_new_surface_handle)) + PanicAlertFmt("Failed to recreate Vulkan surface. Cannot continue."); + m_new_surface_handle = nullptr; + + // Handle case where the dimensions are now different. + OnSwapChainResized(); +} + +void Renderer::CheckForSurfaceResize() +{ + if (!m_surface_resized.TestAndClear()) + return; + + // If we don't have a surface, how can we resize the swap chain? + // CheckForSurfaceChange should handle this case. + if (!m_swap_chain) + { + WARN_LOG_FMT(VIDEO, "Surface resize event received without active surface, ignoring"); + return; + } + + // Wait for the GPU to catch up since we're going to destroy the swap chain. + ExecuteCommandBuffer(false, true); + + // Clear the present failed flag, since we don't want to resize after recreating. + g_command_buffer_mgr->CheckLastPresentFail(); + + // Resize the swap chain. + m_swap_chain->RecreateSwapChain(); + OnSwapChainResized(); +} + +void Renderer::OnConfigChanged(u32 bits) +{ + if (bits & CONFIG_CHANGE_BIT_HOST_CONFIG) + g_object_cache->ReloadPipelineCache(); + + // For vsync, we need to change the present mode, which means recreating the swap chain. + if (m_swap_chain && bits & CONFIG_CHANGE_BIT_VSYNC) + { + ExecuteCommandBuffer(false, true); + m_swap_chain->SetVSync(g_ActiveConfig.bVSyncActive); + } + + // For quad-buffered stereo we need to change the layer count, so recreate the swap chain. + if (m_swap_chain && bits & CONFIG_CHANGE_BIT_STEREO_MODE) + { + ExecuteCommandBuffer(false, true); + m_swap_chain->RecreateSwapChain(); + } + + // Wipe sampler cache if force texture filtering or anisotropy changes. + if (bits & (CONFIG_CHANGE_BIT_ANISOTROPY | CONFIG_CHANGE_BIT_FORCE_TEXTURE_FILTERING)) + { + ExecuteCommandBuffer(false, true); + ResetSamplerStates(); + } +} + +void Renderer::OnSwapChainResized() +{ + m_backbuffer_width = m_swap_chain->GetWidth(); + m_backbuffer_height = m_swap_chain->GetHeight(); +} + +void Renderer::BindFramebuffer(VKFramebuffer* fb) +{ + StateTracker::GetInstance()->EndRenderPass(); + + // Shouldn't be bound as a texture. + if (fb->GetColorAttachment()) + { + StateTracker::GetInstance()->UnbindTexture( + static_cast(fb->GetColorAttachment())->GetView()); + } + if (fb->GetDepthAttachment()) + { + StateTracker::GetInstance()->UnbindTexture( + static_cast(fb->GetDepthAttachment())->GetView()); + } + + fb->TransitionForRender(); + StateTracker::GetInstance()->SetFramebuffer(fb); + m_current_framebuffer = fb; +} + +void Renderer::SetFramebuffer(AbstractFramebuffer* framebuffer) +{ + if (m_current_framebuffer == framebuffer) + return; + + VKFramebuffer* vkfb = static_cast(framebuffer); + BindFramebuffer(vkfb); +} + +void Renderer::SetAndDiscardFramebuffer(AbstractFramebuffer* framebuffer) +{ + if (m_current_framebuffer == framebuffer) + return; + + VKFramebuffer* vkfb = static_cast(framebuffer); + BindFramebuffer(vkfb); + + // If we're discarding, begin the discard pass, then switch to a load pass. + // This way if the command buffer is flushed, we don't start another discard pass. + StateTracker::GetInstance()->BeginDiscardRenderPass(); +} + +void Renderer::SetAndClearFramebuffer(AbstractFramebuffer* framebuffer, + const ClearColor& color_value, float depth_value) +{ + VKFramebuffer* vkfb = static_cast(framebuffer); + BindFramebuffer(vkfb); + + std::array clear_values; + u32 num_clear_values = 0; + if (vkfb->GetColorFormat() != AbstractTextureFormat::Undefined) + { + std::memcpy(clear_values[num_clear_values].color.float32, color_value.data(), + sizeof(clear_values[num_clear_values].color.float32)); + num_clear_values++; + } + if (vkfb->GetDepthFormat() != AbstractTextureFormat::Undefined) + { + clear_values[num_clear_values].depthStencil.depth = depth_value; + clear_values[num_clear_values].depthStencil.stencil = 0; + num_clear_values++; + } + StateTracker::GetInstance()->BeginClearRenderPass(vkfb->GetRect(), clear_values.data(), + num_clear_values); +} + +void Renderer::SetTexture(u32 index, const AbstractTexture* texture) +{ + // Texture should always be in SHADER_READ_ONLY layout prior to use. + // This is so we don't need to transition during render passes. + const VKTexture* tex = static_cast(texture); + if (tex) + { + if (tex->GetLayout() != VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) + { + if (StateTracker::GetInstance()->InRenderPass()) + { + WARN_LOG_FMT(VIDEO, "Transitioning image in render pass in Renderer::SetTexture()"); + StateTracker::GetInstance()->EndRenderPass(); + } + + tex->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + } + + StateTracker::GetInstance()->SetTexture(index, tex->GetView()); + } + else + { + StateTracker::GetInstance()->SetTexture(0, VK_NULL_HANDLE); + } +} + +void Renderer::SetSamplerState(u32 index, const SamplerState& state) +{ + // Skip lookup if the state hasn't changed. + if (m_sampler_states[index].hex == state.hex) + return; + + // Look up new state and replace in state tracker. + VkSampler sampler = g_object_cache->GetSampler(state); + if (sampler == VK_NULL_HANDLE) + { + ERROR_LOG_FMT(VIDEO, "Failed to create sampler"); + sampler = g_object_cache->GetPointSampler(); + } + + StateTracker::GetInstance()->SetSampler(index, sampler); + m_sampler_states[index].hex = state.hex; +} + +void Renderer::SetComputeImageTexture(AbstractTexture* texture, bool read, bool write) +{ + VKTexture* vk_texture = static_cast(texture); + if (vk_texture) + { + StateTracker::GetInstance()->EndRenderPass(); + StateTracker::GetInstance()->SetImageTexture(vk_texture->GetView()); + vk_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), + read ? (write ? VKTexture::ComputeImageLayout::ReadWrite : + VKTexture::ComputeImageLayout::ReadOnly) : + VKTexture::ComputeImageLayout::WriteOnly); + } + else + { + StateTracker::GetInstance()->SetImageTexture(VK_NULL_HANDLE); + } +} + +void Renderer::UnbindTexture(const AbstractTexture* texture) +{ + StateTracker::GetInstance()->UnbindTexture(static_cast(texture)->GetView()); +} + +void Renderer::ResetSamplerStates() +{ + // Invalidate all sampler states, next draw will re-initialize them. + for (u32 i = 0; i < m_sampler_states.size(); i++) + { + m_sampler_states[i].hex = RenderState::GetPointSamplerState().hex; + StateTracker::GetInstance()->SetSampler(i, g_object_cache->GetPointSampler()); + } + + // Invalidate all sampler objects (some will be unused now). + g_object_cache->ClearSamplerCache(); +} + +void Renderer::SetScissorRect(const MathUtil::Rectangle& rc) +{ + VkRect2D scissor = {{rc.left, rc.top}, + {static_cast(rc.GetWidth()), static_cast(rc.GetHeight())}}; + + // See Vulkan spec for vkCmdSetScissor: + // The x and y members of offset must be greater than or equal to 0. + if (scissor.offset.x < 0) + { + scissor.extent.width -= -scissor.offset.x; + scissor.offset.x = 0; + } + if (scissor.offset.y < 0) + { + scissor.extent.height -= -scissor.offset.y; + scissor.offset.y = 0; + } + StateTracker::GetInstance()->SetScissor(scissor); +} + +void Renderer::SetViewport(float x, float y, float width, float height, float near_depth, + float far_depth) +{ + VkViewport viewport = {x, y, width, height, near_depth, far_depth}; + StateTracker::GetInstance()->SetViewport(viewport); +} + +void Renderer::Draw(u32 base_vertex, u32 num_vertices) +{ + if (!StateTracker::GetInstance()->Bind()) + return; + + vkCmdDraw(g_command_buffer_mgr->GetCurrentCommandBuffer(), num_vertices, 1, base_vertex, 0); +} + +void Renderer::DrawIndexed(u32 base_index, u32 num_indices, u32 base_vertex) +{ + if (!StateTracker::GetInstance()->Bind()) + return; + + vkCmdDrawIndexed(g_command_buffer_mgr->GetCurrentCommandBuffer(), num_indices, 1, base_index, + base_vertex, 0); +} + +void Renderer::DispatchComputeShader(const AbstractShader* shader, u32 groups_x, u32 groups_y, + u32 groups_z) +{ + StateTracker::GetInstance()->SetComputeShader(static_cast(shader)); + if (StateTracker::GetInstance()->BindCompute()) + vkCmdDispatch(g_command_buffer_mgr->GetCurrentCommandBuffer(), groups_x, groups_y, groups_z); +} + +} // namespace Vulkan diff --git a/Source/Core/VideoBackends/Vulkan/VKRenderer.h b/Source/Core/VideoBackends/Vulkan/VKRenderer.h new file mode 100644 index 0000000000..43ed3bd45b --- /dev/null +++ b/Source/Core/VideoBackends/Vulkan/VKRenderer.h @@ -0,0 +1,108 @@ +// Copyright 2016 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include + +#include "Common/CommonTypes.h" +#include "VideoBackends/Vulkan/Constants.h" +#include "VideoCommon/RenderBase.h" + +struct XFBSourceBase; + +namespace Vulkan +{ +class BoundingBox; +class SwapChain; +class StagingTexture2D; +class VKFramebuffer; +class VKPipeline; +class VKTexture; + +class Renderer : public ::Renderer +{ +public: + Renderer(std::unique_ptr swap_chain, float backbuffer_scale); + ~Renderer() override; + + static Renderer* GetInstance() { return static_cast(g_renderer.get()); } + + bool IsHeadless() const override; + + bool Initialize() override; + void Shutdown() override; + + std::unique_ptr CreateTexture(const TextureConfig& config) override; + std::unique_ptr + CreateStagingTexture(StagingTextureType type, const TextureConfig& config) override; + std::unique_ptr + CreateFramebuffer(AbstractTexture* color_attachment, AbstractTexture* depth_attachment) override; + + std::unique_ptr CreateShaderFromSource(ShaderStage stage, + std::string_view source) override; + std::unique_ptr CreateShaderFromBinary(ShaderStage stage, const void* data, + size_t length) override; + std::unique_ptr + CreateNativeVertexFormat(const PortableVertexDeclaration& vtx_decl) override; + std::unique_ptr CreatePipeline(const AbstractPipelineConfig& config, + const void* cache_data = nullptr, + size_t cache_data_length = 0) override; + + SwapChain* GetSwapChain() const { return m_swap_chain.get(); } + BoundingBox* GetBoundingBox() const { return m_bounding_box.get(); } + u16 BBoxRead(int index) override; + void BBoxWrite(int index, u16 value) override; + void BBoxFlush() override; + + void Flush() override; + void WaitForGPUIdle() override; + void OnConfigChanged(u32 bits) override; + + void ClearScreen(const MathUtil::Rectangle& rc, bool color_enable, bool alpha_enable, + bool z_enable, u32 color, u32 z) override; + + void SetPipeline(const AbstractPipeline* pipeline) override; + void SetFramebuffer(AbstractFramebuffer* framebuffer) override; + void SetAndDiscardFramebuffer(AbstractFramebuffer* framebuffer) override; + void SetAndClearFramebuffer(AbstractFramebuffer* framebuffer, const ClearColor& color_value = {}, + float depth_value = 0.0f) override; + void SetScissorRect(const MathUtil::Rectangle& rc) override; + void SetTexture(u32 index, const AbstractTexture* texture) override; + void SetSamplerState(u32 index, const SamplerState& state) override; + void SetComputeImageTexture(AbstractTexture* texture, bool read, bool write) override; + void UnbindTexture(const AbstractTexture* texture) override; + void SetViewport(float x, float y, float width, float height, float near_depth, + float far_depth) override; + void Draw(u32 base_vertex, u32 num_vertices) override; + void DrawIndexed(u32 base_index, u32 num_indices, u32 base_vertex) override; + void DispatchComputeShader(const AbstractShader* shader, u32 groups_x, u32 groups_y, + u32 groups_z) override; + void BindBackbuffer(const ClearColor& clear_color = {}) override; + void PresentBackbuffer() override; + void SetFullscreen(bool enable_fullscreen) override; + bool IsFullscreen() const override; + + // Completes the current render pass, executes the command buffer, and restores state ready for + // next render. Use when you want to kick the current buffer to make room for new data. + void ExecuteCommandBuffer(bool execute_off_thread, bool wait_for_completion = false); + +private: + void CheckForSurfaceChange(); + void CheckForSurfaceResize(); + + void ResetSamplerStates(); + + void OnSwapChainResized(); + void BindFramebuffer(VKFramebuffer* fb); + + std::unique_ptr m_swap_chain; + std::unique_ptr m_bounding_box; + + // Keep a copy of sampler states to avoid cache lookups every draw + std::array m_sampler_states = {}; +}; +} // namespace Vulkan diff --git a/Source/Core/VideoBackends/Vulkan/VKStreamBuffer.cpp b/Source/Core/VideoBackends/Vulkan/VKStreamBuffer.cpp new file mode 100644 index 0000000000..864fabf831 --- /dev/null +++ b/Source/Core/VideoBackends/Vulkan/VKStreamBuffer.cpp @@ -0,0 +1,328 @@ +// Copyright 2016 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "VideoBackends/Vulkan/VKStreamBuffer.h" + +#include +#include +#include + +#include "Common/Align.h" +#include "Common/Assert.h" +#include "Common/MsgHandler.h" + +#include "VideoBackends/Vulkan/CommandBufferManager.h" +#include "VideoBackends/Vulkan/VulkanContext.h" + +namespace Vulkan +{ +StreamBuffer::StreamBuffer(VkBufferUsageFlags usage, u32 size) : m_usage(usage), m_size(size) +{ +} + +StreamBuffer::~StreamBuffer() +{ + if (m_host_pointer) + vkUnmapMemory(g_vulkan_context->GetDevice(), m_memory); + + if (m_buffer != VK_NULL_HANDLE) + g_command_buffer_mgr->DeferBufferDestruction(m_buffer); + if (m_memory != VK_NULL_HANDLE) + g_command_buffer_mgr->DeferDeviceMemoryDestruction(m_memory); +} + +std::unique_ptr StreamBuffer::Create(VkBufferUsageFlags usage, u32 size) +{ + std::unique_ptr buffer = std::make_unique(usage, size); + if (!buffer->AllocateBuffer()) + return nullptr; + + return buffer; +} + +bool StreamBuffer::AllocateBuffer() +{ + // Create the buffer descriptor + VkBufferCreateInfo buffer_create_info = { + VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, // VkStructureType sType + nullptr, // const void* pNext + 0, // VkBufferCreateFlags flags + static_cast(m_size), // VkDeviceSize size + m_usage, // VkBufferUsageFlags usage + VK_SHARING_MODE_EXCLUSIVE, // VkSharingMode sharingMode + 0, // uint32_t queueFamilyIndexCount + nullptr // const uint32_t* pQueueFamilyIndices + }; + + VkBuffer buffer = VK_NULL_HANDLE; + VkResult res = + vkCreateBuffer(g_vulkan_context->GetDevice(), &buffer_create_info, nullptr, &buffer); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkCreateBuffer failed: "); + return false; + } + + // Get memory requirements (types etc) for this buffer + VkMemoryRequirements memory_requirements; + vkGetBufferMemoryRequirements(g_vulkan_context->GetDevice(), buffer, &memory_requirements); + + // Aim for a coherent mapping if possible. + u32 memory_type_index = g_vulkan_context->GetUploadMemoryType(memory_requirements.memoryTypeBits, + &m_coherent_mapping); + + // Allocate memory for backing this buffer + 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 = VK_NULL_HANDLE; + 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; + } + + // Bind memory to buffer + 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; + } + + // Map this buffer into user-space + void* mapped_ptr = nullptr; + res = vkMapMemory(g_vulkan_context->GetDevice(), memory, 0, m_size, 0, &mapped_ptr); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkMapMemory failed: "); + vkDestroyBuffer(g_vulkan_context->GetDevice(), buffer, nullptr); + vkFreeMemory(g_vulkan_context->GetDevice(), memory, nullptr); + return false; + } + + // Unmap current host pointer (if there was a previous buffer) + if (m_host_pointer) + vkUnmapMemory(g_vulkan_context->GetDevice(), m_memory); + + // Destroy the backings for the buffer after the command buffer executes + if (m_buffer != VK_NULL_HANDLE) + g_command_buffer_mgr->DeferBufferDestruction(m_buffer); + if (m_memory != VK_NULL_HANDLE) + g_command_buffer_mgr->DeferDeviceMemoryDestruction(m_memory); + + // Replace with the new buffer + m_buffer = buffer; + m_memory = memory; + m_host_pointer = reinterpret_cast(mapped_ptr); + m_current_offset = 0; + m_current_gpu_position = 0; + m_tracked_fences.clear(); + return true; +} + +bool StreamBuffer::ReserveMemory(u32 num_bytes, u32 alignment) +{ + const u32 required_bytes = num_bytes + alignment; + + // Check for sane allocations + if (required_bytes > m_size) + { + PanicAlertFmt("Attempting to allocate {} bytes from a {} byte stream buffer", num_bytes, + m_size); + + return false; + } + + // Is the GPU behind or up to date with our current offset? + UpdateCurrentFencePosition(); + if (m_current_offset >= m_current_gpu_position) + { + const u32 remaining_bytes = m_size - m_current_offset; + if (required_bytes <= remaining_bytes) + { + // Place at the current position, after the GPU position. + m_current_offset = Common::AlignUp(m_current_offset, alignment); + m_last_allocation_size = num_bytes; + return true; + } + + // Check for space at the start of the buffer + // We use < here because we don't want to have the case of m_current_offset == + // m_current_gpu_position. That would mean the code above would assume the + // GPU has caught up to us, which it hasn't. + if (required_bytes < m_current_gpu_position) + { + // Reset offset to zero, since we're allocating behind the gpu now + m_current_offset = 0; + m_last_allocation_size = num_bytes; + return true; + } + } + + // Is the GPU ahead of our current offset? + if (m_current_offset < m_current_gpu_position) + { + // We have from m_current_offset..m_current_gpu_position space to use. + const u32 remaining_bytes = m_current_gpu_position - m_current_offset; + if (required_bytes < remaining_bytes) + { + // Place at the current position, since this is still behind the GPU. + m_current_offset = Common::AlignUp(m_current_offset, alignment); + m_last_allocation_size = num_bytes; + return true; + } + } + + // Can we find a fence to wait on that will give us enough memory? + if (WaitForClearSpace(required_bytes)) + { + m_current_offset = Common::AlignUp(m_current_offset, alignment); + m_last_allocation_size = num_bytes; + return true; + } + + // We tried everything we could, and still couldn't get anything. This means that too much space + // in the buffer is being used by the command buffer currently being recorded. Therefore, the + // only option is to execute it, and wait until it's done. + return false; +} + +void StreamBuffer::CommitMemory(u32 final_num_bytes) +{ + ASSERT((m_current_offset + final_num_bytes) <= m_size); + ASSERT(final_num_bytes <= m_last_allocation_size); + + // For non-coherent mappings, flush the memory range + if (!m_coherent_mapping) + { + VkMappedMemoryRange range = {VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE, nullptr, m_memory, + m_current_offset, final_num_bytes}; + vkFlushMappedMemoryRanges(g_vulkan_context->GetDevice(), 1, &range); + } + + m_current_offset += final_num_bytes; +} + +void StreamBuffer::UpdateCurrentFencePosition() +{ + // Don't create a tracking entry if the GPU is caught up with the buffer. + if (m_current_offset == m_current_gpu_position) + return; + + // Has the offset changed since the last fence? + const u64 counter = g_command_buffer_mgr->GetCurrentFenceCounter(); + if (!m_tracked_fences.empty() && m_tracked_fences.back().first == counter) + { + // Still haven't executed a command buffer, so just update the offset. + m_tracked_fences.back().second = m_current_offset; + return; + } + + // New buffer, so update the GPU position while we're at it. + UpdateGPUPosition(); + m_tracked_fences.emplace_back(counter, m_current_offset); +} + +void StreamBuffer::UpdateGPUPosition() +{ + auto start = m_tracked_fences.begin(); + auto end = start; + + const u64 completed_counter = g_command_buffer_mgr->GetCompletedFenceCounter(); + while (end != m_tracked_fences.end() && completed_counter >= end->first) + { + m_current_gpu_position = end->second; + ++end; + } + + if (start != end) + m_tracked_fences.erase(start, end); +} + +bool StreamBuffer::WaitForClearSpace(u32 num_bytes) +{ + u32 new_offset = 0; + u32 new_gpu_position = 0; + + auto iter = m_tracked_fences.begin(); + for (; iter != m_tracked_fences.end(); ++iter) + { + // Would this fence bring us in line with the GPU? + // This is the "last resort" case, where a command buffer execution has been forced + // after no additional data has been written to it, so we can assume that after the + // fence has been signaled the entire buffer is now consumed. + u32 gpu_position = iter->second; + if (m_current_offset == gpu_position) + { + new_offset = 0; + new_gpu_position = 0; + break; + } + + // Assuming that we wait for this fence, are we allocating in front of the GPU? + if (m_current_offset > gpu_position) + { + // This would suggest the GPU has now followed us and wrapped around, so we have from + // m_current_position..m_size free, as well as and 0..gpu_position. + const u32 remaining_space_after_offset = m_size - m_current_offset; + if (remaining_space_after_offset >= num_bytes) + { + // Switch to allocating in front of the GPU, using the remainder of the buffer. + new_offset = m_current_offset; + new_gpu_position = gpu_position; + break; + } + + // We can wrap around to the start, behind the GPU, if there is enough space. + // We use > here because otherwise we'd end up lining up with the GPU, and then the + // allocator would assume that the GPU has consumed what we just wrote. + if (gpu_position > num_bytes) + { + new_offset = 0; + new_gpu_position = gpu_position; + break; + } + } + else + { + // We're currently allocating behind the GPU. This would give us between the current + // offset and the GPU position worth of space to work with. Again, > because we can't + // align the GPU position with the buffer offset. + u32 available_space_inbetween = gpu_position - m_current_offset; + if (available_space_inbetween > num_bytes) + { + // Leave the offset as-is, but update the GPU position. + new_offset = m_current_offset; + new_gpu_position = gpu_position; + break; + } + } + } + + // Did any fences satisfy this condition? + // Has the command buffer been executed yet? If not, the caller should execute it. + if (iter == m_tracked_fences.end() || + iter->first == g_command_buffer_mgr->GetCurrentFenceCounter()) + { + return false; + } + + // Wait until this fence is signaled. This will fire the callback, updating the GPU position. + g_command_buffer_mgr->WaitForFenceCounter(iter->first); + m_tracked_fences.erase(m_tracked_fences.begin(), + m_current_offset == iter->second ? m_tracked_fences.end() : ++iter); + m_current_offset = new_offset; + m_current_gpu_position = new_gpu_position; + return true; +} + +} // namespace Vulkan diff --git a/Source/Core/VideoBackends/Vulkan/VKStreamBuffer.h b/Source/Core/VideoBackends/Vulkan/VKStreamBuffer.h new file mode 100644 index 0000000000..677313939a --- /dev/null +++ b/Source/Core/VideoBackends/Vulkan/VKStreamBuffer.h @@ -0,0 +1,58 @@ +// Copyright 2016 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include + +#include "Common/CommonTypes.h" +#include "VideoBackends/Vulkan/Constants.h" + +namespace Vulkan +{ +class StreamBuffer +{ +public: + StreamBuffer(VkBufferUsageFlags usage, u32 size); + ~StreamBuffer(); + + VkBuffer GetBuffer() const { return m_buffer; } + VkDeviceMemory GetDeviceMemory() const { return m_memory; } + u8* GetHostPointer() const { return m_host_pointer; } + u8* GetCurrentHostPointer() const { return m_host_pointer + m_current_offset; } + u32 GetCurrentSize() const { return m_size; } + u32 GetCurrentOffset() const { return m_current_offset; } + bool ReserveMemory(u32 num_bytes, u32 alignment); + void CommitMemory(u32 final_num_bytes); + + static std::unique_ptr Create(VkBufferUsageFlags usage, u32 size); + +private: + bool AllocateBuffer(); + void UpdateCurrentFencePosition(); + void UpdateGPUPosition(); + + // Waits for as many fences as needed to allocate num_bytes bytes from the buffer. + bool WaitForClearSpace(u32 num_bytes); + + VkBufferUsageFlags m_usage; + u32 m_size; + u32 m_current_offset = 0; + u32 m_current_gpu_position = 0; + u32 m_last_allocation_size = 0; + + VkBuffer m_buffer = VK_NULL_HANDLE; + VkDeviceMemory m_memory = VK_NULL_HANDLE; + u8* m_host_pointer = nullptr; + + // List of fences and the corresponding positions in the buffer + std::deque> m_tracked_fences; + + bool m_coherent_mapping = false; +}; + +} // namespace Vulkan diff --git a/Source/Core/VideoBackends/Vulkan/VKSwapChain.cpp b/Source/Core/VideoBackends/Vulkan/VKSwapChain.cpp new file mode 100644 index 0000000000..8928b60806 --- /dev/null +++ b/Source/Core/VideoBackends/Vulkan/VKSwapChain.cpp @@ -0,0 +1,583 @@ +// Copyright 2016 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "VideoBackends/Vulkan/VKSwapChain.h" + +#include +#include + +#include "Common/Assert.h" +#include "Common/CommonFuncs.h" +#include "Common/Logging/Log.h" +#include "Common/MsgHandler.h" + +#include "VideoBackends/Vulkan/CommandBufferManager.h" +#include "VideoBackends/Vulkan/ObjectCache.h" +#include "VideoBackends/Vulkan/VKTexture.h" +#include "VideoBackends/Vulkan/VulkanContext.h" +#include "VideoCommon/RenderBase.h" + +#if defined(VK_USE_PLATFORM_XLIB_KHR) +#include +#endif + +namespace Vulkan +{ +SwapChain::SwapChain(const WindowSystemInfo& wsi, VkSurfaceKHR surface, bool vsync) + : m_wsi(wsi), m_surface(surface), m_vsync_enabled(vsync), + m_fullscreen_supported(g_vulkan_context->SupportsExclusiveFullscreen(wsi, surface)) +{ +} + +SwapChain::~SwapChain() +{ + DestroySwapChainImages(); + DestroySwapChain(); + DestroySurface(); +} + +VkSurfaceKHR SwapChain::CreateVulkanSurface(VkInstance instance, const WindowSystemInfo& wsi) +{ +#if defined(VK_USE_PLATFORM_WIN32_KHR) + if (wsi.type == WindowSystemType::Windows) + { + VkWin32SurfaceCreateInfoKHR surface_create_info = { + VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR, // VkStructureType sType + nullptr, // const void* pNext + 0, // VkWin32SurfaceCreateFlagsKHR flags + nullptr, // HINSTANCE hinstance + reinterpret_cast(wsi.render_surface) // HWND hwnd + }; + + VkSurfaceKHR surface; + VkResult res = vkCreateWin32SurfaceKHR(instance, &surface_create_info, nullptr, &surface); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkCreateWin32SurfaceKHR failed: "); + return VK_NULL_HANDLE; + } + + return surface; + } +#endif + +#if defined(VK_USE_PLATFORM_XLIB_KHR) + if (wsi.type == WindowSystemType::X11) + { + VkXlibSurfaceCreateInfoKHR surface_create_info = { + VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR, // VkStructureType sType + nullptr, // const void* pNext + 0, // VkXlibSurfaceCreateFlagsKHR flags + static_cast(wsi.display_connection), // Display* dpy + reinterpret_cast(wsi.render_surface) // Window window + }; + + VkSurfaceKHR surface; + VkResult res = vkCreateXlibSurfaceKHR(instance, &surface_create_info, nullptr, &surface); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkCreateXlibSurfaceKHR failed: "); + return VK_NULL_HANDLE; + } + + return surface; + } +#endif + +#if defined(VK_USE_PLATFORM_ANDROID_KHR) + if (wsi.type == WindowSystemType::Android) + { + VkAndroidSurfaceCreateInfoKHR surface_create_info = { + VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR, // VkStructureType sType + nullptr, // const void* pNext + 0, // VkAndroidSurfaceCreateFlagsKHR flags + reinterpret_cast(wsi.render_surface) // ANativeWindow* window + }; + + VkSurfaceKHR surface; + VkResult res = vkCreateAndroidSurfaceKHR(instance, &surface_create_info, nullptr, &surface); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkCreateAndroidSurfaceKHR failed: "); + return VK_NULL_HANDLE; + } + + return surface; + } +#endif + +#if defined(VK_USE_PLATFORM_METAL_EXT) + if (wsi.type == WindowSystemType::MacOS) + { + VkMetalSurfaceCreateInfoEXT surface_create_info = { + VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT, nullptr, 0, + static_cast(wsi.render_surface)}; + + VkSurfaceKHR surface; + VkResult res = vkCreateMetalSurfaceEXT(instance, &surface_create_info, nullptr, &surface); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkCreateMetalSurfaceEXT failed: "); + return VK_NULL_HANDLE; + } + + return surface; + } +#endif + + return VK_NULL_HANDLE; +} + +std::unique_ptr SwapChain::Create(const WindowSystemInfo& wsi, VkSurfaceKHR surface, + bool vsync) +{ + std::unique_ptr swap_chain = std::make_unique(wsi, surface, vsync); + if (!swap_chain->CreateSwapChain() || !swap_chain->SetupSwapChainImages()) + return nullptr; + + return swap_chain; +} + +bool SwapChain::SelectSurfaceFormat() +{ + u32 format_count; + VkResult res = vkGetPhysicalDeviceSurfaceFormatsKHR(g_vulkan_context->GetPhysicalDevice(), + m_surface, &format_count, nullptr); + if (res != VK_SUCCESS || format_count == 0) + { + LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceFormatsKHR failed: "); + return false; + } + + std::vector surface_formats(format_count); + res = vkGetPhysicalDeviceSurfaceFormatsKHR(g_vulkan_context->GetPhysicalDevice(), m_surface, + &format_count, surface_formats.data()); + ASSERT(res == VK_SUCCESS); + + // If there is a single undefined surface format, the device doesn't care, so we'll just use RGBA + if (surface_formats[0].format == VK_FORMAT_UNDEFINED) + { + m_surface_format.format = VK_FORMAT_R8G8B8A8_UNORM; + m_surface_format.colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; + return true; + } + + // Try to find a suitable format. + for (const VkSurfaceFormatKHR& surface_format : surface_formats) + { + // Some drivers seem to return a SRGB format here (Intel Mesa). + // This results in gamma correction when presenting to the screen, which we don't want. + // Use a linear format instead, if this is the case. + VkFormat format = VKTexture::GetLinearFormat(surface_format.format); + if (format == VK_FORMAT_R8G8B8A8_UNORM) + m_texture_format = AbstractTextureFormat::RGBA8; + else if (format == VK_FORMAT_B8G8R8A8_UNORM) + m_texture_format = AbstractTextureFormat::BGRA8; + else + continue; + + m_surface_format.format = format; + m_surface_format.colorSpace = surface_format.colorSpace; + return true; + } + + PanicAlertFmt("Failed to find a suitable format for swap chain buffers."); + return false; +} + +bool SwapChain::SelectPresentMode() +{ + VkResult res; + u32 mode_count; + res = vkGetPhysicalDeviceSurfacePresentModesKHR(g_vulkan_context->GetPhysicalDevice(), m_surface, + &mode_count, nullptr); + if (res != VK_SUCCESS || mode_count == 0) + { + LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceFormatsKHR failed: "); + return false; + } + + std::vector present_modes(mode_count); + res = vkGetPhysicalDeviceSurfacePresentModesKHR(g_vulkan_context->GetPhysicalDevice(), m_surface, + &mode_count, present_modes.data()); + ASSERT(res == VK_SUCCESS); + + // Checks if a particular mode is supported, if it is, returns that mode. + auto CheckForMode = [&present_modes](VkPresentModeKHR check_mode) { + auto it = std::find_if(present_modes.begin(), present_modes.end(), + [check_mode](VkPresentModeKHR mode) { return check_mode == mode; }); + return it != present_modes.end(); + }; + + // If vsync is enabled, use VK_PRESENT_MODE_FIFO_KHR. + // This check should not fail with conforming drivers, as the FIFO present mode is mandated by + // the specification (VK_KHR_swapchain). In case it isn't though, fall through to any other mode. + if (m_vsync_enabled && CheckForMode(VK_PRESENT_MODE_FIFO_KHR)) + { + m_present_mode = VK_PRESENT_MODE_FIFO_KHR; + return true; + } + + // Prefer screen-tearing, if possible, for lowest latency. + if (CheckForMode(VK_PRESENT_MODE_IMMEDIATE_KHR)) + { + m_present_mode = VK_PRESENT_MODE_IMMEDIATE_KHR; + return true; + } + + // Use optimized-vsync above vsync. + if (CheckForMode(VK_PRESENT_MODE_MAILBOX_KHR)) + { + m_present_mode = VK_PRESENT_MODE_MAILBOX_KHR; + return true; + } + + // Fall back to whatever is available. + m_present_mode = present_modes[0]; + return true; +} + +bool SwapChain::CreateSwapChain() +{ + // Look up surface properties to determine image count and dimensions + VkSurfaceCapabilitiesKHR surface_capabilities; + VkResult res = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(g_vulkan_context->GetPhysicalDevice(), + m_surface, &surface_capabilities); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceCapabilitiesKHR failed: "); + return false; + } + + // Select swap chain format and present mode + if (!SelectSurfaceFormat() || !SelectPresentMode()) + return false; + + // Select number of images in swap chain, we prefer one buffer in the background to work on + uint32_t image_count = surface_capabilities.minImageCount + 1; + + // maxImageCount can be zero, in which case there isn't an upper limit on the number of buffers. + if (surface_capabilities.maxImageCount > 0) + image_count = std::min(image_count, surface_capabilities.maxImageCount); + + // Determine the dimensions of the swap chain. Values of -1 indicate the size we specify here + // determines window size? + VkExtent2D size = surface_capabilities.currentExtent; + if (size.width == UINT32_MAX) + { + size.width = std::max(g_renderer->GetBackbufferWidth(), 1); + size.height = std::max(g_renderer->GetBackbufferHeight(), 1); + } + size.width = std::clamp(size.width, surface_capabilities.minImageExtent.width, + surface_capabilities.maxImageExtent.width); + size.height = std::clamp(size.height, surface_capabilities.minImageExtent.height, + surface_capabilities.maxImageExtent.height); + + // Prefer identity transform if possible + VkSurfaceTransformFlagBitsKHR transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; + if (!(surface_capabilities.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR)) + transform = surface_capabilities.currentTransform; + + // Select swap chain flags, we only need a colour attachment + VkImageUsageFlags image_usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + if (!(surface_capabilities.supportedUsageFlags & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT)) + { + ERROR_LOG_FMT(VIDEO, "Vulkan: Swap chain does not support usage as color attachment"); + return false; + } + + // Select the number of image layers for Quad-Buffered stereoscopy + uint32_t image_layers = g_ActiveConfig.stereo_mode == StereoMode::QuadBuffer ? 2 : 1; + + // Store the old/current swap chain when recreating for resize + VkSwapchainKHR old_swap_chain = m_swap_chain; + m_swap_chain = VK_NULL_HANDLE; + + // Now we can actually create the swap chain + VkSwapchainCreateInfoKHR swap_chain_info = {VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, + nullptr, + 0, + m_surface, + image_count, + m_surface_format.format, + m_surface_format.colorSpace, + size, + image_layers, + image_usage, + VK_SHARING_MODE_EXCLUSIVE, + 0, + nullptr, + transform, + VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, + m_present_mode, + VK_TRUE, + old_swap_chain}; + std::array indices = {{ + g_vulkan_context->GetGraphicsQueueFamilyIndex(), + g_vulkan_context->GetPresentQueueFamilyIndex(), + }}; + if (g_vulkan_context->GetGraphicsQueueFamilyIndex() != + g_vulkan_context->GetPresentQueueFamilyIndex()) + { + swap_chain_info.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + swap_chain_info.queueFamilyIndexCount = 2; + swap_chain_info.pQueueFamilyIndices = indices.data(); + } + +#ifdef SUPPORTS_VULKAN_EXCLUSIVE_FULLSCREEN + if (m_fullscreen_supported) + { + VkSurfaceFullScreenExclusiveInfoEXT fullscreen_support = {}; + swap_chain_info.pNext = &fullscreen_support; + fullscreen_support.sType = VK_STRUCTURE_TYPE_SURFACE_FULL_SCREEN_EXCLUSIVE_INFO_EXT; + fullscreen_support.fullScreenExclusive = VK_FULL_SCREEN_EXCLUSIVE_APPLICATION_CONTROLLED_EXT; + + auto platform_info = g_vulkan_context->GetPlatformExclusiveFullscreenInfo(m_wsi); + fullscreen_support.pNext = &platform_info; + + res = vkCreateSwapchainKHR(g_vulkan_context->GetDevice(), &swap_chain_info, nullptr, + &m_swap_chain); + if (res != VK_SUCCESS) + { + // Try without exclusive fullscreen. + WARN_LOG_FMT(VIDEO, "Failed to create exclusive fullscreen swapchain, trying without."); + swap_chain_info.pNext = nullptr; + g_Config.backend_info.bSupportsExclusiveFullscreen = false; + g_ActiveConfig.backend_info.bSupportsExclusiveFullscreen = false; + m_fullscreen_supported = false; + } + } +#endif + + if (m_swap_chain == VK_NULL_HANDLE) + { + res = vkCreateSwapchainKHR(g_vulkan_context->GetDevice(), &swap_chain_info, nullptr, + &m_swap_chain); + } + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkCreateSwapchainKHR failed: "); + return false; + } + + // Now destroy the old swap chain, since it's been recreated. + // We can do this immediately since all work should have been completed before calling resize. + if (old_swap_chain != VK_NULL_HANDLE) + vkDestroySwapchainKHR(g_vulkan_context->GetDevice(), old_swap_chain, nullptr); + + m_width = size.width; + m_height = size.height; + m_layers = image_layers; + return true; +} + +bool SwapChain::SetupSwapChainImages() +{ + ASSERT(m_swap_chain_images.empty()); + + uint32_t image_count; + VkResult res = + vkGetSwapchainImagesKHR(g_vulkan_context->GetDevice(), m_swap_chain, &image_count, nullptr); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkGetSwapchainImagesKHR failed: "); + return false; + } + + std::vector images(image_count); + res = vkGetSwapchainImagesKHR(g_vulkan_context->GetDevice(), m_swap_chain, &image_count, + images.data()); + ASSERT(res == VK_SUCCESS); + + const TextureConfig texture_config(TextureConfig( + m_width, m_height, 1, m_layers, 1, m_texture_format, AbstractTextureFlag_RenderTarget)); + const VkRenderPass load_render_pass = g_object_cache->GetRenderPass( + m_surface_format.format, VK_FORMAT_UNDEFINED, 1, VK_ATTACHMENT_LOAD_OP_LOAD); + const VkRenderPass clear_render_pass = g_object_cache->GetRenderPass( + m_surface_format.format, VK_FORMAT_UNDEFINED, 1, VK_ATTACHMENT_LOAD_OP_CLEAR); + if (load_render_pass == VK_NULL_HANDLE || clear_render_pass == VK_NULL_HANDLE) + { + PanicAlertFmt("Failed to get swap chain render passes."); + return false; + } + + m_swap_chain_images.reserve(image_count); + for (uint32_t i = 0; i < image_count; i++) + { + SwapChainImage image; + image.image = images[i]; + + // Create texture object, which creates a view of the backbuffer + image.texture = + VKTexture::CreateAdopted(texture_config, image.image, + m_layers > 1 ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D, + VK_IMAGE_LAYOUT_UNDEFINED); + if (!image.texture) + return false; + + image.framebuffer = VKFramebuffer::Create(image.texture.get(), nullptr); + if (!image.framebuffer) + { + image.texture.reset(); + return false; + } + + m_swap_chain_images.emplace_back(std::move(image)); + } + + return true; +} + +void SwapChain::DestroySwapChainImages() +{ + for (auto& it : m_swap_chain_images) + { + // Images themselves are cleaned up by the swap chain object + it.framebuffer.reset(); + it.texture.reset(); + } + m_swap_chain_images.clear(); +} + +void SwapChain::DestroySwapChain() +{ + if (m_swap_chain == VK_NULL_HANDLE) + return; + + // Release exclusive fullscreen before destroying. + if (m_current_fullscreen_state) + SetFullscreenState(false); + + vkDestroySwapchainKHR(g_vulkan_context->GetDevice(), m_swap_chain, nullptr); + m_swap_chain = VK_NULL_HANDLE; +} + +VkResult SwapChain::AcquireNextImage() +{ + VkResult res = vkAcquireNextImageKHR(g_vulkan_context->GetDevice(), m_swap_chain, UINT64_MAX, + g_command_buffer_mgr->GetCurrentCommandBufferSemaphore(), + VK_NULL_HANDLE, &m_current_swap_chain_image_index); + if (res != VK_SUCCESS && res != VK_ERROR_OUT_OF_DATE_KHR && res != VK_SUBOPTIMAL_KHR) + LOG_VULKAN_ERROR(res, "vkAcquireNextImageKHR failed: "); + + return res; +} + +bool SwapChain::ResizeSwapChain() +{ + DestroySwapChainImages(); + if (!CreateSwapChain() || !SetupSwapChainImages()) + { + PanicAlertFmt("Failed to re-configure swap chain images, this is fatal (for now)"); + return false; + } + + return true; +} + +bool SwapChain::RecreateSwapChain() +{ + DestroySwapChainImages(); + DestroySwapChain(); + if (!CreateSwapChain() || !SetupSwapChainImages()) + { + PanicAlertFmt("Failed to re-configure swap chain images, this is fatal (for now)"); + return false; + } + + return true; +} + +bool SwapChain::SetVSync(bool enabled) +{ + if (m_vsync_enabled == enabled) + return true; + + // Recreate the swap chain with the new present mode. + m_vsync_enabled = enabled; + return RecreateSwapChain(); +} + +bool SwapChain::SetFullscreenState(bool state) +{ +#ifdef SUPPORTS_VULKAN_EXCLUSIVE_FULLSCREEN + if (m_current_fullscreen_state == state) + return true; + + if (state) + { + VkResult res = vkAcquireFullScreenExclusiveModeEXT(g_vulkan_context->GetDevice(), m_swap_chain); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkAcquireFullScreenExclusiveModeEXT failed:"); + return false; + } + + INFO_LOG_FMT(VIDEO, "Exclusive fullscreen acquired."); + } + else + { + VkResult res = vkReleaseFullScreenExclusiveModeEXT(g_vulkan_context->GetDevice(), m_swap_chain); + if (res != VK_SUCCESS) + LOG_VULKAN_ERROR(res, "vkReleaseFullScreenExclusiveModeEXT failed:"); + + INFO_LOG_FMT(VIDEO, "Exclusive fullscreen released."); + } + + m_current_fullscreen_state = state; + return true; +#else + return false; +#endif +} + +bool SwapChain::RecreateSurface(void* native_handle) +{ + // Destroy the old swap chain, images, and surface. + DestroySwapChainImages(); + DestroySwapChain(); + DestroySurface(); + + // Re-create the surface with the new native handle + m_wsi.render_surface = native_handle; + m_surface = CreateVulkanSurface(g_vulkan_context->GetVulkanInstance(), m_wsi); + if (m_surface == VK_NULL_HANDLE) + return false; + + // The validation layers get angry at us if we don't call this before creating the swapchain. + VkBool32 present_supported = VK_TRUE; + VkResult res = vkGetPhysicalDeviceSurfaceSupportKHR( + g_vulkan_context->GetPhysicalDevice(), g_vulkan_context->GetPresentQueueFamilyIndex(), + m_surface, &present_supported); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceSupportKHR failed: "); + return false; + } + if (!present_supported) + { + PanicAlertFmt("Recreated surface does not support presenting."); + return false; + } + + // Update exclusive fullscreen support (unlikely to change). + m_fullscreen_supported = g_vulkan_context->SupportsExclusiveFullscreen(m_wsi, m_surface); + g_Config.backend_info.bSupportsExclusiveFullscreen = m_fullscreen_supported; + g_ActiveConfig.backend_info.bSupportsExclusiveFullscreen = m_fullscreen_supported; + m_current_fullscreen_state = false; + m_next_fullscreen_state = false; + + // Finally re-create the swap chain + if (!CreateSwapChain() || !SetupSwapChainImages()) + return false; + + return true; +} + +void SwapChain::DestroySurface() +{ + vkDestroySurfaceKHR(g_vulkan_context->GetVulkanInstance(), m_surface, nullptr); + m_surface = VK_NULL_HANDLE; +} +} // namespace Vulkan diff --git a/Source/Core/VideoBackends/Vulkan/VKSwapChain.h b/Source/Core/VideoBackends/Vulkan/VKSwapChain.h new file mode 100644 index 0000000000..a6ee28d965 --- /dev/null +++ b/Source/Core/VideoBackends/Vulkan/VKSwapChain.h @@ -0,0 +1,114 @@ +// Copyright 2016 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include + +#include "Common/CommonTypes.h" +#include "Common/WindowSystemInfo.h" +#include "VideoBackends/Vulkan/Constants.h" +#include "VideoCommon/TextureConfig.h" + +namespace Vulkan +{ +class CommandBufferManager; +class ObjectCache; +class VKTexture; +class VKFramebuffer; + +class SwapChain +{ +public: + SwapChain(const WindowSystemInfo& wsi, VkSurfaceKHR surface, bool vsync); + ~SwapChain(); + + // Creates a vulkan-renderable surface for the specified window handle. + static VkSurfaceKHR CreateVulkanSurface(VkInstance instance, const WindowSystemInfo& wsi); + + // Create a new swap chain from a pre-existing surface. + static std::unique_ptr Create(const WindowSystemInfo& wsi, VkSurfaceKHR surface, + bool vsync); + + VkSurfaceKHR GetSurface() const { return m_surface; } + VkSurfaceFormatKHR GetSurfaceFormat() const { return m_surface_format; } + AbstractTextureFormat GetTextureFormat() const { return m_texture_format; } + bool IsVSyncEnabled() const { return m_vsync_enabled; } + bool IsStereoEnabled() const { return m_layers == 2; } + VkSwapchainKHR GetSwapChain() const { return m_swap_chain; } + u32 GetWidth() const { return m_width; } + u32 GetHeight() const { return m_height; } + u32 GetCurrentImageIndex() const { return m_current_swap_chain_image_index; } + VkImage GetCurrentImage() const + { + return m_swap_chain_images[m_current_swap_chain_image_index].image; + } + VKTexture* GetCurrentTexture() const + { + return m_swap_chain_images[m_current_swap_chain_image_index].texture.get(); + } + VKFramebuffer* GetCurrentFramebuffer() const + { + return m_swap_chain_images[m_current_swap_chain_image_index].framebuffer.get(); + } + VkResult AcquireNextImage(); + + bool RecreateSurface(void* native_handle); + bool ResizeSwapChain(); + bool RecreateSwapChain(); + + // Change vsync enabled state. This may fail as it causes a swapchain recreation. + bool SetVSync(bool enabled); + + // Is exclusive fullscreen supported? + bool IsFullscreenSupported() const { return m_fullscreen_supported; } + + // Retrieves the "next" fullscreen state. Safe to call off-thread. + bool GetCurrentFullscreenState() const { return m_current_fullscreen_state; } + bool GetNextFullscreenState() const { return m_next_fullscreen_state; } + void SetNextFullscreenState(bool state) { m_next_fullscreen_state = state; } + + // Updates the fullscreen state. Must call on-thread. + bool SetFullscreenState(bool state); + +private: + bool SelectSurfaceFormat(); + bool SelectPresentMode(); + + bool CreateSwapChain(); + void DestroySwapChain(); + + bool SetupSwapChainImages(); + void DestroySwapChainImages(); + + void DestroySurface(); + + struct SwapChainImage + { + VkImage image; + std::unique_ptr texture; + std::unique_ptr framebuffer; + }; + + WindowSystemInfo m_wsi; + VkSurfaceKHR m_surface = VK_NULL_HANDLE; + VkSurfaceFormatKHR m_surface_format = {}; + VkPresentModeKHR m_present_mode = VK_PRESENT_MODE_RANGE_SIZE_KHR; + AbstractTextureFormat m_texture_format = AbstractTextureFormat::Undefined; + bool m_vsync_enabled = false; + bool m_fullscreen_supported = false; + bool m_current_fullscreen_state = false; + bool m_next_fullscreen_state = false; + + VkSwapchainKHR m_swap_chain = VK_NULL_HANDLE; + std::vector m_swap_chain_images; + u32 m_current_swap_chain_image_index = 0; + + u32 m_width = 0; + u32 m_height = 0; + u32 m_layers = 0; +}; + +} // namespace Vulkan diff --git a/Source/Core/VideoBackends/Vulkan/VKTexture.cpp b/Source/Core/VideoBackends/Vulkan/VKTexture.cpp index 66ec56c57c..17039191cb 100644 --- a/Source/Core/VideoBackends/Vulkan/VKTexture.cpp +++ b/Source/Core/VideoBackends/Vulkan/VKTexture.cpp @@ -14,10 +14,10 @@ #include "VideoBackends/Vulkan/CommandBufferManager.h" #include "VideoBackends/Vulkan/ObjectCache.h" -#include "VideoBackends/Vulkan/Renderer.h" #include "VideoBackends/Vulkan/StagingBuffer.h" #include "VideoBackends/Vulkan/StateTracker.h" -#include "VideoBackends/Vulkan/StreamBuffer.h" +#include "VideoBackends/Vulkan/VKRenderer.h" +#include "VideoBackends/Vulkan/VKStreamBuffer.h" #include "VideoBackends/Vulkan/VKTexture.h" #include "VideoBackends/Vulkan/VulkanContext.h" diff --git a/Source/Core/VideoBackends/Vulkan/VKVertexFormat.cpp b/Source/Core/VideoBackends/Vulkan/VKVertexFormat.cpp new file mode 100644 index 0000000000..c458e04121 --- /dev/null +++ b/Source/Core/VideoBackends/Vulkan/VKVertexFormat.cpp @@ -0,0 +1,129 @@ +// Copyright 2016 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "VideoBackends/Vulkan/VKVertexFormat.h" + +#include "Common/Assert.h" + +#include "VideoBackends/Vulkan/CommandBufferManager.h" +#include "VideoBackends/Vulkan/ObjectCache.h" + +#include "VideoCommon/VertexLoaderManager.h" +#include "VideoCommon/VertexShaderGen.h" + +namespace Vulkan +{ +static VkFormat VarToVkFormat(VarType t, uint32_t components, bool integer) +{ + static const VkFormat float_type_lookup[][4] = { + {VK_FORMAT_R8_UNORM, VK_FORMAT_R8G8_UNORM, VK_FORMAT_R8G8B8_UNORM, + VK_FORMAT_R8G8B8A8_UNORM}, // VAR_UNSIGNED_BYTE + {VK_FORMAT_R8_SNORM, VK_FORMAT_R8G8_SNORM, VK_FORMAT_R8G8B8_SNORM, + VK_FORMAT_R8G8B8A8_SNORM}, // VAR_BYTE + {VK_FORMAT_R16_UNORM, VK_FORMAT_R16G16_UNORM, VK_FORMAT_R16G16B16_UNORM, + VK_FORMAT_R16G16B16A16_UNORM}, // VAR_UNSIGNED_SHORT + {VK_FORMAT_R16_SNORM, VK_FORMAT_R16G16_SNORM, VK_FORMAT_R16G16B16_SNORM, + VK_FORMAT_R16G16B16A16_SNORM}, // VAR_SHORT + {VK_FORMAT_R32_SFLOAT, VK_FORMAT_R32G32_SFLOAT, VK_FORMAT_R32G32B32_SFLOAT, + VK_FORMAT_R32G32B32A32_SFLOAT} // VAR_FLOAT + }; + + static const VkFormat integer_type_lookup[][4] = { + {VK_FORMAT_R8_UINT, VK_FORMAT_R8G8_UINT, VK_FORMAT_R8G8B8_UINT, + VK_FORMAT_R8G8B8A8_UINT}, // VAR_UNSIGNED_BYTE + {VK_FORMAT_R8_SINT, VK_FORMAT_R8G8_SINT, VK_FORMAT_R8G8B8_SINT, + VK_FORMAT_R8G8B8A8_SINT}, // VAR_BYTE + {VK_FORMAT_R16_UINT, VK_FORMAT_R16G16_UINT, VK_FORMAT_R16G16B16_UINT, + VK_FORMAT_R16G16B16A16_UINT}, // VAR_UNSIGNED_SHORT + {VK_FORMAT_R16_SINT, VK_FORMAT_R16G16_SINT, VK_FORMAT_R16G16B16_SINT, + VK_FORMAT_R16G16B16A16_SINT}, // VAR_SHORT + {VK_FORMAT_R32_SFLOAT, VK_FORMAT_R32G32_SFLOAT, VK_FORMAT_R32G32B32_SFLOAT, + VK_FORMAT_R32G32B32A32_SFLOAT} // VAR_FLOAT + }; + + ASSERT(components > 0 && components <= 4); + return integer ? integer_type_lookup[t][components - 1] : float_type_lookup[t][components - 1]; +} + +VertexFormat::VertexFormat(const PortableVertexDeclaration& vtx_decl) : NativeVertexFormat(vtx_decl) +{ + MapAttributes(); + SetupInputState(); +} + +const VkPipelineVertexInputStateCreateInfo& VertexFormat::GetVertexInputStateInfo() const +{ + return m_input_state_info; +} + +void VertexFormat::MapAttributes() +{ + m_num_attributes = 0; + + if (m_decl.position.enable) + AddAttribute( + SHADER_POSITION_ATTRIB, 0, + VarToVkFormat(m_decl.position.type, m_decl.position.components, m_decl.position.integer), + m_decl.position.offset); + + for (uint32_t i = 0; i < 3; i++) + { + if (m_decl.normals[i].enable) + AddAttribute(SHADER_NORM0_ATTRIB + i, 0, + VarToVkFormat(m_decl.normals[i].type, m_decl.normals[i].components, + m_decl.normals[i].integer), + m_decl.normals[i].offset); + } + + for (uint32_t i = 0; i < 2; i++) + { + if (m_decl.colors[i].enable) + AddAttribute(SHADER_COLOR0_ATTRIB + i, 0, + VarToVkFormat(m_decl.colors[i].type, m_decl.colors[i].components, + m_decl.colors[i].integer), + m_decl.colors[i].offset); + } + + for (uint32_t i = 0; i < 8; i++) + { + if (m_decl.texcoords[i].enable) + AddAttribute(SHADER_TEXTURE0_ATTRIB + i, 0, + VarToVkFormat(m_decl.texcoords[i].type, m_decl.texcoords[i].components, + m_decl.texcoords[i].integer), + m_decl.texcoords[i].offset); + } + + if (m_decl.posmtx.enable) + AddAttribute(SHADER_POSMTX_ATTRIB, 0, + VarToVkFormat(m_decl.posmtx.type, m_decl.posmtx.components, m_decl.posmtx.integer), + m_decl.posmtx.offset); +} + +void VertexFormat::SetupInputState() +{ + m_binding_description.binding = 0; + m_binding_description.stride = m_decl.stride; + m_binding_description.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + + m_input_state_info.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + m_input_state_info.pNext = nullptr; + m_input_state_info.flags = 0; + m_input_state_info.vertexBindingDescriptionCount = 1; + m_input_state_info.pVertexBindingDescriptions = &m_binding_description; + m_input_state_info.vertexAttributeDescriptionCount = m_num_attributes; + m_input_state_info.pVertexAttributeDescriptions = m_attribute_descriptions.data(); +} + +void VertexFormat::AddAttribute(uint32_t location, uint32_t binding, VkFormat format, + uint32_t offset) +{ + ASSERT(m_num_attributes < MAX_VERTEX_ATTRIBUTES); + + m_attribute_descriptions[m_num_attributes].location = location; + m_attribute_descriptions[m_num_attributes].binding = binding; + m_attribute_descriptions[m_num_attributes].format = format; + m_attribute_descriptions[m_num_attributes].offset = offset; + m_num_attributes++; +} +} // namespace Vulkan diff --git a/Source/Core/VideoBackends/Vulkan/VKVertexFormat.h b/Source/Core/VideoBackends/Vulkan/VKVertexFormat.h new file mode 100644 index 0000000000..617967202b --- /dev/null +++ b/Source/Core/VideoBackends/Vulkan/VKVertexFormat.h @@ -0,0 +1,38 @@ +// Copyright 2016 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include + +#include "VideoBackends/Vulkan/Constants.h" +#include "VideoCommon/NativeVertexFormat.h" + +namespace Vulkan +{ +class VertexFormat : public ::NativeVertexFormat +{ +public: + VertexFormat(const PortableVertexDeclaration& vtx_decl); + + // Passed to pipeline state creation + const VkPipelineVertexInputStateCreateInfo& GetVertexInputStateInfo() const; + + // Converting PortableVertexDeclaration -> Vulkan types + void MapAttributes(); + void SetupInputState(); + +private: + void AddAttribute(uint32_t location, uint32_t binding, VkFormat format, uint32_t offset); + + VkVertexInputBindingDescription m_binding_description = {}; + + std::array m_attribute_descriptions = + {}; + + VkPipelineVertexInputStateCreateInfo m_input_state_info = {}; + + uint32_t m_num_attributes = 0; +}; +} // namespace Vulkan diff --git a/Source/Core/VideoBackends/Vulkan/VKVertexManager.cpp b/Source/Core/VideoBackends/Vulkan/VKVertexManager.cpp new file mode 100644 index 0000000000..27c88c88ae --- /dev/null +++ b/Source/Core/VideoBackends/Vulkan/VKVertexManager.cpp @@ -0,0 +1,397 @@ +// Copyright 2016 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "VideoBackends/Vulkan/VKVertexManager.h" + +#include "Common/Align.h" +#include "Common/CommonTypes.h" +#include "Common/Logging/Log.h" +#include "Common/MsgHandler.h" + +#include "VideoBackends/Vulkan/CommandBufferManager.h" +#include "VideoBackends/Vulkan/StateTracker.h" +#include "VideoBackends/Vulkan/VKRenderer.h" +#include "VideoBackends/Vulkan/VKStreamBuffer.h" +#include "VideoBackends/Vulkan/VKVertexFormat.h" +#include "VideoBackends/Vulkan/VulkanContext.h" + +#include "VideoCommon/GeometryShaderManager.h" +#include "VideoCommon/IndexGenerator.h" +#include "VideoCommon/PixelShaderManager.h" +#include "VideoCommon/Statistics.h" +#include "VideoCommon/VertexLoaderManager.h" +#include "VideoCommon/VertexShaderManager.h" +#include "VideoCommon/VideoConfig.h" + +namespace Vulkan +{ +static VkBufferView CreateTexelBufferView(VkBuffer buffer, VkFormat vk_format) +{ + // Create a view of the whole buffer, we'll offset our texel load into it + VkBufferViewCreateInfo view_info = { + VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO, // VkStructureType sType + nullptr, // const void* pNext + 0, // VkBufferViewCreateFlags flags + buffer, // VkBuffer buffer + vk_format, // VkFormat format + 0, // VkDeviceSize offset + VK_WHOLE_SIZE // VkDeviceSize range + }; + + VkBufferView view; + VkResult res = vkCreateBufferView(g_vulkan_context->GetDevice(), &view_info, nullptr, &view); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkCreateBufferView failed: "); + return VK_NULL_HANDLE; + } + + return view; +} + +VertexManager::VertexManager() = default; + +VertexManager::~VertexManager() +{ + DestroyTexelBufferViews(); +} + +bool VertexManager::Initialize() +{ + if (!VertexManagerBase::Initialize()) + return false; + + m_vertex_stream_buffer = + StreamBuffer::Create(VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VERTEX_STREAM_BUFFER_SIZE); + m_index_stream_buffer = + StreamBuffer::Create(VK_BUFFER_USAGE_INDEX_BUFFER_BIT, INDEX_STREAM_BUFFER_SIZE); + m_uniform_stream_buffer = + StreamBuffer::Create(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, UNIFORM_STREAM_BUFFER_SIZE); + if (!m_vertex_stream_buffer || !m_index_stream_buffer || !m_uniform_stream_buffer) + { + PanicAlertFmt("Failed to allocate streaming buffers"); + return false; + } + + // The validation layer complains if max(offsets) + max(ubo_ranges) >= ubo_size. + // To work around this we reserve the maximum buffer size at all times, but only commit + // as many bytes as we use. + m_uniform_buffer_reserve_size = sizeof(PixelShaderConstants); + m_uniform_buffer_reserve_size = Common::AlignUp(m_uniform_buffer_reserve_size, + g_vulkan_context->GetUniformBufferAlignment()) + + sizeof(VertexShaderConstants); + m_uniform_buffer_reserve_size = Common::AlignUp(m_uniform_buffer_reserve_size, + g_vulkan_context->GetUniformBufferAlignment()) + + sizeof(GeometryShaderConstants); + + // Prefer an 8MB buffer if possible, but use less if the device doesn't support this. + // This buffer is potentially going to be addressed as R8s in the future, so we assume + // that one element is one byte. This doesn't use min() because of a NDK compiler bug.. + const u32 texel_buffer_size = + TEXEL_STREAM_BUFFER_SIZE > g_vulkan_context->GetDeviceLimits().maxTexelBufferElements ? + g_vulkan_context->GetDeviceLimits().maxTexelBufferElements : + TEXEL_STREAM_BUFFER_SIZE; + m_texel_stream_buffer = + StreamBuffer::Create(VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT, texel_buffer_size); + if (!m_texel_stream_buffer) + { + PanicAlertFmt("Failed to allocate streaming texel buffer"); + return false; + } + + static constexpr std::array, NUM_TEXEL_BUFFER_FORMATS> + format_mapping = {{ + {TEXEL_BUFFER_FORMAT_R8_UINT, VK_FORMAT_R8_UINT}, + {TEXEL_BUFFER_FORMAT_R16_UINT, VK_FORMAT_R16_UINT}, + {TEXEL_BUFFER_FORMAT_RGBA8_UINT, VK_FORMAT_R8G8B8A8_UINT}, + {TEXEL_BUFFER_FORMAT_R32G32_UINT, VK_FORMAT_R32G32_UINT}, + }}; + for (const auto& it : format_mapping) + { + if ((m_texel_buffer_views[it.first] = CreateTexelBufferView(m_texel_stream_buffer->GetBuffer(), + it.second)) == VK_NULL_HANDLE) + { + PanicAlertFmt("Failed to create texel buffer view"); + return false; + } + } + + // Bind the buffers to all the known spots even if it's not used, to keep the driver happy. + UploadAllConstants(); + StateTracker::GetInstance()->SetUtilityUniformBuffer(m_uniform_stream_buffer->GetBuffer(), 0, + sizeof(VertexShaderConstants)); + for (u32 i = 0; i < NUM_COMPUTE_TEXEL_BUFFERS; i++) + { + StateTracker::GetInstance()->SetTexelBuffer(i, + m_texel_buffer_views[TEXEL_BUFFER_FORMAT_R8_UINT]); + } + + return true; +} + +void VertexManager::DestroyTexelBufferViews() +{ + for (VkBufferView view : m_texel_buffer_views) + { + if (view != VK_NULL_HANDLE) + vkDestroyBufferView(g_vulkan_context->GetDevice(), view, nullptr); + } +} + +void VertexManager::ResetBuffer(u32 vertex_stride) +{ + // Attempt to allocate from buffers + bool has_vbuffer_allocation = + m_vertex_stream_buffer->ReserveMemory(MAXVBUFFERSIZE, vertex_stride); + bool has_ibuffer_allocation = + m_index_stream_buffer->ReserveMemory(MAXIBUFFERSIZE * sizeof(u16), sizeof(u16)); + if (!has_vbuffer_allocation || !has_ibuffer_allocation) + { + // Flush any pending commands first, so that we can wait on the fences + WARN_LOG_FMT(VIDEO, "Executing command list while waiting for space in vertex/index buffer"); + Renderer::GetInstance()->ExecuteCommandBuffer(false); + + // Attempt to allocate again, this may cause a fence wait + if (!has_vbuffer_allocation) + has_vbuffer_allocation = m_vertex_stream_buffer->ReserveMemory(MAXVBUFFERSIZE, vertex_stride); + if (!has_ibuffer_allocation) + has_ibuffer_allocation = + m_index_stream_buffer->ReserveMemory(MAXIBUFFERSIZE * sizeof(u16), sizeof(u16)); + + // If we still failed, that means the allocation was too large and will never succeed, so panic + if (!has_vbuffer_allocation || !has_ibuffer_allocation) + PanicAlertFmt("Failed to allocate space in streaming buffers for pending draw"); + } + + // Update pointers + m_base_buffer_pointer = m_vertex_stream_buffer->GetHostPointer(); + m_end_buffer_pointer = m_vertex_stream_buffer->GetCurrentHostPointer() + MAXVBUFFERSIZE; + m_cur_buffer_pointer = m_vertex_stream_buffer->GetCurrentHostPointer(); + m_index_generator.Start(reinterpret_cast(m_index_stream_buffer->GetCurrentHostPointer())); +} + +void VertexManager::CommitBuffer(u32 num_vertices, u32 vertex_stride, u32 num_indices, + u32* out_base_vertex, u32* out_base_index) +{ + const u32 vertex_data_size = num_vertices * vertex_stride; + const u32 index_data_size = num_indices * sizeof(u16); + + *out_base_vertex = + vertex_stride > 0 ? (m_vertex_stream_buffer->GetCurrentOffset() / vertex_stride) : 0; + *out_base_index = m_index_stream_buffer->GetCurrentOffset() / sizeof(u16); + + m_vertex_stream_buffer->CommitMemory(vertex_data_size); + m_index_stream_buffer->CommitMemory(index_data_size); + + ADDSTAT(g_stats.this_frame.bytes_vertex_streamed, static_cast(vertex_data_size)); + ADDSTAT(g_stats.this_frame.bytes_index_streamed, static_cast(index_data_size)); + + StateTracker::GetInstance()->SetVertexBuffer(m_vertex_stream_buffer->GetBuffer(), 0); + StateTracker::GetInstance()->SetIndexBuffer(m_index_stream_buffer->GetBuffer(), 0, + VK_INDEX_TYPE_UINT16); +} + +void VertexManager::UploadUniforms() +{ + UpdateVertexShaderConstants(); + UpdateGeometryShaderConstants(); + UpdatePixelShaderConstants(); +} + +void VertexManager::UpdateVertexShaderConstants() +{ + if (!VertexShaderManager::dirty || !ReserveConstantStorage()) + return; + + StateTracker::GetInstance()->SetGXUniformBuffer( + UBO_DESCRIPTOR_SET_BINDING_VS, m_uniform_stream_buffer->GetBuffer(), + m_uniform_stream_buffer->GetCurrentOffset(), sizeof(VertexShaderConstants)); + std::memcpy(m_uniform_stream_buffer->GetCurrentHostPointer(), &VertexShaderManager::constants, + sizeof(VertexShaderConstants)); + m_uniform_stream_buffer->CommitMemory(sizeof(VertexShaderConstants)); + ADDSTAT(g_stats.this_frame.bytes_uniform_streamed, sizeof(VertexShaderConstants)); + VertexShaderManager::dirty = false; +} + +void VertexManager::UpdateGeometryShaderConstants() +{ + if (!GeometryShaderManager::dirty || !ReserveConstantStorage()) + return; + + StateTracker::GetInstance()->SetGXUniformBuffer( + UBO_DESCRIPTOR_SET_BINDING_GS, m_uniform_stream_buffer->GetBuffer(), + m_uniform_stream_buffer->GetCurrentOffset(), sizeof(GeometryShaderConstants)); + std::memcpy(m_uniform_stream_buffer->GetCurrentHostPointer(), &GeometryShaderManager::constants, + sizeof(GeometryShaderConstants)); + m_uniform_stream_buffer->CommitMemory(sizeof(GeometryShaderConstants)); + ADDSTAT(g_stats.this_frame.bytes_uniform_streamed, sizeof(GeometryShaderConstants)); + GeometryShaderManager::dirty = false; +} + +void VertexManager::UpdatePixelShaderConstants() +{ + if (!PixelShaderManager::dirty || !ReserveConstantStorage()) + return; + + StateTracker::GetInstance()->SetGXUniformBuffer( + UBO_DESCRIPTOR_SET_BINDING_PS, m_uniform_stream_buffer->GetBuffer(), + m_uniform_stream_buffer->GetCurrentOffset(), sizeof(PixelShaderConstants)); + std::memcpy(m_uniform_stream_buffer->GetCurrentHostPointer(), &PixelShaderManager::constants, + sizeof(PixelShaderConstants)); + m_uniform_stream_buffer->CommitMemory(sizeof(PixelShaderConstants)); + ADDSTAT(g_stats.this_frame.bytes_uniform_streamed, sizeof(PixelShaderConstants)); + PixelShaderManager::dirty = false; +} + +bool VertexManager::ReserveConstantStorage() +{ + if (m_uniform_stream_buffer->ReserveMemory(m_uniform_buffer_reserve_size, + g_vulkan_context->GetUniformBufferAlignment())) + { + return true; + } + + // The only places that call constant updates are safe to have state restored. + WARN_LOG_FMT(VIDEO, "Executing command buffer while waiting for space in uniform buffer"); + Renderer::GetInstance()->ExecuteCommandBuffer(false); + + // Since we are on a new command buffer, all constants have been invalidated, and we need + // to reupload them. We may as well do this now, since we're issuing a draw anyway. + UploadAllConstants(); + return false; +} + +void VertexManager::UploadAllConstants() +{ + // We are free to re-use parts of the buffer now since we're uploading all constants. + const u32 ub_alignment = static_cast(g_vulkan_context->GetUniformBufferAlignment()); + const u32 pixel_constants_offset = 0; + const u32 vertex_constants_offset = + Common::AlignUp(pixel_constants_offset + sizeof(PixelShaderConstants), ub_alignment); + const u32 geometry_constants_offset = + Common::AlignUp(vertex_constants_offset + sizeof(VertexShaderConstants), ub_alignment); + const u32 allocation_size = geometry_constants_offset + sizeof(GeometryShaderConstants); + + // Allocate everything at once. + // We should only be here if the buffer was full and a command buffer was submitted anyway. + if (!m_uniform_stream_buffer->ReserveMemory(allocation_size, ub_alignment)) + { + PanicAlertFmt("Failed to allocate space for constants in streaming buffer"); + return; + } + + // Update bindings + StateTracker::GetInstance()->SetGXUniformBuffer( + UBO_DESCRIPTOR_SET_BINDING_PS, m_uniform_stream_buffer->GetBuffer(), + m_uniform_stream_buffer->GetCurrentOffset() + pixel_constants_offset, + sizeof(PixelShaderConstants)); + StateTracker::GetInstance()->SetGXUniformBuffer( + UBO_DESCRIPTOR_SET_BINDING_VS, m_uniform_stream_buffer->GetBuffer(), + m_uniform_stream_buffer->GetCurrentOffset() + vertex_constants_offset, + sizeof(VertexShaderConstants)); + StateTracker::GetInstance()->SetGXUniformBuffer( + UBO_DESCRIPTOR_SET_BINDING_GS, m_uniform_stream_buffer->GetBuffer(), + m_uniform_stream_buffer->GetCurrentOffset() + geometry_constants_offset, + sizeof(GeometryShaderConstants)); + + // Copy the actual data in + std::memcpy(m_uniform_stream_buffer->GetCurrentHostPointer() + pixel_constants_offset, + &PixelShaderManager::constants, sizeof(PixelShaderConstants)); + std::memcpy(m_uniform_stream_buffer->GetCurrentHostPointer() + vertex_constants_offset, + &VertexShaderManager::constants, sizeof(VertexShaderConstants)); + std::memcpy(m_uniform_stream_buffer->GetCurrentHostPointer() + geometry_constants_offset, + &GeometryShaderManager::constants, sizeof(GeometryShaderConstants)); + + // Finally, flush buffer memory after copying + m_uniform_stream_buffer->CommitMemory(allocation_size); + ADDSTAT(g_stats.this_frame.bytes_uniform_streamed, allocation_size); + + // Clear dirty flags + VertexShaderManager::dirty = false; + GeometryShaderManager::dirty = false; + PixelShaderManager::dirty = false; +} + +void VertexManager::UploadUtilityUniforms(const void* data, u32 data_size) +{ + InvalidateConstants(); + if (!m_uniform_stream_buffer->ReserveMemory(data_size, + g_vulkan_context->GetUniformBufferAlignment())) + { + WARN_LOG_FMT(VIDEO, "Executing command buffer while waiting for ext space in uniform buffer"); + Renderer::GetInstance()->ExecuteCommandBuffer(false); + } + + StateTracker::GetInstance()->SetUtilityUniformBuffer( + m_uniform_stream_buffer->GetBuffer(), m_uniform_stream_buffer->GetCurrentOffset(), data_size); + std::memcpy(m_uniform_stream_buffer->GetCurrentHostPointer(), data, data_size); + m_uniform_stream_buffer->CommitMemory(data_size); + ADDSTAT(g_stats.this_frame.bytes_uniform_streamed, data_size); +} + +bool VertexManager::UploadTexelBuffer(const void* data, u32 data_size, TexelBufferFormat format, + u32* out_offset) +{ + if (data_size > m_texel_stream_buffer->GetCurrentSize()) + return false; + + const u32 elem_size = GetTexelBufferElementSize(format); + if (!m_texel_stream_buffer->ReserveMemory(data_size, elem_size)) + { + // Try submitting cmdbuffer. + WARN_LOG_FMT(VIDEO, "Submitting command buffer while waiting for space in texel buffer"); + Renderer::GetInstance()->ExecuteCommandBuffer(false, false); + if (!m_texel_stream_buffer->ReserveMemory(data_size, elem_size)) + { + PanicAlertFmt("Failed to allocate {} bytes from texel buffer", data_size); + return false; + } + } + + std::memcpy(m_texel_stream_buffer->GetCurrentHostPointer(), data, data_size); + *out_offset = static_cast(m_texel_stream_buffer->GetCurrentOffset()) / elem_size; + m_texel_stream_buffer->CommitMemory(data_size); + ADDSTAT(g_stats.this_frame.bytes_uniform_streamed, data_size); + StateTracker::GetInstance()->SetTexelBuffer(0, m_texel_buffer_views[format]); + return true; +} + +bool VertexManager::UploadTexelBuffer(const void* data, u32 data_size, TexelBufferFormat format, + u32* out_offset, const void* palette_data, u32 palette_size, + TexelBufferFormat palette_format, u32* out_palette_offset) +{ + const u32 elem_size = GetTexelBufferElementSize(format); + const u32 palette_elem_size = GetTexelBufferElementSize(palette_format); + const u32 reserve_size = data_size + palette_size + palette_elem_size; + if (reserve_size > m_texel_stream_buffer->GetCurrentSize()) + return false; + + if (!m_texel_stream_buffer->ReserveMemory(reserve_size, elem_size)) + { + // Try submitting cmdbuffer. + WARN_LOG_FMT(VIDEO, "Submitting command buffer while waiting for space in texel buffer"); + Renderer::GetInstance()->ExecuteCommandBuffer(false, false); + if (!m_texel_stream_buffer->ReserveMemory(reserve_size, elem_size)) + { + PanicAlertFmt("Failed to allocate {} bytes from texel buffer", reserve_size); + return false; + } + } + + const u32 palette_byte_offset = Common::AlignUp(data_size, palette_elem_size); + std::memcpy(m_texel_stream_buffer->GetCurrentHostPointer(), data, data_size); + std::memcpy(m_texel_stream_buffer->GetCurrentHostPointer() + palette_byte_offset, palette_data, + palette_size); + *out_offset = static_cast(m_texel_stream_buffer->GetCurrentOffset()) / elem_size; + *out_palette_offset = + (static_cast(m_texel_stream_buffer->GetCurrentOffset()) + palette_byte_offset) / + palette_elem_size; + + m_texel_stream_buffer->CommitMemory(palette_byte_offset + palette_size); + ADDSTAT(g_stats.this_frame.bytes_uniform_streamed, palette_byte_offset + palette_size); + StateTracker::GetInstance()->SetTexelBuffer(0, m_texel_buffer_views[format]); + StateTracker::GetInstance()->SetTexelBuffer(1, m_texel_buffer_views[palette_format]); + return true; +} +} // namespace Vulkan diff --git a/Source/Core/VideoBackends/Vulkan/VKVertexManager.h b/Source/Core/VideoBackends/Vulkan/VKVertexManager.h new file mode 100644 index 0000000000..0a71903c83 --- /dev/null +++ b/Source/Core/VideoBackends/Vulkan/VKVertexManager.h @@ -0,0 +1,58 @@ +// 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" +#include "VideoCommon/VertexManagerBase.h" + +namespace Vulkan +{ +class StreamBuffer; + +class VertexManager : public VertexManagerBase +{ +public: + VertexManager(); + ~VertexManager(); + + bool Initialize() override; + + void UploadUtilityUniforms(const void* uniforms, u32 uniforms_size) override; + bool UploadTexelBuffer(const void* data, u32 data_size, TexelBufferFormat format, + u32* out_offset) override; + bool UploadTexelBuffer(const void* data, u32 data_size, TexelBufferFormat format, u32* out_offset, + const void* palette_data, u32 palette_size, + TexelBufferFormat palette_format, u32* out_palette_offset) override; + +protected: + void ResetBuffer(u32 vertex_stride) override; + void CommitBuffer(u32 num_vertices, u32 vertex_stride, u32 num_indices, u32* out_base_vertex, + u32* out_base_index) override; + void UploadUniforms() override; + + void DestroyTexelBufferViews(); + + void UpdateVertexShaderConstants(); + void UpdateGeometryShaderConstants(); + void UpdatePixelShaderConstants(); + + // Allocates storage in the uniform buffer of the specified size. If this storage cannot be + // allocated immediately, the current command buffer will be submitted and all stage's + // constants will be re-uploaded. false will be returned in this case, otherwise true. + bool ReserveConstantStorage(); + void UploadAllConstants(); + + std::unique_ptr m_vertex_stream_buffer; + std::unique_ptr m_index_stream_buffer; + std::unique_ptr m_uniform_stream_buffer; + std::unique_ptr m_texel_stream_buffer; + std::array m_texel_buffer_views = {}; + u32 m_uniform_buffer_reserve_size = 0; +}; +} // namespace Vulkan diff --git a/Source/Core/VideoBackends/Vulkan/VertexFormat.cpp b/Source/Core/VideoBackends/Vulkan/VertexFormat.cpp deleted file mode 100644 index 5d8006c2c0..0000000000 --- a/Source/Core/VideoBackends/Vulkan/VertexFormat.cpp +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright 2016 Dolphin Emulator Project -// Licensed under GPLv2+ -// Refer to the license.txt file included. - -#include "VideoBackends/Vulkan/VertexFormat.h" - -#include "Common/Assert.h" - -#include "VideoBackends/Vulkan/CommandBufferManager.h" -#include "VideoBackends/Vulkan/ObjectCache.h" - -#include "VideoCommon/VertexLoaderManager.h" -#include "VideoCommon/VertexShaderGen.h" - -namespace Vulkan -{ -static VkFormat VarToVkFormat(VarType t, uint32_t components, bool integer) -{ - static const VkFormat float_type_lookup[][4] = { - {VK_FORMAT_R8_UNORM, VK_FORMAT_R8G8_UNORM, VK_FORMAT_R8G8B8_UNORM, - VK_FORMAT_R8G8B8A8_UNORM}, // VAR_UNSIGNED_BYTE - {VK_FORMAT_R8_SNORM, VK_FORMAT_R8G8_SNORM, VK_FORMAT_R8G8B8_SNORM, - VK_FORMAT_R8G8B8A8_SNORM}, // VAR_BYTE - {VK_FORMAT_R16_UNORM, VK_FORMAT_R16G16_UNORM, VK_FORMAT_R16G16B16_UNORM, - VK_FORMAT_R16G16B16A16_UNORM}, // VAR_UNSIGNED_SHORT - {VK_FORMAT_R16_SNORM, VK_FORMAT_R16G16_SNORM, VK_FORMAT_R16G16B16_SNORM, - VK_FORMAT_R16G16B16A16_SNORM}, // VAR_SHORT - {VK_FORMAT_R32_SFLOAT, VK_FORMAT_R32G32_SFLOAT, VK_FORMAT_R32G32B32_SFLOAT, - VK_FORMAT_R32G32B32A32_SFLOAT} // VAR_FLOAT - }; - - static const VkFormat integer_type_lookup[][4] = { - {VK_FORMAT_R8_UINT, VK_FORMAT_R8G8_UINT, VK_FORMAT_R8G8B8_UINT, - VK_FORMAT_R8G8B8A8_UINT}, // VAR_UNSIGNED_BYTE - {VK_FORMAT_R8_SINT, VK_FORMAT_R8G8_SINT, VK_FORMAT_R8G8B8_SINT, - VK_FORMAT_R8G8B8A8_SINT}, // VAR_BYTE - {VK_FORMAT_R16_UINT, VK_FORMAT_R16G16_UINT, VK_FORMAT_R16G16B16_UINT, - VK_FORMAT_R16G16B16A16_UINT}, // VAR_UNSIGNED_SHORT - {VK_FORMAT_R16_SINT, VK_FORMAT_R16G16_SINT, VK_FORMAT_R16G16B16_SINT, - VK_FORMAT_R16G16B16A16_SINT}, // VAR_SHORT - {VK_FORMAT_R32_SFLOAT, VK_FORMAT_R32G32_SFLOAT, VK_FORMAT_R32G32B32_SFLOAT, - VK_FORMAT_R32G32B32A32_SFLOAT} // VAR_FLOAT - }; - - ASSERT(components > 0 && components <= 4); - return integer ? integer_type_lookup[t][components - 1] : float_type_lookup[t][components - 1]; -} - -VertexFormat::VertexFormat(const PortableVertexDeclaration& vtx_decl) : NativeVertexFormat(vtx_decl) -{ - MapAttributes(); - SetupInputState(); -} - -const VkPipelineVertexInputStateCreateInfo& VertexFormat::GetVertexInputStateInfo() const -{ - return m_input_state_info; -} - -void VertexFormat::MapAttributes() -{ - m_num_attributes = 0; - - if (m_decl.position.enable) - AddAttribute( - SHADER_POSITION_ATTRIB, 0, - VarToVkFormat(m_decl.position.type, m_decl.position.components, m_decl.position.integer), - m_decl.position.offset); - - for (uint32_t i = 0; i < 3; i++) - { - if (m_decl.normals[i].enable) - AddAttribute(SHADER_NORM0_ATTRIB + i, 0, - VarToVkFormat(m_decl.normals[i].type, m_decl.normals[i].components, - m_decl.normals[i].integer), - m_decl.normals[i].offset); - } - - for (uint32_t i = 0; i < 2; i++) - { - if (m_decl.colors[i].enable) - AddAttribute(SHADER_COLOR0_ATTRIB + i, 0, - VarToVkFormat(m_decl.colors[i].type, m_decl.colors[i].components, - m_decl.colors[i].integer), - m_decl.colors[i].offset); - } - - for (uint32_t i = 0; i < 8; i++) - { - if (m_decl.texcoords[i].enable) - AddAttribute(SHADER_TEXTURE0_ATTRIB + i, 0, - VarToVkFormat(m_decl.texcoords[i].type, m_decl.texcoords[i].components, - m_decl.texcoords[i].integer), - m_decl.texcoords[i].offset); - } - - if (m_decl.posmtx.enable) - AddAttribute(SHADER_POSMTX_ATTRIB, 0, - VarToVkFormat(m_decl.posmtx.type, m_decl.posmtx.components, m_decl.posmtx.integer), - m_decl.posmtx.offset); -} - -void VertexFormat::SetupInputState() -{ - m_binding_description.binding = 0; - m_binding_description.stride = m_decl.stride; - m_binding_description.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; - - m_input_state_info.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; - m_input_state_info.pNext = nullptr; - m_input_state_info.flags = 0; - m_input_state_info.vertexBindingDescriptionCount = 1; - m_input_state_info.pVertexBindingDescriptions = &m_binding_description; - m_input_state_info.vertexAttributeDescriptionCount = m_num_attributes; - m_input_state_info.pVertexAttributeDescriptions = m_attribute_descriptions.data(); -} - -void VertexFormat::AddAttribute(uint32_t location, uint32_t binding, VkFormat format, - uint32_t offset) -{ - ASSERT(m_num_attributes < MAX_VERTEX_ATTRIBUTES); - - m_attribute_descriptions[m_num_attributes].location = location; - m_attribute_descriptions[m_num_attributes].binding = binding; - m_attribute_descriptions[m_num_attributes].format = format; - m_attribute_descriptions[m_num_attributes].offset = offset; - m_num_attributes++; -} -} // namespace Vulkan diff --git a/Source/Core/VideoBackends/Vulkan/VertexFormat.h b/Source/Core/VideoBackends/Vulkan/VertexFormat.h deleted file mode 100644 index 617967202b..0000000000 --- a/Source/Core/VideoBackends/Vulkan/VertexFormat.h +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2016 Dolphin Emulator Project -// Licensed under GPLv2+ -// Refer to the license.txt file included. - -#pragma once - -#include - -#include "VideoBackends/Vulkan/Constants.h" -#include "VideoCommon/NativeVertexFormat.h" - -namespace Vulkan -{ -class VertexFormat : public ::NativeVertexFormat -{ -public: - VertexFormat(const PortableVertexDeclaration& vtx_decl); - - // Passed to pipeline state creation - const VkPipelineVertexInputStateCreateInfo& GetVertexInputStateInfo() const; - - // Converting PortableVertexDeclaration -> Vulkan types - void MapAttributes(); - void SetupInputState(); - -private: - void AddAttribute(uint32_t location, uint32_t binding, VkFormat format, uint32_t offset); - - VkVertexInputBindingDescription m_binding_description = {}; - - std::array m_attribute_descriptions = - {}; - - VkPipelineVertexInputStateCreateInfo m_input_state_info = {}; - - uint32_t m_num_attributes = 0; -}; -} // namespace Vulkan diff --git a/Source/Core/VideoBackends/Vulkan/VertexManager.cpp b/Source/Core/VideoBackends/Vulkan/VertexManager.cpp deleted file mode 100644 index 30ba8e2bad..0000000000 --- a/Source/Core/VideoBackends/Vulkan/VertexManager.cpp +++ /dev/null @@ -1,397 +0,0 @@ -// Copyright 2016 Dolphin Emulator Project -// Licensed under GPLv2+ -// Refer to the license.txt file included. - -#include "VideoBackends/Vulkan/VertexManager.h" - -#include "Common/Align.h" -#include "Common/CommonTypes.h" -#include "Common/Logging/Log.h" -#include "Common/MsgHandler.h" - -#include "VideoBackends/Vulkan/CommandBufferManager.h" -#include "VideoBackends/Vulkan/Renderer.h" -#include "VideoBackends/Vulkan/StateTracker.h" -#include "VideoBackends/Vulkan/StreamBuffer.h" -#include "VideoBackends/Vulkan/VertexFormat.h" -#include "VideoBackends/Vulkan/VulkanContext.h" - -#include "VideoCommon/GeometryShaderManager.h" -#include "VideoCommon/IndexGenerator.h" -#include "VideoCommon/PixelShaderManager.h" -#include "VideoCommon/Statistics.h" -#include "VideoCommon/VertexLoaderManager.h" -#include "VideoCommon/VertexShaderManager.h" -#include "VideoCommon/VideoConfig.h" - -namespace Vulkan -{ -static VkBufferView CreateTexelBufferView(VkBuffer buffer, VkFormat vk_format) -{ - // Create a view of the whole buffer, we'll offset our texel load into it - VkBufferViewCreateInfo view_info = { - VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO, // VkStructureType sType - nullptr, // const void* pNext - 0, // VkBufferViewCreateFlags flags - buffer, // VkBuffer buffer - vk_format, // VkFormat format - 0, // VkDeviceSize offset - VK_WHOLE_SIZE // VkDeviceSize range - }; - - VkBufferView view; - VkResult res = vkCreateBufferView(g_vulkan_context->GetDevice(), &view_info, nullptr, &view); - if (res != VK_SUCCESS) - { - LOG_VULKAN_ERROR(res, "vkCreateBufferView failed: "); - return VK_NULL_HANDLE; - } - - return view; -} - -VertexManager::VertexManager() = default; - -VertexManager::~VertexManager() -{ - DestroyTexelBufferViews(); -} - -bool VertexManager::Initialize() -{ - if (!VertexManagerBase::Initialize()) - return false; - - m_vertex_stream_buffer = - StreamBuffer::Create(VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VERTEX_STREAM_BUFFER_SIZE); - m_index_stream_buffer = - StreamBuffer::Create(VK_BUFFER_USAGE_INDEX_BUFFER_BIT, INDEX_STREAM_BUFFER_SIZE); - m_uniform_stream_buffer = - StreamBuffer::Create(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, UNIFORM_STREAM_BUFFER_SIZE); - if (!m_vertex_stream_buffer || !m_index_stream_buffer || !m_uniform_stream_buffer) - { - PanicAlertFmt("Failed to allocate streaming buffers"); - return false; - } - - // The validation layer complains if max(offsets) + max(ubo_ranges) >= ubo_size. - // To work around this we reserve the maximum buffer size at all times, but only commit - // as many bytes as we use. - m_uniform_buffer_reserve_size = sizeof(PixelShaderConstants); - m_uniform_buffer_reserve_size = Common::AlignUp(m_uniform_buffer_reserve_size, - g_vulkan_context->GetUniformBufferAlignment()) + - sizeof(VertexShaderConstants); - m_uniform_buffer_reserve_size = Common::AlignUp(m_uniform_buffer_reserve_size, - g_vulkan_context->GetUniformBufferAlignment()) + - sizeof(GeometryShaderConstants); - - // Prefer an 8MB buffer if possible, but use less if the device doesn't support this. - // This buffer is potentially going to be addressed as R8s in the future, so we assume - // that one element is one byte. This doesn't use min() because of a NDK compiler bug.. - const u32 texel_buffer_size = - TEXEL_STREAM_BUFFER_SIZE > g_vulkan_context->GetDeviceLimits().maxTexelBufferElements ? - g_vulkan_context->GetDeviceLimits().maxTexelBufferElements : - TEXEL_STREAM_BUFFER_SIZE; - m_texel_stream_buffer = - StreamBuffer::Create(VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT, texel_buffer_size); - if (!m_texel_stream_buffer) - { - PanicAlertFmt("Failed to allocate streaming texel buffer"); - return false; - } - - static constexpr std::array, NUM_TEXEL_BUFFER_FORMATS> - format_mapping = {{ - {TEXEL_BUFFER_FORMAT_R8_UINT, VK_FORMAT_R8_UINT}, - {TEXEL_BUFFER_FORMAT_R16_UINT, VK_FORMAT_R16_UINT}, - {TEXEL_BUFFER_FORMAT_RGBA8_UINT, VK_FORMAT_R8G8B8A8_UINT}, - {TEXEL_BUFFER_FORMAT_R32G32_UINT, VK_FORMAT_R32G32_UINT}, - }}; - for (const auto& it : format_mapping) - { - if ((m_texel_buffer_views[it.first] = CreateTexelBufferView(m_texel_stream_buffer->GetBuffer(), - it.second)) == VK_NULL_HANDLE) - { - PanicAlertFmt("Failed to create texel buffer view"); - return false; - } - } - - // Bind the buffers to all the known spots even if it's not used, to keep the driver happy. - UploadAllConstants(); - StateTracker::GetInstance()->SetUtilityUniformBuffer(m_uniform_stream_buffer->GetBuffer(), 0, - sizeof(VertexShaderConstants)); - for (u32 i = 0; i < NUM_COMPUTE_TEXEL_BUFFERS; i++) - { - StateTracker::GetInstance()->SetTexelBuffer(i, - m_texel_buffer_views[TEXEL_BUFFER_FORMAT_R8_UINT]); - } - - return true; -} - -void VertexManager::DestroyTexelBufferViews() -{ - for (VkBufferView view : m_texel_buffer_views) - { - if (view != VK_NULL_HANDLE) - vkDestroyBufferView(g_vulkan_context->GetDevice(), view, nullptr); - } -} - -void VertexManager::ResetBuffer(u32 vertex_stride) -{ - // Attempt to allocate from buffers - bool has_vbuffer_allocation = - m_vertex_stream_buffer->ReserveMemory(MAXVBUFFERSIZE, vertex_stride); - bool has_ibuffer_allocation = - m_index_stream_buffer->ReserveMemory(MAXIBUFFERSIZE * sizeof(u16), sizeof(u16)); - if (!has_vbuffer_allocation || !has_ibuffer_allocation) - { - // Flush any pending commands first, so that we can wait on the fences - WARN_LOG_FMT(VIDEO, "Executing command list while waiting for space in vertex/index buffer"); - Renderer::GetInstance()->ExecuteCommandBuffer(false); - - // Attempt to allocate again, this may cause a fence wait - if (!has_vbuffer_allocation) - has_vbuffer_allocation = m_vertex_stream_buffer->ReserveMemory(MAXVBUFFERSIZE, vertex_stride); - if (!has_ibuffer_allocation) - has_ibuffer_allocation = - m_index_stream_buffer->ReserveMemory(MAXIBUFFERSIZE * sizeof(u16), sizeof(u16)); - - // If we still failed, that means the allocation was too large and will never succeed, so panic - if (!has_vbuffer_allocation || !has_ibuffer_allocation) - PanicAlertFmt("Failed to allocate space in streaming buffers for pending draw"); - } - - // Update pointers - m_base_buffer_pointer = m_vertex_stream_buffer->GetHostPointer(); - m_end_buffer_pointer = m_vertex_stream_buffer->GetCurrentHostPointer() + MAXVBUFFERSIZE; - m_cur_buffer_pointer = m_vertex_stream_buffer->GetCurrentHostPointer(); - m_index_generator.Start(reinterpret_cast(m_index_stream_buffer->GetCurrentHostPointer())); -} - -void VertexManager::CommitBuffer(u32 num_vertices, u32 vertex_stride, u32 num_indices, - u32* out_base_vertex, u32* out_base_index) -{ - const u32 vertex_data_size = num_vertices * vertex_stride; - const u32 index_data_size = num_indices * sizeof(u16); - - *out_base_vertex = - vertex_stride > 0 ? (m_vertex_stream_buffer->GetCurrentOffset() / vertex_stride) : 0; - *out_base_index = m_index_stream_buffer->GetCurrentOffset() / sizeof(u16); - - m_vertex_stream_buffer->CommitMemory(vertex_data_size); - m_index_stream_buffer->CommitMemory(index_data_size); - - ADDSTAT(g_stats.this_frame.bytes_vertex_streamed, static_cast(vertex_data_size)); - ADDSTAT(g_stats.this_frame.bytes_index_streamed, static_cast(index_data_size)); - - StateTracker::GetInstance()->SetVertexBuffer(m_vertex_stream_buffer->GetBuffer(), 0); - StateTracker::GetInstance()->SetIndexBuffer(m_index_stream_buffer->GetBuffer(), 0, - VK_INDEX_TYPE_UINT16); -} - -void VertexManager::UploadUniforms() -{ - UpdateVertexShaderConstants(); - UpdateGeometryShaderConstants(); - UpdatePixelShaderConstants(); -} - -void VertexManager::UpdateVertexShaderConstants() -{ - if (!VertexShaderManager::dirty || !ReserveConstantStorage()) - return; - - StateTracker::GetInstance()->SetGXUniformBuffer( - UBO_DESCRIPTOR_SET_BINDING_VS, m_uniform_stream_buffer->GetBuffer(), - m_uniform_stream_buffer->GetCurrentOffset(), sizeof(VertexShaderConstants)); - std::memcpy(m_uniform_stream_buffer->GetCurrentHostPointer(), &VertexShaderManager::constants, - sizeof(VertexShaderConstants)); - m_uniform_stream_buffer->CommitMemory(sizeof(VertexShaderConstants)); - ADDSTAT(g_stats.this_frame.bytes_uniform_streamed, sizeof(VertexShaderConstants)); - VertexShaderManager::dirty = false; -} - -void VertexManager::UpdateGeometryShaderConstants() -{ - if (!GeometryShaderManager::dirty || !ReserveConstantStorage()) - return; - - StateTracker::GetInstance()->SetGXUniformBuffer( - UBO_DESCRIPTOR_SET_BINDING_GS, m_uniform_stream_buffer->GetBuffer(), - m_uniform_stream_buffer->GetCurrentOffset(), sizeof(GeometryShaderConstants)); - std::memcpy(m_uniform_stream_buffer->GetCurrentHostPointer(), &GeometryShaderManager::constants, - sizeof(GeometryShaderConstants)); - m_uniform_stream_buffer->CommitMemory(sizeof(GeometryShaderConstants)); - ADDSTAT(g_stats.this_frame.bytes_uniform_streamed, sizeof(GeometryShaderConstants)); - GeometryShaderManager::dirty = false; -} - -void VertexManager::UpdatePixelShaderConstants() -{ - if (!PixelShaderManager::dirty || !ReserveConstantStorage()) - return; - - StateTracker::GetInstance()->SetGXUniformBuffer( - UBO_DESCRIPTOR_SET_BINDING_PS, m_uniform_stream_buffer->GetBuffer(), - m_uniform_stream_buffer->GetCurrentOffset(), sizeof(PixelShaderConstants)); - std::memcpy(m_uniform_stream_buffer->GetCurrentHostPointer(), &PixelShaderManager::constants, - sizeof(PixelShaderConstants)); - m_uniform_stream_buffer->CommitMemory(sizeof(PixelShaderConstants)); - ADDSTAT(g_stats.this_frame.bytes_uniform_streamed, sizeof(PixelShaderConstants)); - PixelShaderManager::dirty = false; -} - -bool VertexManager::ReserveConstantStorage() -{ - if (m_uniform_stream_buffer->ReserveMemory(m_uniform_buffer_reserve_size, - g_vulkan_context->GetUniformBufferAlignment())) - { - return true; - } - - // The only places that call constant updates are safe to have state restored. - WARN_LOG_FMT(VIDEO, "Executing command buffer while waiting for space in uniform buffer"); - Renderer::GetInstance()->ExecuteCommandBuffer(false); - - // Since we are on a new command buffer, all constants have been invalidated, and we need - // to reupload them. We may as well do this now, since we're issuing a draw anyway. - UploadAllConstants(); - return false; -} - -void VertexManager::UploadAllConstants() -{ - // We are free to re-use parts of the buffer now since we're uploading all constants. - const u32 ub_alignment = static_cast(g_vulkan_context->GetUniformBufferAlignment()); - const u32 pixel_constants_offset = 0; - const u32 vertex_constants_offset = - Common::AlignUp(pixel_constants_offset + sizeof(PixelShaderConstants), ub_alignment); - const u32 geometry_constants_offset = - Common::AlignUp(vertex_constants_offset + sizeof(VertexShaderConstants), ub_alignment); - const u32 allocation_size = geometry_constants_offset + sizeof(GeometryShaderConstants); - - // Allocate everything at once. - // We should only be here if the buffer was full and a command buffer was submitted anyway. - if (!m_uniform_stream_buffer->ReserveMemory(allocation_size, ub_alignment)) - { - PanicAlertFmt("Failed to allocate space for constants in streaming buffer"); - return; - } - - // Update bindings - StateTracker::GetInstance()->SetGXUniformBuffer( - UBO_DESCRIPTOR_SET_BINDING_PS, m_uniform_stream_buffer->GetBuffer(), - m_uniform_stream_buffer->GetCurrentOffset() + pixel_constants_offset, - sizeof(PixelShaderConstants)); - StateTracker::GetInstance()->SetGXUniformBuffer( - UBO_DESCRIPTOR_SET_BINDING_VS, m_uniform_stream_buffer->GetBuffer(), - m_uniform_stream_buffer->GetCurrentOffset() + vertex_constants_offset, - sizeof(VertexShaderConstants)); - StateTracker::GetInstance()->SetGXUniformBuffer( - UBO_DESCRIPTOR_SET_BINDING_GS, m_uniform_stream_buffer->GetBuffer(), - m_uniform_stream_buffer->GetCurrentOffset() + geometry_constants_offset, - sizeof(GeometryShaderConstants)); - - // Copy the actual data in - std::memcpy(m_uniform_stream_buffer->GetCurrentHostPointer() + pixel_constants_offset, - &PixelShaderManager::constants, sizeof(PixelShaderConstants)); - std::memcpy(m_uniform_stream_buffer->GetCurrentHostPointer() + vertex_constants_offset, - &VertexShaderManager::constants, sizeof(VertexShaderConstants)); - std::memcpy(m_uniform_stream_buffer->GetCurrentHostPointer() + geometry_constants_offset, - &GeometryShaderManager::constants, sizeof(GeometryShaderConstants)); - - // Finally, flush buffer memory after copying - m_uniform_stream_buffer->CommitMemory(allocation_size); - ADDSTAT(g_stats.this_frame.bytes_uniform_streamed, allocation_size); - - // Clear dirty flags - VertexShaderManager::dirty = false; - GeometryShaderManager::dirty = false; - PixelShaderManager::dirty = false; -} - -void VertexManager::UploadUtilityUniforms(const void* data, u32 data_size) -{ - InvalidateConstants(); - if (!m_uniform_stream_buffer->ReserveMemory(data_size, - g_vulkan_context->GetUniformBufferAlignment())) - { - WARN_LOG_FMT(VIDEO, "Executing command buffer while waiting for ext space in uniform buffer"); - Renderer::GetInstance()->ExecuteCommandBuffer(false); - } - - StateTracker::GetInstance()->SetUtilityUniformBuffer( - m_uniform_stream_buffer->GetBuffer(), m_uniform_stream_buffer->GetCurrentOffset(), data_size); - std::memcpy(m_uniform_stream_buffer->GetCurrentHostPointer(), data, data_size); - m_uniform_stream_buffer->CommitMemory(data_size); - ADDSTAT(g_stats.this_frame.bytes_uniform_streamed, data_size); -} - -bool VertexManager::UploadTexelBuffer(const void* data, u32 data_size, TexelBufferFormat format, - u32* out_offset) -{ - if (data_size > m_texel_stream_buffer->GetCurrentSize()) - return false; - - const u32 elem_size = GetTexelBufferElementSize(format); - if (!m_texel_stream_buffer->ReserveMemory(data_size, elem_size)) - { - // Try submitting cmdbuffer. - WARN_LOG_FMT(VIDEO, "Submitting command buffer while waiting for space in texel buffer"); - Renderer::GetInstance()->ExecuteCommandBuffer(false, false); - if (!m_texel_stream_buffer->ReserveMemory(data_size, elem_size)) - { - PanicAlertFmt("Failed to allocate {} bytes from texel buffer", data_size); - return false; - } - } - - std::memcpy(m_texel_stream_buffer->GetCurrentHostPointer(), data, data_size); - *out_offset = static_cast(m_texel_stream_buffer->GetCurrentOffset()) / elem_size; - m_texel_stream_buffer->CommitMemory(data_size); - ADDSTAT(g_stats.this_frame.bytes_uniform_streamed, data_size); - StateTracker::GetInstance()->SetTexelBuffer(0, m_texel_buffer_views[format]); - return true; -} - -bool VertexManager::UploadTexelBuffer(const void* data, u32 data_size, TexelBufferFormat format, - u32* out_offset, const void* palette_data, u32 palette_size, - TexelBufferFormat palette_format, u32* out_palette_offset) -{ - const u32 elem_size = GetTexelBufferElementSize(format); - const u32 palette_elem_size = GetTexelBufferElementSize(palette_format); - const u32 reserve_size = data_size + palette_size + palette_elem_size; - if (reserve_size > m_texel_stream_buffer->GetCurrentSize()) - return false; - - if (!m_texel_stream_buffer->ReserveMemory(reserve_size, elem_size)) - { - // Try submitting cmdbuffer. - WARN_LOG_FMT(VIDEO, "Submitting command buffer while waiting for space in texel buffer"); - Renderer::GetInstance()->ExecuteCommandBuffer(false, false); - if (!m_texel_stream_buffer->ReserveMemory(reserve_size, elem_size)) - { - PanicAlertFmt("Failed to allocate {} bytes from texel buffer", reserve_size); - return false; - } - } - - const u32 palette_byte_offset = Common::AlignUp(data_size, palette_elem_size); - std::memcpy(m_texel_stream_buffer->GetCurrentHostPointer(), data, data_size); - std::memcpy(m_texel_stream_buffer->GetCurrentHostPointer() + palette_byte_offset, palette_data, - palette_size); - *out_offset = static_cast(m_texel_stream_buffer->GetCurrentOffset()) / elem_size; - *out_palette_offset = - (static_cast(m_texel_stream_buffer->GetCurrentOffset()) + palette_byte_offset) / - palette_elem_size; - - m_texel_stream_buffer->CommitMemory(palette_byte_offset + palette_size); - ADDSTAT(g_stats.this_frame.bytes_uniform_streamed, palette_byte_offset + palette_size); - StateTracker::GetInstance()->SetTexelBuffer(0, m_texel_buffer_views[format]); - StateTracker::GetInstance()->SetTexelBuffer(1, m_texel_buffer_views[palette_format]); - return true; -} -} // namespace Vulkan diff --git a/Source/Core/VideoBackends/Vulkan/VertexManager.h b/Source/Core/VideoBackends/Vulkan/VertexManager.h deleted file mode 100644 index 0a71903c83..0000000000 --- a/Source/Core/VideoBackends/Vulkan/VertexManager.h +++ /dev/null @@ -1,58 +0,0 @@ -// 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" -#include "VideoCommon/VertexManagerBase.h" - -namespace Vulkan -{ -class StreamBuffer; - -class VertexManager : public VertexManagerBase -{ -public: - VertexManager(); - ~VertexManager(); - - bool Initialize() override; - - void UploadUtilityUniforms(const void* uniforms, u32 uniforms_size) override; - bool UploadTexelBuffer(const void* data, u32 data_size, TexelBufferFormat format, - u32* out_offset) override; - bool UploadTexelBuffer(const void* data, u32 data_size, TexelBufferFormat format, u32* out_offset, - const void* palette_data, u32 palette_size, - TexelBufferFormat palette_format, u32* out_palette_offset) override; - -protected: - void ResetBuffer(u32 vertex_stride) override; - void CommitBuffer(u32 num_vertices, u32 vertex_stride, u32 num_indices, u32* out_base_vertex, - u32* out_base_index) override; - void UploadUniforms() override; - - void DestroyTexelBufferViews(); - - void UpdateVertexShaderConstants(); - void UpdateGeometryShaderConstants(); - void UpdatePixelShaderConstants(); - - // Allocates storage in the uniform buffer of the specified size. If this storage cannot be - // allocated immediately, the current command buffer will be submitted and all stage's - // constants will be re-uploaded. false will be returned in this case, otherwise true. - bool ReserveConstantStorage(); - void UploadAllConstants(); - - std::unique_ptr m_vertex_stream_buffer; - std::unique_ptr m_index_stream_buffer; - std::unique_ptr m_uniform_stream_buffer; - std::unique_ptr m_texel_stream_buffer; - std::array m_texel_buffer_views = {}; - u32 m_uniform_buffer_reserve_size = 0; -}; -} // namespace Vulkan diff --git a/Source/Core/VideoBackends/Vulkan/Vulkan.vcxproj b/Source/Core/VideoBackends/Vulkan/Vulkan.vcxproj index f92f3678a9..8ea2daa54c 100644 --- a/Source/Core/VideoBackends/Vulkan/Vulkan.vcxproj +++ b/Source/Core/VideoBackends/Vulkan/Vulkan.vcxproj @@ -16,43 +16,43 @@ - - - - - - + - - - + + + + + + + + - - - - - + - - - + + + + + + + diff --git a/Source/Core/VideoBackends/Vulkan/main.cpp b/Source/Core/VideoBackends/Vulkan/main.cpp deleted file mode 100644 index b3b3008006..0000000000 --- a/Source/Core/VideoBackends/Vulkan/main.cpp +++ /dev/null @@ -1,361 +0,0 @@ -// Copyright 2016 Dolphin Emulator Project -// Licensed under GPLv2+ -// Refer to the license.txt file included. - -#include - -#include "Common/Logging/LogManager.h" -#include "Common/MsgHandler.h" - -#include "VideoBackends/Vulkan/CommandBufferManager.h" -#include "VideoBackends/Vulkan/Constants.h" -#include "VideoBackends/Vulkan/ObjectCache.h" -#include "VideoBackends/Vulkan/PerfQuery.h" -#include "VideoBackends/Vulkan/Renderer.h" -#include "VideoBackends/Vulkan/StateTracker.h" -#include "VideoBackends/Vulkan/SwapChain.h" -#include "VideoBackends/Vulkan/VertexManager.h" -#include "VideoBackends/Vulkan/VideoBackend.h" -#include "VideoBackends/Vulkan/VulkanContext.h" - -#include "VideoCommon/FramebufferManager.h" -#include "VideoCommon/TextureCacheBase.h" -#include "VideoCommon/VideoBackendBase.h" -#include "VideoCommon/VideoConfig.h" - -#if defined(VK_USE_PLATFORM_METAL_EXT) -#include -#endif - -namespace Vulkan -{ -void VideoBackend::InitBackendInfo() -{ - VulkanContext::PopulateBackendInfo(&g_Config); - - if (LoadVulkanLibrary()) - { - VkInstance temp_instance = - VulkanContext::CreateVulkanInstance(WindowSystemType::Headless, false, false); - if (temp_instance) - { - if (LoadVulkanInstanceFunctions(temp_instance)) - { - VulkanContext::GPUList gpu_list = VulkanContext::EnumerateGPUs(temp_instance); - VulkanContext::PopulateBackendInfoAdapters(&g_Config, gpu_list); - - if (!gpu_list.empty()) - { - // Use the selected adapter, or the first to fill features. - size_t device_index = static_cast(g_Config.iAdapter); - if (device_index >= gpu_list.size()) - device_index = 0; - - VkPhysicalDevice gpu = gpu_list[device_index]; - VkPhysicalDeviceProperties properties; - vkGetPhysicalDeviceProperties(gpu, &properties); - VkPhysicalDeviceFeatures features; - vkGetPhysicalDeviceFeatures(gpu, &features); - VulkanContext::PopulateBackendInfoFeatures(&g_Config, gpu, properties, features); - VulkanContext::PopulateBackendInfoMultisampleModes(&g_Config, gpu, properties); - } - } - - vkDestroyInstance(temp_instance, nullptr); - } - else - { - PanicAlertFmt("Failed to create Vulkan instance."); - } - - UnloadVulkanLibrary(); - } - else - { - PanicAlertFmt("Failed to load Vulkan library."); - } -} - -// Helper method to check whether the Host GPU logging category is enabled. -static bool IsHostGPULoggingEnabled() -{ - return Common::Log::LogManager::GetInstance()->IsEnabled(Common::Log::HOST_GPU, - Common::Log::LERROR); -} - -// Helper method to determine whether to enable the debug report extension. -static bool ShouldEnableDebugReports(bool enable_validation_layers) -{ - // Enable debug reports if the Host GPU log option is checked, or validation layers are enabled. - // The only issue here is that if Host GPU is not checked when the instance is created, the debug - // report extension will not be enabled, requiring the game to be restarted before any reports - // will be logged. Otherwise, we'd have to enable debug reports on every instance, when most - // users will never check the Host GPU logging category. - return enable_validation_layers || IsHostGPULoggingEnabled(); -} - -bool VideoBackend::Initialize(const WindowSystemInfo& wsi) -{ - if (!LoadVulkanLibrary()) - { - PanicAlertFmt("Failed to load Vulkan library."); - return false; - } - - // Check for presence of the validation layers before trying to enable it - bool enable_validation_layer = g_Config.bEnableValidationLayer; - if (enable_validation_layer && !VulkanContext::CheckValidationLayerAvailablility()) - { - WARN_LOG_FMT(VIDEO, "Validation layer requested but not available, disabling."); - enable_validation_layer = false; - } - - // Create Vulkan instance, needed before we can create a surface, or enumerate devices. - // We use this instance to fill in backend info, then re-use it for the actual device. - bool enable_surface = wsi.type != WindowSystemType::Headless; - bool enable_debug_reports = ShouldEnableDebugReports(enable_validation_layer); - VkInstance instance = - VulkanContext::CreateVulkanInstance(wsi.type, enable_debug_reports, enable_validation_layer); - if (instance == VK_NULL_HANDLE) - { - PanicAlertFmt("Failed to create Vulkan instance."); - UnloadVulkanLibrary(); - return false; - } - - // Load instance function pointers. - if (!LoadVulkanInstanceFunctions(instance)) - { - PanicAlertFmt("Failed to load Vulkan instance functions."); - vkDestroyInstance(instance, nullptr); - UnloadVulkanLibrary(); - return false; - } - - // Obtain a list of physical devices (GPUs) from the instance. - // We'll re-use this list later when creating the device. - VulkanContext::GPUList gpu_list = VulkanContext::EnumerateGPUs(instance); - if (gpu_list.empty()) - { - PanicAlertFmt("No Vulkan physical devices available."); - vkDestroyInstance(instance, nullptr); - UnloadVulkanLibrary(); - return false; - } - - // Populate BackendInfo with as much information as we can at this point. - VulkanContext::PopulateBackendInfo(&g_Config); - VulkanContext::PopulateBackendInfoAdapters(&g_Config, gpu_list); - - // We need the surface before we can create a device, as some parameters depend on it. - VkSurfaceKHR surface = VK_NULL_HANDLE; - if (enable_surface) - { - surface = SwapChain::CreateVulkanSurface(instance, wsi); - if (surface == VK_NULL_HANDLE) - { - PanicAlertFmt("Failed to create Vulkan surface."); - vkDestroyInstance(instance, nullptr); - UnloadVulkanLibrary(); - return false; - } - } - - // Since we haven't called InitializeShared yet, iAdapter may be out of range, - // so we have to check it ourselves. - size_t selected_adapter_index = static_cast(g_Config.iAdapter); - if (selected_adapter_index >= gpu_list.size()) - { - WARN_LOG_FMT(VIDEO, "Vulkan adapter index out of range, selecting first adapter."); - selected_adapter_index = 0; - } - - // Now we can create the Vulkan device. VulkanContext takes ownership of the instance and surface. - g_vulkan_context = VulkanContext::Create(instance, gpu_list[selected_adapter_index], surface, - enable_debug_reports, enable_validation_layer); - if (!g_vulkan_context) - { - PanicAlertFmt("Failed to create Vulkan device"); - UnloadVulkanLibrary(); - return false; - } - - // Since VulkanContext maintains a copy of the device features and properties, we can use this - // to initialize the backend information, so that we don't need to enumerate everything again. - VulkanContext::PopulateBackendInfoFeatures(&g_Config, g_vulkan_context->GetPhysicalDevice(), - g_vulkan_context->GetDeviceProperties(), - g_vulkan_context->GetDeviceFeatures()); - VulkanContext::PopulateBackendInfoMultisampleModes( - &g_Config, g_vulkan_context->GetPhysicalDevice(), g_vulkan_context->GetDeviceProperties()); - g_Config.backend_info.bSupportsExclusiveFullscreen = - enable_surface && g_vulkan_context->SupportsExclusiveFullscreen(wsi, surface); - - // With the backend information populated, we can now initialize videocommon. - InitializeShared(); - - // Create command buffers. We do this separately because the other classes depend on it. - g_command_buffer_mgr = std::make_unique(g_Config.bBackendMultithreading); - if (!g_command_buffer_mgr->Initialize()) - { - PanicAlertFmt("Failed to create Vulkan command buffers"); - Shutdown(); - return false; - } - - // Remaining classes are also dependent on object cache. - g_object_cache = std::make_unique(); - if (!g_object_cache->Initialize()) - { - PanicAlertFmt("Failed to initialize Vulkan object cache."); - Shutdown(); - return false; - } - - // Create swap chain. This has to be done early so that the target size is correct for auto-scale. - std::unique_ptr swap_chain; - if (surface != VK_NULL_HANDLE) - { - swap_chain = SwapChain::Create(wsi, surface, g_ActiveConfig.bVSyncActive); - if (!swap_chain) - { - PanicAlertFmt("Failed to create Vulkan swap chain."); - Shutdown(); - return false; - } - } - - if (!StateTracker::CreateInstance()) - { - PanicAlertFmt("Failed to create state tracker"); - Shutdown(); - return false; - } - - // Create main wrapper instances. - g_renderer = std::make_unique(std::move(swap_chain), wsi.render_surface_scale); - g_vertex_manager = std::make_unique(); - g_shader_cache = std::make_unique(); - g_framebuffer_manager = std::make_unique(); - g_texture_cache = std::make_unique(); - g_perf_query = std::make_unique(); - - if (!g_vertex_manager->Initialize() || !g_shader_cache->Initialize() || - !g_renderer->Initialize() || !g_framebuffer_manager->Initialize() || - !g_texture_cache->Initialize() || !PerfQuery::GetInstance()->Initialize()) - { - PanicAlertFmt("Failed to initialize renderer classes"); - Shutdown(); - return false; - } - - g_shader_cache->InitializeShaderCache(); - return true; -} - -void VideoBackend::Shutdown() -{ - if (g_vulkan_context) - vkDeviceWaitIdle(g_vulkan_context->GetDevice()); - - if (g_shader_cache) - g_shader_cache->Shutdown(); - - if (g_object_cache) - g_object_cache->Shutdown(); - - if (g_renderer) - g_renderer->Shutdown(); - - g_perf_query.reset(); - g_texture_cache.reset(); - g_framebuffer_manager.reset(); - g_shader_cache.reset(); - g_vertex_manager.reset(); - g_renderer.reset(); - g_object_cache.reset(); - StateTracker::DestroyInstance(); - g_command_buffer_mgr.reset(); - g_vulkan_context.reset(); - ShutdownShared(); - UnloadVulkanLibrary(); -} - -#if defined(VK_USE_PLATFORM_METAL_EXT) -static bool IsRunningOnMojaveOrHigher() -{ - // id processInfo = [NSProcessInfo processInfo] - id processInfo = reinterpret_cast(objc_msgSend)( - objc_getClass("NSProcessInfo"), sel_getUid("processInfo")); - if (!processInfo) - return false; - - struct OSVersion // NSOperatingSystemVersion - { - size_t major_version; // NSInteger majorVersion - size_t minor_version; // NSInteger minorVersion - size_t patch_version; // NSInteger patchVersion - }; - - // const bool meets_requirement = [processInfo isOperatingSystemAtLeastVersion:required_version]; - constexpr OSVersion required_version = {10, 14, 0}; - const bool meets_requirement = reinterpret_cast(objc_msgSend)( - processInfo, sel_getUid("isOperatingSystemAtLeastVersion:"), required_version); - return meets_requirement; -} -#endif - -void VideoBackend::PrepareWindow(WindowSystemInfo& wsi) -{ -#if defined(VK_USE_PLATFORM_METAL_EXT) - // This is kinda messy, but it avoids having to write Objective C++ just to create a metal layer. - id view = reinterpret_cast(wsi.render_surface); - Class clsCAMetalLayer = objc_getClass("CAMetalLayer"); - if (!clsCAMetalLayer) - { - ERROR_LOG_FMT(VIDEO, "Failed to get CAMetalLayer class."); - return; - } - - // [CAMetalLayer layer] - id layer = reinterpret_cast(objc_msgSend)(objc_getClass("CAMetalLayer"), - sel_getUid("layer")); - if (!layer) - { - ERROR_LOG_FMT(VIDEO, "Failed to create Metal layer."); - return; - } - - // [view setWantsLayer:YES] - reinterpret_cast(objc_msgSend)(view, sel_getUid("setWantsLayer:"), YES); - - // [view setLayer:layer] - reinterpret_cast(objc_msgSend)(view, sel_getUid("setLayer:"), layer); - - // NSScreen* screen = [NSScreen mainScreen] - id screen = reinterpret_cast(objc_msgSend)(objc_getClass("NSScreen"), - sel_getUid("mainScreen")); - - // CGFloat factor = [screen backingScaleFactor] - double factor = - reinterpret_cast(objc_msgSend)(screen, sel_getUid("backingScaleFactor")); - - // layer.contentsScale = factor - reinterpret_cast(objc_msgSend)(layer, sel_getUid("setContentsScale:"), - factor); - - // Store the layer pointer, that way MoltenVK doesn't call [NSView layer] outside the main thread. - wsi.render_surface = layer; - - // The Metal version included with MacOS 10.13 and below does not support several features we - // require. Furthermore, the drivers seem to choke on our shaders (mainly Intel). So, we warn - // the user that this is an unsupported configuration, but permit them to continue. - if (!IsRunningOnMojaveOrHigher()) - { - PanicAlertFmtT( - "You are attempting to use the Vulkan (Metal) backend on an unsupported operating system. " - "For all functionality to be enabled, you must use macOS 10.14 (Mojave) or newer. Please " - "do not report any issues encountered unless they also occur on 10.14+."); - } -#endif -} -} // namespace Vulkan -- cgit v1.2.3