Skip to content

Commit

Permalink
Implement experimental Vulkan backend
Browse files Browse the repository at this point in the history
  • Loading branch information
stenzek committed Sep 30, 2016
1 parent fdd954e commit 77a128a
Show file tree
Hide file tree
Showing 59 changed files with 14,533 additions and 1 deletion.
1 change: 1 addition & 0 deletions Source/Core/Core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ set(LIBS
sfml-network
sfml-system
videonull
videovulkan
videoogl
videosoftware
z
Expand Down
3 changes: 3 additions & 0 deletions Source/Core/DolphinQt2/DolphinQt2.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,9 @@
<ProjectReference Include="..\VideoBackends\D3D12\D3D12.vcxproj">
<Project>{570215b7-e32f-4438-95ae-c8d955f9fca3}</Project>
</ProjectReference>
<ProjectReference Include="..\VideoBackends\Vulkan\Vulkan.vcxproj">
<Project>{29f29a19-f141-45ad-9679-5a2923b49da3}</Project>
</ProjectReference>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
Expand Down
3 changes: 3 additions & 0 deletions Source/Core/DolphinWX/DolphinWX.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,9 @@
<ProjectReference Include="$(CoreDir)VideoBackends\Null\Null.vcxproj">
<Project>{53A5391B-737E-49A8-BC8F-312ADA00736F}</Project>
</ProjectReference>
<ProjectReference Include="$(CoreDir)VideoBackends\Vulkan\Vulkan.vcxproj">
<Project>{29F29A19-F141-45AD-9679-5A2923B49DA3}</Project>
</ProjectReference>
<ProjectReference Include="$(CoreDir)VideoCommon\VideoCommon.vcxproj">
<Project>{3de9ee35-3e91-4f27-a014-2866ad8c3fe3}</Project>
</ProjectReference>
Expand Down
1 change: 1 addition & 0 deletions Source/Core/VideoBackends/CMakeLists.txt
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!
249 changes: 249 additions & 0 deletions Source/Core/VideoBackends/Vulkan/BoundingBox.cpp
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), &current_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, &region);

// 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
52 changes: 52 additions & 0 deletions Source/Core/VideoBackends/Vulkan/BoundingBox.h
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
42 changes: 42 additions & 0 deletions Source/Core/VideoBackends/Vulkan/CMakeLists.txt
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)

Loading

0 comments on commit 77a128a

Please sign in to comment.