mirror of https://git.suyu.dev/suyu/suyu
Merge pull request #4674 from ReinUsesLisp/timeline-semaphores
renderer_vulkan: Make unconditional use of VK_KHR_timeline_semaphoremerge-requests/60/head
commit
d66b897a6d
@ -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
|
Loading…
Reference in New Issue