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