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/Buffer.h"
12 #include "src/gpu/graphite/Caps.h"
13 #include "src/gpu/graphite/ContextPriv.h"
14 #include "src/gpu/graphite/CopyTask.h"
15 #include "src/gpu/graphite/Log.h"
16 #include "src/gpu/graphite/QueueManager.h"
17 #include "src/gpu/graphite/RecordingPriv.h"
18 #include "src/gpu/graphite/ResourceProvider.h"
19 #include "src/gpu/graphite/SharedContext.h"
20
21 namespace skgpu::graphite {
22
23 namespace {
24
25 // TODO: Tune these values on real world data
26 static constexpr size_t kVertexBufferSize = 16 << 10; // 16 KB
27 static constexpr size_t kIndexBufferSize = 2 << 10; // 2 KB
28 static constexpr size_t kUniformBufferSize = 2 << 10; // 2 KB
29 static constexpr size_t kStorageBufferSize = 2 << 10; // 2 KB
30
31 // TODO: Is it better to keep this above the max data size so we typically have one transfer buffer
32 // allocation? Or have it line up with kVertexBufferSize so if we end up needing to use transfer
33 // buffers for dynamic vertex data we can just reuse the first one?
34 static constexpr size_t kStaticTransferBufferSize = 2 << 10; // 2 KB
35
36 // The limit for all data created by the StaticBufferManager. This data remains alive for
37 // the entire SharedContext so we want to keep it small and give a concrete upper bound to
38 // clients for our steady-state memory usage.
39 // FIXME The current usage is 4732 bytes across static vertex and index buffers, but that includes
40 // multiple copies of tessellation data, and an unoptimized AnalyticRRect mesh. Once those issues
41 // are addressed, we can tighten this and decide on the transfer buffer sizing as well.
42 [[maybe_unused]] static constexpr size_t kMaxStaticDataSize = 6 << 10;
43
sufficient_block_size(size_t requiredBytes,size_t blockSize)44 size_t sufficient_block_size(size_t requiredBytes, size_t blockSize) {
45 // Always request a buffer at least 'requiredBytes', but keep them in multiples of
46 // 'blockSize' for improved reuse.
47 static constexpr size_t kMaxSize = std::numeric_limits<size_t>::max();
48 size_t maxBlocks = kMaxSize / blockSize;
49 size_t blocks = (requiredBytes / blockSize) + 1;
50 size_t bufferSize = blocks > maxBlocks ? kMaxSize : (blocks * blockSize);
51 SkASSERT(requiredBytes < bufferSize);
52 return bufferSize;
53 }
54
can_fit(size_t requestedSize,Buffer * buffer,size_t currentOffset,size_t alignment)55 bool can_fit(size_t requestedSize,
56 Buffer* buffer,
57 size_t currentOffset,
58 size_t alignment) {
59 size_t startOffset = SkAlignTo(currentOffset, alignment);
60 return requestedSize <= (buffer->size() - startOffset);
61 }
62
starting_alignment(BufferType type,bool useTransferBuffers,const Caps * caps)63 size_t starting_alignment(BufferType type, bool useTransferBuffers, const Caps* caps) {
64 // Both vertex and index data is aligned to 4 bytes by default
65 size_t alignment = 4;
66 if (type == BufferType::kUniform) {
67 alignment = caps->requiredUniformBufferAlignment();
68 } else if (type == BufferType::kStorage || type == BufferType::kVertexStorage ||
69 type == BufferType::kIndexStorage || type == BufferType::kIndirect) {
70 alignment = caps->requiredStorageBufferAlignment();
71 }
72 if (useTransferBuffers) {
73 alignment = std::max(alignment, caps->requiredTransferBufferAlignment());
74 }
75 return alignment;
76 }
77
78 } // anonymous namespace
79
80 // ------------------------------------------------------------------------------------------------
81 // DrawBufferManager
82
DrawBufferManager(ResourceProvider * resourceProvider,const Caps * caps)83 DrawBufferManager::DrawBufferManager(ResourceProvider* resourceProvider, const Caps* caps)
84 : fResourceProvider(resourceProvider)
85 , fCaps(caps)
86 , fCurrentBuffers{{
87 { BufferType::kVertex, kVertexBufferSize, caps },
88 { BufferType::kIndex, kIndexBufferSize, caps },
89 { BufferType::kUniform, kUniformBufferSize, caps },
90 { BufferType::kStorage, kStorageBufferSize, caps }, // mapped storage
91 { BufferType::kStorage, kStorageBufferSize, caps }, // GPU-only storage
92 { BufferType::kVertexStorage, kVertexBufferSize, caps },
93 { BufferType::kIndexStorage, kIndexBufferSize, caps },
94 { BufferType::kIndirect, kStorageBufferSize, caps } }} {}
95
~DrawBufferManager()96 DrawBufferManager::~DrawBufferManager() {}
97
98 // For simplicity, if transfer buffers are being used, we align the data to the max alignment of
99 // either the final buffer type or cpu->gpu transfer alignment so that the buffers are laid out
100 // the same in memory.
BufferInfo(BufferType type,size_t blockSize,const Caps * caps)101 DrawBufferManager::BufferInfo::BufferInfo(BufferType type, size_t blockSize, const Caps* caps)
102 : fType(type)
103 , fStartAlignment(starting_alignment(type, !caps->drawBufferCanBeMapped(), caps))
104 , fBlockSize(SkAlignTo(blockSize, fStartAlignment)) {}
105
getVertexWriter(size_t requiredBytes)106 std::tuple<VertexWriter, BindBufferInfo> DrawBufferManager::getVertexWriter(size_t requiredBytes) {
107 if (!requiredBytes) {
108 return {};
109 }
110
111 auto& info = fCurrentBuffers[kVertexBufferIndex];
112 auto [ptr, bindInfo] = this->prepareMappedBindBuffer(&info, requiredBytes);
113 if (!ptr) {
114 return {};
115 }
116
117 return {VertexWriter(ptr, requiredBytes), bindInfo};
118 }
119
returnVertexBytes(size_t unusedBytes)120 void DrawBufferManager::returnVertexBytes(size_t unusedBytes) {
121 SkASSERT(fCurrentBuffers[kVertexBufferIndex].fOffset >= unusedBytes);
122 fCurrentBuffers[kVertexBufferIndex].fOffset -= unusedBytes;
123 }
124
getIndexWriter(size_t requiredBytes)125 std::tuple<IndexWriter, BindBufferInfo> DrawBufferManager::getIndexWriter(size_t requiredBytes) {
126 if (!requiredBytes) {
127 return {};
128 }
129
130 auto& info = fCurrentBuffers[kIndexBufferIndex];
131 auto [ptr, bindInfo] = this->prepareMappedBindBuffer(&info, requiredBytes);
132 if (!ptr) {
133 return {};
134 }
135
136 return {IndexWriter(ptr, requiredBytes), bindInfo};
137 }
138
getUniformWriter(size_t requiredBytes)139 std::tuple<UniformWriter, BindBufferInfo> DrawBufferManager::getUniformWriter(
140 size_t requiredBytes) {
141 if (!requiredBytes) {
142 return {};
143 }
144
145 auto& info = fCurrentBuffers[kUniformBufferIndex];
146 auto [ptr, bindInfo] = this->prepareMappedBindBuffer(&info, requiredBytes);
147 if (!ptr) {
148 return {};
149 }
150
151 return {UniformWriter(ptr, requiredBytes), bindInfo};
152 }
153
getSsboWriter(size_t requiredBytes)154 std::tuple<UniformWriter, BindBufferInfo> DrawBufferManager::getSsboWriter(size_t requiredBytes) {
155 if (!requiredBytes) {
156 return {};
157 }
158
159 auto& info = fCurrentBuffers[kStorageBufferIndex];
160 auto [ptr, bindInfo] = this->prepareMappedBindBuffer(&info, requiredBytes);
161 if (!ptr) {
162 return {};
163 }
164 return {UniformWriter(ptr, requiredBytes), bindInfo};
165 }
166
getMappedStorage(size_t requiredBytes)167 std::tuple<void*, BindBufferInfo> DrawBufferManager::getMappedStorage(size_t requiredBytes) {
168 if (!requiredBytes) {
169 return {};
170 }
171
172 auto& info = fCurrentBuffers[kStorageBufferIndex];
173 return this->prepareMappedBindBuffer(&info, requiredBytes);
174 }
175
getStorage(size_t requiredBytes)176 BindBufferInfo DrawBufferManager::getStorage(size_t requiredBytes) {
177 if (!requiredBytes) {
178 return {};
179 }
180
181 auto& info = fCurrentBuffers[kGpuOnlyStorageBufferIndex];
182 return this->prepareBindBuffer(&info, requiredBytes);
183 }
184
getVertexStorage(size_t requiredBytes)185 BindBufferInfo DrawBufferManager::getVertexStorage(size_t requiredBytes) {
186 if (!requiredBytes) {
187 return {};
188 }
189
190 auto& info = fCurrentBuffers[kVertexStorageBufferIndex];
191 return this->prepareBindBuffer(&info, requiredBytes);
192 }
193
getIndexStorage(size_t requiredBytes)194 BindBufferInfo DrawBufferManager::getIndexStorage(size_t requiredBytes) {
195 if (!requiredBytes) {
196 return {};
197 }
198
199 auto& info = fCurrentBuffers[kIndexStorageBufferIndex];
200 return this->prepareBindBuffer(&info, requiredBytes);
201 }
202
getIndirectStorage(size_t requiredBytes)203 BindBufferInfo DrawBufferManager::getIndirectStorage(size_t requiredBytes) {
204 if (!requiredBytes) {
205 return {};
206 }
207
208 auto& info = fCurrentBuffers[kIndirectStorageBufferIndex];
209 return this->prepareBindBuffer(&info, requiredBytes);
210 }
211
transferToRecording(Recording * recording)212 void DrawBufferManager::transferToRecording(Recording* recording) {
213 bool useTransferBuffer = !fCaps->drawBufferCanBeMapped();
214 for (auto& [buffer, transferBuffer] : fUsedBuffers) {
215 if (useTransferBuffer) {
216 if (transferBuffer) {
217 SkASSERT(buffer);
218 // A transfer buffer should always be mapped at this stage
219 transferBuffer->unmap();
220 recording->priv().addTask(CopyBufferToBufferTask::Make(std::move(transferBuffer),
221 std::move(buffer)));
222 }
223 } else {
224 if (buffer->isMapped()) {
225 buffer->unmap();
226 }
227 recording->priv().addResourceRef(std::move(buffer));
228 }
229 }
230 fUsedBuffers.clear();
231
232 // The current draw buffers have not been added to fUsedBuffers,
233 // so we need to handle them as well.
234 for (auto &info : fCurrentBuffers) {
235 if (!info.fBuffer) {
236 continue;
237 }
238 if (useTransferBuffer) {
239 if (info.fTransferBuffer) {
240 // A transfer buffer should always be mapped at this stage
241 info.fTransferBuffer->unmap();
242 SkASSERT(info.fBuffer);
243 recording->priv().addTask(CopyBufferToBufferTask::Make(
244 std::move(info.fTransferBuffer), info.fBuffer));
245 }
246 } else {
247 if (info.fBuffer->isMapped()) {
248 info.fBuffer->unmap();
249 }
250 recording->priv().addResourceRef(std::move(info.fBuffer));
251 }
252 info.fOffset = 0;
253 }
254 }
255
prepareMappedBindBuffer(BufferInfo * info,size_t requiredBytes)256 std::pair<void*, BindBufferInfo> DrawBufferManager::prepareMappedBindBuffer(BufferInfo* info,
257 size_t requiredBytes) {
258 auto bindInfo = this->prepareBindBuffer(info, requiredBytes, /*mappable=*/true);
259 if (!bindInfo) {
260 return {nullptr, {}};
261 }
262
263 void* ptr = SkTAddOffset<void>(info->getMappableBuffer()->map(),
264 static_cast<ptrdiff_t>(bindInfo.fOffset));
265 return {ptr, bindInfo};
266 }
267
prepareBindBuffer(BufferInfo * info,size_t requiredBytes,bool mappable)268 BindBufferInfo DrawBufferManager::prepareBindBuffer(BufferInfo* info,
269 size_t requiredBytes,
270 bool mappable) {
271 SkASSERT(info);
272 SkASSERT(requiredBytes);
273
274 bool useTransferBuffer = mappable && !fCaps->drawBufferCanBeMapped();
275
276 if (info->fBuffer &&
277 !can_fit(requiredBytes, info->fBuffer.get(), info->fOffset, info->fStartAlignment)) {
278 SkASSERT(!info->fTransferBuffer || info->fBuffer->size() == info->fTransferBuffer->size());
279 fUsedBuffers.emplace_back(std::move(info->fBuffer), std::move(info->fTransferBuffer));
280 }
281
282 if (!info->fBuffer) {
283 size_t bufferSize = sufficient_block_size(requiredBytes, info->fBlockSize);
284 info->fBuffer = fResourceProvider->findOrCreateBuffer(
285 bufferSize,
286 info->fType,
287 useTransferBuffer ? PrioritizeGpuReads::kYes : PrioritizeGpuReads::kNo);
288 info->fOffset = 0;
289 if (!info->fBuffer) {
290 return {};
291 }
292 }
293
294 if (useTransferBuffer && !info->fTransferBuffer) {
295 info->fTransferBuffer = fResourceProvider->findOrCreateBuffer(
296 info->fBuffer->size(),
297 BufferType::kXferCpuToGpu,
298 PrioritizeGpuReads::kNo);
299 SkASSERT(info->fBuffer->size() == info->fTransferBuffer->size());
300 SkASSERT(info->fOffset == 0);
301 if (!info->fTransferBuffer) {
302 return {};
303 }
304 }
305
306 info->fOffset = SkAlignTo(info->fOffset, info->fStartAlignment);
307 BindBufferInfo bindInfo{info->fBuffer.get(), info->fOffset};
308 info->fOffset += requiredBytes;
309
310 return bindInfo;
311 }
312
313 // ------------------------------------------------------------------------------------------------
314 // StaticBufferManager
315
StaticBufferManager(ResourceProvider * resourceProvider,const Caps * caps)316 StaticBufferManager::StaticBufferManager(ResourceProvider* resourceProvider,
317 const Caps* caps)
318 : fResourceProvider(resourceProvider)
319 , fVertexBufferInfo(BufferType::kVertex, caps)
320 , fIndexBufferInfo(BufferType::kIndex, caps)
321 , fCurrentTransferBuffer(nullptr)
322 , fCurrentOffset(0) {}
323 StaticBufferManager::~StaticBufferManager() = default;
324
BufferInfo(BufferType type,const Caps * caps)325 StaticBufferManager::BufferInfo::BufferInfo(BufferType type, const Caps* caps)
326 : fBufferType(type)
327 , fAlignment(starting_alignment(type, /*useTransferBuffers=*/true, caps))
328 , fTotalRequiredBytes(0) {}
329
getVertexWriter(size_t size,BindBufferInfo * binding)330 VertexWriter StaticBufferManager::getVertexWriter(size_t size, BindBufferInfo* binding) {
331 void* data = this->prepareStaticData(&fVertexBufferInfo, size, binding);
332 return VertexWriter{data, size};
333 }
334
getIndexWriter(size_t size,BindBufferInfo * binding)335 VertexWriter StaticBufferManager::getIndexWriter(size_t size, BindBufferInfo* binding) {
336 void* data = this->prepareStaticData(&fIndexBufferInfo, size, binding);
337 return VertexWriter{data, size};
338 }
339
prepareStaticData(BufferInfo * info,size_t size,BindBufferInfo * target)340 void* StaticBufferManager::prepareStaticData(BufferInfo* info,
341 size_t size,
342 BindBufferInfo* target) {
343 // Zero-out the target binding in the event of any failure in actually transfering data later.
344 SkASSERT(target);
345 *target = {nullptr, 0};
346 if (!size) {
347 return nullptr;
348 }
349
350 // Both the transfer buffer and static buffers are aligned to the max required alignment for
351 // the pair of buffer types involved (transfer cpu->gpu and either index or vertex). Copies
352 // must also copy an aligned amount of bytes.
353 size = SkAlignTo(size, info->fAlignment);
354 if (fCurrentTransferBuffer &&
355 !can_fit(size, fCurrentTransferBuffer.get(), fCurrentOffset, info->fAlignment)) {
356 fCurrentTransferBuffer->unmap();
357 fUsedBuffers.push_back(std::move(fCurrentTransferBuffer));
358 }
359 if (!fCurrentTransferBuffer) {
360 size_t bufferSize = sufficient_block_size(size, kStaticTransferBufferSize);
361 fCurrentTransferBuffer = fResourceProvider->findOrCreateBuffer(
362 bufferSize,
363 BufferType::kXferCpuToGpu,
364 PrioritizeGpuReads::kNo);
365 fCurrentOffset = 0;
366 }
367
368 fCurrentOffset = SkAlignTo(fCurrentOffset, info->fAlignment);
369 info->fData.push_back({BindBufferInfo{fCurrentTransferBuffer.get(), fCurrentOffset},
370 target, size});
371 void* ptr = SkTAddOffset<void>(fCurrentTransferBuffer->map(),
372 static_cast<ptrdiff_t>(fCurrentOffset));
373 fCurrentOffset += size;
374 info->fTotalRequiredBytes += size;
375 return ptr;
376 }
377
createAndUpdateBindings(ResourceProvider * resourceProvider,Context * context,QueueManager * queueManager,GlobalCache * globalCache) const378 bool StaticBufferManager::BufferInfo::createAndUpdateBindings(
379 ResourceProvider* resourceProvider,
380 Context* context,
381 QueueManager* queueManager,
382 GlobalCache* globalCache) const {
383 if (!fTotalRequiredBytes) {
384 return true; // No buffer needed
385 }
386
387 sk_sp<Buffer> staticBuffer = resourceProvider->findOrCreateBuffer(
388 fTotalRequiredBytes,
389 fBufferType,
390 PrioritizeGpuReads::kYes);
391 if (!staticBuffer) {
392 SKGPU_LOG_E("Failed to create static buffer for type %d of size %zu bytes.\n",
393 (int) fBufferType, fTotalRequiredBytes);
394 return false;
395 }
396
397 size_t offset = 0;
398 for (const CopyRange& data : fData) {
399 // Each copy range's size should be aligned to the max of the required buffer alignment and
400 // the transfer alignment, so we can just increment the offset into the static buffer.
401 SkASSERT(offset % fAlignment == 0);
402 data.fTarget->fBuffer = staticBuffer.get();
403 data.fTarget->fOffset = offset;
404
405 auto copyTask = CopyBufferToBufferTask::Make(
406 sk_ref_sp(data.fSource.fBuffer), data.fSource.fOffset,
407 sk_ref_sp(data.fTarget->fBuffer), data.fTarget->fOffset,
408 data.fSize);
409 if (!queueManager->addTask(copyTask.get(), context)) {
410 SKGPU_LOG_E("Failed to copy data to static buffer.\n");
411 return false;
412 }
413
414 offset += data.fSize;
415 }
416
417 SkASSERT(offset == fTotalRequiredBytes);
418 globalCache->addStaticResource(std::move(staticBuffer));
419 return true;
420 }
421
finalize(Context * context,QueueManager * queueManager,GlobalCache * globalCache)422 StaticBufferManager::FinishResult StaticBufferManager::finalize(Context* context,
423 QueueManager* queueManager,
424 GlobalCache* globalCache) {
425 // Used buffers were already unmapped, but we're also done with the current transfer buffer
426 if (fCurrentTransferBuffer) {
427 fCurrentTransferBuffer->unmap();
428 }
429
430 const size_t totalRequiredBytes = fVertexBufferInfo.fTotalRequiredBytes +
431 fIndexBufferInfo.fTotalRequiredBytes;
432 SkASSERT(totalRequiredBytes <= kMaxStaticDataSize);
433 if (!totalRequiredBytes) {
434 return FinishResult::kNoWork;
435 }
436
437 if (!fVertexBufferInfo.createAndUpdateBindings(fResourceProvider, context,
438 queueManager, globalCache)) {
439 return FinishResult::kFailure;
440 }
441 if (!fIndexBufferInfo.createAndUpdateBindings(fResourceProvider, context,
442 queueManager, globalCache)) {
443 return FinishResult::kFailure;
444 }
445
446 // Reset the static buffer manager since the Recording's copy tasks now manage ownership of
447 // the transfer buffers and the GlobalCache owns the final static buffers.
448 fCurrentTransferBuffer = nullptr;
449 fCurrentOffset = 0;
450 fUsedBuffers.clear();
451 fVertexBufferInfo.reset();
452 fIndexBufferInfo.reset();
453
454 return FinishResult::kSuccess;
455 }
456
457 } // namespace skgpu::graphite
458