mirror of
https://github.com/izzy2lost/dolphin.git
synced 2026-06-19 01:16:48 -07:00
Implement experimental Vulkan backend
This commit is contained in:
@@ -233,6 +233,7 @@ set(LIBS
|
||||
sfml-network
|
||||
sfml-system
|
||||
videonull
|
||||
videovulkan
|
||||
videoogl
|
||||
videosoftware
|
||||
z
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
add_subdirectory(OGL)
|
||||
add_subdirectory(Null)
|
||||
add_subdirectory(Software)
|
||||
add_subdirectory(Vulkan)
|
||||
# TODO: Add other backends here!
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -0,0 +1,457 @@
|
||||
// Copyright 2016 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
|
||||
#include "Common/Assert.h"
|
||||
#include "Common/CommonFuncs.h"
|
||||
#include "Common/MsgHandler.h"
|
||||
|
||||
#include "VideoBackends/Vulkan/CommandBufferManager.h"
|
||||
#include "VideoBackends/Vulkan/VulkanContext.h"
|
||||
|
||||
namespace Vulkan
|
||||
{
|
||||
CommandBufferManager::CommandBufferManager(bool use_threaded_submission)
|
||||
: m_submit_semaphore(1, 1), m_use_threaded_submission(use_threaded_submission)
|
||||
{
|
||||
}
|
||||
|
||||
CommandBufferManager::~CommandBufferManager()
|
||||
{
|
||||
// If the worker thread is enabled, wait for it to exit.
|
||||
if (m_use_threaded_submission)
|
||||
{
|
||||
// Wait for all command buffers to be consumed by the worker thread.
|
||||
m_submit_semaphore.Wait();
|
||||
m_submit_loop->Stop();
|
||||
m_submit_thread.join();
|
||||
}
|
||||
|
||||
vkDeviceWaitIdle(g_vulkan_context->GetDevice());
|
||||
|
||||
DestroyCommandBuffers();
|
||||
DestroyCommandPool();
|
||||
}
|
||||
|
||||
bool CommandBufferManager::Initialize()
|
||||
{
|
||||
if (!CreateCommandPool())
|
||||
return false;
|
||||
|
||||
if (!CreateCommandBuffers())
|
||||
return false;
|
||||
|
||||
if (m_use_threaded_submission && !CreateSubmitThread())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CommandBufferManager::CreateCommandPool()
|
||||
{
|
||||
VkCommandPoolCreateInfo info = {VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, nullptr,
|
||||
VK_COMMAND_POOL_CREATE_TRANSIENT_BIT |
|
||||
VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
|
||||
g_vulkan_context->GetGraphicsQueueFamilyIndex()};
|
||||
|
||||
VkResult res =
|
||||
vkCreateCommandPool(g_vulkan_context->GetDevice(), &info, nullptr, &m_command_pool);
|
||||
if (res != VK_SUCCESS)
|
||||
{
|
||||
LOG_VULKAN_ERROR(res, "vkCreateCommandPool failed: ");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CommandBufferManager::DestroyCommandPool()
|
||||
{
|
||||
if (m_command_pool)
|
||||
{
|
||||
vkDestroyCommandPool(g_vulkan_context->GetDevice(), m_command_pool, nullptr);
|
||||
m_command_pool = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool CommandBufferManager::CreateCommandBuffers()
|
||||
{
|
||||
VkDevice device = g_vulkan_context->GetDevice();
|
||||
|
||||
for (FrameResources& resources : m_frame_resources)
|
||||
{
|
||||
resources.needs_fence_wait = false;
|
||||
|
||||
VkCommandBufferAllocateInfo allocate_info = {
|
||||
VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, nullptr, m_command_pool,
|
||||
VK_COMMAND_BUFFER_LEVEL_PRIMARY, static_cast<uint32_t>(resources.command_buffers.size())};
|
||||
|
||||
VkResult res =
|
||||
vkAllocateCommandBuffers(device, &allocate_info, resources.command_buffers.data());
|
||||
if (res != VK_SUCCESS)
|
||||
{
|
||||
LOG_VULKAN_ERROR(res, "vkAllocateCommandBuffers failed: ");
|
||||
return false;
|
||||
}
|
||||
|
||||
VkFenceCreateInfo fence_info = {VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, nullptr,
|
||||
VK_FENCE_CREATE_SIGNALED_BIT};
|
||||
|
||||
res = vkCreateFence(device, &fence_info, nullptr, &resources.fence);
|
||||
if (res != VK_SUCCESS)
|
||||
{
|
||||
LOG_VULKAN_ERROR(res, "vkCreateFence failed: ");
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: A better way to choose the number of descriptors.
|
||||
VkDescriptorPoolSize pool_sizes[] = {{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 500000},
|
||||
{VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 500000},
|
||||
{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 16},
|
||||
{VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1024}};
|
||||
|
||||
VkDescriptorPoolCreateInfo pool_create_info = {VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
|
||||
nullptr,
|
||||
0,
|
||||
100000, // tweak this
|
||||
static_cast<u32>(ArraySize(pool_sizes)),
|
||||
pool_sizes};
|
||||
|
||||
res = vkCreateDescriptorPool(device, &pool_create_info, nullptr, &resources.descriptor_pool);
|
||||
if (res != VK_SUCCESS)
|
||||
{
|
||||
LOG_VULKAN_ERROR(res, "vkCreateDescriptorPool failed: ");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Activate the first command buffer. ActivateCommandBuffer moves forward, so start with the last
|
||||
m_current_frame = m_frame_resources.size() - 1;
|
||||
ActivateCommandBuffer();
|
||||
return true;
|
||||
}
|
||||
|
||||
void CommandBufferManager::DestroyCommandBuffers()
|
||||
{
|
||||
VkDevice device = g_vulkan_context->GetDevice();
|
||||
|
||||
for (FrameResources& resources : m_frame_resources)
|
||||
{
|
||||
for (const auto& it : resources.cleanup_resources)
|
||||
it.destroy_callback(device, it.object);
|
||||
resources.cleanup_resources.clear();
|
||||
|
||||
if (resources.fence != VK_NULL_HANDLE)
|
||||
{
|
||||
vkDestroyFence(device, resources.fence, nullptr);
|
||||
resources.fence = VK_NULL_HANDLE;
|
||||
}
|
||||
if (resources.descriptor_pool != VK_NULL_HANDLE)
|
||||
{
|
||||
vkDestroyDescriptorPool(device, resources.descriptor_pool, nullptr);
|
||||
resources.descriptor_pool = VK_NULL_HANDLE;
|
||||
}
|
||||
if (resources.command_buffers[0] != VK_NULL_HANDLE)
|
||||
{
|
||||
vkFreeCommandBuffers(device, m_command_pool,
|
||||
static_cast<u32>(resources.command_buffers.size()),
|
||||
resources.command_buffers.data());
|
||||
|
||||
resources.command_buffers.fill(VK_NULL_HANDLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VkDescriptorSet CommandBufferManager::AllocateDescriptorSet(VkDescriptorSetLayout set_layout)
|
||||
{
|
||||
VkDescriptorSetAllocateInfo allocate_info = {
|
||||
VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, nullptr,
|
||||
m_frame_resources[m_current_frame].descriptor_pool, 1, &set_layout};
|
||||
|
||||
VkDescriptorSet descriptor_set;
|
||||
VkResult res =
|
||||
vkAllocateDescriptorSets(g_vulkan_context->GetDevice(), &allocate_info, &descriptor_set);
|
||||
if (res != VK_SUCCESS)
|
||||
{
|
||||
// Failing to allocate a descriptor set is not a fatal error, we can
|
||||
// recover by moving to the next command buffer.
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
return descriptor_set;
|
||||
}
|
||||
|
||||
bool CommandBufferManager::CreateSubmitThread()
|
||||
{
|
||||
m_submit_loop = std::make_unique<Common::BlockingLoop>();
|
||||
m_submit_thread = std::thread([this]() {
|
||||
m_submit_loop->Run([this]() {
|
||||
PendingCommandBufferSubmit submit;
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(m_pending_submit_lock);
|
||||
if (m_pending_submits.empty())
|
||||
{
|
||||
m_submit_loop->AllowSleep();
|
||||
return;
|
||||
}
|
||||
|
||||
submit = m_pending_submits.front();
|
||||
m_pending_submits.pop_front();
|
||||
}
|
||||
|
||||
SubmitCommandBuffer(submit.index, submit.wait_semaphore, submit.signal_semaphore,
|
||||
submit.present_swap_chain, submit.present_image_index);
|
||||
});
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CommandBufferManager::PrepareToSubmitCommandBuffer()
|
||||
{
|
||||
// Grab the semaphore before submitting command buffer either on-thread or off-thread.
|
||||
// This prevents a race from occurring where a second command buffer is executed
|
||||
// before the worker thread has woken and executed the first one yet.
|
||||
m_submit_semaphore.Wait();
|
||||
}
|
||||
|
||||
void CommandBufferManager::WaitForWorkerThreadIdle()
|
||||
{
|
||||
// Drain the semaphore, then allow another request in the future.
|
||||
m_submit_semaphore.Wait();
|
||||
m_submit_semaphore.Post();
|
||||
}
|
||||
|
||||
void CommandBufferManager::WaitForGPUIdle()
|
||||
{
|
||||
WaitForWorkerThreadIdle();
|
||||
vkDeviceWaitIdle(g_vulkan_context->GetDevice());
|
||||
}
|
||||
|
||||
void CommandBufferManager::WaitForFence(VkFence fence)
|
||||
{
|
||||
// Find the command buffer that this fence corresponds to.
|
||||
size_t command_buffer_index = 0;
|
||||
for (; command_buffer_index < m_frame_resources.size(); command_buffer_index++)
|
||||
{
|
||||
if (m_frame_resources[command_buffer_index].fence == fence)
|
||||
break;
|
||||
}
|
||||
_assert_(command_buffer_index < m_frame_resources.size());
|
||||
|
||||
// Has this command buffer already been waited for?
|
||||
if (!m_frame_resources[command_buffer_index].needs_fence_wait)
|
||||
return;
|
||||
|
||||
// Wait for this command buffer to be completed.
|
||||
VkResult res =
|
||||
vkWaitForFences(g_vulkan_context->GetDevice(), 1,
|
||||
&m_frame_resources[command_buffer_index].fence, VK_TRUE, UINT64_MAX);
|
||||
if (res != VK_SUCCESS)
|
||||
LOG_VULKAN_ERROR(res, "vkWaitForFences failed: ");
|
||||
|
||||
// Immediately fire callbacks and cleanups, since the commands has been completed.
|
||||
m_frame_resources[command_buffer_index].needs_fence_wait = false;
|
||||
OnCommandBufferExecuted(command_buffer_index);
|
||||
}
|
||||
|
||||
void CommandBufferManager::SubmitCommandBuffer(bool submit_on_worker_thread,
|
||||
VkSemaphore wait_semaphore,
|
||||
VkSemaphore signal_semaphore,
|
||||
VkSwapchainKHR present_swap_chain,
|
||||
uint32_t present_image_index)
|
||||
{
|
||||
FrameResources& resources = m_frame_resources[m_current_frame];
|
||||
|
||||
// Fire fence tracking callbacks. This can't happen on the worker thread.
|
||||
// We invoke these before submitting so that any last-minute commands can be added.
|
||||
for (const auto& iter : m_fence_point_callbacks)
|
||||
iter.second.first(resources.command_buffers[1], resources.fence);
|
||||
|
||||
// End the current command buffer.
|
||||
for (VkCommandBuffer command_buffer : resources.command_buffers)
|
||||
{
|
||||
VkResult res = vkEndCommandBuffer(command_buffer);
|
||||
if (res != VK_SUCCESS)
|
||||
{
|
||||
LOG_VULKAN_ERROR(res, "vkEndCommandBuffer failed: ");
|
||||
PanicAlert("Failed to end command buffer");
|
||||
}
|
||||
}
|
||||
|
||||
// This command buffer now has commands, so can't be re-used without waiting.
|
||||
resources.needs_fence_wait = true;
|
||||
|
||||
// Submitting off-thread?
|
||||
if (m_use_threaded_submission && submit_on_worker_thread)
|
||||
{
|
||||
// Push to the pending submit queue.
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(m_pending_submit_lock);
|
||||
m_pending_submits.push_back({m_current_frame, wait_semaphore, signal_semaphore,
|
||||
present_swap_chain, present_image_index});
|
||||
}
|
||||
|
||||
// Wake up the worker thread for a single iteration.
|
||||
m_submit_loop->Wakeup();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Pass through to normal submission path.
|
||||
SubmitCommandBuffer(m_current_frame, wait_semaphore, signal_semaphore, present_swap_chain,
|
||||
present_image_index);
|
||||
}
|
||||
}
|
||||
|
||||
void CommandBufferManager::SubmitCommandBuffer(size_t index, VkSemaphore wait_semaphore,
|
||||
VkSemaphore signal_semaphore,
|
||||
VkSwapchainKHR present_swap_chain,
|
||||
uint32_t present_image_index)
|
||||
{
|
||||
FrameResources& resources = m_frame_resources[index];
|
||||
|
||||
// This may be executed on the worker thread, so don't modify any state of the manager class.
|
||||
uint32_t wait_bits = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||
VkSubmitInfo submit_info = {VK_STRUCTURE_TYPE_SUBMIT_INFO,
|
||||
nullptr,
|
||||
0,
|
||||
nullptr,
|
||||
&wait_bits,
|
||||
static_cast<u32>(resources.command_buffers.size()),
|
||||
resources.command_buffers.data(),
|
||||
0,
|
||||
nullptr};
|
||||
|
||||
if (wait_semaphore != VK_NULL_HANDLE)
|
||||
{
|
||||
submit_info.pWaitSemaphores = &wait_semaphore;
|
||||
submit_info.waitSemaphoreCount = 1;
|
||||
}
|
||||
|
||||
if (signal_semaphore != VK_NULL_HANDLE)
|
||||
{
|
||||
submit_info.signalSemaphoreCount = 1;
|
||||
submit_info.pSignalSemaphores = &signal_semaphore;
|
||||
}
|
||||
|
||||
VkResult res =
|
||||
vkQueueSubmit(g_vulkan_context->GetGraphicsQueue(), 1, &submit_info, resources.fence);
|
||||
if (res != VK_SUCCESS)
|
||||
{
|
||||
LOG_VULKAN_ERROR(res, "vkQueueSubmit failed: ");
|
||||
PanicAlert("Failed to submit command buffer.");
|
||||
}
|
||||
|
||||
// Do we have a swap chain to present?
|
||||
if (present_swap_chain != VK_NULL_HANDLE)
|
||||
{
|
||||
// Should have a signal semaphore.
|
||||
_assert_(signal_semaphore != VK_NULL_HANDLE);
|
||||
VkPresentInfoKHR present_info = {VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
|
||||
nullptr,
|
||||
1,
|
||||
&signal_semaphore,
|
||||
1,
|
||||
&present_swap_chain,
|
||||
&present_image_index,
|
||||
nullptr};
|
||||
|
||||
res = vkQueuePresentKHR(g_vulkan_context->GetGraphicsQueue(), &present_info);
|
||||
if (res != VK_SUCCESS && res != VK_ERROR_OUT_OF_DATE_KHR && res != VK_SUBOPTIMAL_KHR)
|
||||
LOG_VULKAN_ERROR(res, "vkQueuePresentKHR failed: ");
|
||||
}
|
||||
|
||||
// Command buffer has been queued, so permit the next one.
|
||||
m_submit_semaphore.Post();
|
||||
}
|
||||
|
||||
void CommandBufferManager::OnCommandBufferExecuted(size_t index)
|
||||
{
|
||||
FrameResources& resources = m_frame_resources[index];
|
||||
|
||||
// Fire fence tracking callbacks.
|
||||
for (const auto& iter : m_fence_point_callbacks)
|
||||
iter.second.second(resources.fence);
|
||||
|
||||
// Clean up all objects pending destruction on this command buffer
|
||||
for (const auto& it : resources.cleanup_resources)
|
||||
it.destroy_callback(g_vulkan_context->GetDevice(), it.object);
|
||||
resources.cleanup_resources.clear();
|
||||
}
|
||||
|
||||
void CommandBufferManager::ActivateCommandBuffer()
|
||||
{
|
||||
// Move to the next command buffer.
|
||||
m_current_frame = (m_current_frame + 1) % NUM_COMMAND_BUFFERS;
|
||||
FrameResources& resources = m_frame_resources[m_current_frame];
|
||||
|
||||
// Wait for the GPU to finish with all resources for this command buffer.
|
||||
if (resources.needs_fence_wait)
|
||||
{
|
||||
VkResult res =
|
||||
vkWaitForFences(g_vulkan_context->GetDevice(), 1, &resources.fence, true, UINT64_MAX);
|
||||
if (res != VK_SUCCESS)
|
||||
LOG_VULKAN_ERROR(res, "vkWaitForFences failed: ");
|
||||
|
||||
OnCommandBufferExecuted(m_current_frame);
|
||||
}
|
||||
|
||||
// Reset fence to unsignaled before starting.
|
||||
VkResult res = vkResetFences(g_vulkan_context->GetDevice(), 1, &resources.fence);
|
||||
if (res != VK_SUCCESS)
|
||||
LOG_VULKAN_ERROR(res, "vkResetFences failed: ");
|
||||
|
||||
// Reset command buffer to beginning since we can re-use the memory now
|
||||
VkCommandBufferBeginInfo begin_info = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, nullptr,
|
||||
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, nullptr};
|
||||
for (VkCommandBuffer command_buffer : resources.command_buffers)
|
||||
{
|
||||
res = vkResetCommandBuffer(command_buffer, 0);
|
||||
if (res != VK_SUCCESS)
|
||||
LOG_VULKAN_ERROR(res, "vkResetCommandBuffer failed: ");
|
||||
|
||||
res = vkBeginCommandBuffer(command_buffer, &begin_info);
|
||||
if (res != VK_SUCCESS)
|
||||
LOG_VULKAN_ERROR(res, "vkBeginCommandBuffer failed: ");
|
||||
}
|
||||
|
||||
// Also can do the same for the descriptor pools
|
||||
res = vkResetDescriptorPool(g_vulkan_context->GetDevice(), resources.descriptor_pool, 0);
|
||||
if (res != VK_SUCCESS)
|
||||
LOG_VULKAN_ERROR(res, "vkResetDescriptorPool failed: ");
|
||||
}
|
||||
|
||||
void CommandBufferManager::ExecuteCommandBuffer(bool submit_off_thread, bool wait_for_completion)
|
||||
{
|
||||
VkFence pending_fence = GetCurrentCommandBufferFence();
|
||||
|
||||
// If we're waiting for completion, don't bother waking the worker thread.
|
||||
PrepareToSubmitCommandBuffer();
|
||||
SubmitCommandBuffer((submit_off_thread && wait_for_completion));
|
||||
ActivateCommandBuffer();
|
||||
|
||||
if (wait_for_completion)
|
||||
WaitForFence(pending_fence);
|
||||
}
|
||||
|
||||
void CommandBufferManager::AddFencePointCallback(
|
||||
const void* key, const CommandBufferQueuedCallback& queued_callback,
|
||||
const CommandBufferExecutedCallback& executed_callback)
|
||||
{
|
||||
// Shouldn't be adding twice.
|
||||
_assert_(m_fence_point_callbacks.find(key) == m_fence_point_callbacks.end());
|
||||
m_fence_point_callbacks.emplace(key, std::make_pair(queued_callback, executed_callback));
|
||||
}
|
||||
|
||||
void CommandBufferManager::RemoveFencePointCallback(const void* key)
|
||||
{
|
||||
auto iter = m_fence_point_callbacks.find(key);
|
||||
_assert_(iter != m_fence_point_callbacks.end());
|
||||
m_fence_point_callbacks.erase(iter);
|
||||
}
|
||||
|
||||
std::unique_ptr<CommandBufferManager> g_command_buffer_mgr;
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
// Copyright 2016 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/BlockingLoop.h"
|
||||
#include "Common/Semaphore.h"
|
||||
|
||||
#include "VideoCommon/VideoCommon.h"
|
||||
|
||||
#include "VideoBackends/Vulkan/Constants.h"
|
||||
#include "VideoBackends/Vulkan/Util.h"
|
||||
|
||||
namespace Vulkan
|
||||
{
|
||||
class CommandBufferManager
|
||||
{
|
||||
public:
|
||||
explicit CommandBufferManager(bool use_threaded_submission);
|
||||
~CommandBufferManager();
|
||||
|
||||
bool Initialize();
|
||||
|
||||
VkCommandPool GetCommandPool() const { return m_command_pool; }
|
||||
// These command buffers are allocated per-frame. They are valid until the command buffer
|
||||
// is submitted, after that you should call these functions again.
|
||||
VkCommandBuffer GetCurrentInitCommandBuffer() const
|
||||
{
|
||||
return m_frame_resources[m_current_frame].command_buffers[0];
|
||||
}
|
||||
VkCommandBuffer GetCurrentCommandBuffer() const
|
||||
{
|
||||
return m_frame_resources[m_current_frame].command_buffers[1];
|
||||
}
|
||||
VkDescriptorPool GetCurrentDescriptorPool() const
|
||||
{
|
||||
return m_frame_resources[m_current_frame].descriptor_pool;
|
||||
}
|
||||
// Allocates a descriptors set from the pool reserved for the current frame.
|
||||
VkDescriptorSet AllocateDescriptorSet(VkDescriptorSetLayout set_layout);
|
||||
|
||||
// Gets the fence that will be signaled when the currently executing command buffer is
|
||||
// queued and executed. Do not wait for this fence before the buffer is executed.
|
||||
VkFence GetCurrentCommandBufferFence() const { return m_frame_resources[m_current_frame].fence; }
|
||||
// Ensure the worker thread has submitted the previous frame's command buffer.
|
||||
void PrepareToSubmitCommandBuffer();
|
||||
|
||||
// Ensure that the worker thread has submitted any previous command buffers and is idle.
|
||||
void WaitForWorkerThreadIdle();
|
||||
|
||||
// Ensure that the worker thread has both submitted all commands, and the GPU has caught up.
|
||||
// Use with caution, huge performance penalty.
|
||||
void WaitForGPUIdle();
|
||||
|
||||
// Wait for a fence to be completed.
|
||||
// Also invokes callbacks for completion.
|
||||
void WaitForFence(VkFence fence);
|
||||
|
||||
void SubmitCommandBuffer(bool submit_on_worker_thread,
|
||||
VkSemaphore wait_semaphore = VK_NULL_HANDLE,
|
||||
VkSemaphore signal_semaphore = VK_NULL_HANDLE,
|
||||
VkSwapchainKHR present_swap_chain = VK_NULL_HANDLE,
|
||||
uint32_t present_image_index = 0xFFFFFFFF);
|
||||
|
||||
void ActivateCommandBuffer();
|
||||
|
||||
void ExecuteCommandBuffer(bool submit_off_thread, bool wait_for_completion);
|
||||
|
||||
// Schedule a vulkan resource for destruction later on. This will occur when the command buffer
|
||||
// is next re-used, and the GPU has finished working with the specified resource.
|
||||
template <typename T>
|
||||
void DeferResourceDestruction(T object)
|
||||
{
|
||||
DeferredResourceDestruction wrapper = DeferredResourceDestruction::Wrapper<T>(object);
|
||||
m_frame_resources[m_current_frame].cleanup_resources.push_back(wrapper);
|
||||
}
|
||||
|
||||
// Instruct the manager to fire the specified callback when a fence is flagged to be signaled.
|
||||
// This happens when command buffers are executed, and can be tested if signaled, which means
|
||||
// that all commands up to the point when the callback was fired have completed.
|
||||
using CommandBufferQueuedCallback = std::function<void(VkCommandBuffer, VkFence)>;
|
||||
using CommandBufferExecutedCallback = std::function<void(VkFence)>;
|
||||
|
||||
void AddFencePointCallback(const void* key, const CommandBufferQueuedCallback& queued_callback,
|
||||
const CommandBufferExecutedCallback& executed_callback);
|
||||
|
||||
void RemoveFencePointCallback(const void* key);
|
||||
|
||||
private:
|
||||
bool CreateCommandPool();
|
||||
void DestroyCommandPool();
|
||||
|
||||
bool CreateCommandBuffers();
|
||||
void DestroyCommandBuffers();
|
||||
|
||||
bool CreateSubmitThread();
|
||||
|
||||
void SubmitCommandBuffer(size_t index, VkSemaphore wait_semaphore, VkSemaphore signal_semaphore,
|
||||
VkSwapchainKHR present_swap_chain, uint32_t present_image_index);
|
||||
|
||||
void OnCommandBufferExecuted(size_t index);
|
||||
|
||||
VkCommandPool m_command_pool = VK_NULL_HANDLE;
|
||||
|
||||
struct FrameResources
|
||||
{
|
||||
// [0] - Init (upload) command buffer, [1] - draw command buffer
|
||||
std::array<VkCommandBuffer, 2> command_buffers;
|
||||
VkDescriptorPool descriptor_pool;
|
||||
VkFence fence;
|
||||
bool needs_fence_wait;
|
||||
|
||||
std::vector<DeferredResourceDestruction> cleanup_resources;
|
||||
};
|
||||
|
||||
std::array<FrameResources, NUM_COMMAND_BUFFERS> m_frame_resources = {};
|
||||
size_t m_current_frame;
|
||||
|
||||
// callbacks when a fence point is set
|
||||
std::map<const void*, std::pair<CommandBufferQueuedCallback, CommandBufferExecutedCallback>>
|
||||
m_fence_point_callbacks;
|
||||
|
||||
// Threaded command buffer execution
|
||||
// Semaphore determines when a command buffer can be queued
|
||||
Common::Semaphore m_submit_semaphore;
|
||||
std::thread m_submit_thread;
|
||||
std::unique_ptr<Common::BlockingLoop> m_submit_loop;
|
||||
struct PendingCommandBufferSubmit
|
||||
{
|
||||
size_t index;
|
||||
VkSemaphore wait_semaphore;
|
||||
VkSemaphore signal_semaphore;
|
||||
VkSwapchainKHR present_swap_chain;
|
||||
uint32_t present_image_index;
|
||||
};
|
||||
std::deque<PendingCommandBufferSubmit> m_pending_submits;
|
||||
std::mutex m_pending_submit_lock;
|
||||
bool m_use_threaded_submission = false;
|
||||
};
|
||||
|
||||
extern std::unique_ptr<CommandBufferManager> g_command_buffer_mgr;
|
||||
|
||||
} // namespace Vulkan
|
||||
@@ -0,0 +1,136 @@
|
||||
// Copyright 2016 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Common/BitField.h"
|
||||
#include "VideoBackends/Vulkan/VulkanLoader.h"
|
||||
|
||||
namespace Vulkan
|
||||
{
|
||||
// Number of command buffers. Having two allows one buffer to be
|
||||
// executed whilst another is being built.
|
||||
constexpr size_t NUM_COMMAND_BUFFERS = 2;
|
||||
|
||||
// Staging buffer usage - optimize for uploads or readbacks
|
||||
enum STAGING_BUFFER_TYPE
|
||||
{
|
||||
STAGING_BUFFER_TYPE_UPLOAD,
|
||||
STAGING_BUFFER_TYPE_READBACK
|
||||
};
|
||||
|
||||
// Descriptor sets
|
||||
enum DESCRIPTOR_SET
|
||||
{
|
||||
DESCRIPTOR_SET_UNIFORM_BUFFERS,
|
||||
DESCRIPTOR_SET_PIXEL_SHADER_SAMPLERS,
|
||||
DESCRIPTOR_SET_SHADER_STORAGE_BUFFERS,
|
||||
NUM_DESCRIPTOR_SETS
|
||||
};
|
||||
|
||||
// Uniform buffer bindings within the first descriptor set
|
||||
enum UNIFORM_BUFFER_DESCRIPTOR_SET_BINDING
|
||||
{
|
||||
UBO_DESCRIPTOR_SET_BINDING_PS,
|
||||
UBO_DESCRIPTOR_SET_BINDING_VS,
|
||||
UBO_DESCRIPTOR_SET_BINDING_GS,
|
||||
NUM_UBO_DESCRIPTOR_SET_BINDINGS
|
||||
};
|
||||
|
||||
// Maximum number of attributes per vertex (we don't have any more than this?)
|
||||
constexpr size_t MAX_VERTEX_ATTRIBUTES = 16;
|
||||
|
||||
// Number of pixel shader texture slots
|
||||
constexpr size_t NUM_PIXEL_SHADER_SAMPLERS = 8;
|
||||
|
||||
// Total number of binding points in the pipeline layout
|
||||
constexpr size_t TOTAL_PIPELINE_BINDING_POINTS =
|
||||
NUM_UBO_DESCRIPTOR_SET_BINDINGS + NUM_PIXEL_SHADER_SAMPLERS + 1;
|
||||
|
||||
// Format of EFB textures
|
||||
constexpr VkFormat EFB_COLOR_TEXTURE_FORMAT = VK_FORMAT_R8G8B8A8_UNORM;
|
||||
constexpr VkFormat EFB_DEPTH_TEXTURE_FORMAT = VK_FORMAT_D32_SFLOAT;
|
||||
constexpr VkFormat EFB_DEPTH_AS_COLOR_TEXTURE_FORMAT = VK_FORMAT_R32_SFLOAT;
|
||||
|
||||
// Format of texturecache textures
|
||||
constexpr VkFormat TEXTURECACHE_TEXTURE_FORMAT = VK_FORMAT_R8G8B8A8_UNORM;
|
||||
|
||||
// Textures that don't fit into this buffer will be uploaded with a separate buffer (see below).
|
||||
constexpr size_t INITIAL_TEXTURE_UPLOAD_BUFFER_SIZE = 16 * 1024 * 1024;
|
||||
constexpr size_t MAXIMUM_TEXTURE_UPLOAD_BUFFER_SIZE = 64 * 1024 * 1024;
|
||||
|
||||
// Textures greater than 1024*1024 will be put in staging textures that are released after
|
||||
// execution instead. A 2048x2048 texture is 16MB, and we'd only fit four of these in our
|
||||
// streaming buffer and be blocking frequently. Games are unlikely to have textures this
|
||||
// large anyway, so it's only really an issue for HD texture packs, and memory is not
|
||||
// a limiting factor in these scenarios anyway.
|
||||
constexpr size_t STAGING_TEXTURE_UPLOAD_THRESHOLD = 1024 * 1024 * 4;
|
||||
|
||||
// Streaming uniform buffer size
|
||||
constexpr size_t INITIAL_UNIFORM_STREAM_BUFFER_SIZE = 16 * 1024 * 1024;
|
||||
constexpr size_t MAXIMUM_UNIFORM_STREAM_BUFFER_SIZE = 32 * 1024 * 1024;
|
||||
|
||||
// Push constant buffer size for utility shaders
|
||||
constexpr u32 PUSH_CONSTANT_BUFFER_SIZE = 128;
|
||||
|
||||
// Rasterization state info
|
||||
union RasterizationState {
|
||||
BitField<0, 2, VkCullModeFlags> cull_mode;
|
||||
BitField<2, 7, VkSampleCountFlagBits> samples;
|
||||
BitField<9, 1, VkBool32> per_sample_shading;
|
||||
BitField<10, 1, VkBool32> depth_clamp;
|
||||
|
||||
u32 bits;
|
||||
};
|
||||
|
||||
// Depth state info
|
||||
union DepthStencilState {
|
||||
BitField<0, 1, VkBool32> test_enable;
|
||||
BitField<1, 1, VkBool32> write_enable;
|
||||
BitField<2, 3, VkCompareOp> compare_op;
|
||||
|
||||
u32 bits;
|
||||
};
|
||||
|
||||
// Blend state info
|
||||
union BlendState {
|
||||
struct
|
||||
{
|
||||
union {
|
||||
BitField<0, 1, VkBool32> blend_enable;
|
||||
BitField<1, 3, VkBlendOp> blend_op;
|
||||
BitField<4, 5, VkBlendFactor> src_blend;
|
||||
BitField<9, 5, VkBlendFactor> dst_blend;
|
||||
BitField<14, 3, VkBlendOp> alpha_blend_op;
|
||||
BitField<17, 5, VkBlendFactor> src_alpha_blend;
|
||||
BitField<22, 5, VkBlendFactor> dst_alpha_blend;
|
||||
BitField<27, 4, VkColorComponentFlags> write_mask;
|
||||
u32 low_bits;
|
||||
};
|
||||
union {
|
||||
BitField<0, 1, VkBool32> logic_op_enable;
|
||||
BitField<1, 4, VkLogicOp> logic_op;
|
||||
u32 high_bits;
|
||||
};
|
||||
};
|
||||
|
||||
u64 bits;
|
||||
};
|
||||
|
||||
// Sampler info
|
||||
union SamplerState {
|
||||
BitField<0, 1, VkFilter> min_filter;
|
||||
BitField<1, 1, VkFilter> mag_filter;
|
||||
BitField<2, 1, VkSamplerMipmapMode> mipmap_mode;
|
||||
BitField<3, 2, VkSamplerAddressMode> wrap_u;
|
||||
BitField<5, 2, VkSamplerAddressMode> wrap_v;
|
||||
BitField<7, 8, u32> min_lod;
|
||||
BitField<15, 8, u32> max_lod;
|
||||
BitField<23, 6, s32> lod_bias; // tm0.lod_bias (8 bits) / 32 gives us 0-7.
|
||||
BitField<29, 3, u32> anisotropy; // max_anisotropy = 1 << anisotropy, max of 16, so range 0-4.
|
||||
|
||||
u32 bits;
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,170 @@
|
||||
// Copyright 2016 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "VideoCommon/FramebufferManagerBase.h"
|
||||
|
||||
#include "VideoBackends/Vulkan/Constants.h"
|
||||
|
||||
namespace Vulkan
|
||||
{
|
||||
class StagingTexture2D;
|
||||
class StateTracker;
|
||||
class StreamBuffer;
|
||||
class Texture2D;
|
||||
class VertexFormat;
|
||||
|
||||
class XFBSource : public XFBSourceBase
|
||||
{
|
||||
void DecodeToTexture(u32 xfb_addr, u32 fb_width, u32 fb_height) override {}
|
||||
void CopyEFB(float gamma) override {}
|
||||
};
|
||||
|
||||
class FramebufferManager : public FramebufferManagerBase
|
||||
{
|
||||
public:
|
||||
FramebufferManager();
|
||||
~FramebufferManager();
|
||||
|
||||
bool Initialize();
|
||||
|
||||
VkRenderPass GetEFBRenderPass() const { return m_efb_render_pass; }
|
||||
u32 GetEFBWidth() const { return m_efb_width; }
|
||||
u32 GetEFBHeight() const { return m_efb_height; }
|
||||
u32 GetEFBLayers() const { return m_efb_layers; }
|
||||
VkSampleCountFlagBits GetEFBSamples() const { return m_efb_samples; }
|
||||
Texture2D* GetEFBColorTexture() const { return m_efb_color_texture.get(); }
|
||||
Texture2D* GetEFBDepthTexture() const { return m_efb_depth_texture.get(); }
|
||||
VkFramebuffer GetEFBFramebuffer() const { return m_efb_framebuffer; }
|
||||
void GetTargetSize(unsigned int* width, unsigned int* height) override;
|
||||
|
||||
std::unique_ptr<XFBSourceBase> CreateXFBSource(unsigned int target_width,
|
||||
unsigned int target_height,
|
||||
unsigned int layers) override
|
||||
{
|
||||
return std::make_unique<XFBSource>();
|
||||
}
|
||||
|
||||
void CopyToRealXFB(u32 xfb_addr, u32 fb_stride, u32 fb_height, const EFBRectangle& source_rc,
|
||||
float gamma = 1.0f) override
|
||||
{
|
||||
}
|
||||
|
||||
void ResizeEFBTextures();
|
||||
|
||||
// Recompile shaders, use when MSAA mode changes.
|
||||
void RecreateRenderPass();
|
||||
void RecompileShaders();
|
||||
|
||||
// Reinterpret pixel format of EFB color texture.
|
||||
// Assumes no render pass is currently in progress.
|
||||
// Swaps EFB framebuffers, so re-bind afterwards.
|
||||
void ReinterpretPixelData(int convtype);
|
||||
|
||||
// Resolve color/depth textures to a non-msaa texture, and return it.
|
||||
Texture2D* ResolveEFBColorTexture(StateTracker* state_tracker, const VkRect2D& region);
|
||||
Texture2D* ResolveEFBDepthTexture(StateTracker* state_tracker, const VkRect2D& region);
|
||||
|
||||
// Reads a framebuffer value back from the GPU. This may block if the cache is not current.
|
||||
u32 PeekEFBColor(StateTracker* state_tracker, u32 x, u32 y);
|
||||
float PeekEFBDepth(StateTracker* state_tracker, u32 x, u32 y);
|
||||
void InvalidatePeekCache();
|
||||
|
||||
// Writes a value to the framebuffer. This will never block, and writes will be batched.
|
||||
void PokeEFBColor(StateTracker* state_tracker, u32 x, u32 y, u32 color);
|
||||
void PokeEFBDepth(StateTracker* state_tracker, u32 x, u32 y, float depth);
|
||||
void FlushEFBPokes(StateTracker* state_tracker);
|
||||
|
||||
private:
|
||||
struct EFBPokeVertex
|
||||
{
|
||||
float position[4];
|
||||
u32 color;
|
||||
};
|
||||
|
||||
bool CreateEFBRenderPass();
|
||||
void DestroyEFBRenderPass();
|
||||
bool CreateEFBFramebuffer();
|
||||
void DestroyEFBFramebuffer();
|
||||
|
||||
bool CompileConversionShaders();
|
||||
void DestroyConversionShaders();
|
||||
|
||||
bool CreateReadbackRenderPasses();
|
||||
void DestroyReadbackRenderPasses();
|
||||
bool CompileReadbackShaders();
|
||||
void DestroyReadbackShaders();
|
||||
bool CreateReadbackTextures();
|
||||
void DestroyReadbackTextures();
|
||||
bool CreateReadbackFramebuffer();
|
||||
void DestroyReadbackFramebuffer();
|
||||
|
||||
void CreatePokeVertexFormat();
|
||||
bool CreatePokeVertexBuffer();
|
||||
void DestroyPokeVertexBuffer();
|
||||
bool CompilePokeShaders();
|
||||
void DestroyPokeShaders();
|
||||
|
||||
bool PopulateColorReadbackTexture(StateTracker* state_tracker);
|
||||
bool PopulateDepthReadbackTexture(StateTracker* state_tracker);
|
||||
|
||||
void CreatePokeVertices(std::vector<EFBPokeVertex>* destination_list, u32 x, u32 y, float z,
|
||||
u32 color);
|
||||
|
||||
void DrawPokeVertices(StateTracker* state_tracker, const EFBPokeVertex* vertices,
|
||||
size_t vertex_count, bool write_color, bool write_depth);
|
||||
|
||||
VkRenderPass m_efb_render_pass = VK_NULL_HANDLE;
|
||||
VkRenderPass m_depth_resolve_render_pass = VK_NULL_HANDLE;
|
||||
|
||||
u32 m_efb_width = 0;
|
||||
u32 m_efb_height = 0;
|
||||
u32 m_efb_layers = 1;
|
||||
VkSampleCountFlagBits m_efb_samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
|
||||
std::unique_ptr<Texture2D> m_efb_color_texture;
|
||||
std::unique_ptr<Texture2D> m_efb_convert_color_texture;
|
||||
std::unique_ptr<Texture2D> m_efb_depth_texture;
|
||||
std::unique_ptr<Texture2D> m_efb_resolve_color_texture;
|
||||
std::unique_ptr<Texture2D> m_efb_resolve_depth_texture;
|
||||
VkFramebuffer m_efb_framebuffer = VK_NULL_HANDLE;
|
||||
VkFramebuffer m_efb_convert_framebuffer = VK_NULL_HANDLE;
|
||||
VkFramebuffer m_depth_resolve_framebuffer = VK_NULL_HANDLE;
|
||||
|
||||
// Format conversion shaders
|
||||
VkShaderModule m_ps_rgb8_to_rgba6 = VK_NULL_HANDLE;
|
||||
VkShaderModule m_ps_rgba6_to_rgb8 = VK_NULL_HANDLE;
|
||||
VkShaderModule m_ps_depth_resolve = VK_NULL_HANDLE;
|
||||
|
||||
// EFB readback texture
|
||||
std::unique_ptr<Texture2D> m_color_copy_texture;
|
||||
std::unique_ptr<Texture2D> m_depth_copy_texture;
|
||||
VkFramebuffer m_color_copy_framebuffer = VK_NULL_HANDLE;
|
||||
VkFramebuffer m_depth_copy_framebuffer = VK_NULL_HANDLE;
|
||||
|
||||
// CPU-side EFB readback texture
|
||||
std::unique_ptr<StagingTexture2D> m_color_readback_texture;
|
||||
std::unique_ptr<StagingTexture2D> m_depth_readback_texture;
|
||||
bool m_color_readback_texture_valid = false;
|
||||
bool m_depth_readback_texture_valid = false;
|
||||
|
||||
// EFB poke drawing setup
|
||||
std::unique_ptr<VertexFormat> m_poke_vertex_format;
|
||||
std::unique_ptr<StreamBuffer> m_poke_vertex_stream_buffer;
|
||||
std::vector<EFBPokeVertex> m_color_poke_vertices;
|
||||
std::vector<EFBPokeVertex> m_depth_poke_vertices;
|
||||
VkPrimitiveTopology m_poke_primitive_topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
||||
|
||||
VkRenderPass m_copy_color_render_pass = VK_NULL_HANDLE;
|
||||
VkRenderPass m_copy_depth_render_pass = VK_NULL_HANDLE;
|
||||
VkShaderModule m_copy_color_shader = VK_NULL_HANDLE;
|
||||
VkShaderModule m_copy_depth_shader = VK_NULL_HANDLE;
|
||||
|
||||
VkShaderModule m_poke_vertex_shader = VK_NULL_HANDLE;
|
||||
VkShaderModule m_poke_geometry_shader = VK_NULL_HANDLE;
|
||||
VkShaderModule m_poke_fragment_shader = VK_NULL_HANDLE;
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,189 @@
|
||||
// Copyright 2016 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "Common/LinearDiskCache.h"
|
||||
|
||||
#include "VideoBackends/Vulkan/Constants.h"
|
||||
|
||||
#include "VideoCommon/GeometryShaderGen.h"
|
||||
#include "VideoCommon/PixelShaderGen.h"
|
||||
#include "VideoCommon/VertexShaderGen.h"
|
||||
|
||||
namespace Vulkan
|
||||
{
|
||||
class CommandBufferManager;
|
||||
class VertexFormat;
|
||||
class StreamBuffer;
|
||||
|
||||
struct PipelineInfo
|
||||
{
|
||||
// These are packed in descending order of size, to avoid any padding so that the structure
|
||||
// can be copied/compared as a single block of memory. 64-bit pointer size is assumed.
|
||||
const VertexFormat* vertex_format;
|
||||
VkPipelineLayout pipeline_layout;
|
||||
VkShaderModule vs;
|
||||
VkShaderModule gs;
|
||||
VkShaderModule ps;
|
||||
VkRenderPass render_pass;
|
||||
BlendState blend_state;
|
||||
RasterizationState rasterization_state;
|
||||
DepthStencilState depth_stencil_state;
|
||||
VkPrimitiveTopology primitive_topology;
|
||||
};
|
||||
|
||||
struct PipelineInfoHash
|
||||
{
|
||||
std::size_t operator()(const PipelineInfo& key) const;
|
||||
};
|
||||
|
||||
bool operator==(const PipelineInfo& lhs, const PipelineInfo& rhs);
|
||||
bool operator!=(const PipelineInfo& lhs, const PipelineInfo& rhs);
|
||||
bool operator<(const PipelineInfo& lhs, const PipelineInfo& rhs);
|
||||
bool operator>(const PipelineInfo& lhs, const PipelineInfo& rhs);
|
||||
bool operator==(const SamplerState& lhs, const SamplerState& rhs);
|
||||
bool operator!=(const SamplerState& lhs, const SamplerState& rhs);
|
||||
bool operator>(const SamplerState& lhs, const SamplerState& rhs);
|
||||
bool operator<(const SamplerState& lhs, const SamplerState& rhs);
|
||||
|
||||
class ObjectCache
|
||||
{
|
||||
public:
|
||||
ObjectCache();
|
||||
~ObjectCache();
|
||||
|
||||
// We have four shared pipeline layouts:
|
||||
// - Standard
|
||||
// - Per-stage UBO (VS/GS/PS, VS constants accessible from PS)
|
||||
// - 8 combined image samplers (accessible from PS)
|
||||
// - BBox Enabled
|
||||
// - Same as standard, plus a single SSBO accessible from PS
|
||||
// - Push Constant
|
||||
// - Same as standard, plus 128 bytes of push constants, accessible from all stages.
|
||||
//
|
||||
// All three pipeline layouts use the same descriptor set layouts, but the final descriptor set
|
||||
// (SSBO) is only required when using the BBox Enabled pipeline layout.
|
||||
//
|
||||
VkDescriptorSetLayout GetDescriptorSetLayout(DESCRIPTOR_SET set) const
|
||||
{
|
||||
return m_descriptor_set_layouts[set];
|
||||
}
|
||||
VkPipelineLayout GetStandardPipelineLayout() const { return m_standard_pipeline_layout; }
|
||||
VkPipelineLayout GetBBoxPipelineLayout() const { return m_bbox_pipeline_layout; }
|
||||
VkPipelineLayout GetPushConstantPipelineLayout() const { return m_push_constant_pipeline_layout; }
|
||||
// Shared utility shader resources
|
||||
VertexFormat* GetUtilityShaderVertexFormat() const
|
||||
{
|
||||
return m_utility_shader_vertex_format.get();
|
||||
}
|
||||
StreamBuffer* GetUtilityShaderVertexBuffer() const
|
||||
{
|
||||
return m_utility_shader_vertex_buffer.get();
|
||||
}
|
||||
StreamBuffer* GetUtilityShaderUniformBuffer() const
|
||||
{
|
||||
return m_utility_shader_uniform_buffer.get();
|
||||
}
|
||||
|
||||
// Get utility shader header based on current config.
|
||||
std::string GetUtilityShaderHeader() const;
|
||||
|
||||
// Accesses ShaderGen shader caches
|
||||
VkShaderModule GetVertexShaderForUid(const VertexShaderUid& uid);
|
||||
VkShaderModule GetGeometryShaderForUid(const GeometryShaderUid& uid);
|
||||
VkShaderModule GetPixelShaderForUid(const PixelShaderUid& uid, DSTALPHA_MODE dstalpha_mode);
|
||||
|
||||
// Static samplers
|
||||
VkSampler GetPointSampler() const { return m_point_sampler; }
|
||||
VkSampler GetLinearSampler() const { return m_linear_sampler; }
|
||||
VkSampler GetSampler(const SamplerState& info);
|
||||
|
||||
// Perform at startup, create descriptor layouts, compiles all static shaders.
|
||||
bool Initialize();
|
||||
|
||||
// Find a pipeline by the specified description, if not found, attempts to create it
|
||||
VkPipeline GetPipeline(const PipelineInfo& info);
|
||||
|
||||
// Wipes out the pipeline cache, use when MSAA modes change, for example
|
||||
// Also destroys the data that would be stored in the disk cache.
|
||||
void ClearPipelineCache();
|
||||
|
||||
// Saves the pipeline cache to disk. Call when shutting down.
|
||||
void SavePipelineCache();
|
||||
|
||||
// Clear sampler cache, use when anisotropy mode changes
|
||||
// WARNING: Ensure none of the objects from here are in use when calling
|
||||
void ClearSamplerCache();
|
||||
|
||||
// Recompile shared shaders, call when stereo mode changes.
|
||||
void RecompileSharedShaders();
|
||||
|
||||
// Shared shader accessors
|
||||
VkShaderModule GetScreenQuadVertexShader() const { return m_screen_quad_vertex_shader; }
|
||||
VkShaderModule GetPassthroughVertexShader() const { return m_passthrough_vertex_shader; }
|
||||
VkShaderModule GetScreenQuadGeometryShader() const { return m_screen_quad_geometry_shader; }
|
||||
VkShaderModule GetPassthroughGeometryShader() const { return m_passthrough_geometry_shader; }
|
||||
private:
|
||||
bool CreatePipelineCache(bool load_from_disk);
|
||||
void DestroyPipelineCache();
|
||||
void LoadShaderCaches();
|
||||
void DestroyShaderCaches();
|
||||
bool CreateDescriptorSetLayouts();
|
||||
void DestroyDescriptorSetLayouts();
|
||||
bool CreatePipelineLayouts();
|
||||
void DestroyPipelineLayouts();
|
||||
bool CreateUtilityShaderVertexFormat();
|
||||
bool CreateStaticSamplers();
|
||||
bool CompileSharedShaders();
|
||||
void DestroySharedShaders();
|
||||
void DestroySamplers();
|
||||
|
||||
std::string GetDiskCacheFileName(const char* type);
|
||||
|
||||
std::array<VkDescriptorSetLayout, NUM_DESCRIPTOR_SETS> m_descriptor_set_layouts = {};
|
||||
|
||||
VkPipelineLayout m_standard_pipeline_layout = VK_NULL_HANDLE;
|
||||
VkPipelineLayout m_bbox_pipeline_layout = VK_NULL_HANDLE;
|
||||
VkPipelineLayout m_push_constant_pipeline_layout = VK_NULL_HANDLE;
|
||||
|
||||
std::unique_ptr<VertexFormat> m_utility_shader_vertex_format;
|
||||
std::unique_ptr<StreamBuffer> m_utility_shader_vertex_buffer;
|
||||
std::unique_ptr<StreamBuffer> m_utility_shader_uniform_buffer;
|
||||
|
||||
template <typename Uid>
|
||||
struct ShaderCache
|
||||
{
|
||||
std::map<Uid, VkShaderModule> shader_map;
|
||||
LinearDiskCache<Uid, u32> disk_cache;
|
||||
};
|
||||
ShaderCache<VertexShaderUid> m_vs_cache;
|
||||
ShaderCache<GeometryShaderUid> m_gs_cache;
|
||||
ShaderCache<PixelShaderUid> m_ps_cache;
|
||||
|
||||
std::unordered_map<PipelineInfo, VkPipeline, PipelineInfoHash> m_pipeline_objects;
|
||||
VkPipelineCache m_pipeline_cache = VK_NULL_HANDLE;
|
||||
std::string m_pipeline_cache_filename;
|
||||
|
||||
VkSampler m_point_sampler = VK_NULL_HANDLE;
|
||||
VkSampler m_linear_sampler = VK_NULL_HANDLE;
|
||||
|
||||
std::map<SamplerState, VkSampler> m_sampler_cache;
|
||||
|
||||
// Utility/shared shaders
|
||||
VkShaderModule m_screen_quad_vertex_shader = VK_NULL_HANDLE;
|
||||
VkShaderModule m_passthrough_vertex_shader = VK_NULL_HANDLE;
|
||||
VkShaderModule m_screen_quad_geometry_shader = VK_NULL_HANDLE;
|
||||
VkShaderModule m_passthrough_geometry_shader = VK_NULL_HANDLE;
|
||||
};
|
||||
|
||||
extern std::unique_ptr<ObjectCache> g_object_cache;
|
||||
|
||||
} // namespace Vulkan
|
||||
@@ -0,0 +1,311 @@
|
||||
// Copyright 2016 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
#include "Common/Assert.h"
|
||||
#include "Common/CommonFuncs.h"
|
||||
|
||||
#include "VideoBackends/Vulkan/CommandBufferManager.h"
|
||||
#include "VideoBackends/Vulkan/FramebufferManager.h"
|
||||
#include "VideoBackends/Vulkan/ObjectCache.h"
|
||||
#include "VideoBackends/Vulkan/PaletteTextureConverter.h"
|
||||
#include "VideoBackends/Vulkan/Renderer.h"
|
||||
#include "VideoBackends/Vulkan/StateTracker.h"
|
||||
#include "VideoBackends/Vulkan/StreamBuffer.h"
|
||||
#include "VideoBackends/Vulkan/Texture2D.h"
|
||||
#include "VideoBackends/Vulkan/Util.h"
|
||||
#include "VideoBackends/Vulkan/VulkanContext.h"
|
||||
|
||||
namespace Vulkan
|
||||
{
|
||||
PaletteTextureConverter::PaletteTextureConverter()
|
||||
{
|
||||
}
|
||||
|
||||
PaletteTextureConverter::~PaletteTextureConverter()
|
||||
{
|
||||
for (const auto& it : m_shaders)
|
||||
{
|
||||
if (it != VK_NULL_HANDLE)
|
||||
vkDestroyShaderModule(g_vulkan_context->GetDevice(), it, nullptr);
|
||||
}
|
||||
|
||||
if (m_palette_buffer_view != VK_NULL_HANDLE)
|
||||
vkDestroyBufferView(g_vulkan_context->GetDevice(), m_palette_buffer_view, nullptr);
|
||||
|
||||
if (m_palette_set_layout != VK_NULL_HANDLE)
|
||||
vkDestroyDescriptorSetLayout(g_vulkan_context->GetDevice(), m_palette_set_layout, nullptr);
|
||||
}
|
||||
|
||||
bool PaletteTextureConverter::Initialize()
|
||||
{
|
||||
if (!CreateBuffers())
|
||||
return false;
|
||||
|
||||
if (!CompileShaders())
|
||||
return false;
|
||||
|
||||
if (!CreateDescriptorLayout())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PaletteTextureConverter::ConvertTexture(StateTracker* state_tracker, VkRenderPass render_pass,
|
||||
VkFramebuffer dst_framebuffer, Texture2D* src_texture,
|
||||
u32 width, u32 height, void* palette,
|
||||
TlutFormat format)
|
||||
{
|
||||
struct PSUniformBlock
|
||||
{
|
||||
float multiplier;
|
||||
int texel_buffer_offset;
|
||||
int pad[2];
|
||||
};
|
||||
|
||||
_assert_(static_cast<size_t>(format) < NUM_PALETTE_CONVERSION_SHADERS);
|
||||
|
||||
size_t palette_size = ((format & 0xF) == GX_TF_I4) ? 32 : 512;
|
||||
VkDescriptorSet texel_buffer_descriptor_set;
|
||||
|
||||
// Allocate memory for the palette, and descriptor sets for the buffer.
|
||||
// If any of these fail, execute a command buffer, and try again.
|
||||
if (!m_palette_stream_buffer->ReserveMemory(palette_size,
|
||||
g_vulkan_context->GetTexelBufferAlignment()) ||
|
||||
(texel_buffer_descriptor_set =
|
||||
g_command_buffer_mgr->AllocateDescriptorSet(m_palette_set_layout)) == VK_NULL_HANDLE)
|
||||
{
|
||||
WARN_LOG(VIDEO, "Executing command list while waiting for space in palette buffer");
|
||||
Util::ExecuteCurrentCommandsAndRestoreState(state_tracker, false);
|
||||
|
||||
if (!m_palette_stream_buffer->ReserveMemory(palette_size,
|
||||
g_vulkan_context->GetTexelBufferAlignment()) ||
|
||||
(texel_buffer_descriptor_set =
|
||||
g_command_buffer_mgr->AllocateDescriptorSet(m_palette_set_layout)) == VK_NULL_HANDLE)
|
||||
{
|
||||
PanicAlert("Failed to allocate space for texture conversion");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Fill descriptor set #2 (texel buffer)
|
||||
u32 palette_offset = static_cast<u32>(m_palette_stream_buffer->GetCurrentOffset());
|
||||
VkWriteDescriptorSet texel_set_write = {VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
|
||||
nullptr,
|
||||
texel_buffer_descriptor_set,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER,
|
||||
nullptr,
|
||||
nullptr,
|
||||
&m_palette_buffer_view};
|
||||
vkUpdateDescriptorSets(g_vulkan_context->GetDevice(), 1, &texel_set_write, 0, nullptr);
|
||||
|
||||
Util::BufferMemoryBarrier(g_command_buffer_mgr->GetCurrentInitCommandBuffer(),
|
||||
m_palette_stream_buffer->GetBuffer(), VK_ACCESS_HOST_WRITE_BIT,
|
||||
VK_ACCESS_SHADER_READ_BIT, palette_offset, palette_size,
|
||||
VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT);
|
||||
|
||||
// Set up draw
|
||||
UtilityShaderDraw draw(g_command_buffer_mgr->GetCurrentInitCommandBuffer(), m_pipeline_layout,
|
||||
render_pass, g_object_cache->GetScreenQuadVertexShader(), VK_NULL_HANDLE,
|
||||
m_shaders[format]);
|
||||
|
||||
VkRect2D region = {{0, 0}, {width, height}};
|
||||
draw.BeginRenderPass(dst_framebuffer, region);
|
||||
|
||||
// Copy in palette
|
||||
memcpy(m_palette_stream_buffer->GetCurrentHostPointer(), palette, palette_size);
|
||||
m_palette_stream_buffer->CommitMemory(palette_size);
|
||||
|
||||
// PS Uniforms/Samplers
|
||||
PSUniformBlock uniforms = {};
|
||||
uniforms.multiplier = ((format & 0xF)) == GX_TF_I4 ? 15.0f : 255.0f;
|
||||
uniforms.texel_buffer_offset = static_cast<int>(palette_offset / sizeof(u16));
|
||||
draw.SetPushConstants(&uniforms, sizeof(uniforms));
|
||||
draw.SetPSSampler(0, src_texture->GetView(), g_object_cache->GetPointSampler());
|
||||
|
||||
// We have to bind the texel buffer descriptor set separately.
|
||||
vkCmdBindDescriptorSets(g_command_buffer_mgr->GetCurrentInitCommandBuffer(),
|
||||
VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipeline_layout, 0, 1,
|
||||
&texel_buffer_descriptor_set, 0, nullptr);
|
||||
|
||||
// Draw
|
||||
draw.SetViewportAndScissor(0, 0, width, height);
|
||||
draw.DrawWithoutVertexBuffer(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP, 4);
|
||||
draw.EndRenderPass();
|
||||
}
|
||||
|
||||
bool PaletteTextureConverter::CreateBuffers()
|
||||
{
|
||||
// TODO: Check against maximum size
|
||||
static const size_t BUFFER_SIZE = 1024 * 1024;
|
||||
|
||||
m_palette_stream_buffer =
|
||||
StreamBuffer::Create(VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT, BUFFER_SIZE, BUFFER_SIZE);
|
||||
if (!m_palette_stream_buffer)
|
||||
return false;
|
||||
|
||||
// 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
|
||||
m_palette_stream_buffer->GetBuffer(), // VkBuffer buffer
|
||||
VK_FORMAT_R16_UINT, // VkFormat format
|
||||
0, // VkDeviceSize offset
|
||||
BUFFER_SIZE // VkDeviceSize range
|
||||
};
|
||||
|
||||
VkResult res = vkCreateBufferView(g_vulkan_context->GetDevice(), &view_info, nullptr,
|
||||
&m_palette_buffer_view);
|
||||
if (res != VK_SUCCESS)
|
||||
{
|
||||
LOG_VULKAN_ERROR(res, "vkCreateBufferView failed: ");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PaletteTextureConverter::CompileShaders()
|
||||
{
|
||||
static const char PALETTE_CONVERSION_FRAGMENT_SHADER_SOURCE[] = R"(
|
||||
layout(std140, push_constant) uniform PCBlock
|
||||
{
|
||||
float multiplier;
|
||||
int texture_buffer_offset;
|
||||
} PC;
|
||||
|
||||
layout(set = 1, binding = 0) uniform sampler2DArray samp0;
|
||||
layout(set = 0, binding = 0) uniform usamplerBuffer samp1;
|
||||
|
||||
layout(location = 0) in vec3 f_uv0;
|
||||
layout(location = 0) out vec4 ocol0;
|
||||
|
||||
int Convert3To8(int v)
|
||||
{
|
||||
// Swizzle bits: 00000123 -> 12312312
|
||||
return (v << 5) | (v << 2) | (v >> 1);
|
||||
}
|
||||
int Convert4To8(int v)
|
||||
{
|
||||
// Swizzle bits: 00001234 -> 12341234
|
||||
return (v << 4) | v;
|
||||
}
|
||||
int Convert5To8(int v)
|
||||
{
|
||||
// Swizzle bits: 00012345 -> 12345123
|
||||
return (v << 3) | (v >> 2);
|
||||
}
|
||||
int Convert6To8(int v)
|
||||
{
|
||||
// Swizzle bits: 00123456 -> 12345612
|
||||
return (v << 2) | (v >> 4);
|
||||
}
|
||||
float4 DecodePixel_RGB5A3(int val)
|
||||
{
|
||||
int r,g,b,a;
|
||||
if ((val&0x8000) > 0)
|
||||
{
|
||||
r=Convert5To8((val>>10) & 0x1f);
|
||||
g=Convert5To8((val>>5 ) & 0x1f);
|
||||
b=Convert5To8((val ) & 0x1f);
|
||||
a=0xFF;
|
||||
}
|
||||
else
|
||||
{
|
||||
a=Convert3To8((val>>12) & 0x7);
|
||||
r=Convert4To8((val>>8 ) & 0xf);
|
||||
g=Convert4To8((val>>4 ) & 0xf);
|
||||
b=Convert4To8((val ) & 0xf);
|
||||
}
|
||||
return float4(r, g, b, a) / 255.0;
|
||||
}
|
||||
float4 DecodePixel_RGB565(int val)
|
||||
{
|
||||
int r, g, b, a;
|
||||
r = Convert5To8((val >> 11) & 0x1f);
|
||||
g = Convert6To8((val >> 5) & 0x3f);
|
||||
b = Convert5To8((val) & 0x1f);
|
||||
a = 0xFF;
|
||||
return float4(r, g, b, a) / 255.0;
|
||||
}
|
||||
float4 DecodePixel_IA8(int val)
|
||||
{
|
||||
int i = val & 0xFF;
|
||||
int a = val >> 8;
|
||||
return float4(i, i, i, a) / 255.0;
|
||||
}
|
||||
void main()
|
||||
{
|
||||
int src = int(round(texture(samp0, f_uv0).r * PC.multiplier));
|
||||
src = int(texelFetch(samp1, src + PC.texture_buffer_offset).r);
|
||||
src = ((src << 8) & 0xFF00) | (src >> 8);
|
||||
ocol0 = DECODE(src);
|
||||
}
|
||||
|
||||
)";
|
||||
|
||||
std::string palette_ia8_program = StringFromFormat("%s\n%s", "#define DECODE DecodePixel_IA8",
|
||||
PALETTE_CONVERSION_FRAGMENT_SHADER_SOURCE);
|
||||
std::string palette_rgb565_program = StringFromFormat(
|
||||
"%s\n%s", "#define DECODE DecodePixel_RGB565", PALETTE_CONVERSION_FRAGMENT_SHADER_SOURCE);
|
||||
std::string palette_rgb5a3_program = StringFromFormat(
|
||||
"%s\n%s", "#define DECODE DecodePixel_RGB5A3", PALETTE_CONVERSION_FRAGMENT_SHADER_SOURCE);
|
||||
|
||||
m_shaders[GX_TL_IA8] = Util::CompileAndCreateFragmentShader(palette_ia8_program);
|
||||
m_shaders[GX_TL_RGB565] = Util::CompileAndCreateFragmentShader(palette_rgb565_program);
|
||||
m_shaders[GX_TL_RGB5A3] = Util::CompileAndCreateFragmentShader(palette_rgb5a3_program);
|
||||
|
||||
return (m_shaders[GX_TL_IA8] != VK_NULL_HANDLE && m_shaders[GX_TL_RGB565] != VK_NULL_HANDLE &&
|
||||
m_shaders[GX_TL_RGB5A3] != VK_NULL_HANDLE);
|
||||
}
|
||||
|
||||
bool PaletteTextureConverter::CreateDescriptorLayout()
|
||||
{
|
||||
static const VkDescriptorSetLayoutBinding set_bindings[] = {
|
||||
{0, VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1, VK_SHADER_STAGE_FRAGMENT_BIT},
|
||||
};
|
||||
static const VkDescriptorSetLayoutCreateInfo set_info = {
|
||||
VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, nullptr, 0,
|
||||
static_cast<u32>(ArraySize(set_bindings)), set_bindings};
|
||||
|
||||
VkResult res = vkCreateDescriptorSetLayout(g_vulkan_context->GetDevice(), &set_info, nullptr,
|
||||
&m_palette_set_layout);
|
||||
if (res != VK_SUCCESS)
|
||||
{
|
||||
LOG_VULKAN_ERROR(res, "vkCreateDescriptorSetLayout failed: ");
|
||||
return false;
|
||||
}
|
||||
|
||||
VkDescriptorSetLayout sets[] = {m_palette_set_layout, g_object_cache->GetDescriptorSetLayout(
|
||||
DESCRIPTOR_SET_PIXEL_SHADER_SAMPLERS)};
|
||||
|
||||
VkPushConstantRange push_constant_range = {
|
||||
VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, PUSH_CONSTANT_BUFFER_SIZE};
|
||||
|
||||
VkPipelineLayoutCreateInfo pipeline_layout_info = {VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
|
||||
nullptr,
|
||||
0,
|
||||
static_cast<u32>(ArraySize(sets)),
|
||||
sets,
|
||||
1,
|
||||
&push_constant_range};
|
||||
|
||||
res = vkCreatePipelineLayout(g_vulkan_context->GetDevice(), &pipeline_layout_info, nullptr,
|
||||
&m_pipeline_layout);
|
||||
if (res != VK_SUCCESS)
|
||||
{
|
||||
LOG_VULKAN_ERROR(res, "vkCreatePipelineLayout failed: ");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright 2016 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "VideoBackends/Vulkan/StreamBuffer.h"
|
||||
|
||||
#include "VideoCommon/TextureDecoder.h"
|
||||
|
||||
namespace Vulkan
|
||||
{
|
||||
class StateTracker;
|
||||
class Texture2D;
|
||||
|
||||
// Since this converter uses a uniform texel buffer, we can't use the general pipeline generators.
|
||||
|
||||
class PaletteTextureConverter
|
||||
{
|
||||
public:
|
||||
PaletteTextureConverter();
|
||||
~PaletteTextureConverter();
|
||||
|
||||
bool Initialize();
|
||||
|
||||
void ConvertTexture(StateTracker* state_tracker, VkRenderPass render_pass,
|
||||
VkFramebuffer dst_framebuffer, Texture2D* src_texture, u32 width, u32 height,
|
||||
void* palette, TlutFormat format);
|
||||
|
||||
private:
|
||||
static const size_t NUM_PALETTE_CONVERSION_SHADERS = 3;
|
||||
|
||||
bool CreateBuffers();
|
||||
bool CompileShaders();
|
||||
bool CreateDescriptorLayout();
|
||||
|
||||
VkDescriptorSetLayout m_palette_set_layout = VK_NULL_HANDLE;
|
||||
VkPipelineLayout m_pipeline_layout = VK_NULL_HANDLE;
|
||||
|
||||
std::array<VkShaderModule, NUM_PALETTE_CONVERSION_SHADERS> m_shaders = {};
|
||||
|
||||
std::unique_ptr<StreamBuffer> m_palette_stream_buffer;
|
||||
VkBufferView m_palette_buffer_view = VK_NULL_HANDLE;
|
||||
|
||||
std::unique_ptr<StreamBuffer> m_uniform_buffer;
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
||||
@@ -0,0 +1,370 @@
|
||||
// Copyright 2016 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
|
||||
#include "Common/Assert.h"
|
||||
|
||||
#include "VideoBackends/Vulkan/CommandBufferManager.h"
|
||||
#include "VideoBackends/Vulkan/PerfQuery.h"
|
||||
#include "VideoBackends/Vulkan/Renderer.h"
|
||||
#include "VideoBackends/Vulkan/StagingBuffer.h"
|
||||
#include "VideoBackends/Vulkan/StateTracker.h"
|
||||
#include "VideoBackends/Vulkan/Util.h"
|
||||
#include "VideoBackends/Vulkan/VulkanContext.h"
|
||||
|
||||
namespace Vulkan
|
||||
{
|
||||
PerfQuery::PerfQuery()
|
||||
{
|
||||
}
|
||||
|
||||
PerfQuery::~PerfQuery()
|
||||
{
|
||||
g_command_buffer_mgr->RemoveFencePointCallback(this);
|
||||
|
||||
if (m_query_pool != VK_NULL_HANDLE)
|
||||
vkDestroyQueryPool(g_vulkan_context->GetDevice(), m_query_pool, nullptr);
|
||||
}
|
||||
|
||||
bool PerfQuery::Initialize(StateTracker* state_tracker)
|
||||
{
|
||||
m_state_tracker = state_tracker;
|
||||
|
||||
if (!CreateQueryPool())
|
||||
{
|
||||
PanicAlert("Failed to create query pool");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CreateReadbackBuffer())
|
||||
{
|
||||
PanicAlert("Failed to create readback buffer");
|
||||
return false;
|
||||
}
|
||||
|
||||
g_command_buffer_mgr->AddFencePointCallback(
|
||||
this, std::bind(&PerfQuery::OnCommandBufferQueued, this, std::placeholders::_1,
|
||||
std::placeholders::_2),
|
||||
std::bind(&PerfQuery::OnCommandBufferExecuted, this, std::placeholders::_1));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PerfQuery::EnableQuery(PerfQueryGroup type)
|
||||
{
|
||||
// Have we used half of the query buffer already?
|
||||
if (m_query_count > m_query_buffer.size() / 2)
|
||||
NonBlockingPartialFlush();
|
||||
|
||||
// Block if there are no free slots.
|
||||
if (m_query_count == PERF_QUERY_BUFFER_SIZE)
|
||||
{
|
||||
// ERROR_LOG(VIDEO, "Flushed query buffer early!");
|
||||
BlockingPartialFlush();
|
||||
}
|
||||
|
||||
if (type == PQG_ZCOMP_ZCOMPLOC || type == PQG_ZCOMP)
|
||||
{
|
||||
u32 index = (m_query_read_pos + m_query_count) % PERF_QUERY_BUFFER_SIZE;
|
||||
ActiveQuery& entry = m_query_buffer[index];
|
||||
_assert_(!entry.active && !entry.available);
|
||||
entry.active = true;
|
||||
m_query_count++;
|
||||
|
||||
DEBUG_LOG(VIDEO, "start query %u", index);
|
||||
|
||||
// Use precise queries if supported, otherwise boolean (which will be incorrect).
|
||||
VkQueryControlFlags flags = 0;
|
||||
if (g_vulkan_context->SupportsPreciseOcclusionQueries())
|
||||
flags = VK_QUERY_CONTROL_PRECISE_BIT;
|
||||
|
||||
// Ensure the query starts within a render pass.
|
||||
// TODO: Is this needed?
|
||||
m_state_tracker->BeginRenderPass();
|
||||
vkCmdBeginQuery(g_command_buffer_mgr->GetCurrentCommandBuffer(), m_query_pool, index, flags);
|
||||
|
||||
// Prevent background command buffer submission while the query is active.
|
||||
m_state_tracker->SetBackgroundCommandBufferExecution(false);
|
||||
}
|
||||
}
|
||||
|
||||
void PerfQuery::DisableQuery(PerfQueryGroup type)
|
||||
{
|
||||
if (type == PQG_ZCOMP_ZCOMPLOC || type == PQG_ZCOMP)
|
||||
{
|
||||
// DisableQuery should be called for each EnableQuery, so subtract one to get the previous one.
|
||||
u32 index = (m_query_read_pos + m_query_count - 1) % PERF_QUERY_BUFFER_SIZE;
|
||||
vkCmdEndQuery(g_command_buffer_mgr->GetCurrentCommandBuffer(), m_query_pool, index);
|
||||
m_state_tracker->SetBackgroundCommandBufferExecution(true);
|
||||
DEBUG_LOG(VIDEO, "end query %u", index);
|
||||
}
|
||||
}
|
||||
|
||||
void PerfQuery::ResetQuery()
|
||||
{
|
||||
m_query_count = 0;
|
||||
m_query_read_pos = 0;
|
||||
std::fill_n(m_results, ArraySize(m_results), 0);
|
||||
|
||||
// Reset entire query pool, ensuring all queries are ready to write to.
|
||||
m_state_tracker->EndRenderPass();
|
||||
vkCmdResetQueryPool(g_command_buffer_mgr->GetCurrentCommandBuffer(), m_query_pool, 0,
|
||||
PERF_QUERY_BUFFER_SIZE);
|
||||
|
||||
for (auto& entry : m_query_buffer)
|
||||
{
|
||||
entry.pending_fence = VK_NULL_HANDLE;
|
||||
entry.available = false;
|
||||
entry.active = false;
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
||||
BlockingPartialFlush();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
bool PerfQuery::CreateReadbackBuffer()
|
||||
{
|
||||
m_readback_buffer = StagingBuffer::Create(STAGING_BUFFER_TYPE_READBACK,
|
||||
PERF_QUERY_BUFFER_SIZE * sizeof(PerfQueryDataType),
|
||||
VK_BUFFER_USAGE_TRANSFER_DST_BIT);
|
||||
|
||||
// Leave the buffer persistently mapped, we invalidate it when we need to read.
|
||||
if (!m_readback_buffer || !m_readback_buffer->Map())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PerfQuery::QueueCopyQueryResults(VkCommandBuffer command_buffer, VkFence fence,
|
||||
u32 start_index, u32 query_count)
|
||||
{
|
||||
DEBUG_LOG(VIDEO, "queue copy of queries %u-%u", start_index, start_index + query_count - 1);
|
||||
|
||||
// Transition buffer for GPU write
|
||||
// TODO: Is this needed?
|
||||
m_readback_buffer->PrepareForGPUWrite(command_buffer, VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT);
|
||||
|
||||
// Copy from queries -> buffer
|
||||
vkCmdCopyQueryPoolResults(command_buffer, m_query_pool, start_index, query_count,
|
||||
m_readback_buffer->GetBuffer(), start_index * sizeof(PerfQueryDataType),
|
||||
sizeof(PerfQueryDataType), VK_QUERY_RESULT_WAIT_BIT);
|
||||
|
||||
// Prepare for host readback
|
||||
m_readback_buffer->FlushGPUCache(command_buffer, VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT);
|
||||
|
||||
// Reset queries so they're ready to use again
|
||||
vkCmdResetQueryPool(command_buffer, m_query_pool, start_index, query_count);
|
||||
|
||||
// Flag all queries as available, but with a fence that has to be completed first
|
||||
for (u32 i = 0; i < query_count; i++)
|
||||
{
|
||||
u32 index = start_index + i;
|
||||
ActiveQuery& entry = m_query_buffer[index];
|
||||
entry.pending_fence = fence;
|
||||
entry.available = true;
|
||||
entry.active = false;
|
||||
}
|
||||
}
|
||||
|
||||
void PerfQuery::OnCommandBufferQueued(VkCommandBuffer command_buffer, VkFence fence)
|
||||
{
|
||||
// Flag all pending queries that aren't available as available after execution.
|
||||
u32 copy_start_index = 0;
|
||||
u32 copy_count = 0;
|
||||
for (u32 i = 0; i < m_query_count; i++)
|
||||
{
|
||||
u32 index = (m_query_read_pos + i) % PERF_QUERY_BUFFER_SIZE;
|
||||
ActiveQuery& entry = m_query_buffer[index];
|
||||
|
||||
// Skip already-copied queries (will happen if a flush hasn't occurred and
|
||||
// a command buffer hasn't finished executing).
|
||||
if (entry.available)
|
||||
{
|
||||
// These should be grouped together, and at the start.
|
||||
_assert_(copy_count == 0);
|
||||
continue;
|
||||
}
|
||||
|
||||
// If this wrapped around, we need to flush the entries before the end of the buffer.
|
||||
_assert_(entry.active);
|
||||
if (index < copy_start_index)
|
||||
{
|
||||
QueueCopyQueryResults(command_buffer, fence, copy_start_index, copy_count);
|
||||
copy_start_index = index;
|
||||
copy_count = 0;
|
||||
}
|
||||
else if (copy_count == 0)
|
||||
{
|
||||
copy_start_index = index;
|
||||
}
|
||||
copy_count++;
|
||||
}
|
||||
|
||||
if (copy_count > 0)
|
||||
QueueCopyQueryResults(command_buffer, fence, copy_start_index, copy_count);
|
||||
}
|
||||
|
||||
void PerfQuery::OnCommandBufferExecuted(VkFence fence)
|
||||
{
|
||||
// Need to save these since ProcessResults will modify them.
|
||||
u32 query_read_pos = m_query_read_pos;
|
||||
u32 query_count = m_query_count;
|
||||
|
||||
// Flush as many queries as are bound to this fence.
|
||||
u32 flush_start_index = 0;
|
||||
u32 flush_count = 0;
|
||||
for (u32 i = 0; i < query_count; i++)
|
||||
{
|
||||
u32 index = (query_read_pos + i) % PERF_QUERY_BUFFER_SIZE;
|
||||
if (m_query_buffer[index].pending_fence != fence)
|
||||
{
|
||||
// These should be grouped together, at the end.
|
||||
break;
|
||||
}
|
||||
|
||||
// If this wrapped around, we need to flush the entries before the end of the buffer.
|
||||
if (index < flush_start_index)
|
||||
{
|
||||
ProcessResults(flush_start_index, flush_count);
|
||||
flush_start_index = index;
|
||||
flush_count = 0;
|
||||
}
|
||||
else if (flush_count == 0)
|
||||
{
|
||||
flush_start_index = index;
|
||||
}
|
||||
flush_count++;
|
||||
}
|
||||
|
||||
if (flush_count > 0)
|
||||
ProcessResults(flush_start_index, flush_count);
|
||||
}
|
||||
|
||||
void PerfQuery::ProcessResults(u32 start_index, u32 query_count)
|
||||
{
|
||||
// Invalidate CPU caches before reading back.
|
||||
m_readback_buffer->InvalidateCPUCache(start_index * sizeof(PerfQueryDataType),
|
||||
query_count * sizeof(PerfQueryDataType));
|
||||
|
||||
// Should be at maximum query_count queries pending.
|
||||
_assert_(query_count <= m_query_count);
|
||||
DEBUG_LOG(VIDEO, "process queries %u-%u", start_index, start_index + query_count - 1);
|
||||
|
||||
// Remove pending queries.
|
||||
for (u32 i = 0; i < query_count; i++)
|
||||
{
|
||||
u32 index = (m_query_read_pos + i) % PERF_QUERY_BUFFER_SIZE;
|
||||
ActiveQuery& entry = m_query_buffer[index];
|
||||
|
||||
// Should have a fence associated with it (waiting for a result).
|
||||
_assert_(entry.pending_fence != VK_NULL_HANDLE);
|
||||
entry.pending_fence = VK_NULL_HANDLE;
|
||||
entry.available = false;
|
||||
entry.active = false;
|
||||
|
||||
// Grab result from readback buffer, it will already have been invalidated.
|
||||
u32 result;
|
||||
m_readback_buffer->Read(index * sizeof(PerfQueryDataType), &result, sizeof(result), false);
|
||||
DEBUG_LOG(VIDEO, " query result %u", result);
|
||||
|
||||
// NOTE: Reported pixel metrics should be referenced to native resolution
|
||||
m_results[entry.query_type] +=
|
||||
static_cast<u32>(static_cast<u64>(result) * EFB_WIDTH / g_renderer->GetTargetWidth() *
|
||||
EFB_HEIGHT / g_renderer->GetTargetHeight());
|
||||
}
|
||||
|
||||
m_query_read_pos = (m_query_read_pos + query_count) % PERF_QUERY_BUFFER_SIZE;
|
||||
m_query_count -= query_count;
|
||||
}
|
||||
|
||||
void PerfQuery::NonBlockingPartialFlush()
|
||||
{
|
||||
if (IsFlushed())
|
||||
return;
|
||||
|
||||
// Submit a command buffer in the background if the front query is not bound to one.
|
||||
// Ideally this will complete before the buffer fills.
|
||||
if (m_query_buffer[m_query_read_pos].pending_fence == VK_NULL_HANDLE)
|
||||
Util::ExecuteCurrentCommandsAndRestoreState(m_state_tracker, true, false);
|
||||
}
|
||||
|
||||
void PerfQuery::BlockingPartialFlush()
|
||||
{
|
||||
if (IsFlushed())
|
||||
return;
|
||||
|
||||
// If the first pending query is needing command buffer execution, do that.
|
||||
ActiveQuery& entry = m_query_buffer[m_query_read_pos];
|
||||
if (entry.pending_fence == VK_NULL_HANDLE)
|
||||
{
|
||||
// This will callback OnCommandBufferQueued which will set the fence on the entry.
|
||||
// We wait for completion, which will also call OnCommandBufferExecuted, and clear the fence.
|
||||
Util::ExecuteCurrentCommandsAndRestoreState(m_state_tracker, false, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
// The command buffer has been submitted, but is awaiting completion.
|
||||
// Wait for the fence to complete, which will call OnCommandBufferExecuted.
|
||||
g_command_buffer_mgr->WaitForFence(entry.pending_fence);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
// Copyright 2016 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
|
||||
#include "VideoBackends/Vulkan/Constants.h"
|
||||
#include "VideoCommon/PerfQueryBase.h"
|
||||
|
||||
namespace Vulkan
|
||||
{
|
||||
class StagingBuffer;
|
||||
class StateTracker;
|
||||
|
||||
class PerfQuery : public PerfQueryBase
|
||||
{
|
||||
public:
|
||||
PerfQuery();
|
||||
~PerfQuery();
|
||||
|
||||
bool Initialize(StateTracker* state_tracker);
|
||||
|
||||
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:
|
||||
struct ActiveQuery
|
||||
{
|
||||
PerfQueryType query_type;
|
||||
VkFence pending_fence;
|
||||
bool available;
|
||||
bool active;
|
||||
};
|
||||
|
||||
bool CreateQueryPool();
|
||||
bool CreateReadbackBuffer();
|
||||
void QueueCopyQueryResults(VkCommandBuffer command_buffer, VkFence fence, u32 start_index,
|
||||
u32 query_count);
|
||||
void ProcessResults(u32 start_index, u32 query_count);
|
||||
|
||||
void OnCommandBufferQueued(VkCommandBuffer command_buffer, VkFence fence);
|
||||
void OnCommandBufferExecuted(VkFence fence);
|
||||
|
||||
void NonBlockingPartialFlush();
|
||||
void BlockingPartialFlush();
|
||||
|
||||
StateTracker* m_state_tracker = nullptr;
|
||||
|
||||
// when testing in SMS: 64 was too small, 128 was ok
|
||||
// TODO: This should be size_t, but the base class uses u32s
|
||||
using PerfQueryDataType = u32;
|
||||
static const u32 PERF_QUERY_BUFFER_SIZE = 512;
|
||||
std::array<ActiveQuery, PERF_QUERY_BUFFER_SIZE> m_query_buffer = {};
|
||||
u32 m_query_read_pos = 0;
|
||||
|
||||
// TODO: Investigate using pipeline statistics to implement other query types
|
||||
VkQueryPool m_query_pool = VK_NULL_HANDLE;
|
||||
|
||||
// Buffer containing query results. Each query is a u32.
|
||||
std::unique_ptr<StagingBuffer> m_readback_buffer;
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
||||
@@ -0,0 +1,408 @@
|
||||
// Copyright 2016 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "VideoBackends/Vulkan/CommandBufferManager.h"
|
||||
#include "VideoBackends/Vulkan/RasterFont.h"
|
||||
#include "VideoBackends/Vulkan/Texture2D.h"
|
||||
#include "VideoBackends/Vulkan/Util.h"
|
||||
#include "VideoBackends/Vulkan/VulkanContext.h"
|
||||
|
||||
// Based on OGL RasterFont
|
||||
// TODO: We should move this to common.
|
||||
|
||||
namespace Vulkan
|
||||
{
|
||||
constexpr int CHAR_WIDTH = 8;
|
||||
constexpr int CHAR_HEIGHT = 13;
|
||||
constexpr int CHAR_OFFSET = 32;
|
||||
constexpr int CHAR_COUNT = 95;
|
||||
|
||||
static const u8 rasters[CHAR_COUNT][CHAR_HEIGHT] = {
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
|
||||
{0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18},
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x36, 0x36, 0x36},
|
||||
{0x00, 0x00, 0x00, 0x66, 0x66, 0xff, 0x66, 0x66, 0xff, 0x66, 0x66, 0x00, 0x00},
|
||||
{0x00, 0x00, 0x18, 0x7e, 0xff, 0x1b, 0x1f, 0x7e, 0xf8, 0xd8, 0xff, 0x7e, 0x18},
|
||||
{0x00, 0x00, 0x0e, 0x1b, 0xdb, 0x6e, 0x30, 0x18, 0x0c, 0x76, 0xdb, 0xd8, 0x70},
|
||||
{0x00, 0x00, 0x7f, 0xc6, 0xcf, 0xd8, 0x70, 0x70, 0xd8, 0xcc, 0xcc, 0x6c, 0x38},
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x1c, 0x0c, 0x0e},
|
||||
{0x00, 0x00, 0x0c, 0x18, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x18, 0x0c},
|
||||
{0x00, 0x00, 0x30, 0x18, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x18, 0x30},
|
||||
{0x00, 0x00, 0x00, 0x00, 0x99, 0x5a, 0x3c, 0xff, 0x3c, 0x5a, 0x99, 0x00, 0x00},
|
||||
{0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0xff, 0xff, 0x18, 0x18, 0x18, 0x00, 0x00},
|
||||
{0x00, 0x00, 0x30, 0x18, 0x1c, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00},
|
||||
{0x00, 0x00, 0x00, 0x38, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
|
||||
{0x00, 0x60, 0x60, 0x30, 0x30, 0x18, 0x18, 0x0c, 0x0c, 0x06, 0x06, 0x03, 0x03},
|
||||
{0x00, 0x00, 0x3c, 0x66, 0xc3, 0xe3, 0xf3, 0xdb, 0xcf, 0xc7, 0xc3, 0x66, 0x3c},
|
||||
{0x00, 0x00, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x78, 0x38, 0x18},
|
||||
{0x00, 0x00, 0xff, 0xc0, 0xc0, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x03, 0xe7, 0x7e},
|
||||
{0x00, 0x00, 0x7e, 0xe7, 0x03, 0x03, 0x07, 0x7e, 0x07, 0x03, 0x03, 0xe7, 0x7e},
|
||||
{0x00, 0x00, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0xff, 0xcc, 0x6c, 0x3c, 0x1c, 0x0c},
|
||||
{0x00, 0x00, 0x7e, 0xe7, 0x03, 0x03, 0x07, 0xfe, 0xc0, 0xc0, 0xc0, 0xc0, 0xff},
|
||||
{0x00, 0x00, 0x7e, 0xe7, 0xc3, 0xc3, 0xc7, 0xfe, 0xc0, 0xc0, 0xc0, 0xe7, 0x7e},
|
||||
{0x00, 0x00, 0x30, 0x30, 0x30, 0x30, 0x18, 0x0c, 0x06, 0x03, 0x03, 0x03, 0xff},
|
||||
{0x00, 0x00, 0x7e, 0xe7, 0xc3, 0xc3, 0xe7, 0x7e, 0xe7, 0xc3, 0xc3, 0xe7, 0x7e},
|
||||
{0x00, 0x00, 0x7e, 0xe7, 0x03, 0x03, 0x03, 0x7f, 0xe7, 0xc3, 0xc3, 0xe7, 0x7e},
|
||||
{0x00, 0x00, 0x00, 0x38, 0x38, 0x00, 0x00, 0x38, 0x38, 0x00, 0x00, 0x00, 0x00},
|
||||
{0x00, 0x00, 0x30, 0x18, 0x1c, 0x1c, 0x00, 0x00, 0x1c, 0x1c, 0x00, 0x00, 0x00},
|
||||
{0x00, 0x00, 0x06, 0x0c, 0x18, 0x30, 0x60, 0xc0, 0x60, 0x30, 0x18, 0x0c, 0x06},
|
||||
{0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00},
|
||||
{0x00, 0x00, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x03, 0x06, 0x0c, 0x18, 0x30, 0x60},
|
||||
{0x00, 0x00, 0x18, 0x00, 0x00, 0x18, 0x18, 0x0c, 0x06, 0x03, 0xc3, 0xc3, 0x7e},
|
||||
{0x00, 0x00, 0x3f, 0x60, 0xcf, 0xdb, 0xd3, 0xdd, 0xc3, 0x7e, 0x00, 0x00, 0x00},
|
||||
{0x00, 0x00, 0xc3, 0xc3, 0xc3, 0xc3, 0xff, 0xc3, 0xc3, 0xc3, 0x66, 0x3c, 0x18},
|
||||
{0x00, 0x00, 0xfe, 0xc7, 0xc3, 0xc3, 0xc7, 0xfe, 0xc7, 0xc3, 0xc3, 0xc7, 0xfe},
|
||||
{0x00, 0x00, 0x7e, 0xe7, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xe7, 0x7e},
|
||||
{0x00, 0x00, 0xfc, 0xce, 0xc7, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc7, 0xce, 0xfc},
|
||||
{0x00, 0x00, 0xff, 0xc0, 0xc0, 0xc0, 0xc0, 0xfc, 0xc0, 0xc0, 0xc0, 0xc0, 0xff},
|
||||
{0x00, 0x00, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xfc, 0xc0, 0xc0, 0xc0, 0xff},
|
||||
{0x00, 0x00, 0x7e, 0xe7, 0xc3, 0xc3, 0xcf, 0xc0, 0xc0, 0xc0, 0xc0, 0xe7, 0x7e},
|
||||
{0x00, 0x00, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xff, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3},
|
||||
{0x00, 0x00, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7e},
|
||||
{0x00, 0x00, 0x7c, 0xee, 0xc6, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06},
|
||||
{0x00, 0x00, 0xc3, 0xc6, 0xcc, 0xd8, 0xf0, 0xe0, 0xf0, 0xd8, 0xcc, 0xc6, 0xc3},
|
||||
{0x00, 0x00, 0xff, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0},
|
||||
{0x00, 0x00, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xdb, 0xff, 0xff, 0xe7, 0xc3},
|
||||
{0x00, 0x00, 0xc7, 0xc7, 0xcf, 0xcf, 0xdf, 0xdb, 0xfb, 0xf3, 0xf3, 0xe3, 0xe3},
|
||||
{0x00, 0x00, 0x7e, 0xe7, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xe7, 0x7e},
|
||||
{0x00, 0x00, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xfe, 0xc7, 0xc3, 0xc3, 0xc7, 0xfe},
|
||||
{0x00, 0x00, 0x3f, 0x6e, 0xdf, 0xdb, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0x66, 0x3c},
|
||||
{0x00, 0x00, 0xc3, 0xc6, 0xcc, 0xd8, 0xf0, 0xfe, 0xc7, 0xc3, 0xc3, 0xc7, 0xfe},
|
||||
{0x00, 0x00, 0x7e, 0xe7, 0x03, 0x03, 0x07, 0x7e, 0xe0, 0xc0, 0xc0, 0xe7, 0x7e},
|
||||
{0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xff},
|
||||
{0x00, 0x00, 0x7e, 0xe7, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3},
|
||||
{0x00, 0x00, 0x18, 0x3c, 0x3c, 0x66, 0x66, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3},
|
||||
{0x00, 0x00, 0xc3, 0xe7, 0xff, 0xff, 0xdb, 0xdb, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3},
|
||||
{0x00, 0x00, 0xc3, 0x66, 0x66, 0x3c, 0x3c, 0x18, 0x3c, 0x3c, 0x66, 0x66, 0xc3},
|
||||
{0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x3c, 0x66, 0x66, 0xc3},
|
||||
{0x00, 0x00, 0xff, 0xc0, 0xc0, 0x60, 0x30, 0x7e, 0x0c, 0x06, 0x03, 0x03, 0xff},
|
||||
{0x00, 0x00, 0x3c, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3c},
|
||||
{0x00, 0x03, 0x03, 0x06, 0x06, 0x0c, 0x0c, 0x18, 0x18, 0x30, 0x30, 0x60, 0x60},
|
||||
{0x00, 0x00, 0x3c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x3c},
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc3, 0x66, 0x3c, 0x18},
|
||||
{0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x38, 0x30, 0x70},
|
||||
{0x00, 0x00, 0x7f, 0xc3, 0xc3, 0x7f, 0x03, 0xc3, 0x7e, 0x00, 0x00, 0x00, 0x00},
|
||||
{0x00, 0x00, 0xfe, 0xc3, 0xc3, 0xc3, 0xc3, 0xfe, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0},
|
||||
{0x00, 0x00, 0x7e, 0xc3, 0xc0, 0xc0, 0xc0, 0xc3, 0x7e, 0x00, 0x00, 0x00, 0x00},
|
||||
{0x00, 0x00, 0x7f, 0xc3, 0xc3, 0xc3, 0xc3, 0x7f, 0x03, 0x03, 0x03, 0x03, 0x03},
|
||||
{0x00, 0x00, 0x7f, 0xc0, 0xc0, 0xfe, 0xc3, 0xc3, 0x7e, 0x00, 0x00, 0x00, 0x00},
|
||||
{0x00, 0x00, 0x30, 0x30, 0x30, 0x30, 0x30, 0xfc, 0x30, 0x30, 0x30, 0x33, 0x1e},
|
||||
{0x7e, 0xc3, 0x03, 0x03, 0x7f, 0xc3, 0xc3, 0xc3, 0x7e, 0x00, 0x00, 0x00, 0x00},
|
||||
{0x00, 0x00, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xfe, 0xc0, 0xc0, 0xc0, 0xc0},
|
||||
{0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x18, 0x00},
|
||||
{0x38, 0x6c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x00, 0x00, 0x0c, 0x00},
|
||||
{0x00, 0x00, 0xc6, 0xcc, 0xf8, 0xf0, 0xd8, 0xcc, 0xc6, 0xc0, 0xc0, 0xc0, 0xc0},
|
||||
{0x00, 0x00, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x78},
|
||||
{0x00, 0x00, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xfe, 0x00, 0x00, 0x00, 0x00},
|
||||
{0x00, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xfc, 0x00, 0x00, 0x00, 0x00},
|
||||
{0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00},
|
||||
{0xc0, 0xc0, 0xc0, 0xfe, 0xc3, 0xc3, 0xc3, 0xc3, 0xfe, 0x00, 0x00, 0x00, 0x00},
|
||||
{0x03, 0x03, 0x03, 0x7f, 0xc3, 0xc3, 0xc3, 0xc3, 0x7f, 0x00, 0x00, 0x00, 0x00},
|
||||
{0x00, 0x00, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xe0, 0xfe, 0x00, 0x00, 0x00, 0x00},
|
||||
{0x00, 0x00, 0xfe, 0x03, 0x03, 0x7e, 0xc0, 0xc0, 0x7f, 0x00, 0x00, 0x00, 0x00},
|
||||
{0x00, 0x00, 0x1c, 0x36, 0x30, 0x30, 0x30, 0x30, 0xfc, 0x30, 0x30, 0x30, 0x00},
|
||||
{0x00, 0x00, 0x7e, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00},
|
||||
{0x00, 0x00, 0x18, 0x3c, 0x3c, 0x66, 0x66, 0xc3, 0xc3, 0x00, 0x00, 0x00, 0x00},
|
||||
{0x00, 0x00, 0xc3, 0xe7, 0xff, 0xdb, 0xc3, 0xc3, 0xc3, 0x00, 0x00, 0x00, 0x00},
|
||||
{0x00, 0x00, 0xc3, 0x66, 0x3c, 0x18, 0x3c, 0x66, 0xc3, 0x00, 0x00, 0x00, 0x00},
|
||||
{0xc0, 0x60, 0x60, 0x30, 0x18, 0x3c, 0x66, 0x66, 0xc3, 0x00, 0x00, 0x00, 0x00},
|
||||
{0x00, 0x00, 0xff, 0x60, 0x30, 0x18, 0x0c, 0x06, 0xff, 0x00, 0x00, 0x00, 0x00},
|
||||
{0x00, 0x00, 0x0f, 0x18, 0x18, 0x18, 0x38, 0xf0, 0x38, 0x18, 0x18, 0x18, 0x0f},
|
||||
{0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18},
|
||||
{0x00, 0x00, 0xf0, 0x18, 0x18, 0x18, 0x1c, 0x0f, 0x1c, 0x18, 0x18, 0x18, 0xf0},
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x8f, 0xf1, 0x60, 0x00, 0x00, 0x00}};
|
||||
|
||||
static const char VERTEX_SHADER_SOURCE[] = R"(
|
||||
|
||||
layout(std140, push_constant) uniform PCBlock {
|
||||
vec2 char_size;
|
||||
vec2 offset;
|
||||
vec4 color;
|
||||
} PC;
|
||||
|
||||
layout(location = 0) in vec4 ipos;
|
||||
layout(location = 5) in vec4 icol0;
|
||||
layout(location = 8) in vec3 itex0;
|
||||
|
||||
layout(location = 0) out vec2 uv0;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position = vec4(ipos.xy + PC.offset, 0.0f, 1.0f);
|
||||
gl_Position.y = -gl_Position.y;
|
||||
uv0 = itex0.xy * PC.char_size;
|
||||
}
|
||||
|
||||
)";
|
||||
|
||||
static const char FRAGMENT_SHADER_SOURCE[] = R"(
|
||||
|
||||
layout(std140, push_constant) uniform PCBlock {
|
||||
vec2 char_size;
|
||||
vec2 offset;
|
||||
vec4 color;
|
||||
} PC;
|
||||
|
||||
layout(set = 1, binding = 0) uniform sampler2D samp0;
|
||||
|
||||
layout(location = 0) in vec2 uv0;
|
||||
|
||||
layout(location = 0) out vec4 ocol0;
|
||||
|
||||
void main()
|
||||
{
|
||||
ocol0 = texture(samp0, uv0) * PC.color;
|
||||
}
|
||||
|
||||
)";
|
||||
|
||||
RasterFont::RasterFont()
|
||||
{
|
||||
}
|
||||
|
||||
RasterFont::~RasterFont()
|
||||
{
|
||||
if (m_vertex_shader != VK_NULL_HANDLE)
|
||||
g_command_buffer_mgr->DeferResourceDestruction(m_vertex_shader);
|
||||
if (m_fragment_shader != VK_NULL_HANDLE)
|
||||
g_command_buffer_mgr->DeferResourceDestruction(m_fragment_shader);
|
||||
}
|
||||
|
||||
bool RasterFont::Initialize()
|
||||
{
|
||||
// Create shaders and texture
|
||||
if (!CreateShaders() || !CreateTexture())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RasterFont::CreateTexture()
|
||||
{
|
||||
// generate the texture
|
||||
std::vector<u32> texture_data(CHAR_WIDTH * CHAR_COUNT * CHAR_HEIGHT);
|
||||
for (int y = 0; y < CHAR_HEIGHT; y++)
|
||||
{
|
||||
for (int c = 0; c < CHAR_COUNT; c++)
|
||||
{
|
||||
for (int x = 0; x < CHAR_WIDTH; x++)
|
||||
{
|
||||
bool pixel = (0 != (rasters[c][y] & (1 << (CHAR_WIDTH - x - 1))));
|
||||
texture_data[CHAR_WIDTH * CHAR_COUNT * y + CHAR_WIDTH * c + x] = pixel ? -1 : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// create the actual texture object
|
||||
m_texture =
|
||||
Texture2D::Create(CHAR_WIDTH * CHAR_COUNT, CHAR_HEIGHT, 1, 1, VK_FORMAT_R8G8B8A8_UNORM,
|
||||
VK_SAMPLE_COUNT_1_BIT, VK_IMAGE_VIEW_TYPE_2D, VK_IMAGE_TILING_OPTIMAL,
|
||||
VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT);
|
||||
if (!m_texture)
|
||||
return false;
|
||||
|
||||
// create temporary buffer for uploading texture
|
||||
VkBufferCreateInfo buffer_info = {VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
|
||||
nullptr,
|
||||
0,
|
||||
static_cast<VkDeviceSize>(texture_data.size() * sizeof(u32)),
|
||||
VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
|
||||
VK_SHARING_MODE_EXCLUSIVE,
|
||||
0,
|
||||
nullptr};
|
||||
VkBuffer temp_buffer;
|
||||
VkResult res = vkCreateBuffer(g_vulkan_context->GetDevice(), &buffer_info, nullptr, &temp_buffer);
|
||||
if (res != VK_SUCCESS)
|
||||
{
|
||||
LOG_VULKAN_ERROR(res, "vkCreateBuffer failed: ");
|
||||
return false;
|
||||
}
|
||||
|
||||
VkMemoryRequirements memory_requirements;
|
||||
vkGetBufferMemoryRequirements(g_vulkan_context->GetDevice(), temp_buffer, &memory_requirements);
|
||||
uint32_t memory_type_index = g_vulkan_context->GetMemoryType(memory_requirements.memoryTypeBits,
|
||||
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT);
|
||||
VkMemoryAllocateInfo memory_allocate_info = {VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, nullptr,
|
||||
memory_requirements.size, memory_type_index};
|
||||
VkDeviceMemory temp_buffer_memory;
|
||||
res = vkAllocateMemory(g_vulkan_context->GetDevice(), &memory_allocate_info, nullptr,
|
||||
&temp_buffer_memory);
|
||||
if (res != VK_SUCCESS)
|
||||
{
|
||||
LOG_VULKAN_ERROR(res, "vkAllocateMemory failed: ");
|
||||
vkDestroyBuffer(g_vulkan_context->GetDevice(), temp_buffer, nullptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Bind buffer to memory
|
||||
res = vkBindBufferMemory(g_vulkan_context->GetDevice(), temp_buffer, temp_buffer_memory, 0);
|
||||
if (res != VK_SUCCESS)
|
||||
{
|
||||
LOG_VULKAN_ERROR(res, "vkBindBufferMemory failed: ");
|
||||
vkDestroyBuffer(g_vulkan_context->GetDevice(), temp_buffer, nullptr);
|
||||
vkFreeMemory(g_vulkan_context->GetDevice(), temp_buffer_memory, nullptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Copy into buffer
|
||||
void* mapped_ptr;
|
||||
res = vkMapMemory(g_vulkan_context->GetDevice(), temp_buffer_memory, 0, buffer_info.size, 0,
|
||||
&mapped_ptr);
|
||||
if (res != VK_SUCCESS)
|
||||
{
|
||||
LOG_VULKAN_ERROR(res, "vkMapMemory failed: ");
|
||||
vkDestroyBuffer(g_vulkan_context->GetDevice(), temp_buffer, nullptr);
|
||||
vkFreeMemory(g_vulkan_context->GetDevice(), temp_buffer_memory, nullptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Copy texture data into staging buffer
|
||||
memcpy(mapped_ptr, texture_data.data(), texture_data.size() * sizeof(u32));
|
||||
vkUnmapMemory(g_vulkan_context->GetDevice(), temp_buffer_memory);
|
||||
|
||||
// Copy from staging buffer to the final texture
|
||||
VkBufferImageCopy region = {0, CHAR_WIDTH * CHAR_COUNT,
|
||||
CHAR_HEIGHT, {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1},
|
||||
{0, 0, 0}, {CHAR_WIDTH * CHAR_COUNT, CHAR_HEIGHT, 1}};
|
||||
m_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentInitCommandBuffer(),
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
|
||||
vkCmdCopyBufferToImage(g_command_buffer_mgr->GetCurrentInitCommandBuffer(), temp_buffer,
|
||||
m_texture->GetImage(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
|
||||
|
||||
// Free temp buffers after command buffer executes
|
||||
m_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentInitCommandBuffer(),
|
||||
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
g_command_buffer_mgr->DeferResourceDestruction(temp_buffer);
|
||||
g_command_buffer_mgr->DeferResourceDestruction(temp_buffer_memory);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RasterFont::CreateShaders()
|
||||
{
|
||||
m_vertex_shader = Util::CompileAndCreateVertexShader(VERTEX_SHADER_SOURCE);
|
||||
m_fragment_shader = Util::CompileAndCreateFragmentShader(FRAGMENT_SHADER_SOURCE);
|
||||
return m_vertex_shader != VK_NULL_HANDLE && m_fragment_shader != VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
void RasterFont::PrintMultiLineText(VkRenderPass render_pass, const std::string& text,
|
||||
float start_x, float start_y, u32 bbWidth, u32 bbHeight,
|
||||
u32 color)
|
||||
{
|
||||
// skip empty strings
|
||||
if (text.empty())
|
||||
return;
|
||||
|
||||
UtilityShaderDraw draw(g_command_buffer_mgr->GetCurrentCommandBuffer(),
|
||||
g_object_cache->GetPushConstantPipelineLayout(), render_pass,
|
||||
m_vertex_shader, VK_NULL_HANDLE, m_fragment_shader);
|
||||
|
||||
UtilityShaderVertex* vertices =
|
||||
draw.ReserveVertices(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, text.length() * 6);
|
||||
size_t num_vertices = 0;
|
||||
if (!vertices)
|
||||
return;
|
||||
|
||||
float delta_x = float(2 * CHAR_WIDTH) / float(bbWidth);
|
||||
float delta_y = float(2 * CHAR_HEIGHT) / float(bbHeight);
|
||||
float border_x = 2.0f / float(bbWidth);
|
||||
float border_y = 4.0f / float(bbHeight);
|
||||
|
||||
float x = float(start_x);
|
||||
float y = float(start_y);
|
||||
|
||||
for (const char& c : text)
|
||||
{
|
||||
if (c == '\n')
|
||||
{
|
||||
x = float(start_x);
|
||||
y -= delta_y + border_y;
|
||||
continue;
|
||||
}
|
||||
|
||||
// do not print spaces, they can be skipped easily
|
||||
if (c == ' ')
|
||||
{
|
||||
x += delta_x + border_x;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c < CHAR_OFFSET || c >= CHAR_COUNT + CHAR_OFFSET)
|
||||
continue;
|
||||
|
||||
vertices[num_vertices].SetPosition(x, y);
|
||||
vertices[num_vertices].SetTextureCoordinates(static_cast<float>(c - CHAR_OFFSET), 0.0f);
|
||||
num_vertices++;
|
||||
|
||||
vertices[num_vertices].SetPosition(x + delta_x, y);
|
||||
vertices[num_vertices].SetTextureCoordinates(static_cast<float>(c - CHAR_OFFSET + 1), 0.0f);
|
||||
num_vertices++;
|
||||
|
||||
vertices[num_vertices].SetPosition(x + delta_x, y + delta_y);
|
||||
vertices[num_vertices].SetTextureCoordinates(static_cast<float>(c - CHAR_OFFSET + 1), 1.0f);
|
||||
num_vertices++;
|
||||
|
||||
vertices[num_vertices].SetPosition(x, y);
|
||||
vertices[num_vertices].SetTextureCoordinates(static_cast<float>(c - CHAR_OFFSET), 0.0f);
|
||||
num_vertices++;
|
||||
|
||||
vertices[num_vertices].SetPosition(x + delta_x, y + delta_y);
|
||||
vertices[num_vertices].SetTextureCoordinates(static_cast<float>(c - CHAR_OFFSET + 1), 1.0f);
|
||||
num_vertices++;
|
||||
|
||||
vertices[num_vertices].SetPosition(x, y + delta_y);
|
||||
vertices[num_vertices].SetTextureCoordinates(static_cast<float>(c - CHAR_OFFSET), 1.0f);
|
||||
num_vertices++;
|
||||
|
||||
x += delta_x + border_x;
|
||||
}
|
||||
|
||||
// skip all whitespace strings
|
||||
if (num_vertices == 0)
|
||||
return;
|
||||
|
||||
draw.CommitVertices(num_vertices);
|
||||
|
||||
struct PCBlock
|
||||
{
|
||||
float char_size[2];
|
||||
float offset[2];
|
||||
float color[4];
|
||||
} pc_block = {};
|
||||
|
||||
pc_block.char_size[0] = 1.0f / static_cast<float>(CHAR_COUNT);
|
||||
pc_block.char_size[1] = 1.0f;
|
||||
|
||||
// shadows
|
||||
pc_block.offset[0] = 2.0f / bbWidth;
|
||||
pc_block.offset[1] = -2.0f / bbHeight;
|
||||
pc_block.color[3] = (color >> 24) / 255.0f;
|
||||
|
||||
draw.SetPushConstants(&pc_block, sizeof(pc_block));
|
||||
draw.SetPSSampler(0, m_texture->GetView(), g_object_cache->GetLinearSampler());
|
||||
|
||||
// Setup alpha blending
|
||||
BlendState blend_state = Util::GetNoBlendingBlendState();
|
||||
blend_state.blend_enable = VK_TRUE;
|
||||
blend_state.src_blend = VK_BLEND_FACTOR_SRC_ALPHA;
|
||||
blend_state.blend_op = VK_BLEND_OP_ADD;
|
||||
blend_state.dst_blend = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
|
||||
draw.SetBlendState(blend_state);
|
||||
|
||||
draw.Draw();
|
||||
|
||||
// non-shadowed part
|
||||
pc_block.offset[0] = 0.0f;
|
||||
pc_block.offset[1] = 0.0f;
|
||||
pc_block.color[0] = ((color >> 16) & 0xFF) / 255.0f;
|
||||
pc_block.color[1] = ((color >> 8) & 0xFF) / 255.0f;
|
||||
pc_block.color[2] = (color & 0xFF) / 255.0f;
|
||||
draw.SetPushConstants(&pc_block, sizeof(pc_block));
|
||||
draw.Draw();
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
@@ -0,0 +1,39 @@
|
||||
// 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/Constants.h"
|
||||
|
||||
namespace Vulkan
|
||||
{
|
||||
class Texture2D;
|
||||
|
||||
class RasterFont
|
||||
{
|
||||
public:
|
||||
RasterFont();
|
||||
~RasterFont();
|
||||
|
||||
bool Initialize();
|
||||
|
||||
void PrintMultiLineText(VkRenderPass render_pass, const std::string& text, float start_x,
|
||||
float start_y, u32 bbWidth, u32 bbHeight, u32 color);
|
||||
|
||||
private:
|
||||
bool CreateTexture();
|
||||
bool CreateShaders();
|
||||
|
||||
std::unique_ptr<Texture2D> m_texture;
|
||||
|
||||
VkShaderModule m_vertex_shader = VK_NULL_HANDLE;
|
||||
VkShaderModule m_fragment_shader = VK_NULL_HANDLE;
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user