forked from dolphin-emu/dolphin
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement experimental Vulkan backend
- Loading branch information
Showing
59 changed files
with
14,533 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -233,6 +233,7 @@ set(LIBS | |
sfml-network | ||
sfml-system | ||
videonull | ||
videovulkan | ||
videoogl | ||
videosoftware | ||
z | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
add_subdirectory(OGL) | ||
add_subdirectory(Null) | ||
add_subdirectory(Software) | ||
add_subdirectory(Vulkan) | ||
# TODO: Add other backends here! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,249 @@ | ||
// Copyright 2016 Dolphin Emulator Project | ||
// Licensed under GPLv2+ | ||
// Refer to the license.txt file included. | ||
|
||
#include <vector> | ||
|
||
#include "Common/Assert.h" | ||
|
||
#include "VideoBackends/Vulkan/BoundingBox.h" | ||
#include "VideoBackends/Vulkan/CommandBufferManager.h" | ||
#include "VideoBackends/Vulkan/ObjectCache.h" | ||
#include "VideoBackends/Vulkan/StagingBuffer.h" | ||
#include "VideoBackends/Vulkan/StateTracker.h" | ||
#include "VideoBackends/Vulkan/Util.h" | ||
#include "VideoBackends/Vulkan/VulkanContext.h" | ||
|
||
namespace Vulkan | ||
{ | ||
BoundingBox::BoundingBox() | ||
{ | ||
} | ||
|
||
BoundingBox::~BoundingBox() | ||
{ | ||
if (m_gpu_buffer != VK_NULL_HANDLE) | ||
{ | ||
vkDestroyBuffer(g_vulkan_context->GetDevice(), m_gpu_buffer, nullptr); | ||
vkFreeMemory(g_vulkan_context->GetDevice(), m_gpu_memory, nullptr); | ||
} | ||
} | ||
|
||
bool BoundingBox::Initialize() | ||
{ | ||
if (!g_vulkan_context->SupportsBoundingBox()) | ||
{ | ||
WARN_LOG(VIDEO, "Vulkan: Bounding box is unsupported by your device."); | ||
return true; | ||
} | ||
|
||
if (!CreateGPUBuffer()) | ||
return false; | ||
|
||
if (!CreateReadbackBuffer()) | ||
return false; | ||
|
||
return true; | ||
} | ||
|
||
void BoundingBox::Flush(StateTracker* state_tracker) | ||
{ | ||
if (m_gpu_buffer == VK_NULL_HANDLE) | ||
return; | ||
|
||
// Combine updates together, chances are the game would have written all 4. | ||
bool updated_buffer = false; | ||
for (size_t start = 0; start < 4; start++) | ||
{ | ||
if (!m_values_dirty[start]) | ||
continue; | ||
|
||
size_t count = 0; | ||
std::array<s32, 4> write_values; | ||
for (; (start + count) < 4; count++) | ||
{ | ||
if (!m_values_dirty[start + count]) | ||
break; | ||
|
||
m_readback_buffer->Read((start + count) * sizeof(s32), &write_values[count], sizeof(s32), | ||
false); | ||
m_values_dirty[start + count] = false; | ||
} | ||
|
||
// We can't issue vkCmdUpdateBuffer within a render pass. | ||
// However, the writes must be serialized, so we can't put it in the init buffer. | ||
if (!updated_buffer) | ||
{ | ||
state_tracker->EndRenderPass(); | ||
|
||
// Ensure GPU buffer is in a state where it can be transferred to. | ||
Util::BufferMemoryBarrier( | ||
g_command_buffer_mgr->GetCurrentCommandBuffer(), m_gpu_buffer, | ||
VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, 0, | ||
BUFFER_SIZE, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT); | ||
|
||
updated_buffer = true; | ||
} | ||
|
||
vkCmdUpdateBuffer(g_command_buffer_mgr->GetCurrentCommandBuffer(), m_gpu_buffer, | ||
start * sizeof(s32), count * sizeof(s32), | ||
reinterpret_cast<const u32*>(write_values.data())); | ||
} | ||
|
||
// Restore fragment shader access to the buffer. | ||
if (updated_buffer) | ||
{ | ||
Util::BufferMemoryBarrier( | ||
g_command_buffer_mgr->GetCurrentCommandBuffer(), m_gpu_buffer, VK_ACCESS_TRANSFER_WRITE_BIT, | ||
VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, 0, BUFFER_SIZE, | ||
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT); | ||
} | ||
|
||
// We're now up-to-date. | ||
m_valid = true; | ||
} | ||
|
||
void BoundingBox::Invalidate(StateTracker* state_tracker) | ||
{ | ||
if (m_gpu_buffer == VK_NULL_HANDLE) | ||
return; | ||
|
||
m_valid = false; | ||
} | ||
|
||
s32 BoundingBox::Get(StateTracker* state_tracker, size_t index) | ||
{ | ||
_assert_(index < NUM_VALUES); | ||
|
||
if (!m_valid) | ||
Readback(state_tracker); | ||
|
||
s32 value; | ||
m_readback_buffer->Read(index * sizeof(s32), &value, sizeof(value), false); | ||
return value; | ||
} | ||
|
||
void BoundingBox::Set(StateTracker* state_tracker, size_t index, s32 value) | ||
{ | ||
_assert_(index < NUM_VALUES); | ||
|
||
// If we're currently valid, update the stored value in both our cache and the GPU buffer. | ||
if (m_valid) | ||
{ | ||
// Skip when it hasn't changed. | ||
s32 current_value; | ||
m_readback_buffer->Read(index * sizeof(s32), ¤t_value, sizeof(current_value), false); | ||
if (current_value == value) | ||
return; | ||
} | ||
|
||
// Flag as dirty, and update values. | ||
m_readback_buffer->Write(index * sizeof(s32), &value, sizeof(value), true); | ||
m_values_dirty[index] = true; | ||
} | ||
|
||
bool BoundingBox::CreateGPUBuffer() | ||
{ | ||
VkBufferUsageFlags buffer_usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | | ||
VK_BUFFER_USAGE_TRANSFER_SRC_BIT | | ||
VK_BUFFER_USAGE_TRANSFER_DST_BIT; | ||
VkBufferCreateInfo info = { | ||
VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, // VkStructureType sType | ||
nullptr, // const void* pNext | ||
0, // VkBufferCreateFlags flags | ||
BUFFER_SIZE, // VkDeviceSize size | ||
buffer_usage, // VkBufferUsageFlags usage | ||
VK_SHARING_MODE_EXCLUSIVE, // VkSharingMode sharingMode | ||
0, // uint32_t queueFamilyIndexCount | ||
nullptr // const uint32_t* pQueueFamilyIndices | ||
}; | ||
|
||
VkBuffer buffer; | ||
VkResult res = vkCreateBuffer(g_vulkan_context->GetDevice(), &info, nullptr, &buffer); | ||
if (res != VK_SUCCESS) | ||
{ | ||
LOG_VULKAN_ERROR(res, "vkCreateBuffer failed: "); | ||
return false; | ||
} | ||
|
||
VkMemoryRequirements memory_requirements; | ||
vkGetBufferMemoryRequirements(g_vulkan_context->GetDevice(), buffer, &memory_requirements); | ||
|
||
uint32_t memory_type_index = g_vulkan_context->GetMemoryType(memory_requirements.memoryTypeBits, | ||
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); | ||
VkMemoryAllocateInfo memory_allocate_info = { | ||
VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, // VkStructureType sType | ||
nullptr, // const void* pNext | ||
memory_requirements.size, // VkDeviceSize allocationSize | ||
memory_type_index // uint32_t memoryTypeIndex | ||
}; | ||
VkDeviceMemory memory; | ||
res = vkAllocateMemory(g_vulkan_context->GetDevice(), &memory_allocate_info, nullptr, &memory); | ||
if (res != VK_SUCCESS) | ||
{ | ||
LOG_VULKAN_ERROR(res, "vkAllocateMemory failed: "); | ||
vkDestroyBuffer(g_vulkan_context->GetDevice(), buffer, nullptr); | ||
return false; | ||
} | ||
|
||
res = vkBindBufferMemory(g_vulkan_context->GetDevice(), buffer, memory, 0); | ||
if (res != VK_SUCCESS) | ||
{ | ||
LOG_VULKAN_ERROR(res, "vkBindBufferMemory failed: "); | ||
vkDestroyBuffer(g_vulkan_context->GetDevice(), buffer, nullptr); | ||
vkFreeMemory(g_vulkan_context->GetDevice(), memory, nullptr); | ||
return false; | ||
} | ||
|
||
m_gpu_buffer = buffer; | ||
m_gpu_memory = memory; | ||
return true; | ||
} | ||
|
||
bool BoundingBox::CreateReadbackBuffer() | ||
{ | ||
m_readback_buffer = StagingBuffer::Create(STAGING_BUFFER_TYPE_READBACK, BUFFER_SIZE, | ||
VK_BUFFER_USAGE_TRANSFER_DST_BIT); | ||
|
||
if (!m_readback_buffer || !m_readback_buffer->Map()) | ||
return false; | ||
|
||
return true; | ||
} | ||
|
||
void BoundingBox::Readback(StateTracker* state_tracker) | ||
{ | ||
// Can't be done within a render pass. | ||
state_tracker->EndRenderPass(); | ||
|
||
// Ensure all writes are completed to the GPU buffer prior to the transfer. | ||
Util::BufferMemoryBarrier( | ||
g_command_buffer_mgr->GetCurrentCommandBuffer(), m_gpu_buffer, | ||
VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT, 0, | ||
BUFFER_SIZE, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT); | ||
m_readback_buffer->PrepareForGPUWrite(g_command_buffer_mgr->GetCurrentCommandBuffer(), | ||
VK_ACCESS_TRANSFER_WRITE_BIT, | ||
VK_PIPELINE_STAGE_TRANSFER_BIT); | ||
|
||
// Copy from GPU -> readback buffer. | ||
VkBufferCopy region = {0, 0, BUFFER_SIZE}; | ||
vkCmdCopyBuffer(g_command_buffer_mgr->GetCurrentCommandBuffer(), m_gpu_buffer, | ||
m_readback_buffer->GetBuffer(), 1, ®ion); | ||
|
||
// Restore GPU buffer access. | ||
Util::BufferMemoryBarrier(g_command_buffer_mgr->GetCurrentCommandBuffer(), m_gpu_buffer, | ||
VK_ACCESS_TRANSFER_READ_BIT, | ||
VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, 0, BUFFER_SIZE, | ||
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT); | ||
m_readback_buffer->FlushGPUCache(g_command_buffer_mgr->GetCurrentCommandBuffer(), | ||
VK_ACCESS_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT); | ||
|
||
// Wait until these commands complete. | ||
Util::ExecuteCurrentCommandsAndRestoreState(state_tracker, false, true); | ||
|
||
// Cache is now valid. | ||
m_readback_buffer->InvalidateCPUCache(); | ||
m_valid = true; | ||
} | ||
|
||
} // namespace Vulkan |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
// Copyright 2016 Dolphin Emulator Project | ||
// Licensed under GPLv2+ | ||
// Refer to the license.txt file included. | ||
|
||
#pragma once | ||
|
||
#include <memory> | ||
#include <string> | ||
|
||
#include "Common/CommonTypes.h" | ||
|
||
#include "VideoBackends/Vulkan/VulkanLoader.h" | ||
|
||
namespace Vulkan | ||
{ | ||
class StagingBuffer; | ||
class StateTracker; | ||
|
||
class BoundingBox | ||
{ | ||
public: | ||
BoundingBox(); | ||
~BoundingBox(); | ||
|
||
bool Initialize(); | ||
|
||
VkBuffer GetGPUBuffer() const { return m_gpu_buffer; } | ||
VkDeviceSize GetGPUBufferOffset() const { return 0; } | ||
VkDeviceSize GetGPUBufferSize() const { return BUFFER_SIZE; } | ||
s32 Get(StateTracker* state_tracker, size_t index); | ||
void Set(StateTracker* state_tracker, size_t index, s32 value); | ||
|
||
void Invalidate(StateTracker* state_tracker); | ||
void Flush(StateTracker* state_tracker); | ||
|
||
private: | ||
bool CreateGPUBuffer(); | ||
bool CreateReadbackBuffer(); | ||
void Readback(StateTracker* state_tracker); | ||
|
||
VkBuffer m_gpu_buffer = VK_NULL_HANDLE; | ||
VkDeviceMemory m_gpu_memory = nullptr; | ||
|
||
static const size_t NUM_VALUES = 4; | ||
static const size_t BUFFER_SIZE = sizeof(u32) * NUM_VALUES; | ||
|
||
std::unique_ptr<StagingBuffer> m_readback_buffer; | ||
std::array<bool, NUM_VALUES> m_values_dirty = {}; | ||
bool m_valid = true; | ||
}; | ||
|
||
} // namespace Vulkan |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
set(SRCS | ||
BoundingBox.cpp | ||
CommandBufferManager.cpp | ||
FramebufferManager.cpp | ||
ObjectCache.cpp | ||
PaletteTextureConverter.cpp | ||
PerfQuery.cpp | ||
RasterFont.cpp | ||
Renderer.cpp | ||
ShaderCompiler.cpp | ||
StateTracker.cpp | ||
StagingBuffer.cpp | ||
StagingTexture2D.cpp | ||
StreamBuffer.cpp | ||
SwapChain.cpp | ||
Texture2D.cpp | ||
TextureCache.cpp | ||
TextureEncoder.cpp | ||
Util.cpp | ||
VertexFormat.cpp | ||
VertexManager.cpp | ||
VulkanContext.cpp | ||
VulkanLoader.cpp | ||
main.cpp | ||
) | ||
|
||
set(LIBS | ||
videocommon | ||
common | ||
) | ||
|
||
# Only include the Vulkan headers when building the Vulkan backend | ||
include_directories(${CMAKE_SOURCE_DIR}/Externals/Vulkan/Include) | ||
|
||
# Silence warnings on glslang by flagging it as a system include | ||
include_directories(SYSTEM ${CMAKE_SOURCE_DIR}/Externals/glslang/glslang/Public) | ||
include_directories(SYSTEM ${CMAKE_SOURCE_DIR}/Externals/glslang/SPIRV) | ||
|
||
# Link against glslang, the other necessary libraries are referenced by the executable. | ||
add_dolphin_library(videovulkan "${SRCS}" "${LIBS}") | ||
target_link_libraries(videovulkan glslang) | ||
|
Oops, something went wrong.