xref: /aosp_15_r20/external/skia/src/gpu/ganesh/vk/GrVkBuffer.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1 /*
2  * Copyright 2021 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 "src/gpu/ganesh/vk/GrVkBuffer.h"
9 
10 #include "include/gpu/GpuTypes.h"
11 #include "include/gpu/ganesh/GrDirectContext.h"
12 #include "include/gpu/vk/VulkanMemoryAllocator.h"
13 #include "include/private/base/SkAlign.h"
14 #include "include/private/base/SkAssert.h"
15 #include "include/private/base/SkDebug.h"
16 #include "include/private/base/SkTemplates.h"
17 #include "src/gpu/ganesh/GrDirectContextPriv.h"
18 #include "src/gpu/ganesh/GrResourceProvider.h"
19 #include "src/gpu/ganesh/vk/GrVkCaps.h"
20 #include "src/gpu/ganesh/vk/GrVkDescriptorSet.h"
21 #include "src/gpu/ganesh/vk/GrVkGpu.h"
22 #include "src/gpu/ganesh/vk/GrVkResourceProvider.h"
23 #include "src/gpu/ganesh/vk/GrVkUniformHandler.h"
24 #include "src/gpu/ganesh/vk/GrVkUtil.h"
25 #include "src/gpu/vk/VulkanMemory.h"
26 
27 #include <cstring>
28 #include <functional>
29 #include <utility>
30 
31 #define VK_CALL(GPU, X) GR_VK_CALL(GPU->vkInterface(), X)
32 
GrVkBuffer(GrVkGpu * gpu,size_t sizeInBytes,GrGpuBufferType bufferType,GrAccessPattern accessPattern,VkBuffer buffer,const skgpu::VulkanAlloc & alloc,const GrVkDescriptorSet * uniformDescriptorSet,std::string_view label)33 GrVkBuffer::GrVkBuffer(GrVkGpu* gpu,
34                        size_t sizeInBytes,
35                        GrGpuBufferType bufferType,
36                        GrAccessPattern accessPattern,
37                        VkBuffer buffer,
38                        const skgpu::VulkanAlloc& alloc,
39                        const GrVkDescriptorSet* uniformDescriptorSet,
40                        std::string_view label)
41         : GrGpuBuffer(gpu, sizeInBytes, bufferType, accessPattern, label)
42         , fBuffer(buffer)
43         , fAlloc(alloc)
44         , fUniformDescriptorSet(uniformDescriptorSet) {
45     // We always require dynamic buffers to be mappable
46     SkASSERT(accessPattern != kDynamic_GrAccessPattern || this->isVkMappable());
47     SkASSERT(bufferType != GrGpuBufferType::kUniform || uniformDescriptorSet);
48     this->registerWithCache(skgpu::Budgeted::kYes);
49 }
50 
make_uniform_desc_set(GrVkGpu * gpu,VkBuffer buffer,size_t size)51 static const GrVkDescriptorSet* make_uniform_desc_set(GrVkGpu* gpu, VkBuffer buffer, size_t size) {
52     const GrVkDescriptorSet* descriptorSet = gpu->resourceProvider().getUniformDescriptorSet();
53     if (!descriptorSet) {
54         return nullptr;
55     }
56 
57     VkDescriptorBufferInfo bufferInfo;
58     memset(&bufferInfo, 0, sizeof(VkDescriptorBufferInfo));
59     bufferInfo.buffer = buffer;
60     bufferInfo.offset = 0;
61     bufferInfo.range = size;
62 
63     VkWriteDescriptorSet descriptorWrite;
64     memset(&descriptorWrite, 0, sizeof(VkWriteDescriptorSet));
65     descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
66     descriptorWrite.pNext = nullptr;
67     descriptorWrite.dstSet = *descriptorSet->descriptorSet();
68     descriptorWrite.dstBinding = GrVkUniformHandler::kUniformBinding;
69     descriptorWrite.dstArrayElement = 0;
70     descriptorWrite.descriptorCount = 1;
71     descriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
72     descriptorWrite.pImageInfo = nullptr;
73     descriptorWrite.pBufferInfo = &bufferInfo;
74     descriptorWrite.pTexelBufferView = nullptr;
75 
76     GR_VK_CALL(gpu->vkInterface(),
77                UpdateDescriptorSets(gpu->device(), 1, &descriptorWrite, 0, nullptr));
78     return descriptorSet;
79 }
80 
Make(GrVkGpu * gpu,size_t size,GrGpuBufferType bufferType,GrAccessPattern accessPattern)81 sk_sp<GrVkBuffer> GrVkBuffer::Make(GrVkGpu* gpu,
82                                    size_t size,
83                                    GrGpuBufferType bufferType,
84                                    GrAccessPattern accessPattern) {
85     VkBuffer buffer;
86     skgpu::VulkanAlloc alloc;
87 
88     bool isProtected = gpu->protectedContext() &&
89                        accessPattern == kStatic_GrAccessPattern;
90 
91     // Protected memory _never_ uses mappable buffers.
92     // Otherwise, the only time we don't require mappable buffers is when we have a static
93     // access pattern and we're on a device where gpu only memory has faster reads on the gpu than
94     // memory that is also mappable on the cpu.
95     bool requiresMappable = !isProtected &&
96                             (accessPattern == kDynamic_GrAccessPattern ||
97                              accessPattern == kStream_GrAccessPattern ||
98                              !gpu->vkCaps().gpuOnlyBuffersMorePerformant());
99 
100     using BufferUsage = skgpu::VulkanMemoryAllocator::BufferUsage;
101     BufferUsage allocUsage;
102 
103     if (bufferType == GrGpuBufferType::kXferCpuToGpu) {
104         allocUsage = BufferUsage::kTransfersFromCpuToGpu;
105     } else if (bufferType == GrGpuBufferType::kXferGpuToCpu) {
106         allocUsage = BufferUsage::kTransfersFromGpuToCpu;
107     } else {
108         allocUsage = requiresMappable ? BufferUsage::kCpuWritesGpuReads : BufferUsage::kGpuOnly;
109     }
110 
111     // create the buffer object
112     VkBufferCreateInfo bufInfo;
113     memset(&bufInfo, 0, sizeof(VkBufferCreateInfo));
114     bufInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
115     bufInfo.flags = isProtected ? VK_BUFFER_CREATE_PROTECTED_BIT : 0;
116     bufInfo.size = size;
117     // To support SkMesh buffer updates we make Vertex and Index buffers capable of being transfer
118     // dsts.
119     switch (bufferType) {
120         case GrGpuBufferType::kVertex:
121             bufInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
122             break;
123         case GrGpuBufferType::kIndex:
124             bufInfo.usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
125             break;
126         case GrGpuBufferType::kDrawIndirect:
127             bufInfo.usage = VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT;
128             break;
129         case GrGpuBufferType::kUniform:
130             bufInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT;
131             break;
132         case GrGpuBufferType::kXferCpuToGpu:
133             bufInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
134             break;
135         case GrGpuBufferType::kXferGpuToCpu:
136             bufInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT;
137             break;
138     }
139     // We may not always get a mappable buffer for non dynamic access buffers. Thus we set the
140     // transfer dst usage bit in case we need to do a copy to write data.
141     // TODO: It doesn't really hurt setting this extra usage flag, but maybe we can narrow the scope
142     // of buffers we set it on more than just not dynamic.
143     if (!requiresMappable) {
144         bufInfo.usage |= VK_BUFFER_USAGE_TRANSFER_DST_BIT;
145     }
146 
147     bufInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
148     bufInfo.queueFamilyIndexCount = 0;
149     bufInfo.pQueueFamilyIndices = nullptr;
150 
151     VkResult err;
152     err = VK_CALL(gpu, CreateBuffer(gpu->device(), &bufInfo, nullptr, &buffer));
153     if (err) {
154         return nullptr;
155     }
156 
157     bool shouldPersistentlyMapCpuToGpu = gpu->vkCaps().shouldPersistentlyMapCpuToGpuBuffers();
158     auto checkResult = [gpu, allocUsage, shouldPersistentlyMapCpuToGpu](VkResult result) {
159         GR_VK_LOG_IF_NOT_SUCCESS(gpu, result, "skgpu::VulkanMemory::AllocBufferMemory "
160                                  "(allocUsage:%d, shouldPersistentlyMapCpuToGpu:%d)",
161                                  (int)allocUsage, (int)shouldPersistentlyMapCpuToGpu);
162         return gpu->checkVkResult(result);
163     };
164     auto allocator = gpu->memoryAllocator();
165     if (!skgpu::VulkanMemory::AllocBufferMemory(allocator,
166                                                 buffer,
167                                                 skgpu::Protected(isProtected),
168                                                 allocUsage,
169                                                 shouldPersistentlyMapCpuToGpu,
170                                                 checkResult,
171                                                 &alloc)) {
172         VK_CALL(gpu, DestroyBuffer(gpu->device(), buffer, nullptr));
173         return nullptr;
174     }
175 
176     // Bind buffer
177     GR_VK_CALL_RESULT(gpu, err, BindBufferMemory(gpu->device(),
178                                                  buffer,
179                                                  alloc.fMemory,
180                                                  alloc.fOffset));
181     if (err) {
182         skgpu::VulkanMemory::FreeBufferMemory(allocator, alloc);
183         VK_CALL(gpu, DestroyBuffer(gpu->device(), buffer, nullptr));
184         return nullptr;
185     }
186 
187     // If this is a uniform buffer we must setup a descriptor set
188     const GrVkDescriptorSet* uniformDescSet = nullptr;
189     if (bufferType == GrGpuBufferType::kUniform) {
190         uniformDescSet = make_uniform_desc_set(gpu, buffer, size);
191         if (!uniformDescSet) {
192             VK_CALL(gpu, DestroyBuffer(gpu->device(), buffer, nullptr));
193             skgpu::VulkanMemory::FreeBufferMemory(allocator, alloc);
194             return nullptr;
195         }
196     }
197 
198     return sk_sp<GrVkBuffer>(new GrVkBuffer(
199             gpu, size, bufferType, accessPattern, buffer, alloc, uniformDescSet,
200             /*label=*/"MakeVkBuffer"));
201 }
202 
vkMap(size_t readOffset,size_t readSize)203 void GrVkBuffer::vkMap(size_t readOffset, size_t readSize) {
204     SkASSERT(!fMapPtr);
205     if (this->isVkMappable()) {
206         // Not every buffer will use command buffer usage refs and instead the command buffer just
207         // holds normal refs. Systems higher up in Ganesh should be making sure not to reuse a
208         // buffer that currently has a ref held by something else. However, we do need to make sure
209         // there isn't a buffer with just a command buffer usage that is trying to be mapped.
210         SkASSERT(this->internalHasNoCommandBufferUsages());
211         SkASSERT(fAlloc.fSize > 0);
212         SkASSERT(fAlloc.fSize >= readOffset + readSize);
213 
214         GrVkGpu* gpu = this->getVkGpu();
215         auto checkResult_mapAlloc = [gpu](VkResult result) {
216             GR_VK_LOG_IF_NOT_SUCCESS(gpu, result, "skgpu::VulkanMemory::MapAlloc");
217             return gpu->checkVkResult(result);
218         };
219         auto allocator = gpu->memoryAllocator();
220         fMapPtr = skgpu::VulkanMemory::MapAlloc(allocator, fAlloc, checkResult_mapAlloc);
221         if (fMapPtr && readSize != 0) {
222             auto checkResult_invalidateMapAlloc = [gpu, readOffset, readSize](VkResult result) {
223                 GR_VK_LOG_IF_NOT_SUCCESS(gpu, result, "skgpu::VulkanMemory::InvalidateMappedAlloc "
224                                          "(readOffset:%zu, readSize:%zu)",
225                                          readOffset, readSize);
226                 return gpu->checkVkResult(result);
227             };
228             // "Invalidate" here means make device writes visible to the host. That is, it makes
229             // sure any GPU writes are finished in the range we might read from.
230             skgpu::VulkanMemory::InvalidateMappedAlloc(allocator,
231                                                        fAlloc,
232                                                        readOffset,
233                                                        readSize,
234                                                        checkResult_invalidateMapAlloc);
235         }
236     }
237 }
238 
vkUnmap(size_t flushOffset,size_t flushSize)239 void GrVkBuffer::vkUnmap(size_t flushOffset, size_t flushSize) {
240     SkASSERT(fMapPtr && this->isVkMappable());
241 
242     SkASSERT(fAlloc.fSize > 0);
243     SkASSERT(fAlloc.fSize >= flushOffset + flushSize);
244 
245     GrVkGpu* gpu = this->getVkGpu();
246     auto checkResult = [gpu, flushOffset, flushSize](VkResult result) {
247         GR_VK_LOG_IF_NOT_SUCCESS(gpu, result, "skgpu::VulkanMemory::FlushMappedAlloc "
248                                  "(flushOffset:%zu, flushSize:%zu)",
249                                  flushOffset, flushSize);
250         return gpu->checkVkResult(result);
251     };
252     auto allocator = this->getVkGpu()->memoryAllocator();
253     skgpu::VulkanMemory::FlushMappedAlloc(allocator, fAlloc, flushOffset, flushSize, checkResult);
254     skgpu::VulkanMemory::UnmapAlloc(allocator, fAlloc);
255 }
256 
copyCpuDataToGpuBuffer(const void * src,size_t offset,size_t size)257 void GrVkBuffer::copyCpuDataToGpuBuffer(const void* src, size_t offset, size_t size) {
258     SkASSERT(src);
259 
260     GrVkGpu* gpu = this->getVkGpu();
261 
262     // The vulkan api restricts the use of vkCmdUpdateBuffer to updates that are less than or equal
263     // to 65536 bytes and a size and offset that are both 4 byte aligned.
264     if ((size <= 65536) && SkIsAlign4(size) && SkIsAlign4(offset) &&
265         !gpu->vkCaps().avoidUpdateBuffers()) {
266         gpu->updateBuffer(sk_ref_sp(this), src, offset, size);
267     } else {
268         GrResourceProvider* resourceProvider = gpu->getContext()->priv().resourceProvider();
269         sk_sp<GrGpuBuffer> transferBuffer = resourceProvider->createBuffer(
270                 src,
271                 size,
272                 GrGpuBufferType::kXferCpuToGpu,
273                 kDynamic_GrAccessPattern);
274         if (!transferBuffer) {
275             return;
276         }
277 
278         gpu->transferFromBufferToBuffer(std::move(transferBuffer),
279                                         /*srcOffset=*/0,
280                                         sk_ref_sp(this),
281                                         offset,
282                                         size);
283     }
284 }
285 
addMemoryBarrier(VkAccessFlags srcAccessMask,VkAccessFlags dstAccesMask,VkPipelineStageFlags srcStageMask,VkPipelineStageFlags dstStageMask,bool byRegion) const286 void GrVkBuffer::addMemoryBarrier(VkAccessFlags srcAccessMask,
287                                   VkAccessFlags dstAccesMask,
288                                   VkPipelineStageFlags srcStageMask,
289                                   VkPipelineStageFlags dstStageMask,
290                                   bool byRegion) const {
291     VkBufferMemoryBarrier bufferMemoryBarrier = {
292             VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,  // sType
293             nullptr,                                  // pNext
294             srcAccessMask,                            // srcAccessMask
295             dstAccesMask,                             // dstAccessMask
296             VK_QUEUE_FAMILY_IGNORED,                  // srcQueueFamilyIndex
297             VK_QUEUE_FAMILY_IGNORED,                  // dstQueueFamilyIndex
298             fBuffer,                                  // buffer
299             0,                                        // offset
300             this->size(),                             // size
301     };
302 
303     // TODO: restrict to area of buffer we're interested in
304     this->getVkGpu()->addBufferMemoryBarrier(srcStageMask, dstStageMask, byRegion,
305                                              &bufferMemoryBarrier);
306 }
307 
vkRelease()308 void GrVkBuffer::vkRelease() {
309     if (this->wasDestroyed()) {
310         return;
311     }
312 
313     if (fMapPtr) {
314         this->vkUnmap(0, this->size());
315         fMapPtr = nullptr;
316     }
317 
318     if (fUniformDescriptorSet) {
319         fUniformDescriptorSet->recycle();
320         fUniformDescriptorSet = nullptr;
321     }
322 
323     SkASSERT(fBuffer);
324     SkASSERT(fAlloc.fMemory && fAlloc.fBackendMemory);
325     VK_CALL(this->getVkGpu(), DestroyBuffer(this->getVkGpu()->device(), fBuffer, nullptr));
326     fBuffer = VK_NULL_HANDLE;
327 
328     skgpu::VulkanMemory::FreeBufferMemory(this->getVkGpu()->memoryAllocator(), fAlloc);
329     fAlloc.fMemory = VK_NULL_HANDLE;
330     fAlloc.fBackendMemory = 0;
331 }
332 
onRelease()333 void GrVkBuffer::onRelease() {
334     this->vkRelease();
335     this->GrGpuBuffer::onRelease();
336 }
337 
onAbandon()338 void GrVkBuffer::onAbandon() {
339     this->vkRelease();
340     this->GrGpuBuffer::onAbandon();
341 }
342 
onMap(MapType type)343 void GrVkBuffer::onMap(MapType type) {
344     this->vkMap(0, type == MapType::kRead ? this->size() : 0);
345 }
346 
onUnmap(MapType type)347 void GrVkBuffer::onUnmap(MapType type) {
348     this->vkUnmap(0, type == MapType::kWriteDiscard ? this->size() : 0);
349 }
350 
onClearToZero()351 bool GrVkBuffer::onClearToZero() { return this->getVkGpu()->zeroBuffer(sk_ref_sp(this)); }
352 
onUpdateData(const void * src,size_t offset,size_t size,bool)353 bool GrVkBuffer::onUpdateData(const void* src, size_t offset, size_t size, bool /*preserve*/) {
354     if (this->isVkMappable()) {
355         // We won't be reading the mapped memory so pass an empty range.
356         this->vkMap(0, 0);
357         if (!fMapPtr) {
358             return false;
359         }
360         memcpy(SkTAddOffset<void>(fMapPtr, offset), src, size);
361         // We only need to flush the updated portion so pass the true range here.
362         this->vkUnmap(offset, size);
363         fMapPtr = nullptr;
364     } else {
365         this->copyCpuDataToGpuBuffer(src, offset, size);
366     }
367     return true;
368 }
369 
getVkGpu() const370 GrVkGpu* GrVkBuffer::getVkGpu() const {
371     SkASSERT(!this->wasDestroyed());
372     return static_cast<GrVkGpu*>(this->getGpu());
373 }
374 
uniformDescriptorSet() const375 const VkDescriptorSet* GrVkBuffer::uniformDescriptorSet() const {
376     SkASSERT(fUniformDescriptorSet);
377     return fUniformDescriptorSet->descriptorSet();
378 }
379