summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/video_core/CMakeLists.txt4
-rw-r--r--src/video_core/renderer_vulkan/vk_resource_manager.cpp285
-rw-r--r--src/video_core/renderer_vulkan/vk_resource_manager.h180
3 files changed, 468 insertions, 1 deletions
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt
index d35a738d5..59319f206 100644
--- a/src/video_core/CMakeLists.txt
+++ b/src/video_core/CMakeLists.txt
@@ -105,7 +105,9 @@ if (ENABLE_VULKAN)
105 target_sources(video_core PRIVATE 105 target_sources(video_core PRIVATE
106 renderer_vulkan/declarations.h 106 renderer_vulkan/declarations.h
107 renderer_vulkan/vk_device.cpp 107 renderer_vulkan/vk_device.cpp
108 renderer_vulkan/vk_device.h) 108 renderer_vulkan/vk_device.h
109 renderer_vulkan/vk_resource_manager.cpp
110 renderer_vulkan/vk_resource_manager.h)
109 111
110 target_include_directories(video_core PRIVATE ../../externals/Vulkan-Headers/include) 112 target_include_directories(video_core PRIVATE ../../externals/Vulkan-Headers/include)
111 target_compile_definitions(video_core PRIVATE HAS_VULKAN) 113 target_compile_definitions(video_core PRIVATE HAS_VULKAN)
diff --git a/src/video_core/renderer_vulkan/vk_resource_manager.cpp b/src/video_core/renderer_vulkan/vk_resource_manager.cpp
new file mode 100644
index 000000000..1678463c7
--- /dev/null
+++ b/src/video_core/renderer_vulkan/vk_resource_manager.cpp
@@ -0,0 +1,285 @@
1// Copyright 2018 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <algorithm>
6#include <optional>
7#include "common/assert.h"
8#include "common/logging/log.h"
9#include "video_core/renderer_vulkan/declarations.h"
10#include "video_core/renderer_vulkan/vk_device.h"
11#include "video_core/renderer_vulkan/vk_resource_manager.h"
12
13namespace Vulkan {
14
15// TODO(Rodrigo): Fine tune these numbers.
16constexpr std::size_t COMMAND_BUFFER_POOL_SIZE = 0x1000;
17constexpr std::size_t FENCES_GROW_STEP = 0x40;
18
19class CommandBufferPool final : public VKFencedPool {
20public:
21 CommandBufferPool(const VKDevice& device)
22 : VKFencedPool(COMMAND_BUFFER_POOL_SIZE), device{device} {}
23
24 void Allocate(std::size_t begin, std::size_t end) {
25 const auto dev = device.GetLogical();
26 const auto& dld = device.GetDispatchLoader();
27 const u32 graphics_family = device.GetGraphicsFamily();
28
29 auto pool = std::make_unique<Pool>();
30
31 // Command buffers are going to be commited, recorded, executed every single usage cycle.
32 // They are also going to be reseted when commited.
33 const auto pool_flags = vk::CommandPoolCreateFlagBits::eTransient |
34 vk::CommandPoolCreateFlagBits::eResetCommandBuffer;
35 const vk::CommandPoolCreateInfo cmdbuf_pool_ci(pool_flags, graphics_family);
36 pool->handle = dev.createCommandPoolUnique(cmdbuf_pool_ci, nullptr, dld);
37
38 const vk::CommandBufferAllocateInfo cmdbuf_ai(*pool->handle,
39 vk::CommandBufferLevel::ePrimary,
40 static_cast<u32>(COMMAND_BUFFER_POOL_SIZE));
41 pool->cmdbufs =
42 dev.allocateCommandBuffersUnique<std::allocator<UniqueCommandBuffer>>(cmdbuf_ai, dld);
43
44 pools.push_back(std::move(pool));
45 }
46
47 vk::CommandBuffer Commit(VKFence& fence) {
48 const std::size_t index = CommitResource(fence);
49 const auto pool_index = index / COMMAND_BUFFER_POOL_SIZE;
50 const auto sub_index = index % COMMAND_BUFFER_POOL_SIZE;
51 return *pools[pool_index]->cmdbufs[sub_index];
52 }
53
54private:
55 struct Pool {
56 UniqueCommandPool handle;
57 std::vector<UniqueCommandBuffer> cmdbufs;
58 };
59
60 const VKDevice& device;
61
62 std::vector<std::unique_ptr<Pool>> pools;
63};
64
65VKResource::VKResource() = default;
66
67VKResource::~VKResource() = default;
68
69VKFence::VKFence(const VKDevice& device, UniqueFence handle)
70 : device{device}, handle{std::move(handle)} {}
71
72VKFence::~VKFence() = default;
73
74void VKFence::Wait() {
75 const auto dev = device.GetLogical();
76 const auto& dld = device.GetDispatchLoader();
77 dev.waitForFences({*handle}, true, std::numeric_limits<u64>::max(), dld);
78}
79
80void VKFence::Release() {
81 is_owned = false;
82}
83
84void VKFence::Commit() {
85 is_owned = true;
86 is_used = true;
87}
88
89bool VKFence::Tick(bool gpu_wait, bool owner_wait) {
90 if (!is_used) {
91 // If a fence is not used it's always free.
92 return true;
93 }
94 if (is_owned && !owner_wait) {
95 // The fence is still being owned (Release has not been called) and ownership wait has
96 // not been asked.
97 return false;
98 }
99
100 const auto dev = device.GetLogical();
101 const auto& dld = device.GetDispatchLoader();
102 if (gpu_wait) {
103 // Wait for the fence if it has been requested.
104 dev.waitForFences({*handle}, true, std::numeric_limits<u64>::max(), dld);
105 } else {
106 if (dev.getFenceStatus(*handle, dld) != vk::Result::eSuccess) {
107 // Vulkan fence is not ready, not much it can do here
108 return false;
109 }
110 }
111
112 // Broadcast resources their free state.
113 for (auto* resource : protected_resources) {
114 resource->OnFenceRemoval(this);
115 }
116 protected_resources.clear();
117
118 // Prepare fence for reusage.
119 dev.resetFences({*handle}, dld);
120 is_used = false;
121 return true;
122}
123
124void VKFence::Protect(VKResource* resource) {
125 protected_resources.push_back(resource);
126}
127
128void VKFence::Unprotect(const VKResource* resource) {
129 const auto it = std::find(protected_resources.begin(), protected_resources.end(), resource);
130 if (it != protected_resources.end()) {
131 protected_resources.erase(it);
132 }
133}
134
135VKFenceWatch::VKFenceWatch() = default;
136
137VKFenceWatch::~VKFenceWatch() {
138 if (fence) {
139 fence->Unprotect(this);
140 }
141}
142
143void VKFenceWatch::Wait() {
144 if (!fence) {
145 return;
146 }
147 fence->Wait();
148 fence->Unprotect(this);
149 fence = nullptr;
150}
151
152void VKFenceWatch::Watch(VKFence& new_fence) {
153 Wait();
154 fence = &new_fence;
155 fence->Protect(this);
156}
157
158bool VKFenceWatch::TryWatch(VKFence& new_fence) {
159 if (fence) {
160 return false;
161 }
162 fence = &new_fence;
163 fence->Protect(this);
164 return true;
165}
166
167void VKFenceWatch::OnFenceRemoval(VKFence* signaling_fence) {
168 ASSERT_MSG(signaling_fence == fence, "Removing the wrong fence");
169 fence = nullptr;
170}
171
172VKFencedPool::VKFencedPool(std::size_t grow_step) : grow_step{grow_step} {}
173
174VKFencedPool::~VKFencedPool() = default;
175
176std::size_t VKFencedPool::CommitResource(VKFence& fence) {
177 const auto Search = [&](std::size_t begin, std::size_t end) -> std::optional<std::size_t> {
178 for (std::size_t iterator = begin; iterator < end; ++iterator) {
179 if (watches[iterator]->TryWatch(fence)) {
180 // The resource is now being watched, a free resource was successfully found.
181 return iterator;
182 }
183 }
184 return {};
185 };
186 // Try to find a free resource from the hinted position to the end.
187 auto found = Search(free_iterator, watches.size());
188 if (!found) {
189 // Search from beginning to the hinted position.
190 found = Search(0, free_iterator);
191 if (!found) {
192 // Both searches failed, the pool is full; handle it.
193 const std::size_t free_resource = ManageOverflow();
194
195 // Watch will wait for the resource to be free.
196 watches[free_resource]->Watch(fence);
197 found = free_resource;
198 }
199 }
200 // Free iterator is hinted to the resource after the one that's been commited.
201 free_iterator = (*found + 1) % watches.size();
202 return *found;
203}
204
205std::size_t VKFencedPool::ManageOverflow() {
206 const std::size_t old_capacity = watches.size();
207 Grow();
208
209 // The last entry is guaranted to be free, since it's the first element of the freshly
210 // allocated resources.
211 return old_capacity;
212}
213
214void VKFencedPool::Grow() {
215 const std::size_t old_capacity = watches.size();
216 watches.resize(old_capacity + grow_step);
217 std::generate(watches.begin() + old_capacity, watches.end(),
218 []() { return std::make_unique<VKFenceWatch>(); });
219 Allocate(old_capacity, old_capacity + grow_step);
220}
221
222VKResourceManager::VKResourceManager(const VKDevice& device) : device{device} {
223 GrowFences(FENCES_GROW_STEP);
224 command_buffer_pool = std::make_unique<CommandBufferPool>(device);
225}
226
227VKResourceManager::~VKResourceManager() = default;
228
229VKFence& VKResourceManager::CommitFence() {
230 const auto StepFences = [&](bool gpu_wait, bool owner_wait) -> VKFence* {
231 const auto Tick = [=](auto& fence) { return fence->Tick(gpu_wait, owner_wait); };
232 const auto hinted = fences.begin() + fences_iterator;
233
234 auto it = std::find_if(hinted, fences.end(), Tick);
235 if (it == fences.end()) {
236 it = std::find_if(fences.begin(), hinted, Tick);
237 if (it == hinted) {
238 return nullptr;
239 }
240 }
241 fences_iterator = std::distance(fences.begin(), it) + 1;
242 if (fences_iterator >= fences.size())
243 fences_iterator = 0;
244
245 auto& fence = *it;
246 fence->Commit();
247 return fence.get();
248 };
249
250 VKFence* found_fence = StepFences(false, false);
251 if (!found_fence) {
252 // Try again, this time waiting.
253 found_fence = StepFences(true, false);
254
255 if (!found_fence) {
256 // Allocate new fences and try again.
257 LOG_INFO(Render_Vulkan, "Allocating new fences {} -> {}", fences.size(),
258 fences.size() + FENCES_GROW_STEP);
259
260 GrowFences(FENCES_GROW_STEP);
261 found_fence = StepFences(true, false);
262 ASSERT(found_fence != nullptr);
263 }
264 }
265 return *found_fence;
266}
267
268vk::CommandBuffer VKResourceManager::CommitCommandBuffer(VKFence& fence) {
269 return command_buffer_pool->Commit(fence);
270}
271
272void VKResourceManager::GrowFences(std::size_t new_fences_count) {
273 const auto dev = device.GetLogical();
274 const auto& dld = device.GetDispatchLoader();
275 const vk::FenceCreateInfo fence_ci;
276
277 const std::size_t previous_size = fences.size();
278 fences.resize(previous_size + new_fences_count);
279
280 std::generate(fences.begin() + previous_size, fences.end(), [&]() {
281 return std::make_unique<VKFence>(device, dev.createFenceUnique(fence_ci, nullptr, dld));
282 });
283}
284
285} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_resource_manager.h b/src/video_core/renderer_vulkan/vk_resource_manager.h
new file mode 100644
index 000000000..5018dfa44
--- /dev/null
+++ b/src/video_core/renderer_vulkan/vk_resource_manager.h
@@ -0,0 +1,180 @@
1// Copyright 2018 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <cstddef>
8#include <memory>
9#include <vector>
10#include "video_core/renderer_vulkan/declarations.h"
11
12namespace Vulkan {
13
14class VKDevice;
15class VKFence;
16class VKResourceManager;
17
18class CommandBufferPool;
19
20/// Interface for a Vulkan resource
21class VKResource {
22public:
23 explicit VKResource();
24 virtual ~VKResource();
25
26 /**
27 * Signals the object that an owning fence has been signaled.
28 * @param signaling_fence Fence that signals its usage end.
29 */
30 virtual void OnFenceRemoval(VKFence* signaling_fence) = 0;
31};
32
33/**
34 * Fences take ownership of objects, protecting them from GPU-side or driver-side concurrent access.
35 * They must be commited from the resource manager. Their usage flow is: commit the fence from the
36 * resource manager, protect resources with it and use them, send the fence to an execution queue
37 * and Wait for it if needed and then call Release. Used resources will automatically be signaled
38 * when they are free to be reused.
39 * @brief Protects resources for concurrent usage and signals its release.
40 */
41class VKFence {
42 friend class VKResourceManager;
43
44public:
45 explicit VKFence(const VKDevice& device, UniqueFence handle);
46 ~VKFence();
47
48 /**
49 * Waits for the fence to be signaled.
50 * @warning You must have ownership of the fence and it has to be previously sent to a queue to
51 * call this function.
52 */
53 void Wait();
54
55 /**
56 * Releases ownership of the fence. Pass after it has been sent to an execution queue.
57 * Unmanaged usage of the fence after the call will result in undefined behavior because it may
58 * be being used for something else.
59 */
60 void Release();
61
62 /// Protects a resource with this fence.
63 void Protect(VKResource* resource);
64
65 /// Removes protection for a resource.
66 void Unprotect(const VKResource* resource);
67
68 /// Retreives the fence.
69 operator vk::Fence() const {
70 return *handle;
71 }
72
73private:
74 /// Take ownership of the fence.
75 void Commit();
76
77 /**
78 * Updates the fence status.
79 * @warning Waiting for the owner might soft lock the execution.
80 * @param gpu_wait Wait for the fence to be signaled by the driver.
81 * @param owner_wait Wait for the owner to signal its freedom.
82 * @returns True if the fence is free. Waiting for gpu and owner will always return true.
83 */
84 bool Tick(bool gpu_wait, bool owner_wait);
85
86 const VKDevice& device; ///< Device handler
87 UniqueFence handle; ///< Vulkan fence
88 std::vector<VKResource*> protected_resources; ///< List of resources protected by this fence
89 bool is_owned = false; ///< The fence has been commited but not released yet.
90 bool is_used = false; ///< The fence has been commited but it has not been checked to be free.
91};
92
93/**
94 * A fence watch is used to keep track of the usage of a fence and protect a resource or set of
95 * resources without having to inherit VKResource from their handlers.
96 */
97class VKFenceWatch final : public VKResource {
98public:
99 explicit VKFenceWatch();
100 ~VKFenceWatch();
101
102 /// Waits for the fence to be released.
103 void Wait();
104
105 /**
106 * Waits for a previous fence and watches a new one.
107 * @param new_fence New fence to wait to.
108 */
109 void Watch(VKFence& new_fence);
110
111 /**
112 * Checks if it's currently being watched and starts watching it if it's available.
113 * @returns True if a watch has started, false if it's being watched.
114 */
115 bool TryWatch(VKFence& new_fence);
116
117 void OnFenceRemoval(VKFence* signaling_fence) override;
118
119private:
120 VKFence* fence{}; ///< Fence watching this resource. nullptr when the watch is free.
121};
122
123/**
124 * Handles a pool of resources protected by fences. Manages resource overflow allocating more
125 * resources.
126 */
127class VKFencedPool {
128public:
129 explicit VKFencedPool(std::size_t grow_step);
130 virtual ~VKFencedPool();
131
132protected:
133 /**
134 * Commits a free resource and protects it with a fence. It may allocate new resources.
135 * @param fence Fence that protects the commited resource.
136 * @returns Index of the resource commited.
137 */
138 std::size_t CommitResource(VKFence& fence);
139
140 /// Called when a chunk of resources have to be allocated.
141 virtual void Allocate(std::size_t begin, std::size_t end) = 0;
142
143private:
144 /// Manages pool overflow allocating new resources.
145 std::size_t ManageOverflow();
146
147 /// Allocates a new page of resources.
148 void Grow();
149
150 std::size_t grow_step = 0; ///< Number of new resources created after an overflow
151 std::size_t free_iterator = 0; ///< Hint to where the next free resources is likely to be found
152 std::vector<std::unique_ptr<VKFenceWatch>> watches; ///< Set of watched resources
153};
154
155/**
156 * The resource manager handles all resources that can be protected with a fence avoiding
157 * driver-side or GPU-side concurrent usage. Usage is documented in VKFence.
158 */
159class VKResourceManager final {
160public:
161 explicit VKResourceManager(const VKDevice& device);
162 ~VKResourceManager();
163
164 /// Commits a fence. It has to be sent to a queue and released.
165 VKFence& CommitFence();
166
167 /// Commits an unused command buffer and protects it with a fence.
168 vk::CommandBuffer CommitCommandBuffer(VKFence& fence);
169
170private:
171 /// Allocates new fences.
172 void GrowFences(std::size_t new_fences_count);
173
174 const VKDevice& device; ///< Device handler.
175 std::size_t fences_iterator = 0; ///< Index where a free fence is likely to be found.
176 std::vector<std::unique_ptr<VKFence>> fences; ///< Pool of fences.
177 std::unique_ptr<CommandBufferPool> command_buffer_pool; ///< Pool of command buffers.
178};
179
180} // namespace Vulkan