xref: /aosp_15_r20/external/skia/tools/gpu/vk/VkTestMemoryAllocator.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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