1 /*
2 * Copyright 2018 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8 #include "src/gpu/vk/vulkanmemoryallocator/VulkanAMDMemoryAllocator.h"
9
10 #include "include/gpu/vk/VulkanBackendContext.h"
11 #include "include/gpu/vk/VulkanExtensions.h"
12 #include "include/private/base/SkAssert.h"
13 #include "include/private/base/SkTo.h"
14 #include "src/core/SkTraceEvent.h"
15 #include "src/gpu/GpuTypesPriv.h"
16 #include "src/gpu/vk/VulkanInterface.h"
17 #include "src/gpu/vk/VulkanUtilsPriv.h"
18 #include "src/gpu/vk/vulkanmemoryallocator/VulkanMemoryAllocatorPriv.h"
19
20 #include <algorithm>
21 #include <cstring>
22
23 namespace skgpu {
24
Make(VkInstance instance,VkPhysicalDevice physicalDevice,VkDevice device,uint32_t physicalDeviceVersion,const VulkanExtensions * extensions,const VulkanInterface * interface,ThreadSafe threadSafe,std::optional<VkDeviceSize> blockSize)25 sk_sp<VulkanMemoryAllocator> VulkanAMDMemoryAllocator::Make(VkInstance instance,
26 VkPhysicalDevice physicalDevice,
27 VkDevice device,
28 uint32_t physicalDeviceVersion,
29 const VulkanExtensions* extensions,
30 const VulkanInterface* interface,
31 ThreadSafe threadSafe,
32 std::optional<VkDeviceSize> blockSize) {
33 #define SKGPU_COPY_FUNCTION(NAME) functions.vk##NAME = interface->fFunctions.f##NAME
34 #define SKGPU_COPY_FUNCTION_KHR(NAME) functions.vk##NAME##KHR = interface->fFunctions.f##NAME
35
36 VmaVulkanFunctions functions;
37 // We should be setting all the required functions (at least through vulkan 1.1), but this is
38 // just extra belt and suspenders to make sure there isn't unitialized values here.
39 std::memset(&functions, 0, sizeof(VmaVulkanFunctions));
40
41 // We don't use dynamic function getting in the allocator so we set the getProc functions to
42 // null.
43 functions.vkGetInstanceProcAddr = nullptr;
44 functions.vkGetDeviceProcAddr = nullptr;
45 SKGPU_COPY_FUNCTION(GetPhysicalDeviceProperties);
46 SKGPU_COPY_FUNCTION(GetPhysicalDeviceMemoryProperties);
47 SKGPU_COPY_FUNCTION(AllocateMemory);
48 SKGPU_COPY_FUNCTION(FreeMemory);
49 SKGPU_COPY_FUNCTION(MapMemory);
50 SKGPU_COPY_FUNCTION(UnmapMemory);
51 SKGPU_COPY_FUNCTION(FlushMappedMemoryRanges);
52 SKGPU_COPY_FUNCTION(InvalidateMappedMemoryRanges);
53 SKGPU_COPY_FUNCTION(BindBufferMemory);
54 SKGPU_COPY_FUNCTION(BindImageMemory);
55 SKGPU_COPY_FUNCTION(GetBufferMemoryRequirements);
56 SKGPU_COPY_FUNCTION(GetImageMemoryRequirements);
57 SKGPU_COPY_FUNCTION(CreateBuffer);
58 SKGPU_COPY_FUNCTION(DestroyBuffer);
59 SKGPU_COPY_FUNCTION(CreateImage);
60 SKGPU_COPY_FUNCTION(DestroyImage);
61 SKGPU_COPY_FUNCTION(CmdCopyBuffer);
62 SKGPU_COPY_FUNCTION_KHR(GetBufferMemoryRequirements2);
63 SKGPU_COPY_FUNCTION_KHR(GetImageMemoryRequirements2);
64 SKGPU_COPY_FUNCTION_KHR(BindBufferMemory2);
65 SKGPU_COPY_FUNCTION_KHR(BindImageMemory2);
66 SKGPU_COPY_FUNCTION_KHR(GetPhysicalDeviceMemoryProperties2);
67
68 VmaAllocatorCreateInfo info;
69 info.flags = 0;
70 if (threadSafe == ThreadSafe::kNo) {
71 info.flags |= VMA_ALLOCATOR_CREATE_EXTERNALLY_SYNCHRONIZED_BIT;
72 }
73 if (physicalDeviceVersion >= VK_MAKE_VERSION(1, 1, 0) ||
74 (extensions->hasExtension(VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME, 1) &&
75 extensions->hasExtension(VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME, 1))) {
76 info.flags |= VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT;
77 }
78
79 info.physicalDevice = physicalDevice;
80 info.device = device;
81 // 4MB was picked for the size here by looking at memory usage of Android apps and runs of DM.
82 // It seems to be a good compromise of not wasting unused allocated space and not making too
83 // many small allocations. The AMD allocator will start making blocks at 1/8 the max size and
84 // builds up block size as needed before capping at the max set here.
85 info.preferredLargeHeapBlockSize = blockSize.value_or(4 * 1024 * 1024);
86 info.pAllocationCallbacks = nullptr;
87 info.pDeviceMemoryCallbacks = nullptr;
88 info.pHeapSizeLimit = nullptr;
89 info.pVulkanFunctions = &functions;
90 info.instance = instance;
91 // TODO: Update our interface and headers to support vulkan 1.3 and add in the new required
92 // functions for 1.3 that the allocator needs. Until then we just clamp the version to 1.1.
93 info.vulkanApiVersion = std::min(physicalDeviceVersion, VK_MAKE_VERSION(1, 1, 0));
94 info.pTypeExternalMemoryHandleTypes = nullptr;
95
96 VmaAllocator allocator;
97 vmaCreateAllocator(&info, &allocator);
98
99 return sk_sp<VulkanAMDMemoryAllocator>(new VulkanAMDMemoryAllocator(allocator));
100 }
101
VulkanAMDMemoryAllocator(VmaAllocator allocator)102 VulkanAMDMemoryAllocator::VulkanAMDMemoryAllocator(VmaAllocator allocator)
103 : fAllocator(allocator) {}
104
~VulkanAMDMemoryAllocator()105 VulkanAMDMemoryAllocator::~VulkanAMDMemoryAllocator() {
106 vmaDestroyAllocator(fAllocator);
107 fAllocator = VK_NULL_HANDLE;
108 }
109
allocateImageMemory(VkImage image,uint32_t allocationPropertyFlags,skgpu::VulkanBackendMemory * backendMemory)110 VkResult VulkanAMDMemoryAllocator::allocateImageMemory(VkImage image,
111 uint32_t allocationPropertyFlags,
112 skgpu::VulkanBackendMemory* backendMemory) {
113 TRACE_EVENT0_ALWAYS("skia.gpu", TRACE_FUNC);
114 VmaAllocationCreateInfo info;
115 info.flags = 0;
116 info.usage = VMA_MEMORY_USAGE_UNKNOWN;
117 info.requiredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
118 info.preferredFlags = 0;
119 info.memoryTypeBits = 0;
120 info.pool = VK_NULL_HANDLE;
121 info.pUserData = nullptr;
122
123 if (kDedicatedAllocation_AllocationPropertyFlag & allocationPropertyFlags) {
124 info.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
125 }
126 if (kLazyAllocation_AllocationPropertyFlag & allocationPropertyFlags) {
127 info.requiredFlags |= VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT;
128 }
129 if (kProtected_AllocationPropertyFlag & allocationPropertyFlags) {
130 info.requiredFlags |= VK_MEMORY_PROPERTY_PROTECTED_BIT;
131 }
132
133 VmaAllocation allocation;
134 VkResult result = vmaAllocateMemoryForImage(fAllocator, image, &info, &allocation, nullptr);
135 if (VK_SUCCESS == result) {
136 *backendMemory = (VulkanBackendMemory)allocation;
137 }
138 return result;
139 }
140
allocateBufferMemory(VkBuffer buffer,BufferUsage usage,uint32_t allocationPropertyFlags,skgpu::VulkanBackendMemory * backendMemory)141 VkResult VulkanAMDMemoryAllocator::allocateBufferMemory(VkBuffer buffer,
142 BufferUsage usage,
143 uint32_t allocationPropertyFlags,
144 skgpu::VulkanBackendMemory* backendMemory) {
145 TRACE_EVENT0("skia.gpu", TRACE_FUNC);
146 VmaAllocationCreateInfo info;
147 info.flags = 0;
148 info.usage = VMA_MEMORY_USAGE_UNKNOWN;
149 info.memoryTypeBits = 0;
150 info.pool = VK_NULL_HANDLE;
151 info.pUserData = nullptr;
152
153 switch (usage) {
154 case BufferUsage::kGpuOnly:
155 info.requiredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
156 info.preferredFlags = 0;
157 break;
158 case BufferUsage::kCpuWritesGpuReads:
159 // When doing cpu writes and gpu reads the general rule of thumb is to use coherent
160 // memory. Though this depends on the fact that we are not doing any cpu reads and the
161 // cpu writes are sequential. For sparse writes we'd want cpu cached memory, however we
162 // don't do these types of writes in Skia.
163 //
164 // TODO: In the future there may be times where specific types of memory could benefit
165 // from a coherent and cached memory. Typically these allow for the gpu to read cpu
166 // writes from the cache without needing to flush the writes throughout the cache. The
167 // reverse is not true and GPU writes tend to invalidate the cache regardless. Also
168 // these gpu cache read access are typically lower bandwidth than non-cached memory.
169 // For now Skia doesn't really have a need or want of this type of memory. But if we
170 // ever do we could pass in an AllocationPropertyFlag that requests the cached property.
171 info.requiredFlags =
172 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
173 info.preferredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
174 break;
175 case BufferUsage::kTransfersFromCpuToGpu:
176 info.requiredFlags =
177 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
178 info.preferredFlags = 0;
179 break;
180 case BufferUsage::kTransfersFromGpuToCpu:
181 info.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT;
182 info.preferredFlags = VK_MEMORY_PROPERTY_HOST_CACHED_BIT;
183 break;
184 }
185
186 if (kDedicatedAllocation_AllocationPropertyFlag & allocationPropertyFlags) {
187 info.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
188 }
189 if ((kLazyAllocation_AllocationPropertyFlag & allocationPropertyFlags) &&
190 BufferUsage::kGpuOnly == usage) {
191 info.preferredFlags |= VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT;
192 }
193
194 if (kPersistentlyMapped_AllocationPropertyFlag & allocationPropertyFlags) {
195 SkASSERT(BufferUsage::kGpuOnly != usage);
196 info.flags |= VMA_ALLOCATION_CREATE_MAPPED_BIT;
197 }
198
199 if (kProtected_AllocationPropertyFlag & allocationPropertyFlags) {
200 info.requiredFlags |= VK_MEMORY_PROPERTY_PROTECTED_BIT;
201 }
202
203 VmaAllocation allocation;
204 VkResult result = vmaAllocateMemoryForBuffer(fAllocator, buffer, &info, &allocation, nullptr);
205 if (VK_SUCCESS == result) {
206 *backendMemory = (VulkanBackendMemory)allocation;
207 }
208
209 return result;
210 }
211
freeMemory(const VulkanBackendMemory & memoryHandle)212 void VulkanAMDMemoryAllocator::freeMemory(const VulkanBackendMemory& memoryHandle) {
213 TRACE_EVENT0("skia.gpu", TRACE_FUNC);
214 const VmaAllocation allocation = (VmaAllocation)memoryHandle;
215 vmaFreeMemory(fAllocator, allocation);
216 }
217
getAllocInfo(const VulkanBackendMemory & memoryHandle,VulkanAlloc * alloc) const218 void VulkanAMDMemoryAllocator::getAllocInfo(const VulkanBackendMemory& memoryHandle,
219 VulkanAlloc* alloc) const {
220 const VmaAllocation allocation = (VmaAllocation)memoryHandle;
221 VmaAllocationInfo vmaInfo;
222 vmaGetAllocationInfo(fAllocator, allocation, &vmaInfo);
223
224 VkMemoryPropertyFlags memFlags;
225 vmaGetMemoryTypeProperties(fAllocator, vmaInfo.memoryType, &memFlags);
226
227 uint32_t flags = 0;
228 if (VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT & memFlags) {
229 flags |= VulkanAlloc::kMappable_Flag;
230 }
231 if (!SkToBool(VK_MEMORY_PROPERTY_HOST_COHERENT_BIT & memFlags)) {
232 flags |= VulkanAlloc::kNoncoherent_Flag;
233 }
234 if (VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT & memFlags) {
235 flags |= VulkanAlloc::kLazilyAllocated_Flag;
236 }
237
238 alloc->fMemory = vmaInfo.deviceMemory;
239 alloc->fOffset = vmaInfo.offset;
240 alloc->fSize = vmaInfo.size;
241 alloc->fFlags = flags;
242 alloc->fBackendMemory = memoryHandle;
243 }
244
mapMemory(const VulkanBackendMemory & memoryHandle,void ** data)245 VkResult VulkanAMDMemoryAllocator::mapMemory(const VulkanBackendMemory& memoryHandle, void** data) {
246 TRACE_EVENT0("skia.gpu", TRACE_FUNC);
247 const VmaAllocation allocation = (VmaAllocation)memoryHandle;
248 return vmaMapMemory(fAllocator, allocation, data);
249 }
250
unmapMemory(const VulkanBackendMemory & memoryHandle)251 void VulkanAMDMemoryAllocator::unmapMemory(const VulkanBackendMemory& memoryHandle) {
252 TRACE_EVENT0("skia.gpu", TRACE_FUNC);
253 const VmaAllocation allocation = (VmaAllocation)memoryHandle;
254 vmaUnmapMemory(fAllocator, allocation);
255 }
256
flushMemory(const VulkanBackendMemory & memoryHandle,VkDeviceSize offset,VkDeviceSize size)257 VkResult VulkanAMDMemoryAllocator::flushMemory(const VulkanBackendMemory& memoryHandle,
258 VkDeviceSize offset,
259 VkDeviceSize size) {
260 TRACE_EVENT0("skia.gpu", TRACE_FUNC);
261 const VmaAllocation allocation = (VmaAllocation)memoryHandle;
262 return vmaFlushAllocation(fAllocator, allocation, offset, size);
263 }
264
invalidateMemory(const VulkanBackendMemory & memoryHandle,VkDeviceSize offset,VkDeviceSize size)265 VkResult VulkanAMDMemoryAllocator::invalidateMemory(const VulkanBackendMemory& memoryHandle,
266 VkDeviceSize offset,
267 VkDeviceSize size) {
268 TRACE_EVENT0("skia.gpu", TRACE_FUNC);
269 const VmaAllocation allocation = (VmaAllocation)memoryHandle;
270 return vmaInvalidateAllocation(fAllocator, allocation, offset, size);
271 }
272
totalAllocatedAndUsedMemory() const273 std::pair<uint64_t, uint64_t> VulkanAMDMemoryAllocator::totalAllocatedAndUsedMemory() const {
274 VmaTotalStatistics stats;
275 vmaCalculateStatistics(fAllocator, &stats);
276 return {stats.total.statistics.blockBytes, stats.total.statistics.allocationBytes};
277 }
278
279 namespace VulkanMemoryAllocators {
Make(const skgpu::VulkanBackendContext & backendContext,ThreadSafe threadSafe,std::optional<VkDeviceSize> blockSize)280 sk_sp<VulkanMemoryAllocator> Make(const skgpu::VulkanBackendContext& backendContext,
281 ThreadSafe threadSafe,
282 std::optional<VkDeviceSize> blockSize) {
283 SkASSERT(backendContext.fInstance != VK_NULL_HANDLE);
284 SkASSERT(backendContext.fPhysicalDevice != VK_NULL_HANDLE);
285 SkASSERT(backendContext.fDevice != VK_NULL_HANDLE);
286 SkASSERT(backendContext.fQueue != VK_NULL_HANDLE);
287 SkASSERT(backendContext.fGetProc);
288
289 skgpu::VulkanExtensions ext;
290 const skgpu::VulkanExtensions* extensions = &ext;
291 if (backendContext.fVkExtensions) {
292 extensions = backendContext.fVkExtensions;
293 }
294
295 // It is a bit superfluous to create a VulkanInterface here just to create a memory allocator
296 // given that Ganesh and Graphite will create their own. However, there's not a clean way to
297 // have the interface created here persist for potential re-use without refactoring
298 // VulkanMemoryAllocator to hold onto its interface as opposed to "borrowing" it.
299 // Such a refactor could get messy without much actual benefit since interface creation is
300 // not too expensive and this cost is only paid once during initialization.
301 uint32_t physDevVersion = 0;
302 sk_sp<const skgpu::VulkanInterface> interface =
303 skgpu::MakeInterface(backendContext, extensions, &physDevVersion, nullptr);
304 if (!interface) {
305 return nullptr;
306 }
307
308 return VulkanAMDMemoryAllocator::Make(backendContext.fInstance,
309 backendContext.fPhysicalDevice,
310 backendContext.fDevice,
311 physDevVersion,
312 extensions,
313 interface.get(),
314 threadSafe,
315 blockSize);
316 }
317
318 } // namespace VulkanMemoryAllocators
319 } // namespace skgpu
320