Browse Source
Merge pull request #4674 from ReinUsesLisp/timeline-semaphores
Merge pull request #4674 from ReinUsesLisp/timeline-semaphores
renderer_vulkan: Make unconditional use of VK_KHR_timeline_semaphorence_cpp
committed by
GitHub
42 changed files with 647 additions and 814 deletions
-
8src/video_core/CMakeLists.txt
-
6src/video_core/query_cache.h
-
8src/video_core/renderer_opengl/gl_query_cache.cpp
-
11src/video_core/renderer_opengl/gl_query_cache.h
-
33src/video_core/renderer_vulkan/renderer_vulkan.cpp
-
5src/video_core/renderer_vulkan/renderer_vulkan.h
-
31src/video_core/renderer_vulkan/vk_blit_screen.cpp
-
16src/video_core/renderer_vulkan/vk_blit_screen.h
-
41src/video_core/renderer_vulkan/vk_command_pool.cpp
-
35src/video_core/renderer_vulkan/vk_command_pool.h
-
23src/video_core/renderer_vulkan/vk_compute_pass.cpp
-
4src/video_core/renderer_vulkan/vk_compute_pass.h
-
2src/video_core/renderer_vulkan/vk_compute_pipeline.cpp
-
19src/video_core/renderer_vulkan/vk_descriptor_pool.cpp
-
15src/video_core/renderer_vulkan/vk_descriptor_pool.h
-
8src/video_core/renderer_vulkan/vk_device.cpp
-
6src/video_core/renderer_vulkan/vk_fence_manager.cpp
-
2src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
-
56src/video_core/renderer_vulkan/vk_master_semaphore.cpp
-
70src/video_core/renderer_vulkan/vk_master_semaphore.h
-
1src/video_core/renderer_vulkan/vk_pipeline_cache.h
-
54src/video_core/renderer_vulkan/vk_query_cache.cpp
-
20src/video_core/renderer_vulkan/vk_query_cache.h
-
19src/video_core/renderer_vulkan/vk_rasterizer.cpp
-
6src/video_core/renderer_vulkan/vk_rasterizer.h
-
311src/video_core/renderer_vulkan/vk_resource_manager.cpp
-
196src/video_core/renderer_vulkan/vk_resource_manager.h
-
63src/video_core/renderer_vulkan/vk_resource_pool.cpp
-
43src/video_core/renderer_vulkan/vk_resource_pool.h
-
77src/video_core/renderer_vulkan/vk_scheduler.cpp
-
70src/video_core/renderer_vulkan/vk_scheduler.h
-
49src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp
-
14src/video_core/renderer_vulkan/vk_staging_buffer_pool.h
-
5src/video_core/renderer_vulkan/vk_stream_buffer.cpp
-
5src/video_core/renderer_vulkan/vk_stream_buffer.h
-
20src/video_core/renderer_vulkan/vk_swapchain.cpp
-
9src/video_core/renderer_vulkan/vk_swapchain.h
-
19src/video_core/renderer_vulkan/vk_texture_cache.cpp
-
15src/video_core/renderer_vulkan/vk_texture_cache.h
-
7src/video_core/renderer_vulkan/wrapper.cpp
-
48src/video_core/renderer_vulkan/wrapper.h
-
11src/video_core/shader/async_shaders.h
@ -0,0 +1,41 @@ |
|||
// Copyright 2020 yuzu Emulator Project
|
|||
// Licensed under GPLv2 or any later version
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
#include <cstddef>
|
|||
|
|||
#include "video_core/renderer_vulkan/vk_command_pool.h"
|
|||
#include "video_core/renderer_vulkan/vk_device.h"
|
|||
#include "video_core/renderer_vulkan/wrapper.h"
|
|||
|
|||
namespace Vulkan { |
|||
|
|||
constexpr size_t COMMAND_BUFFER_POOL_SIZE = 0x1000; |
|||
|
|||
CommandPool::CommandPool(MasterSemaphore& master_semaphore, const VKDevice& device) |
|||
: ResourcePool(master_semaphore, COMMAND_BUFFER_POOL_SIZE), device{device} {} |
|||
|
|||
CommandPool::~CommandPool() = default; |
|||
|
|||
void CommandPool::Allocate(size_t begin, size_t end) { |
|||
// Command buffers are going to be commited, recorded, executed every single usage cycle.
|
|||
// They are also going to be reseted when commited.
|
|||
Pool& pool = pools.emplace_back(); |
|||
pool.handle = device.GetLogical().CreateCommandPool({ |
|||
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, |
|||
.pNext = nullptr, |
|||
.flags = |
|||
VK_COMMAND_POOL_CREATE_TRANSIENT_BIT | VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, |
|||
.queueFamilyIndex = device.GetGraphicsFamily(), |
|||
}); |
|||
pool.cmdbufs = pool.handle.Allocate(COMMAND_BUFFER_POOL_SIZE); |
|||
} |
|||
|
|||
VkCommandBuffer CommandPool::Commit() { |
|||
const size_t index = CommitResource(); |
|||
const auto pool_index = index / COMMAND_BUFFER_POOL_SIZE; |
|||
const auto sub_index = index % COMMAND_BUFFER_POOL_SIZE; |
|||
return pools[pool_index].cmdbufs[sub_index]; |
|||
} |
|||
|
|||
} // namespace Vulkan
|
|||
@ -0,0 +1,35 @@ |
|||
// Copyright 2020 yuzu Emulator Project |
|||
// Licensed under GPLv2 or any later version |
|||
// Refer to the license.txt file included. |
|||
|
|||
#include <cstddef> |
|||
#include <vector> |
|||
|
|||
#include "video_core/renderer_vulkan/vk_resource_pool.h" |
|||
#include "video_core/renderer_vulkan/wrapper.h" |
|||
|
|||
namespace Vulkan { |
|||
|
|||
class MasterSemaphore; |
|||
class VKDevice; |
|||
|
|||
class CommandPool final : public ResourcePool { |
|||
public: |
|||
explicit CommandPool(MasterSemaphore& master_semaphore, const VKDevice& device); |
|||
virtual ~CommandPool(); |
|||
|
|||
void Allocate(size_t begin, size_t end) override; |
|||
|
|||
VkCommandBuffer Commit(); |
|||
|
|||
private: |
|||
struct Pool { |
|||
vk::CommandPool handle; |
|||
vk::CommandBuffers cmdbufs; |
|||
}; |
|||
|
|||
const VKDevice& device; |
|||
std::vector<Pool> pools; |
|||
}; |
|||
|
|||
} // namespace Vulkan |
|||
@ -0,0 +1,56 @@ |
|||
// Copyright 2020 yuzu Emulator Project
|
|||
// Licensed under GPLv2 or any later version
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
#include <atomic>
|
|||
#include <chrono>
|
|||
|
|||
#include "core/settings.h"
|
|||
#include "video_core/renderer_vulkan/vk_device.h"
|
|||
#include "video_core/renderer_vulkan/vk_master_semaphore.h"
|
|||
#include "video_core/renderer_vulkan/wrapper.h"
|
|||
|
|||
namespace Vulkan { |
|||
|
|||
using namespace std::chrono_literals; |
|||
|
|||
MasterSemaphore::MasterSemaphore(const VKDevice& device) { |
|||
static constexpr VkSemaphoreTypeCreateInfoKHR semaphore_type_ci{ |
|||
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO_KHR, |
|||
.pNext = nullptr, |
|||
.semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE_KHR, |
|||
.initialValue = 0, |
|||
}; |
|||
static constexpr VkSemaphoreCreateInfo semaphore_ci{ |
|||
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, |
|||
.pNext = &semaphore_type_ci, |
|||
.flags = 0, |
|||
}; |
|||
semaphore = device.GetLogical().CreateSemaphore(semaphore_ci); |
|||
|
|||
if (!Settings::values.renderer_debug) { |
|||
return; |
|||
} |
|||
// Validation layers have a bug where they fail to track resource usage when using timeline
|
|||
// semaphores and synchronizing with GetSemaphoreCounterValueKHR. To workaround this issue, have
|
|||
// a separate thread waiting for each timeline semaphore value.
|
|||
debug_thread = std::thread([this] { |
|||
u64 counter = 0; |
|||
while (!shutdown) { |
|||
if (semaphore.Wait(counter, 10'000'000)) { |
|||
++counter; |
|||
} |
|||
} |
|||
}); |
|||
} |
|||
|
|||
MasterSemaphore::~MasterSemaphore() { |
|||
shutdown = true; |
|||
|
|||
// This thread might not be started
|
|||
if (debug_thread.joinable()) { |
|||
debug_thread.join(); |
|||
} |
|||
} |
|||
|
|||
} // namespace Vulkan
|
|||
@ -0,0 +1,70 @@ |
|||
// Copyright 2020 yuzu Emulator Project |
|||
// Licensed under GPLv2 or any later version |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
|
|||
#include <atomic> |
|||
#include <thread> |
|||
|
|||
#include "common/common_types.h" |
|||
#include "video_core/renderer_vulkan/wrapper.h" |
|||
|
|||
namespace Vulkan { |
|||
|
|||
class VKDevice; |
|||
|
|||
class MasterSemaphore { |
|||
public: |
|||
explicit MasterSemaphore(const VKDevice& device); |
|||
~MasterSemaphore(); |
|||
|
|||
/// Returns the current logical tick. |
|||
[[nodiscard]] u64 CurrentTick() const noexcept { |
|||
return current_tick; |
|||
} |
|||
|
|||
/// Returns the timeline semaphore handle. |
|||
[[nodiscard]] VkSemaphore Handle() const noexcept { |
|||
return *semaphore; |
|||
} |
|||
|
|||
/// Returns true when a tick has been hit by the GPU. |
|||
[[nodiscard]] bool IsFree(u64 tick) { |
|||
return gpu_tick >= tick; |
|||
} |
|||
|
|||
/// Advance to the logical tick. |
|||
void NextTick() noexcept { |
|||
++current_tick; |
|||
} |
|||
|
|||
/// Refresh the known GPU tick |
|||
void Refresh() { |
|||
gpu_tick = semaphore.GetCounter(); |
|||
} |
|||
|
|||
/// Waits for a tick to be hit on the GPU |
|||
void Wait(u64 tick) { |
|||
// No need to wait if the GPU is ahead of the tick |
|||
if (IsFree(tick)) { |
|||
return; |
|||
} |
|||
// Update the GPU tick and try again |
|||
Refresh(); |
|||
if (IsFree(tick)) { |
|||
return; |
|||
} |
|||
// If none of the above is hit, fallback to a regular wait |
|||
semaphore.Wait(tick); |
|||
} |
|||
|
|||
private: |
|||
vk::Semaphore semaphore; ///< Timeline semaphore. |
|||
std::atomic<u64> gpu_tick{0}; ///< Current known GPU tick. |
|||
std::atomic<u64> current_tick{1}; ///< Current logical tick. |
|||
std::atomic<bool> shutdown{false}; ///< True when the object is being destroyed. |
|||
std::thread debug_thread; ///< Debug thread to workaround validation layer bugs. |
|||
}; |
|||
|
|||
} // namespace Vulkan |
|||
@ -1,311 +0,0 @@ |
|||
// Copyright 2018 yuzu Emulator Project
|
|||
// Licensed under GPLv2 or any later version
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
#include <algorithm>
|
|||
#include <optional>
|
|||
#include "common/assert.h"
|
|||
#include "common/logging/log.h"
|
|||
#include "video_core/renderer_vulkan/vk_device.h"
|
|||
#include "video_core/renderer_vulkan/vk_resource_manager.h"
|
|||
#include "video_core/renderer_vulkan/wrapper.h"
|
|||
|
|||
namespace Vulkan { |
|||
|
|||
namespace { |
|||
|
|||
// TODO(Rodrigo): Fine tune these numbers.
|
|||
constexpr std::size_t COMMAND_BUFFER_POOL_SIZE = 0x1000; |
|||
constexpr std::size_t FENCES_GROW_STEP = 0x40; |
|||
|
|||
constexpr VkFenceCreateInfo BuildFenceCreateInfo() { |
|||
return { |
|||
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, |
|||
.pNext = nullptr, |
|||
.flags = 0, |
|||
}; |
|||
} |
|||
|
|||
} // Anonymous namespace
|
|||
|
|||
class CommandBufferPool final : public VKFencedPool { |
|||
public: |
|||
explicit CommandBufferPool(const VKDevice& device) |
|||
: VKFencedPool(COMMAND_BUFFER_POOL_SIZE), device{device} {} |
|||
|
|||
void Allocate(std::size_t begin, std::size_t end) override { |
|||
// Command buffers are going to be commited, recorded, executed every single usage cycle.
|
|||
// They are also going to be reseted when commited.
|
|||
Pool& pool = pools.emplace_back(); |
|||
pool.handle = device.GetLogical().CreateCommandPool({ |
|||
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, |
|||
.pNext = nullptr, |
|||
.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT | |
|||
VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, |
|||
.queueFamilyIndex = device.GetGraphicsFamily(), |
|||
}); |
|||
pool.cmdbufs = pool.handle.Allocate(COMMAND_BUFFER_POOL_SIZE); |
|||
} |
|||
|
|||
VkCommandBuffer Commit(VKFence& fence) { |
|||
const std::size_t index = CommitResource(fence); |
|||
const auto pool_index = index / COMMAND_BUFFER_POOL_SIZE; |
|||
const auto sub_index = index % COMMAND_BUFFER_POOL_SIZE; |
|||
return pools[pool_index].cmdbufs[sub_index]; |
|||
} |
|||
|
|||
private: |
|||
struct Pool { |
|||
vk::CommandPool handle; |
|||
vk::CommandBuffers cmdbufs; |
|||
}; |
|||
|
|||
const VKDevice& device; |
|||
std::vector<Pool> pools; |
|||
}; |
|||
|
|||
VKResource::VKResource() = default; |
|||
|
|||
VKResource::~VKResource() = default; |
|||
|
|||
VKFence::VKFence(const VKDevice& device) |
|||
: device{device}, handle{device.GetLogical().CreateFence(BuildFenceCreateInfo())} {} |
|||
|
|||
VKFence::~VKFence() = default; |
|||
|
|||
void VKFence::Wait() { |
|||
switch (const VkResult result = handle.Wait()) { |
|||
case VK_SUCCESS: |
|||
return; |
|||
case VK_ERROR_DEVICE_LOST: |
|||
device.ReportLoss(); |
|||
[[fallthrough]]; |
|||
default: |
|||
throw vk::Exception(result); |
|||
} |
|||
} |
|||
|
|||
void VKFence::Release() { |
|||
ASSERT(is_owned); |
|||
is_owned = false; |
|||
} |
|||
|
|||
void VKFence::Commit() { |
|||
is_owned = true; |
|||
is_used = true; |
|||
} |
|||
|
|||
bool VKFence::Tick(bool gpu_wait, bool owner_wait) { |
|||
if (!is_used) { |
|||
// If a fence is not used it's always free.
|
|||
return true; |
|||
} |
|||
if (is_owned && !owner_wait) { |
|||
// The fence is still being owned (Release has not been called) and ownership wait has
|
|||
// not been asked.
|
|||
return false; |
|||
} |
|||
|
|||
if (gpu_wait) { |
|||
// Wait for the fence if it has been requested.
|
|||
(void)handle.Wait(); |
|||
} else { |
|||
if (handle.GetStatus() != VK_SUCCESS) { |
|||
// Vulkan fence is not ready, not much it can do here
|
|||
return false; |
|||
} |
|||
} |
|||
|
|||
// Broadcast resources their free state.
|
|||
for (auto* resource : protected_resources) { |
|||
resource->OnFenceRemoval(this); |
|||
} |
|||
protected_resources.clear(); |
|||
|
|||
// Prepare fence for reusage.
|
|||
handle.Reset(); |
|||
is_used = false; |
|||
return true; |
|||
} |
|||
|
|||
void VKFence::Protect(VKResource* resource) { |
|||
protected_resources.push_back(resource); |
|||
} |
|||
|
|||
void VKFence::Unprotect(VKResource* resource) { |
|||
const auto it = std::find(protected_resources.begin(), protected_resources.end(), resource); |
|||
ASSERT(it != protected_resources.end()); |
|||
|
|||
resource->OnFenceRemoval(this); |
|||
protected_resources.erase(it); |
|||
} |
|||
|
|||
void VKFence::RedirectProtection(VKResource* old_resource, VKResource* new_resource) noexcept { |
|||
std::replace(std::begin(protected_resources), std::end(protected_resources), old_resource, |
|||
new_resource); |
|||
} |
|||
|
|||
VKFenceWatch::VKFenceWatch() = default; |
|||
|
|||
VKFenceWatch::VKFenceWatch(VKFence& initial_fence) { |
|||
Watch(initial_fence); |
|||
} |
|||
|
|||
VKFenceWatch::VKFenceWatch(VKFenceWatch&& rhs) noexcept { |
|||
fence = std::exchange(rhs.fence, nullptr); |
|||
if (fence) { |
|||
fence->RedirectProtection(&rhs, this); |
|||
} |
|||
} |
|||
|
|||
VKFenceWatch& VKFenceWatch::operator=(VKFenceWatch&& rhs) noexcept { |
|||
fence = std::exchange(rhs.fence, nullptr); |
|||
if (fence) { |
|||
fence->RedirectProtection(&rhs, this); |
|||
} |
|||
return *this; |
|||
} |
|||
|
|||
VKFenceWatch::~VKFenceWatch() { |
|||
if (fence) { |
|||
fence->Unprotect(this); |
|||
} |
|||
} |
|||
|
|||
void VKFenceWatch::Wait() { |
|||
if (fence == nullptr) { |
|||
return; |
|||
} |
|||
fence->Wait(); |
|||
fence->Unprotect(this); |
|||
} |
|||
|
|||
void VKFenceWatch::Watch(VKFence& new_fence) { |
|||
Wait(); |
|||
fence = &new_fence; |
|||
fence->Protect(this); |
|||
} |
|||
|
|||
bool VKFenceWatch::TryWatch(VKFence& new_fence) { |
|||
if (fence) { |
|||
return false; |
|||
} |
|||
fence = &new_fence; |
|||
fence->Protect(this); |
|||
return true; |
|||
} |
|||
|
|||
void VKFenceWatch::OnFenceRemoval(VKFence* signaling_fence) { |
|||
ASSERT_MSG(signaling_fence == fence, "Removing the wrong fence"); |
|||
fence = nullptr; |
|||
} |
|||
|
|||
VKFencedPool::VKFencedPool(std::size_t grow_step) : grow_step{grow_step} {} |
|||
|
|||
VKFencedPool::~VKFencedPool() = default; |
|||
|
|||
std::size_t VKFencedPool::CommitResource(VKFence& fence) { |
|||
const auto Search = [&](std::size_t begin, std::size_t end) -> std::optional<std::size_t> { |
|||
for (std::size_t iterator = begin; iterator < end; ++iterator) { |
|||
if (watches[iterator]->TryWatch(fence)) { |
|||
// The resource is now being watched, a free resource was successfully found.
|
|||
return iterator; |
|||
} |
|||
} |
|||
return {}; |
|||
}; |
|||
// Try to find a free resource from the hinted position to the end.
|
|||
auto found = Search(free_iterator, watches.size()); |
|||
if (!found) { |
|||
// Search from beginning to the hinted position.
|
|||
found = Search(0, free_iterator); |
|||
if (!found) { |
|||
// Both searches failed, the pool is full; handle it.
|
|||
const std::size_t free_resource = ManageOverflow(); |
|||
|
|||
// Watch will wait for the resource to be free.
|
|||
watches[free_resource]->Watch(fence); |
|||
found = free_resource; |
|||
} |
|||
} |
|||
// Free iterator is hinted to the resource after the one that's been commited.
|
|||
free_iterator = (*found + 1) % watches.size(); |
|||
return *found; |
|||
} |
|||
|
|||
std::size_t VKFencedPool::ManageOverflow() { |
|||
const std::size_t old_capacity = watches.size(); |
|||
Grow(); |
|||
|
|||
// The last entry is guaranted to be free, since it's the first element of the freshly
|
|||
// allocated resources.
|
|||
return old_capacity; |
|||
} |
|||
|
|||
void VKFencedPool::Grow() { |
|||
const std::size_t old_capacity = watches.size(); |
|||
watches.resize(old_capacity + grow_step); |
|||
std::generate(watches.begin() + old_capacity, watches.end(), |
|||
[]() { return std::make_unique<VKFenceWatch>(); }); |
|||
Allocate(old_capacity, old_capacity + grow_step); |
|||
} |
|||
|
|||
VKResourceManager::VKResourceManager(const VKDevice& device) : device{device} { |
|||
GrowFences(FENCES_GROW_STEP); |
|||
command_buffer_pool = std::make_unique<CommandBufferPool>(device); |
|||
} |
|||
|
|||
VKResourceManager::~VKResourceManager() = default; |
|||
|
|||
VKFence& VKResourceManager::CommitFence() { |
|||
const auto StepFences = [&](bool gpu_wait, bool owner_wait) -> VKFence* { |
|||
const auto Tick = [=](auto& fence) { return fence->Tick(gpu_wait, owner_wait); }; |
|||
const auto hinted = fences.begin() + fences_iterator; |
|||
|
|||
auto it = std::find_if(hinted, fences.end(), Tick); |
|||
if (it == fences.end()) { |
|||
it = std::find_if(fences.begin(), hinted, Tick); |
|||
if (it == hinted) { |
|||
return nullptr; |
|||
} |
|||
} |
|||
fences_iterator = std::distance(fences.begin(), it) + 1; |
|||
if (fences_iterator >= fences.size()) |
|||
fences_iterator = 0; |
|||
|
|||
auto& fence = *it; |
|||
fence->Commit(); |
|||
return fence.get(); |
|||
}; |
|||
|
|||
VKFence* found_fence = StepFences(false, false); |
|||
if (!found_fence) { |
|||
// Try again, this time waiting.
|
|||
found_fence = StepFences(true, false); |
|||
|
|||
if (!found_fence) { |
|||
// Allocate new fences and try again.
|
|||
LOG_INFO(Render_Vulkan, "Allocating new fences {} -> {}", fences.size(), |
|||
fences.size() + FENCES_GROW_STEP); |
|||
|
|||
GrowFences(FENCES_GROW_STEP); |
|||
found_fence = StepFences(true, false); |
|||
ASSERT(found_fence != nullptr); |
|||
} |
|||
} |
|||
return *found_fence; |
|||
} |
|||
|
|||
VkCommandBuffer VKResourceManager::CommitCommandBuffer(VKFence& fence) { |
|||
return command_buffer_pool->Commit(fence); |
|||
} |
|||
|
|||
void VKResourceManager::GrowFences(std::size_t new_fences_count) { |
|||
const std::size_t previous_size = fences.size(); |
|||
fences.resize(previous_size + new_fences_count); |
|||
|
|||
std::generate(fences.begin() + previous_size, fences.end(), |
|||
[this] { return std::make_unique<VKFence>(device); }); |
|||
} |
|||
|
|||
} // namespace Vulkan
|
|||
@ -1,196 +0,0 @@ |
|||
// Copyright 2018 yuzu Emulator Project |
|||
// Licensed under GPLv2 or any later version |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
|
|||
#include <cstddef> |
|||
#include <memory> |
|||
#include <vector> |
|||
#include "video_core/renderer_vulkan/wrapper.h" |
|||
|
|||
namespace Vulkan { |
|||
|
|||
class VKDevice; |
|||
class VKFence; |
|||
class VKResourceManager; |
|||
|
|||
class CommandBufferPool; |
|||
|
|||
/// Interface for a Vulkan resource |
|||
class VKResource { |
|||
public: |
|||
explicit VKResource(); |
|||
virtual ~VKResource(); |
|||
|
|||
/** |
|||
* Signals the object that an owning fence has been signaled. |
|||
* @param signaling_fence Fence that signals its usage end. |
|||
*/ |
|||
virtual void OnFenceRemoval(VKFence* signaling_fence) = 0; |
|||
}; |
|||
|
|||
/** |
|||
* Fences take ownership of objects, protecting them from GPU-side or driver-side concurrent access. |
|||
* They must be commited from the resource manager. Their usage flow is: commit the fence from the |
|||
* resource manager, protect resources with it and use them, send the fence to an execution queue |
|||
* and Wait for it if needed and then call Release. Used resources will automatically be signaled |
|||
* when they are free to be reused. |
|||
* @brief Protects resources for concurrent usage and signals its release. |
|||
*/ |
|||
class VKFence { |
|||
friend class VKResourceManager; |
|||
|
|||
public: |
|||
explicit VKFence(const VKDevice& device); |
|||
~VKFence(); |
|||
|
|||
/** |
|||
* Waits for the fence to be signaled. |
|||
* @warning You must have ownership of the fence and it has to be previously sent to a queue to |
|||
* call this function. |
|||
*/ |
|||
void Wait(); |
|||
|
|||
/** |
|||
* Releases ownership of the fence. Pass after it has been sent to an execution queue. |
|||
* Unmanaged usage of the fence after the call will result in undefined behavior because it may |
|||
* be being used for something else. |
|||
*/ |
|||
void Release(); |
|||
|
|||
/// Protects a resource with this fence. |
|||
void Protect(VKResource* resource); |
|||
|
|||
/// Removes protection for a resource. |
|||
void Unprotect(VKResource* resource); |
|||
|
|||
/// Redirects one protected resource to a new address. |
|||
void RedirectProtection(VKResource* old_resource, VKResource* new_resource) noexcept; |
|||
|
|||
/// Retreives the fence. |
|||
operator VkFence() const { |
|||
return *handle; |
|||
} |
|||
|
|||
private: |
|||
/// Take ownership of the fence. |
|||
void Commit(); |
|||
|
|||
/** |
|||
* Updates the fence status. |
|||
* @warning Waiting for the owner might soft lock the execution. |
|||
* @param gpu_wait Wait for the fence to be signaled by the driver. |
|||
* @param owner_wait Wait for the owner to signal its freedom. |
|||
* @returns True if the fence is free. Waiting for gpu and owner will always return true. |
|||
*/ |
|||
bool Tick(bool gpu_wait, bool owner_wait); |
|||
|
|||
const VKDevice& device; ///< Device handler |
|||
vk::Fence handle; ///< Vulkan fence |
|||
std::vector<VKResource*> protected_resources; ///< List of resources protected by this fence |
|||
bool is_owned = false; ///< The fence has been commited but not released yet. |
|||
bool is_used = false; ///< The fence has been commited but it has not been checked to be free. |
|||
}; |
|||
|
|||
/** |
|||
* A fence watch is used to keep track of the usage of a fence and protect a resource or set of |
|||
* resources without having to inherit VKResource from their handlers. |
|||
*/ |
|||
class VKFenceWatch final : public VKResource { |
|||
public: |
|||
explicit VKFenceWatch(); |
|||
VKFenceWatch(VKFence& initial_fence); |
|||
VKFenceWatch(VKFenceWatch&&) noexcept; |
|||
VKFenceWatch(const VKFenceWatch&) = delete; |
|||
~VKFenceWatch() override; |
|||
|
|||
VKFenceWatch& operator=(VKFenceWatch&&) noexcept; |
|||
|
|||
/// Waits for the fence to be released. |
|||
void Wait(); |
|||
|
|||
/** |
|||
* Waits for a previous fence and watches a new one. |
|||
* @param new_fence New fence to wait to. |
|||
*/ |
|||
void Watch(VKFence& new_fence); |
|||
|
|||
/** |
|||
* Checks if it's currently being watched and starts watching it if it's available. |
|||
* @returns True if a watch has started, false if it's being watched. |
|||
*/ |
|||
bool TryWatch(VKFence& new_fence); |
|||
|
|||
void OnFenceRemoval(VKFence* signaling_fence) override; |
|||
|
|||
/** |
|||
* Do not use it paired with Watch. Use TryWatch instead. |
|||
* Returns true when the watch is free. |
|||
*/ |
|||
bool IsUsed() const { |
|||
return fence != nullptr; |
|||
} |
|||
|
|||
private: |
|||
VKFence* fence{}; ///< Fence watching this resource. nullptr when the watch is free. |
|||
}; |
|||
|
|||
/** |
|||
* Handles a pool of resources protected by fences. Manages resource overflow allocating more |
|||
* resources. |
|||
*/ |
|||
class VKFencedPool { |
|||
public: |
|||
explicit VKFencedPool(std::size_t grow_step); |
|||
virtual ~VKFencedPool(); |
|||
|
|||
protected: |
|||
/** |
|||
* Commits a free resource and protects it with a fence. It may allocate new resources. |
|||
* @param fence Fence that protects the commited resource. |
|||
* @returns Index of the resource commited. |
|||
*/ |
|||
std::size_t CommitResource(VKFence& fence); |
|||
|
|||
/// Called when a chunk of resources have to be allocated. |
|||
virtual void Allocate(std::size_t begin, std::size_t end) = 0; |
|||
|
|||
private: |
|||
/// Manages pool overflow allocating new resources. |
|||
std::size_t ManageOverflow(); |
|||
|
|||
/// Allocates a new page of resources. |
|||
void Grow(); |
|||
|
|||
std::size_t grow_step = 0; ///< Number of new resources created after an overflow |
|||
std::size_t free_iterator = 0; ///< Hint to where the next free resources is likely to be found |
|||
std::vector<std::unique_ptr<VKFenceWatch>> watches; ///< Set of watched resources |
|||
}; |
|||
|
|||
/** |
|||
* The resource manager handles all resources that can be protected with a fence avoiding |
|||
* driver-side or GPU-side concurrent usage. Usage is documented in VKFence. |
|||
*/ |
|||
class VKResourceManager final { |
|||
public: |
|||
explicit VKResourceManager(const VKDevice& device); |
|||
~VKResourceManager(); |
|||
|
|||
/// Commits a fence. It has to be sent to a queue and released. |
|||
VKFence& CommitFence(); |
|||
|
|||
/// Commits an unused command buffer and protects it with a fence. |
|||
VkCommandBuffer CommitCommandBuffer(VKFence& fence); |
|||
|
|||
private: |
|||
/// Allocates new fences. |
|||
void GrowFences(std::size_t new_fences_count); |
|||
|
|||
const VKDevice& device; ///< Device handler. |
|||
std::size_t fences_iterator = 0; ///< Index where a free fence is likely to be found. |
|||
std::vector<std::unique_ptr<VKFence>> fences; ///< Pool of fences. |
|||
std::unique_ptr<CommandBufferPool> command_buffer_pool; ///< Pool of command buffers. |
|||
}; |
|||
|
|||
} // namespace Vulkan |
|||
@ -0,0 +1,63 @@ |
|||
// Copyright 2020 yuzu Emulator Project
|
|||
// Licensed under GPLv2 or any later version
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
#include <optional>
|
|||
|
|||
#include "video_core/renderer_vulkan/vk_master_semaphore.h"
|
|||
#include "video_core/renderer_vulkan/vk_resource_pool.h"
|
|||
|
|||
namespace Vulkan { |
|||
|
|||
ResourcePool::ResourcePool(MasterSemaphore& master_semaphore_, size_t grow_step_) |
|||
: master_semaphore{master_semaphore_}, grow_step{grow_step_} {} |
|||
|
|||
ResourcePool::~ResourcePool() = default; |
|||
|
|||
size_t ResourcePool::CommitResource() { |
|||
// Refresh semaphore to query updated results
|
|||
master_semaphore.Refresh(); |
|||
|
|||
const auto search = [this](size_t begin, size_t end) -> std::optional<size_t> { |
|||
for (size_t iterator = begin; iterator < end; ++iterator) { |
|||
if (master_semaphore.IsFree(ticks[iterator])) { |
|||
ticks[iterator] = master_semaphore.CurrentTick(); |
|||
return iterator; |
|||
} |
|||
} |
|||
return {}; |
|||
}; |
|||
// Try to find a free resource from the hinted position to the end.
|
|||
auto found = search(free_iterator, ticks.size()); |
|||
if (!found) { |
|||
// Search from beginning to the hinted position.
|
|||
found = search(0, free_iterator); |
|||
if (!found) { |
|||
// Both searches failed, the pool is full; handle it.
|
|||
const size_t free_resource = ManageOverflow(); |
|||
|
|||
ticks[free_resource] = master_semaphore.CurrentTick(); |
|||
found = free_resource; |
|||
} |
|||
} |
|||
// Free iterator is hinted to the resource after the one that's been commited.
|
|||
free_iterator = (*found + 1) % ticks.size(); |
|||
return *found; |
|||
} |
|||
|
|||
size_t ResourcePool::ManageOverflow() { |
|||
const size_t old_capacity = ticks.size(); |
|||
Grow(); |
|||
|
|||
// The last entry is guaranted to be free, since it's the first element of the freshly
|
|||
// allocated resources.
|
|||
return old_capacity; |
|||
} |
|||
|
|||
void ResourcePool::Grow() { |
|||
const size_t old_capacity = ticks.size(); |
|||
ticks.resize(old_capacity + grow_step); |
|||
Allocate(old_capacity, old_capacity + grow_step); |
|||
} |
|||
|
|||
} // namespace Vulkan
|
|||
@ -0,0 +1,43 @@ |
|||
// Copyright 2020 yuzu Emulator Project |
|||
// Licensed under GPLv2 or any later version |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
|
|||
#include <vector> |
|||
|
|||
#include "common/common_types.h" |
|||
|
|||
namespace Vulkan { |
|||
|
|||
class MasterSemaphore; |
|||
|
|||
/** |
|||
* Handles a pool of resources protected by fences. Manages resource overflow allocating more |
|||
* resources. |
|||
*/ |
|||
class ResourcePool { |
|||
public: |
|||
explicit ResourcePool(MasterSemaphore& master_semaphore, size_t grow_step); |
|||
virtual ~ResourcePool(); |
|||
|
|||
protected: |
|||
size_t CommitResource(); |
|||
|
|||
/// Called when a chunk of resources have to be allocated. |
|||
virtual void Allocate(size_t begin, size_t end) = 0; |
|||
|
|||
private: |
|||
/// Manages pool overflow allocating new resources. |
|||
size_t ManageOverflow(); |
|||
|
|||
/// Allocates a new page of resources. |
|||
void Grow(); |
|||
|
|||
MasterSemaphore& master_semaphore; |
|||
size_t grow_step = 0; ///< Number of new resources created after an overflow |
|||
size_t free_iterator = 0; ///< Hint to where the next free resources is likely to be found |
|||
std::vector<u64> ticks; ///< Ticks for each resource |
|||
}; |
|||
|
|||
} // namespace Vulkan |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue