/src/skia/tools/gpu/vk/VkTestMemoryAllocator.cpp
Line | Count | Source (jump to first uncovered line) |
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 | | |
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 | 0 | const skgpu::VulkanInterface* interface) { |
23 | 0 | #define SKGPU_COPY_FUNCTION(NAME) functions.vk##NAME = interface->fFunctions.f##NAME |
24 | 0 | #define SKGPU_COPY_FUNCTION_KHR(NAME) functions.vk##NAME##KHR = interface->fFunctions.f##NAME |
25 | |
|
26 | 0 | 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 | 0 | 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 | 0 | functions.vkGetInstanceProcAddr = nullptr; |
34 | 0 | functions.vkGetDeviceProcAddr = nullptr; |
35 | 0 | SKGPU_COPY_FUNCTION(GetPhysicalDeviceProperties); |
36 | 0 | SKGPU_COPY_FUNCTION(GetPhysicalDeviceMemoryProperties); |
37 | 0 | SKGPU_COPY_FUNCTION(AllocateMemory); |
38 | 0 | SKGPU_COPY_FUNCTION(FreeMemory); |
39 | 0 | SKGPU_COPY_FUNCTION(MapMemory); |
40 | 0 | SKGPU_COPY_FUNCTION(UnmapMemory); |
41 | 0 | SKGPU_COPY_FUNCTION(FlushMappedMemoryRanges); |
42 | 0 | SKGPU_COPY_FUNCTION(InvalidateMappedMemoryRanges); |
43 | 0 | SKGPU_COPY_FUNCTION(BindBufferMemory); |
44 | 0 | SKGPU_COPY_FUNCTION(BindImageMemory); |
45 | 0 | SKGPU_COPY_FUNCTION(GetBufferMemoryRequirements); |
46 | 0 | SKGPU_COPY_FUNCTION(GetImageMemoryRequirements); |
47 | 0 | SKGPU_COPY_FUNCTION(CreateBuffer); |
48 | 0 | SKGPU_COPY_FUNCTION(DestroyBuffer); |
49 | 0 | SKGPU_COPY_FUNCTION(CreateImage); |
50 | 0 | SKGPU_COPY_FUNCTION(DestroyImage); |
51 | 0 | SKGPU_COPY_FUNCTION(CmdCopyBuffer); |
52 | 0 | SKGPU_COPY_FUNCTION_KHR(GetBufferMemoryRequirements2); |
53 | 0 | SKGPU_COPY_FUNCTION_KHR(GetImageMemoryRequirements2); |
54 | 0 | SKGPU_COPY_FUNCTION_KHR(BindBufferMemory2); |
55 | 0 | SKGPU_COPY_FUNCTION_KHR(BindImageMemory2); |
56 | 0 | SKGPU_COPY_FUNCTION_KHR(GetPhysicalDeviceMemoryProperties2); |
57 | |
|
58 | 0 | VmaAllocatorCreateInfo info; |
59 | 0 | info.flags = VMA_ALLOCATOR_CREATE_EXTERNALLY_SYNCHRONIZED_BIT; |
60 | 0 | if (physicalDeviceVersion >= VK_MAKE_VERSION(1, 1, 0) || |
61 | 0 | (extensions->hasExtension(VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME, 1) && |
62 | 0 | extensions->hasExtension(VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME, 1))) { |
63 | 0 | info.flags |= VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT; |
64 | 0 | } |
65 | |
|
66 | 0 | info.physicalDevice = physicalDevice; |
67 | 0 | 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 | 0 | info.preferredLargeHeapBlockSize = 4 * 1024 * 1024; |
73 | 0 | info.pAllocationCallbacks = nullptr; |
74 | 0 | info.pDeviceMemoryCallbacks = nullptr; |
75 | 0 | info.pHeapSizeLimit = nullptr; |
76 | 0 | info.pVulkanFunctions = &functions; |
77 | 0 | 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 | 0 | info.vulkanApiVersion = std::min(physicalDeviceVersion, VK_MAKE_VERSION(1, 1, 0)); |
81 | 0 | info.pTypeExternalMemoryHandleTypes = nullptr; |
82 | |
|
83 | 0 | VmaAllocator allocator; |
84 | 0 | vmaCreateAllocator(&info, &allocator); |
85 | |
|
86 | 0 | return sk_sp<VkTestMemoryAllocator>(new VkTestMemoryAllocator(allocator)); |
87 | 0 | } |
88 | | |
89 | 0 | VkTestMemoryAllocator::VkTestMemoryAllocator(VmaAllocator allocator) : fAllocator(allocator) {} |
90 | | |
91 | 0 | VkTestMemoryAllocator::~VkTestMemoryAllocator() { |
92 | 0 | vmaDestroyAllocator(fAllocator); |
93 | 0 | fAllocator = VK_NULL_HANDLE; |
94 | 0 | } |
95 | | |
96 | | VkResult VkTestMemoryAllocator::allocateImageMemory(VkImage image, |
97 | | uint32_t allocationPropertyFlags, |
98 | 0 | skgpu::VulkanBackendMemory* backendMemory) { |
99 | 0 | TRACE_EVENT0_ALWAYS("skia.gpu", TRACE_FUNC); |
100 | 0 | VmaAllocationCreateInfo info; |
101 | 0 | info.flags = 0; |
102 | 0 | info.usage = VMA_MEMORY_USAGE_UNKNOWN; |
103 | 0 | info.requiredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; |
104 | 0 | info.preferredFlags = 0; |
105 | 0 | info.memoryTypeBits = 0; |
106 | 0 | info.pool = VK_NULL_HANDLE; |
107 | 0 | info.pUserData = nullptr; |
108 | |
|
109 | 0 | if (kDedicatedAllocation_AllocationPropertyFlag & allocationPropertyFlags) { |
110 | 0 | info.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT; |
111 | 0 | } |
112 | 0 | if (kLazyAllocation_AllocationPropertyFlag & allocationPropertyFlags) { |
113 | 0 | info.requiredFlags |= VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT; |
114 | 0 | } |
115 | 0 | if (kProtected_AllocationPropertyFlag & allocationPropertyFlags) { |
116 | 0 | info.requiredFlags |= VK_MEMORY_PROPERTY_PROTECTED_BIT; |
117 | 0 | } |
118 | |
|
119 | 0 | VmaAllocation allocation; |
120 | 0 | VkResult result = vmaAllocateMemoryForImage(fAllocator, image, &info, &allocation, nullptr); |
121 | 0 | if (VK_SUCCESS == result) { |
122 | 0 | *backendMemory = (skgpu::VulkanBackendMemory)allocation; |
123 | 0 | } |
124 | 0 | return result; |
125 | 0 | } |
126 | | |
127 | | VkResult VkTestMemoryAllocator::allocateBufferMemory(VkBuffer buffer, |
128 | | BufferUsage usage, |
129 | | uint32_t allocationPropertyFlags, |
130 | 0 | skgpu::VulkanBackendMemory* backendMemory) { |
131 | 0 | TRACE_EVENT0("skia.gpu", TRACE_FUNC); |
132 | 0 | VmaAllocationCreateInfo info; |
133 | 0 | info.flags = 0; |
134 | 0 | info.usage = VMA_MEMORY_USAGE_UNKNOWN; |
135 | 0 | info.memoryTypeBits = 0; |
136 | 0 | info.pool = VK_NULL_HANDLE; |
137 | 0 | info.pUserData = nullptr; |
138 | |
|
139 | 0 | switch (usage) { |
140 | 0 | case BufferUsage::kGpuOnly: |
141 | 0 | info.requiredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; |
142 | 0 | info.preferredFlags = 0; |
143 | 0 | break; |
144 | 0 | 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 | 0 | info.requiredFlags = |
158 | 0 | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; |
159 | 0 | info.preferredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; |
160 | 0 | break; |
161 | 0 | case BufferUsage::kTransfersFromCpuToGpu: |
162 | 0 | info.requiredFlags = |
163 | 0 | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; |
164 | 0 | info.preferredFlags = 0; |
165 | 0 | break; |
166 | 0 | case BufferUsage::kTransfersFromGpuToCpu: |
167 | 0 | info.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; |
168 | 0 | info.preferredFlags = VK_MEMORY_PROPERTY_HOST_CACHED_BIT; |
169 | 0 | break; |
170 | 0 | } |
171 | | |
172 | 0 | if (kDedicatedAllocation_AllocationPropertyFlag & allocationPropertyFlags) { |
173 | 0 | info.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT; |
174 | 0 | } |
175 | 0 | if ((kLazyAllocation_AllocationPropertyFlag & allocationPropertyFlags) && |
176 | 0 | BufferUsage::kGpuOnly == usage) { |
177 | 0 | info.preferredFlags |= VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT; |
178 | 0 | } |
179 | |
|
180 | 0 | if (kPersistentlyMapped_AllocationPropertyFlag & allocationPropertyFlags) { |
181 | 0 | SkASSERT(BufferUsage::kGpuOnly != usage); |
182 | 0 | info.flags |= VMA_ALLOCATION_CREATE_MAPPED_BIT; |
183 | 0 | } |
184 | |
|
185 | 0 | VmaAllocation allocation; |
186 | 0 | VkResult result = vmaAllocateMemoryForBuffer(fAllocator, buffer, &info, &allocation, nullptr); |
187 | 0 | if (VK_SUCCESS == result) { |
188 | 0 | *backendMemory = (skgpu::VulkanBackendMemory)allocation; |
189 | 0 | } |
190 | |
|
191 | 0 | return result; |
192 | 0 | } Unexecuted instantiation: sk_gpu_test::VkTestMemoryAllocator::allocateBufferMemory(VkBuffer_T*, skgpu::VulkanMemoryAllocator::BufferUsage, unsigned int, long*) Unexecuted instantiation: sk_gpu_test::VkTestMemoryAllocator::allocateBufferMemory(VkBuffer_T*, skgpu::VulkanMemoryAllocator::BufferUsage, unsigned int, long*) |
193 | | |
194 | 0 | void VkTestMemoryAllocator::freeMemory(const skgpu::VulkanBackendMemory& memoryHandle) { |
195 | 0 | TRACE_EVENT0("skia.gpu", TRACE_FUNC); |
196 | 0 | const VmaAllocation allocation = (VmaAllocation)memoryHandle; |
197 | 0 | vmaFreeMemory(fAllocator, allocation); |
198 | 0 | } |
199 | | |
200 | | void VkTestMemoryAllocator::getAllocInfo(const skgpu::VulkanBackendMemory& memoryHandle, |
201 | 0 | skgpu::VulkanAlloc* alloc) const { |
202 | 0 | const VmaAllocation allocation = (VmaAllocation)memoryHandle; |
203 | 0 | VmaAllocationInfo vmaInfo; |
204 | 0 | vmaGetAllocationInfo(fAllocator, allocation, &vmaInfo); |
205 | |
|
206 | 0 | VkMemoryPropertyFlags memFlags; |
207 | 0 | vmaGetMemoryTypeProperties(fAllocator, vmaInfo.memoryType, &memFlags); |
208 | |
|
209 | 0 | uint32_t flags = 0; |
210 | 0 | if (VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT & memFlags) { |
211 | 0 | flags |= skgpu::VulkanAlloc::kMappable_Flag; |
212 | 0 | } |
213 | 0 | if (!SkToBool(VK_MEMORY_PROPERTY_HOST_COHERENT_BIT & memFlags)) { |
214 | 0 | flags |= skgpu::VulkanAlloc::kNoncoherent_Flag; |
215 | 0 | } |
216 | 0 | if (VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT & memFlags) { |
217 | 0 | flags |= skgpu::VulkanAlloc::kLazilyAllocated_Flag; |
218 | 0 | } |
219 | |
|
220 | 0 | alloc->fMemory = vmaInfo.deviceMemory; |
221 | 0 | alloc->fOffset = vmaInfo.offset; |
222 | 0 | alloc->fSize = vmaInfo.size; |
223 | 0 | alloc->fFlags = flags; |
224 | 0 | alloc->fBackendMemory = memoryHandle; |
225 | 0 | } |
226 | | |
227 | | VkResult VkTestMemoryAllocator::mapMemory(const skgpu::VulkanBackendMemory& memoryHandle, |
228 | 0 | void** data) { |
229 | 0 | TRACE_EVENT0("skia.gpu", TRACE_FUNC); |
230 | 0 | const VmaAllocation allocation = (VmaAllocation)memoryHandle; |
231 | 0 | return vmaMapMemory(fAllocator, allocation, data); |
232 | 0 | } |
233 | | |
234 | 0 | void VkTestMemoryAllocator::unmapMemory(const skgpu::VulkanBackendMemory& memoryHandle) { |
235 | 0 | TRACE_EVENT0("skia.gpu", TRACE_FUNC); |
236 | 0 | const VmaAllocation allocation = (VmaAllocation)memoryHandle; |
237 | 0 | vmaUnmapMemory(fAllocator, allocation); |
238 | 0 | } |
239 | | |
240 | | VkResult VkTestMemoryAllocator::flushMemory(const skgpu::VulkanBackendMemory& memoryHandle, |
241 | | VkDeviceSize offset, |
242 | 0 | VkDeviceSize size) { |
243 | 0 | TRACE_EVENT0("skia.gpu", TRACE_FUNC); |
244 | 0 | const VmaAllocation allocation = (VmaAllocation)memoryHandle; |
245 | 0 | return vmaFlushAllocation(fAllocator, allocation, offset, size); |
246 | 0 | } |
247 | | |
248 | | VkResult VkTestMemoryAllocator::invalidateMemory(const skgpu::VulkanBackendMemory& memoryHandle, |
249 | | VkDeviceSize offset, |
250 | 0 | VkDeviceSize size) { |
251 | 0 | TRACE_EVENT0("skia.gpu", TRACE_FUNC); |
252 | 0 | const VmaAllocation allocation = (VmaAllocation)memoryHandle; |
253 | 0 | return vmaInvalidateAllocation(fAllocator, allocation, offset, size); |
254 | 0 | } |
255 | | |
256 | 0 | std::pair<uint64_t, uint64_t> VkTestMemoryAllocator::totalAllocatedAndUsedMemory() const { |
257 | 0 | VmaTotalStatistics stats; |
258 | 0 | vmaCalculateStatistics(fAllocator, &stats); |
259 | 0 | return {stats.total.statistics.blockBytes, stats.total.statistics.allocationBytes}; |
260 | 0 | } |
261 | | |
262 | | } // namespace sk_gpu_test |