diff options
Diffstat (limited to '')
| -rw-r--r-- | src/video_core/CMakeLists.txt | 4 | ||||
| -rw-r--r-- | src/video_core/renderer_vulkan/vk_resource_manager.cpp | 285 | ||||
| -rw-r--r-- | src/video_core/renderer_vulkan/vk_resource_manager.h | 180 |
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 | |||
| 13 | namespace Vulkan { | ||
| 14 | |||
| 15 | // TODO(Rodrigo): Fine tune these numbers. | ||
| 16 | constexpr std::size_t COMMAND_BUFFER_POOL_SIZE = 0x1000; | ||
| 17 | constexpr std::size_t FENCES_GROW_STEP = 0x40; | ||
| 18 | |||
| 19 | class CommandBufferPool final : public VKFencedPool { | ||
| 20 | public: | ||
| 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 | |||
| 54 | private: | ||
| 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 | |||
| 65 | VKResource::VKResource() = default; | ||
| 66 | |||
| 67 | VKResource::~VKResource() = default; | ||
| 68 | |||
| 69 | VKFence::VKFence(const VKDevice& device, UniqueFence handle) | ||
| 70 | : device{device}, handle{std::move(handle)} {} | ||
| 71 | |||
| 72 | VKFence::~VKFence() = default; | ||
| 73 | |||
| 74 | void 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 | |||
| 80 | void VKFence::Release() { | ||
| 81 | is_owned = false; | ||
| 82 | } | ||
| 83 | |||
| 84 | void VKFence::Commit() { | ||
| 85 | is_owned = true; | ||
| 86 | is_used = true; | ||
| 87 | } | ||
| 88 | |||
| 89 | bool 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 | |||
| 124 | void VKFence::Protect(VKResource* resource) { | ||
| 125 | protected_resources.push_back(resource); | ||
| 126 | } | ||
| 127 | |||
| 128 | void 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 | |||
| 135 | VKFenceWatch::VKFenceWatch() = default; | ||
| 136 | |||
| 137 | VKFenceWatch::~VKFenceWatch() { | ||
| 138 | if (fence) { | ||
| 139 | fence->Unprotect(this); | ||
| 140 | } | ||
| 141 | } | ||
| 142 | |||
| 143 | void VKFenceWatch::Wait() { | ||
| 144 | if (!fence) { | ||
| 145 | return; | ||
| 146 | } | ||
| 147 | fence->Wait(); | ||
| 148 | fence->Unprotect(this); | ||
| 149 | fence = nullptr; | ||
| 150 | } | ||
| 151 | |||
| 152 | void VKFenceWatch::Watch(VKFence& new_fence) { | ||
| 153 | Wait(); | ||
| 154 | fence = &new_fence; | ||
| 155 | fence->Protect(this); | ||
| 156 | } | ||
| 157 | |||
| 158 | bool 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 | |||
| 167 | void VKFenceWatch::OnFenceRemoval(VKFence* signaling_fence) { | ||
| 168 | ASSERT_MSG(signaling_fence == fence, "Removing the wrong fence"); | ||
| 169 | fence = nullptr; | ||
| 170 | } | ||
| 171 | |||
| 172 | VKFencedPool::VKFencedPool(std::size_t grow_step) : grow_step{grow_step} {} | ||
| 173 | |||
| 174 | VKFencedPool::~VKFencedPool() = default; | ||
| 175 | |||
| 176 | std::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 | |||
| 205 | std::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 | |||
| 214 | void 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 | |||
| 222 | VKResourceManager::VKResourceManager(const VKDevice& device) : device{device} { | ||
| 223 | GrowFences(FENCES_GROW_STEP); | ||
| 224 | command_buffer_pool = std::make_unique<CommandBufferPool>(device); | ||
| 225 | } | ||
| 226 | |||
| 227 | VKResourceManager::~VKResourceManager() = default; | ||
| 228 | |||
| 229 | VKFence& 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 | |||
| 268 | vk::CommandBuffer VKResourceManager::CommitCommandBuffer(VKFence& fence) { | ||
| 269 | return command_buffer_pool->Commit(fence); | ||
| 270 | } | ||
| 271 | |||
| 272 | void 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 | |||
| 12 | namespace Vulkan { | ||
| 13 | |||
| 14 | class VKDevice; | ||
| 15 | class VKFence; | ||
| 16 | class VKResourceManager; | ||
| 17 | |||
| 18 | class CommandBufferPool; | ||
| 19 | |||
| 20 | /// Interface for a Vulkan resource | ||
| 21 | class VKResource { | ||
| 22 | public: | ||
| 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 | */ | ||
| 41 | class VKFence { | ||
| 42 | friend class VKResourceManager; | ||
| 43 | |||
| 44 | public: | ||
| 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 | |||
| 73 | private: | ||
| 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 | */ | ||
| 97 | class VKFenceWatch final : public VKResource { | ||
| 98 | public: | ||
| 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 | |||
| 119 | private: | ||
| 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 | */ | ||
| 127 | class VKFencedPool { | ||
| 128 | public: | ||
| 129 | explicit VKFencedPool(std::size_t grow_step); | ||
| 130 | virtual ~VKFencedPool(); | ||
| 131 | |||
| 132 | protected: | ||
| 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 | |||
| 143 | private: | ||
| 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 | */ | ||
| 159 | class VKResourceManager final { | ||
| 160 | public: | ||
| 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 | |||
| 170 | private: | ||
| 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 | ||