summaryrefslogtreecommitdiff
path: root/src/video_core/vulkan_common
diff options
context:
space:
mode:
authorGravatar Rodrigo Locatti2021-01-15 21:50:05 -0300
committerGravatar GitHub2021-01-15 21:50:05 -0300
commitc17ee0da5d5e89f6c23a215980d13977b736dee7 (patch)
tree29336263889e745c73a43e7747a8ec96f916e6f5 /src/video_core/vulkan_common
parentMerge pull request #5358 from ReinUsesLisp/rename-insert-padding (diff)
parentvulkan_memory_allocator: Remove unnecesary 'device' memory from commits (diff)
downloadyuzu-c17ee0da5d5e89f6c23a215980d13977b736dee7.tar.gz
yuzu-c17ee0da5d5e89f6c23a215980d13977b736dee7.tar.xz
yuzu-c17ee0da5d5e89f6c23a215980d13977b736dee7.zip
Merge pull request #5297 from ReinUsesLisp/vulkan-allocator-common
vulkan_memory_allocator: Improvements to the memory allocator
Diffstat (limited to 'src/video_core/vulkan_common')
-rw-r--r--src/video_core/vulkan_common/vulkan_memory_allocator.cpp268
-rw-r--r--src/video_core/vulkan_common/vulkan_memory_allocator.h118
2 files changed, 386 insertions, 0 deletions
diff --git a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
new file mode 100644
index 000000000..d6eb3af31
--- /dev/null
+++ b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
@@ -0,0 +1,268 @@
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 <bit>
7#include <optional>
8#include <vector>
9
10#include "common/alignment.h"
11#include "common/assert.h"
12#include "common/common_types.h"
13#include "common/logging/log.h"
14#include "video_core/vulkan_common/vulkan_device.h"
15#include "video_core/vulkan_common/vulkan_memory_allocator.h"
16#include "video_core/vulkan_common/vulkan_wrapper.h"
17
18namespace Vulkan {
19namespace {
20struct Range {
21 u64 begin;
22 u64 end;
23
24 [[nodiscard]] bool Contains(u64 iterator, u64 size) const noexcept {
25 return iterator < end && begin < iterator + size;
26 }
27};
28
29[[nodiscard]] u64 AllocationChunkSize(u64 required_size) {
30 static constexpr std::array sizes{
31 0x1000ULL << 10, 0x1400ULL << 10, 0x1800ULL << 10, 0x1c00ULL << 10, 0x2000ULL << 10,
32 0x3200ULL << 10, 0x4000ULL << 10, 0x6000ULL << 10, 0x8000ULL << 10, 0xA000ULL << 10,
33 0x10000ULL << 10, 0x18000ULL << 10, 0x20000ULL << 10,
34 };
35 static_assert(std::is_sorted(sizes.begin(), sizes.end()));
36
37 const auto it = std::ranges::lower_bound(sizes, required_size);
38 return it != sizes.end() ? *it : Common::AlignUp(required_size, 4ULL << 20);
39}
40
41[[nodiscard]] VkMemoryPropertyFlags MemoryUsagePropertyFlags(MemoryUsage usage) {
42 switch (usage) {
43 case MemoryUsage::DeviceLocal:
44 return VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
45 case MemoryUsage::Upload:
46 return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
47 case MemoryUsage::Download:
48 return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT |
49 VK_MEMORY_PROPERTY_HOST_CACHED_BIT;
50 }
51 UNREACHABLE_MSG("Invalid memory usage={}", usage);
52 return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
53}
54} // Anonymous namespace
55
56class MemoryAllocation {
57public:
58 explicit MemoryAllocation(const Device& device_, vk::DeviceMemory memory_,
59 VkMemoryPropertyFlags properties, u64 allocation_size_, u32 type)
60 : device{device_}, memory{std::move(memory_)}, allocation_size{allocation_size_},
61 property_flags{properties}, shifted_memory_type{1U << type} {}
62
63 [[nodiscard]] std::optional<MemoryCommit> Commit(VkDeviceSize size, VkDeviceSize alignment) {
64 const std::optional<u64> alloc = FindFreeRegion(size, alignment);
65 if (!alloc) {
66 // Signal out of memory, it'll try to do more allocations.
67 return std::nullopt;
68 }
69 const Range range{
70 .begin = *alloc,
71 .end = *alloc + size,
72 };
73 commits.insert(std::ranges::upper_bound(commits, *alloc, {}, &Range::begin), range);
74 return std::make_optional<MemoryCommit>(this, *memory, *alloc, *alloc + size);
75 }
76
77 void Free(u64 begin) {
78 const auto it = std::ranges::find(commits, begin, &Range::begin);
79 ASSERT_MSG(it != commits.end(), "Invalid commit");
80 commits.erase(it);
81 }
82
83 [[nodiscard]] std::span<u8> Map() {
84 if (memory_mapped_span.empty()) {
85 u8* const raw_pointer = memory.Map(0, allocation_size);
86 memory_mapped_span = std::span<u8>(raw_pointer, allocation_size);
87 }
88 return memory_mapped_span;
89 }
90
91 /// Returns whether this allocation is compatible with the arguments.
92 [[nodiscard]] bool IsCompatible(VkMemoryPropertyFlags flags, u32 type_mask) const {
93 return (flags & property_flags) && (type_mask & shifted_memory_type) != 0;
94 }
95
96private:
97 [[nodiscard]] static constexpr u32 ShiftType(u32 type) {
98 return 1U << type;
99 }
100
101 [[nodiscard]] std::optional<u64> FindFreeRegion(u64 size, u64 alignment) noexcept {
102 ASSERT(std::has_single_bit(alignment));
103 const u64 alignment_log2 = std::countr_zero(alignment);
104 std::optional<u64> candidate;
105 u64 iterator = 0;
106 auto commit = commits.begin();
107 while (iterator + size <= allocation_size) {
108 candidate = candidate.value_or(iterator);
109 if (commit == commits.end()) {
110 break;
111 }
112 if (commit->Contains(*candidate, size)) {
113 candidate = std::nullopt;
114 }
115 iterator = Common::AlignUpLog2(commit->end, alignment_log2);
116 ++commit;
117 }
118 return candidate;
119 }
120
121 const Device& device; ///< Vulkan device.
122 const vk::DeviceMemory memory; ///< Vulkan memory allocation handler.
123 const u64 allocation_size; ///< Size of this allocation.
124 const VkMemoryPropertyFlags property_flags; ///< Vulkan memory property flags.
125 const u32 shifted_memory_type; ///< Shifted Vulkan memory type.
126 std::vector<Range> commits; ///< All commit ranges done from this allocation.
127 std::span<u8> memory_mapped_span; ///< Memory mapped span. Empty if not queried before.
128};
129
130MemoryCommit::MemoryCommit(MemoryAllocation* allocation_, VkDeviceMemory memory_, u64 begin_,
131 u64 end_) noexcept
132 : allocation{allocation_}, memory{memory_}, begin{begin_}, end{end_} {}
133
134MemoryCommit::~MemoryCommit() {
135 Release();
136}
137
138MemoryCommit& MemoryCommit::operator=(MemoryCommit&& rhs) noexcept {
139 Release();
140 allocation = std::exchange(rhs.allocation, nullptr);
141 memory = rhs.memory;
142 begin = rhs.begin;
143 end = rhs.end;
144 span = std::exchange(rhs.span, std::span<u8>{});
145 return *this;
146}
147
148MemoryCommit::MemoryCommit(MemoryCommit&& rhs) noexcept
149 : allocation{std::exchange(rhs.allocation, nullptr)}, memory{rhs.memory}, begin{rhs.begin},
150 end{rhs.end}, span{std::exchange(rhs.span, std::span<u8>{})} {}
151
152std::span<u8> MemoryCommit::Map() {
153 if (span.empty()) {
154 span = allocation->Map().subspan(begin, end - begin);
155 }
156 return span;
157}
158
159void MemoryCommit::Release() {
160 if (allocation) {
161 allocation->Free(begin);
162 }
163}
164
165MemoryAllocator::MemoryAllocator(const Device& device_)
166 : device{device_}, properties{device_.GetPhysical().GetMemoryProperties()} {}
167
168MemoryAllocator::~MemoryAllocator() = default;
169
170MemoryCommit MemoryAllocator::Commit(const VkMemoryRequirements& requirements, MemoryUsage usage) {
171 // Find the fastest memory flags we can afford with the current requirements
172 const VkMemoryPropertyFlags flags = MemoryPropertyFlags(requirements.memoryTypeBits, usage);
173 if (std::optional<MemoryCommit> commit = TryCommit(requirements, flags)) {
174 return std::move(*commit);
175 }
176 // Commit has failed, allocate more memory.
177 // TODO(Rodrigo): Handle out of memory situations in some way like flushing to guest memory.
178 AllocMemory(flags, requirements.memoryTypeBits, AllocationChunkSize(requirements.size));
179
180 // Commit again, this time it won't fail since there's a fresh allocation above.
181 // If it does, there's a bug.
182 return TryCommit(requirements, flags).value();
183}
184
185MemoryCommit MemoryAllocator::Commit(const vk::Buffer& buffer, MemoryUsage usage) {
186 auto commit = Commit(device.GetLogical().GetBufferMemoryRequirements(*buffer), usage);
187 buffer.BindMemory(commit.Memory(), commit.Offset());
188 return commit;
189}
190
191MemoryCommit MemoryAllocator::Commit(const vk::Image& image, MemoryUsage usage) {
192 auto commit = Commit(device.GetLogical().GetImageMemoryRequirements(*image), usage);
193 image.BindMemory(commit.Memory(), commit.Offset());
194 return commit;
195}
196
197void MemoryAllocator::AllocMemory(VkMemoryPropertyFlags flags, u32 type_mask, u64 size) {
198 const u32 type = FindType(flags, type_mask).value();
199 vk::DeviceMemory memory = device.GetLogical().AllocateMemory({
200 .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
201 .pNext = nullptr,
202 .allocationSize = size,
203 .memoryTypeIndex = type,
204 });
205 allocations.push_back(
206 std::make_unique<MemoryAllocation>(device, std::move(memory), flags, size, type));
207}
208
209std::optional<MemoryCommit> MemoryAllocator::TryCommit(const VkMemoryRequirements& requirements,
210 VkMemoryPropertyFlags flags) {
211 for (auto& allocation : allocations) {
212 if (!allocation->IsCompatible(flags, requirements.memoryTypeBits)) {
213 continue;
214 }
215 if (auto commit = allocation->Commit(requirements.size, requirements.alignment)) {
216 return commit;
217 }
218 }
219 return std::nullopt;
220}
221
222VkMemoryPropertyFlags MemoryAllocator::MemoryPropertyFlags(u32 type_mask, MemoryUsage usage) const {
223 return MemoryPropertyFlags(type_mask, MemoryUsagePropertyFlags(usage));
224}
225
226VkMemoryPropertyFlags MemoryAllocator::MemoryPropertyFlags(u32 type_mask,
227 VkMemoryPropertyFlags flags) const {
228 if (FindType(flags, type_mask)) {
229 // Found a memory type with those requirements
230 return flags;
231 }
232 if (flags & VK_MEMORY_PROPERTY_HOST_CACHED_BIT) {
233 // Remove host cached bit in case it's not supported
234 return MemoryPropertyFlags(type_mask, flags & ~VK_MEMORY_PROPERTY_HOST_CACHED_BIT);
235 }
236 if (flags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) {
237 // Remove device local, if it's not supported by the requested resource
238 return MemoryPropertyFlags(type_mask, flags & ~VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
239 }
240 UNREACHABLE_MSG("No compatible memory types found");
241 return 0;
242}
243
244std::optional<u32> MemoryAllocator::FindType(VkMemoryPropertyFlags flags, u32 type_mask) const {
245 for (u32 type_index = 0; type_index < properties.memoryTypeCount; ++type_index) {
246 const VkMemoryPropertyFlags type_flags = properties.memoryTypes[type_index].propertyFlags;
247 if ((type_mask & (1U << type_index)) && (type_flags & flags)) {
248 // The type matches in type and in the wanted properties.
249 return type_index;
250 }
251 }
252 // Failed to find index
253 return std::nullopt;
254}
255
256bool IsHostVisible(MemoryUsage usage) noexcept {
257 switch (usage) {
258 case MemoryUsage::DeviceLocal:
259 return false;
260 case MemoryUsage::Upload:
261 case MemoryUsage::Download:
262 return true;
263 }
264 UNREACHABLE_MSG("Invalid memory usage={}", usage);
265 return false;
266}
267
268} // namespace Vulkan
diff --git a/src/video_core/vulkan_common/vulkan_memory_allocator.h b/src/video_core/vulkan_common/vulkan_memory_allocator.h
new file mode 100644
index 000000000..53b3b275a
--- /dev/null
+++ b/src/video_core/vulkan_common/vulkan_memory_allocator.h
@@ -0,0 +1,118 @@
1// Copyright 2019 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 <memory>
8#include <span>
9#include <utility>
10#include <vector>
11#include "common/common_types.h"
12#include "video_core/vulkan_common/vulkan_wrapper.h"
13
14namespace Vulkan {
15
16class Device;
17class MemoryMap;
18class MemoryAllocation;
19
20/// Hints and requirements for the backing memory type of a commit
21enum class MemoryUsage {
22 DeviceLocal, ///< Hints device local usages, fastest memory type to read and write from the GPU
23 Upload, ///< Requires a host visible memory type optimized for CPU to GPU uploads
24 Download, ///< Requires a host visible memory type optimized for GPU to CPU readbacks
25};
26
27/// Ownership handle of a memory commitment.
28/// Points to a subregion of a memory allocation.
29class MemoryCommit {
30public:
31 explicit MemoryCommit() noexcept = default;
32 explicit MemoryCommit(MemoryAllocation* allocation_, VkDeviceMemory memory_, u64 begin_,
33 u64 end_) noexcept;
34 ~MemoryCommit();
35
36 MemoryCommit& operator=(MemoryCommit&&) noexcept;
37 MemoryCommit(MemoryCommit&&) noexcept;
38
39 MemoryCommit& operator=(const MemoryCommit&) = delete;
40 MemoryCommit(const MemoryCommit&) = delete;
41
42 /// Returns a host visible memory map.
43 /// It will map the backing allocation if it hasn't been mapped before.
44 std::span<u8> Map();
45
46 /// Returns the Vulkan memory handler.
47 VkDeviceMemory Memory() const {
48 return memory;
49 }
50
51 /// Returns the start position of the commit relative to the allocation.
52 VkDeviceSize Offset() const {
53 return static_cast<VkDeviceSize>(begin);
54 }
55
56private:
57 void Release();
58
59 MemoryAllocation* allocation{}; ///< Pointer to the large memory allocation.
60 VkDeviceMemory memory{}; ///< Vulkan device memory handler.
61 u64 begin{}; ///< Beginning offset in bytes to where the commit exists.
62 u64 end{}; ///< Offset in bytes where the commit ends.
63 std::span<u8> span; ///< Host visible memory span. Empty if not queried before.
64};
65
66/// Memory allocator container.
67/// Allocates and releases memory allocations on demand.
68class MemoryAllocator {
69public:
70 explicit MemoryAllocator(const Device& device_);
71 ~MemoryAllocator();
72
73 MemoryAllocator& operator=(const MemoryAllocator&) = delete;
74 MemoryAllocator(const MemoryAllocator&) = delete;
75
76 /**
77 * Commits a memory with the specified requeriments.
78 *
79 * @param requirements Requirements returned from a Vulkan call.
80 * @param host_visible Signals the allocator that it *must* use host visible and coherent
81 * memory. When passing false, it will try to allocate device local memory.
82 *
83 * @returns A memory commit.
84 */
85 MemoryCommit Commit(const VkMemoryRequirements& requirements, MemoryUsage usage);
86
87 /// Commits memory required by the buffer and binds it.
88 MemoryCommit Commit(const vk::Buffer& buffer, MemoryUsage usage);
89
90 /// Commits memory required by the image and binds it.
91 MemoryCommit Commit(const vk::Image& image, MemoryUsage usage);
92
93private:
94 /// Allocates a chunk of memory.
95 void AllocMemory(VkMemoryPropertyFlags flags, u32 type_mask, u64 size);
96
97 /// Tries to allocate a memory commit.
98 std::optional<MemoryCommit> TryCommit(const VkMemoryRequirements& requirements,
99 VkMemoryPropertyFlags flags);
100
101 /// Returns the fastest compatible memory property flags from a wanted usage.
102 VkMemoryPropertyFlags MemoryPropertyFlags(u32 type_mask, MemoryUsage usage) const;
103
104 /// Returns the fastest compatible memory property flags from the wanted flags.
105 VkMemoryPropertyFlags MemoryPropertyFlags(u32 type_mask, VkMemoryPropertyFlags flags) const;
106
107 /// Returns index to the fastest memory type compatible with the passed requirements.
108 std::optional<u32> FindType(VkMemoryPropertyFlags flags, u32 type_mask) const;
109
110 const Device& device; ///< Device handle.
111 const VkPhysicalDeviceMemoryProperties properties; ///< Physical device properties.
112 std::vector<std::unique_ptr<MemoryAllocation>> allocations; ///< Current allocations.
113};
114
115/// Returns true when a memory usage is guaranteed to be host visible.
116bool IsHostVisible(MemoryUsage usage) noexcept;
117
118} // namespace Vulkan