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