1 /*
2 * Copyright 2021 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/graphite/BufferManager.h"
9
10 #include "include/gpu/graphite/Recording.h"
11 #include "src/gpu/graphite/Caps.h"
12 #include "src/gpu/graphite/ContextPriv.h"
13 #include "src/gpu/graphite/Log.h"
14 #include "src/gpu/graphite/QueueManager.h"
15 #include "src/gpu/graphite/RecordingPriv.h"
16 #include "src/gpu/graphite/ResourceProvider.h"
17 #include "src/gpu/graphite/SharedContext.h"
18 #include "src/gpu/graphite/UploadBufferManager.h"
19 #include "src/gpu/graphite/task/ClearBuffersTask.h"
20 #include "src/gpu/graphite/task/CopyTask.h"
21 #include "src/gpu/graphite/task/TaskList.h"
22
23 #include <limits>
24
25 namespace skgpu::graphite {
26
27 namespace {
28
29 // TODO: Tune these values on real world data
30 static constexpr uint32_t kVertexBufferMinSize = 16 << 10; // 16 KB
31 static constexpr uint32_t kVertexBufferMaxSize = 1 << 20; // 1 MB
32 static constexpr uint32_t kIndexBufferSize = 2 << 10; // 2 KB
33 static constexpr uint32_t kUniformBufferSize = 2 << 10; // 2 KB
34 static constexpr uint32_t kStorageBufferMinSize = 2 << 10; // 2 KB
35 static constexpr uint32_t kStorageBufferMaxSize = 1 << 20; // 1 MB
36
37 // Make sure the buffer size constants are all powers of two, so we can align to them efficiently
38 // when dynamically sizing buffers.
39 static_assert(SkIsPow2(kVertexBufferMinSize));
40 static_assert(SkIsPow2(kVertexBufferMaxSize));
41 static_assert(SkIsPow2(kIndexBufferSize));
42 static_assert(SkIsPow2(kUniformBufferSize));
43 static_assert(SkIsPow2(kStorageBufferMinSize));
44 static_assert(SkIsPow2(kStorageBufferMaxSize));
45
46 // The limit for all data created by the StaticBufferManager. This data remains alive for
47 // the entire SharedContext so we want to keep it small and give a concrete upper bound to
48 // clients for our steady-state memory usage.
49 // FIXME The current usage is 4732 bytes across static vertex and index buffers, but that includes
50 // multiple copies of tessellation data, and an unoptimized AnalyticRRect mesh. Once those issues
51 // are addressed, we can tighten this and decide on the transfer buffer sizing as well.
52 [[maybe_unused]] static constexpr uint32_t kMaxStaticDataSize = 6 << 10;
53
validate_count_and_stride(size_t count,size_t stride)54 uint32_t validate_count_and_stride(size_t count, size_t stride) {
55 // size_t may just be uint32_t, so this ensures we have enough bits to do
56 // compute the required byte product.
57 uint64_t count64 = SkTo<uint64_t>(count);
58 uint64_t stride64 = SkTo<uint64_t>(stride);
59 uint64_t bytes64 = count64*stride64;
60 if (count64 > std::numeric_limits<uint32_t>::max() ||
61 stride64 > std::numeric_limits<uint32_t>::max() ||
62 bytes64 > std::numeric_limits<uint32_t>::max()) {
63 // Return 0 to skip further allocation attempts.
64 return 0;
65 }
66 // Since count64 and stride64 fit into 32-bits, their product did not overflow, and the product
67 // fits into 32-bits so this cast is safe.
68 return SkTo<uint32_t>(bytes64);
69 }
70
validate_size(size_t requiredBytes)71 uint32_t validate_size(size_t requiredBytes) {
72 return validate_count_and_stride(1, requiredBytes);
73 }
74
sufficient_block_size(uint32_t requiredBytes,uint32_t blockSize)75 uint32_t sufficient_block_size(uint32_t requiredBytes, uint32_t blockSize) {
76 // Always request a buffer at least 'requiredBytes', but keep them in multiples of
77 // 'blockSize' for improved reuse.
78 static constexpr uint32_t kMaxSize = std::numeric_limits<uint32_t>::max();
79 uint32_t maxBlocks = kMaxSize / blockSize;
80 uint32_t blocks = (requiredBytes / blockSize) + 1;
81 uint32_t bufferSize = blocks > maxBlocks ? kMaxSize : (blocks * blockSize);
82 SkASSERT(requiredBytes < bufferSize);
83 return bufferSize;
84 }
85
can_fit(uint32_t requestedSize,uint32_t allocatedSize,uint32_t currentOffset,uint32_t alignment)86 bool can_fit(uint32_t requestedSize,
87 uint32_t allocatedSize,
88 uint32_t currentOffset,
89 uint32_t alignment) {
90 uint32_t startOffset = SkAlignTo(currentOffset, alignment);
91 return requestedSize <= (allocatedSize - startOffset);
92 }
93
starting_alignment(BufferType type,bool useTransferBuffers,const Caps * caps)94 uint32_t starting_alignment(BufferType type, bool useTransferBuffers, const Caps* caps) {
95 // Both vertex and index data is aligned to 4 bytes by default
96 uint32_t alignment = 4;
97 if (type == BufferType::kUniform) {
98 alignment = SkTo<uint32_t>(caps->requiredUniformBufferAlignment());
99 } else if (type == BufferType::kStorage || type == BufferType::kVertexStorage ||
100 type == BufferType::kIndexStorage || type == BufferType::kIndirect) {
101 alignment = SkTo<uint32_t>(caps->requiredStorageBufferAlignment());
102 }
103 if (useTransferBuffers) {
104 alignment = std::max(alignment, SkTo<uint32_t>(caps->requiredTransferBufferAlignment()));
105 }
106 return alignment;
107 }
108
109 } // anonymous namespace
110
111 // ------------------------------------------------------------------------------------------------
112 // ScratchBuffer
113
ScratchBuffer(uint32_t size,uint32_t alignment,sk_sp<Buffer> buffer,DrawBufferManager * owner)114 ScratchBuffer::ScratchBuffer(uint32_t size, uint32_t alignment,
115 sk_sp<Buffer> buffer, DrawBufferManager* owner)
116 : fSize(size)
117 , fAlignment(alignment)
118 , fBuffer(std::move(buffer))
119 , fOwner(owner) {
120 SkASSERT(fSize > 0);
121 SkASSERT(fBuffer);
122 SkASSERT(fOwner);
123 SkASSERT(fSize <= fBuffer->size());
124 }
125
~ScratchBuffer()126 ScratchBuffer::~ScratchBuffer() { this->returnToPool(); }
127
suballocate(size_t requiredBytes)128 BindBufferInfo ScratchBuffer::suballocate(size_t requiredBytes) {
129 const uint32_t requiredBytes32 = validate_size(requiredBytes);
130 if (!this->isValid() || !requiredBytes32) {
131 return {};
132 }
133 if (!can_fit(requiredBytes32, fSize, fOffset, fAlignment)) {
134 return {};
135 }
136 const uint32_t offset = SkAlignTo(fOffset, fAlignment);
137 fOffset = offset + requiredBytes32;
138 return {fBuffer.get(), offset, requiredBytes32};
139 }
140
returnToPool()141 void ScratchBuffer::returnToPool() {
142 if (fOwner && fBuffer) {
143 // TODO: Generalize the pool to other buffer types.
144 fOwner->fReusableScratchStorageBuffers.push_back(std::move(fBuffer));
145 SkASSERT(!fBuffer);
146 }
147 }
148
149 // ------------------------------------------------------------------------------------------------
150 // DrawBufferManager
151
DrawBufferManager(ResourceProvider * resourceProvider,const Caps * caps,UploadBufferManager * uploadManager)152 DrawBufferManager::DrawBufferManager(ResourceProvider* resourceProvider,
153 const Caps* caps,
154 UploadBufferManager* uploadManager)
155 : fResourceProvider(resourceProvider)
156 , fCaps(caps)
157 , fUploadManager(uploadManager)
158 , fCurrentBuffers{{
159 { BufferType::kVertex, kVertexBufferMinSize, kVertexBufferMaxSize, caps },
160 { BufferType::kIndex, kIndexBufferSize, kIndexBufferSize, caps },
161 { BufferType::kUniform, kUniformBufferSize, kUniformBufferSize, caps },
162
163 // mapped storage
164 { BufferType::kStorage, kStorageBufferMinSize, kStorageBufferMaxSize, caps },
165 // GPU-only storage
166 { BufferType::kStorage, kStorageBufferMinSize, kStorageBufferMinSize, caps },
167
168 { BufferType::kVertexStorage, kVertexBufferMinSize, kVertexBufferMinSize, caps },
169 { BufferType::kIndexStorage, kIndexBufferSize, kIndexBufferSize, caps },
170 { BufferType::kIndirect, kStorageBufferMinSize, kStorageBufferMinSize, caps } }} {}
171
~DrawBufferManager()172 DrawBufferManager::~DrawBufferManager() {}
173
174 // For simplicity, if transfer buffers are being used, we align the data to the max alignment of
175 // either the final buffer type or cpu->gpu transfer alignment so that the buffers are laid out
176 // the same in memory.
BufferInfo(BufferType type,uint32_t minBlockSize,uint32_t maxBlockSize,const Caps * caps)177 DrawBufferManager::BufferInfo::BufferInfo(BufferType type,
178 uint32_t minBlockSize,
179 uint32_t maxBlockSize,
180 const Caps* caps)
181 : fType(type)
182 , fStartAlignment(starting_alignment(type, !caps->drawBufferCanBeMapped(), caps))
183 , fMinBlockSize(minBlockSize)
184 , fMaxBlockSize(maxBlockSize)
185 , fCurBlockSize(SkAlignTo(minBlockSize, fStartAlignment)) {}
186
getVertexWriter(size_t count,size_t stride)187 std::pair<VertexWriter, BindBufferInfo> DrawBufferManager::getVertexWriter(size_t count,
188 size_t stride) {
189 uint32_t requiredBytes = validate_count_and_stride(count, stride);
190 if (!requiredBytes) {
191 return {};
192 }
193
194 auto& info = fCurrentBuffers[kVertexBufferIndex];
195 auto [ptr, bindInfo] = this->prepareMappedBindBuffer(&info, "VertexBuffer", requiredBytes);
196 return {VertexWriter(ptr, requiredBytes), bindInfo};
197 }
198
returnVertexBytes(size_t unusedBytes)199 void DrawBufferManager::returnVertexBytes(size_t unusedBytes) {
200 if (fMappingFailed) {
201 // The caller can be unaware that the written data went to no-where and will still call
202 // this function.
203 return;
204 }
205 SkASSERT(fCurrentBuffers[kVertexBufferIndex].fOffset >= unusedBytes);
206 fCurrentBuffers[kVertexBufferIndex].fOffset -= unusedBytes;
207 }
208
getIndexWriter(size_t count,size_t stride)209 std::pair<IndexWriter, BindBufferInfo> DrawBufferManager::getIndexWriter(size_t count,
210 size_t stride) {
211 uint32_t requiredBytes = validate_count_and_stride(count, stride);
212 if (!requiredBytes) {
213 return {};
214 }
215
216 auto& info = fCurrentBuffers[kIndexBufferIndex];
217 auto [ptr, bindInfo] = this->prepareMappedBindBuffer(&info, "IndexBuffer", requiredBytes);
218 return {IndexWriter(ptr, requiredBytes), bindInfo};
219 }
220
getUniformWriter(size_t count,size_t stride)221 std::pair<UniformWriter, BindBufferInfo> DrawBufferManager::getUniformWriter(size_t count,
222 size_t stride) {
223 uint32_t requiredBytes = validate_count_and_stride(count, stride);
224 if (!requiredBytes) {
225 return {};
226 }
227
228 auto& info = fCurrentBuffers[kUniformBufferIndex];
229 auto [ptr, bindInfo] = this->prepareMappedBindBuffer(&info, "UniformBuffer", requiredBytes);
230 return {UniformWriter(ptr, requiredBytes), bindInfo};
231 }
232
getSsboWriter(size_t count,size_t stride,size_t alignment)233 std::pair<UniformWriter, BindBufferInfo> DrawBufferManager::getSsboWriter(size_t count,
234 size_t stride,
235 size_t alignment) {
236 uint32_t requiredBytes = validate_count_and_stride(count, stride);
237 if (!requiredBytes) {
238 return {};
239 }
240
241 auto& info = fCurrentBuffers[kStorageBufferIndex];
242 auto [ptr, bindInfo] =
243 this->prepareMappedBindBuffer(&info, "StorageBuffer", requiredBytes, alignment);
244 return {UniformWriter(ptr, requiredBytes), bindInfo};
245 }
246
getSsboWriter(size_t count,size_t stride)247 std::pair<UniformWriter, BindBufferInfo> DrawBufferManager::getSsboWriter(size_t count,
248 size_t stride) {
249 // By setting alignment=0, use the default buffer alignment requirement for storage buffers.
250 return this->getSsboWriter(count, stride, /*alignment=*/0);
251 }
252
getAlignedSsboWriter(size_t count,size_t stride)253 std::pair<UniformWriter, BindBufferInfo> DrawBufferManager::getAlignedSsboWriter(size_t count,
254 size_t stride) {
255 // Align to the provided element stride.
256 return this->getSsboWriter(count, stride, stride);
257 }
258
getUniformPointer(size_t requiredBytes)259 std::pair<void* /*mappedPtr*/, BindBufferInfo> DrawBufferManager::getUniformPointer(
260 size_t requiredBytes) {
261 uint32_t requiredBytes32 = validate_size(requiredBytes);
262 if (!requiredBytes32) {
263 return {};
264 }
265
266 auto& info = fCurrentBuffers[kUniformBufferIndex];
267 return this->prepareMappedBindBuffer(&info, "UniformBuffer", requiredBytes32);
268 }
269
getStoragePointer(size_t requiredBytes)270 std::pair<void* /*mappedPtr*/, BindBufferInfo> DrawBufferManager::getStoragePointer(
271 size_t requiredBytes) {
272 uint32_t requiredBytes32 = validate_size(requiredBytes);
273 if (!requiredBytes32) {
274 return {};
275 }
276
277 auto& info = fCurrentBuffers[kStorageBufferIndex];
278 return this->prepareMappedBindBuffer(&info, "StorageBuffer", requiredBytes32);
279 }
280
getStorage(size_t requiredBytes,ClearBuffer cleared)281 BindBufferInfo DrawBufferManager::getStorage(size_t requiredBytes, ClearBuffer cleared) {
282 uint32_t requiredBytes32 = validate_size(requiredBytes);
283 if (!requiredBytes32) {
284 return {};
285 }
286
287 auto& info = fCurrentBuffers[kGpuOnlyStorageBufferIndex];
288 return this->prepareBindBuffer(&info,
289 "StorageBuffer",
290 requiredBytes32,
291 /*requiredAlignment=*/0,
292 /*supportCpuUpload=*/false,
293 cleared);
294 }
295
getVertexStorage(size_t requiredBytes)296 BindBufferInfo DrawBufferManager::getVertexStorage(size_t requiredBytes) {
297 uint32_t requiredBytes32 = validate_size(requiredBytes);
298 if (!requiredBytes32) {
299 return {};
300 }
301
302 auto& info = fCurrentBuffers[kVertexStorageBufferIndex];
303 return this->prepareBindBuffer(&info, "VertexStorageBuffer", requiredBytes32);
304 }
305
getIndexStorage(size_t requiredBytes)306 BindBufferInfo DrawBufferManager::getIndexStorage(size_t requiredBytes) {
307 uint32_t requiredBytes32 = validate_size(requiredBytes);
308 if (!requiredBytes32) {
309 return {};
310 }
311
312 auto& info = fCurrentBuffers[kIndexStorageBufferIndex];
313 return this->prepareBindBuffer(&info, "IndexStorageBuffer", requiredBytes32);
314 }
315
getIndirectStorage(size_t requiredBytes,ClearBuffer cleared)316 BindBufferInfo DrawBufferManager::getIndirectStorage(size_t requiredBytes, ClearBuffer cleared) {
317 uint32_t requiredBytes32 = validate_size(requiredBytes);
318 if (!requiredBytes32) {
319 return {};
320 }
321
322 auto& info = fCurrentBuffers[kIndirectStorageBufferIndex];
323 return this->prepareBindBuffer(&info,
324 "IndirectStorageBuffer",
325 requiredBytes32,
326 /*requiredAlignment=*/0,
327 /*supportCpuUpload=*/false,
328 cleared);
329 }
330
getScratchStorage(size_t requiredBytes)331 ScratchBuffer DrawBufferManager::getScratchStorage(size_t requiredBytes) {
332 uint32_t requiredBytes32 = validate_size(requiredBytes);
333 if (!requiredBytes32 || fMappingFailed) {
334 return {};
335 }
336
337 // TODO: Generalize the pool to other buffer types.
338 auto& info = fCurrentBuffers[kStorageBufferIndex];
339 uint32_t bufferSize = sufficient_block_size(requiredBytes32, info.fCurBlockSize);
340 sk_sp<Buffer> buffer = this->findReusableSbo(bufferSize);
341 if (!buffer) {
342 buffer = fResourceProvider->findOrCreateBuffer(
343 bufferSize, BufferType::kStorage, AccessPattern::kGpuOnly, "ScratchStorageBuffer");
344
345 if (!buffer) {
346 this->onFailedBuffer();
347 return {};
348 }
349 }
350 return {requiredBytes32, info.fStartAlignment, std::move(buffer), this};
351 }
352
onFailedBuffer()353 void DrawBufferManager::onFailedBuffer() {
354 fMappingFailed = true;
355
356 // Clean up and unmap everything now
357 fClearList.clear();
358 fReusableScratchStorageBuffers.clear();
359
360 for (auto& [buffer, _] : fUsedBuffers) {
361 if (buffer->isMapped()) {
362 buffer->unmap();
363 }
364 }
365 fUsedBuffers.clear();
366
367 for (auto& info : fCurrentBuffers) {
368 if (info.fBuffer && info.fBuffer->isMapped()) {
369 info.fBuffer->unmap();
370 }
371 info.fBuffer = nullptr;
372 info.fTransferBuffer = {};
373 info.fOffset = 0;
374 }
375 }
376
transferToRecording(Recording * recording)377 bool DrawBufferManager::transferToRecording(Recording* recording) {
378 if (fMappingFailed) {
379 // All state should have been reset by onFailedBuffer() except for this error flag.
380 SkASSERT(fUsedBuffers.empty() &&
381 fClearList.empty() &&
382 fReusableScratchStorageBuffers.empty());
383 fMappingFailed = false;
384 return false;
385 }
386
387 if (!fClearList.empty()) {
388 recording->priv().taskList()->add(ClearBuffersTask::Make(std::move(fClearList)));
389 }
390
391 // Transfer the buffers in the reuse pool to the recording.
392 // TODO: Allow reuse across different Recordings?
393 for (auto& buffer : fReusableScratchStorageBuffers) {
394 recording->priv().addResourceRef(std::move(buffer));
395 }
396 fReusableScratchStorageBuffers.clear();
397
398 for (auto& [buffer, transferBuffer] : fUsedBuffers) {
399 if (transferBuffer) {
400 SkASSERT(buffer);
401 SkASSERT(!fCaps->drawBufferCanBeMapped());
402 // Since the transfer buffer is managed by the UploadManager, we don't manually unmap
403 // it here or need to pass a ref into CopyBufferToBufferTask.
404 size_t copySize = buffer->size();
405 recording->priv().taskList()->add(
406 CopyBufferToBufferTask::Make(transferBuffer.fBuffer,
407 transferBuffer.fOffset,
408 std::move(buffer),
409 /*dstOffset=*/0,
410 copySize));
411 } else {
412 if (buffer->isMapped()) {
413 buffer->unmap();
414 }
415 recording->priv().addResourceRef(std::move(buffer));
416 }
417 }
418 fUsedBuffers.clear();
419
420 // The current draw buffers have not been added to fUsedBuffers,
421 // so we need to handle them as well.
422 for (auto& info : fCurrentBuffers) {
423 if (!info.fBuffer) {
424 continue;
425 }
426 if (info.fTransferBuffer) {
427 // A transfer buffer should always be mapped at this stage
428 SkASSERT(info.fBuffer);
429 SkASSERT(!fCaps->drawBufferCanBeMapped());
430 // Since the transfer buffer is managed by the UploadManager, we don't manually unmap
431 // it here or need to pass a ref into CopyBufferToBufferTask.
432 recording->priv().taskList()->add(
433 CopyBufferToBufferTask::Make(info.fTransferBuffer.fBuffer,
434 info.fTransferBuffer.fOffset,
435 info.fBuffer,
436 /*dstOffset=*/0,
437 info.fBuffer->size()));
438 } else {
439 if (info.fBuffer->isMapped()) {
440 info.fBuffer->unmap();
441 }
442 recording->priv().addResourceRef(std::move(info.fBuffer));
443 }
444
445 // For each buffer type, update the block size to use for new buffers, based on the total
446 // storage used since the last flush.
447 const uint32_t reqSize = SkAlignTo(info.fUsedSize + info.fOffset, info.fMinBlockSize);
448 info.fCurBlockSize = std::clamp(reqSize, info.fMinBlockSize, info.fMaxBlockSize);
449 info.fUsedSize = 0;
450
451 info.fTransferBuffer = {};
452 info.fOffset = 0;
453 }
454
455 return true;
456 }
457
prepareMappedBindBuffer(BufferInfo * info,std::string_view label,uint32_t requiredBytes,uint32_t requiredAlignment)458 std::pair<void*, BindBufferInfo> DrawBufferManager::prepareMappedBindBuffer(
459 BufferInfo* info,
460 std::string_view label,
461 uint32_t requiredBytes,
462 uint32_t requiredAlignment) {
463 BindBufferInfo bindInfo = this->prepareBindBuffer(info,
464 std::move(label),
465 requiredBytes,
466 requiredAlignment,
467 /*supportCpuUpload=*/true);
468 if (!bindInfo) {
469 // prepareBindBuffer() already called onFailedBuffer()
470 SkASSERT(fMappingFailed);
471 return {nullptr, {}};
472 }
473
474 // If there's a transfer buffer, its mapped pointer should already have been validated
475 SkASSERT(!info->fTransferBuffer || info->fTransferMapPtr);
476 void* mapPtr = info->fTransferBuffer ? info->fTransferMapPtr : info->fBuffer->map();
477 if (!mapPtr) {
478 // Mapping a direct draw buffer failed
479 this->onFailedBuffer();
480 return {nullptr, {}};
481 }
482
483 mapPtr = SkTAddOffset<void>(mapPtr, static_cast<ptrdiff_t>(bindInfo.fOffset));
484 return {mapPtr, bindInfo};
485 }
486
prepareBindBuffer(BufferInfo * info,std::string_view label,uint32_t requiredBytes,uint32_t requiredAlignment,bool supportCpuUpload,ClearBuffer cleared)487 BindBufferInfo DrawBufferManager::prepareBindBuffer(BufferInfo* info,
488 std::string_view label,
489 uint32_t requiredBytes,
490 uint32_t requiredAlignment,
491 bool supportCpuUpload,
492 ClearBuffer cleared) {
493 SkASSERT(info);
494 SkASSERT(requiredBytes);
495
496 if (fMappingFailed) {
497 return {};
498 }
499
500 // A transfer buffer is not necessary if the caller does not intend to upload CPU data to it.
501 bool useTransferBuffer = supportCpuUpload && !fCaps->drawBufferCanBeMapped();
502
503 if (requiredAlignment == 0) {
504 // If explicitly required alignment is not provided, use the default buffer alignment.
505 requiredAlignment = info->fStartAlignment;
506
507 } else {
508 // If an explicitly required alignment is provided, use that instead of the default buffer
509 // alignment. This is useful when the offset is used as an index into a storage buffer
510 // rather than an offset for an actual binding.
511 // We can't simply use SkAlignTo here, because that can only align to powers of two.
512 const uint32_t misalignment = info->fOffset % requiredAlignment;
513 if (misalignment > 0) {
514 info->fOffset += requiredAlignment - misalignment;
515 }
516
517 // Don't align the offset any further.
518 requiredAlignment = 1;
519 }
520
521 const bool overflowedBuffer =
522 info->fBuffer && (info->fOffset >= SkTo<uint32_t>(info->fBuffer->size()) ||
523 !can_fit(requiredBytes,
524 SkTo<uint32_t>(info->fBuffer->size()),
525 info->fOffset,
526 requiredAlignment));
527 if (overflowedBuffer) {
528 fUsedBuffers.emplace_back(std::move(info->fBuffer), info->fTransferBuffer);
529 info->fTransferBuffer = {};
530 info->fUsedSize += info->fOffset;
531 }
532
533 if (!info->fBuffer) {
534 // Create the first buffer with the full fCurBlockSize, but create subsequent buffers with a
535 // smaller size if fCurBlockSize has increased from the minimum. This way if we use just a
536 // little more than fCurBlockSize total storage this frame, we won't necessarily double our
537 // total storage allocation.
538 const uint32_t blockSize = overflowedBuffer
539 ? std::max(info->fCurBlockSize / 4, info->fMinBlockSize)
540 : info->fCurBlockSize;
541 const uint32_t bufferSize = sufficient_block_size(requiredBytes, blockSize);
542
543 // This buffer can be GPU-only if
544 // a) the caller does not intend to ever upload CPU data to the buffer; or
545 // b) CPU data will get uploaded to fBuffer only via a transfer buffer
546 AccessPattern accessPattern = (useTransferBuffer || !supportCpuUpload)
547 ? AccessPattern::kGpuOnly
548 : AccessPattern::kHostVisible;
549
550 info->fBuffer = fResourceProvider->findOrCreateBuffer(bufferSize,
551 info->fType,
552 accessPattern,
553 std::move(label));
554 info->fOffset = 0;
555 if (!info->fBuffer) {
556 this->onFailedBuffer();
557 return {};
558 }
559 }
560
561 if (useTransferBuffer && !info->fTransferBuffer) {
562 std::tie(info->fTransferMapPtr, info->fTransferBuffer) =
563 fUploadManager->makeBindInfo(info->fBuffer->size(),
564 fCaps->requiredTransferBufferAlignment(),
565 "TransferForDataBuffer");
566
567 if (!info->fTransferBuffer) {
568 this->onFailedBuffer();
569 return {};
570 }
571 SkASSERT(info->fTransferMapPtr);
572 }
573
574 info->fOffset = SkAlignTo(info->fOffset, requiredAlignment);
575 BindBufferInfo bindInfo{info->fBuffer.get(), info->fOffset, requiredBytes};
576 info->fOffset += requiredBytes;
577
578 if (cleared == ClearBuffer::kYes) {
579 fClearList.push_back(bindInfo);
580 }
581
582 SkASSERT(info->fOffset <= info->fBuffer->size());
583 return bindInfo;
584 }
585
findReusableSbo(size_t bufferSize)586 sk_sp<Buffer> DrawBufferManager::findReusableSbo(size_t bufferSize) {
587 SkASSERT(bufferSize);
588 SkASSERT(!fMappingFailed);
589
590 for (int i = 0; i < fReusableScratchStorageBuffers.size(); ++i) {
591 sk_sp<Buffer>* buffer = &fReusableScratchStorageBuffers[i];
592 if ((*buffer)->size() >= bufferSize) {
593 auto found = std::move(*buffer);
594 // Fill the hole left by the move (if necessary) and shrink the pool.
595 if (i < fReusableScratchStorageBuffers.size() - 1) {
596 *buffer = std::move(fReusableScratchStorageBuffers.back());
597 }
598 fReusableScratchStorageBuffers.pop_back();
599 return found;
600 }
601 }
602 return nullptr;
603 }
604
605 // ------------------------------------------------------------------------------------------------
606 // StaticBufferManager
607
StaticBufferManager(ResourceProvider * resourceProvider,const Caps * caps)608 StaticBufferManager::StaticBufferManager(ResourceProvider* resourceProvider,
609 const Caps* caps)
610 : fResourceProvider(resourceProvider)
611 , fUploadManager(resourceProvider, caps)
612 , fRequiredTransferAlignment(SkTo<uint32_t>(caps->requiredTransferBufferAlignment()))
613 , fVertexBufferInfo(BufferType::kVertex, caps)
614 , fIndexBufferInfo(BufferType::kIndex, caps) {}
615 StaticBufferManager::~StaticBufferManager() = default;
616
BufferInfo(BufferType type,const Caps * caps)617 StaticBufferManager::BufferInfo::BufferInfo(BufferType type, const Caps* caps)
618 : fBufferType(type)
619 , fAlignment(starting_alignment(type, /*useTransferBuffers=*/true, caps))
620 , fTotalRequiredBytes(0) {}
621
getVertexWriter(size_t size,BindBufferInfo * binding)622 VertexWriter StaticBufferManager::getVertexWriter(size_t size, BindBufferInfo* binding) {
623 void* data = this->prepareStaticData(&fVertexBufferInfo, size, binding);
624 return VertexWriter{data, size};
625 }
626
getIndexWriter(size_t size,BindBufferInfo * binding)627 VertexWriter StaticBufferManager::getIndexWriter(size_t size, BindBufferInfo* binding) {
628 void* data = this->prepareStaticData(&fIndexBufferInfo, size, binding);
629 return VertexWriter{data, size};
630 }
631
prepareStaticData(BufferInfo * info,size_t size,BindBufferInfo * target)632 void* StaticBufferManager::prepareStaticData(BufferInfo* info,
633 size_t size,
634 BindBufferInfo* target) {
635 // Zero-out the target binding in the event of any failure in actually transfering data later.
636 SkASSERT(target);
637 *target = {nullptr, 0};
638 uint32_t size32 = validate_size(size);
639 if (!size32 || fMappingFailed) {
640 return nullptr;
641 }
642
643 // Both the transfer buffer and static buffers are aligned to the max required alignment for
644 // the pair of buffer types involved (transfer cpu->gpu and either index or vertex). Copies
645 // must also copy an aligned amount of bytes.
646 size32 = SkAlignTo(size32, info->fAlignment);
647
648 auto [transferMapPtr, transferBindInfo] =
649 fUploadManager.makeBindInfo(size32,
650 fRequiredTransferAlignment,
651 "TransferForStaticBuffer");
652 if (!transferMapPtr) {
653 SKGPU_LOG_E("Failed to create or map transfer buffer that initializes static GPU data.");
654 fMappingFailed = true;
655 return nullptr;
656 }
657
658 info->fData.push_back({transferBindInfo, target});
659 info->fTotalRequiredBytes += size32;
660 return transferMapPtr;
661 }
662
createAndUpdateBindings(ResourceProvider * resourceProvider,Context * context,QueueManager * queueManager,GlobalCache * globalCache,std::string_view label) const663 bool StaticBufferManager::BufferInfo::createAndUpdateBindings(
664 ResourceProvider* resourceProvider,
665 Context* context,
666 QueueManager* queueManager,
667 GlobalCache* globalCache,
668 std::string_view label) const {
669 if (!fTotalRequiredBytes) {
670 return true; // No buffer needed
671 }
672
673 sk_sp<Buffer> staticBuffer = resourceProvider->findOrCreateBuffer(
674 fTotalRequiredBytes, fBufferType, AccessPattern::kGpuOnly, std::move(label));
675 if (!staticBuffer) {
676 SKGPU_LOG_E("Failed to create static buffer for type %d of size %u bytes.\n",
677 (int) fBufferType, fTotalRequiredBytes);
678 return false;
679 }
680
681 uint32_t offset = 0;
682 for (const CopyRange& data : fData) {
683 // Each copy range's size should be aligned to the max of the required buffer alignment and
684 // the transfer alignment, so we can just increment the offset into the static buffer.
685 SkASSERT(offset % fAlignment == 0);
686 uint32_t size = data.fSource.fSize;
687 data.fTarget->fBuffer = staticBuffer.get();
688 data.fTarget->fOffset = offset;
689 data.fTarget->fSize = size;
690
691 auto copyTask = CopyBufferToBufferTask::Make(
692 data.fSource.fBuffer, data.fSource.fOffset,
693 sk_ref_sp(data.fTarget->fBuffer), data.fTarget->fOffset,
694 size);
695 // For static buffers, we want them all to be optimized as GPU only buffers. If we are in
696 // a protected context, this means the buffers must be non-protected since they will be
697 // read in the vertex shader which doesn't allow protected memory access. Thus all the
698 // uploads to these buffers must be done as non-protected commands.
699 if (!queueManager->addTask(copyTask.get(), context, Protected::kNo)) {
700 SKGPU_LOG_E("Failed to copy data to static buffer.\n");
701 return false;
702 }
703
704 offset += size;
705 }
706
707 SkASSERT(offset == fTotalRequiredBytes);
708 globalCache->addStaticResource(std::move(staticBuffer));
709 return true;
710 }
711
finalize(Context * context,QueueManager * queueManager,GlobalCache * globalCache)712 StaticBufferManager::FinishResult StaticBufferManager::finalize(Context* context,
713 QueueManager* queueManager,
714 GlobalCache* globalCache) {
715 if (fMappingFailed) {
716 return FinishResult::kFailure;
717 }
718
719 const size_t totalRequiredBytes = fVertexBufferInfo.fTotalRequiredBytes +
720 fIndexBufferInfo.fTotalRequiredBytes;
721 SkASSERT(totalRequiredBytes <= kMaxStaticDataSize);
722 if (!totalRequiredBytes) {
723 return FinishResult::kNoWork;
724 }
725
726 if (!fVertexBufferInfo.createAndUpdateBindings(fResourceProvider,
727 context,
728 queueManager,
729 globalCache,
730 "StaticVertexBuffer")) {
731 return FinishResult::kFailure;
732 }
733 if (!fIndexBufferInfo.createAndUpdateBindings(fResourceProvider,
734 context,
735 queueManager,
736 globalCache,
737 "StaticIndexBuffer")) {
738 return FinishResult::kFailure;
739 }
740 queueManager->addUploadBufferManagerRefs(&fUploadManager);
741
742 // Reset the static buffer manager since the Recording's copy tasks now manage ownership of
743 // the transfer buffers and the GlobalCache owns the final static buffers.
744 fVertexBufferInfo.reset();
745 fIndexBufferInfo.reset();
746
747 return FinishResult::kSuccess;
748 }
749
750 } // namespace skgpu::graphite
751