1 /*
2 * Copyright 2022 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/graphite/task/UploadTask.h"
9
10 #include "include/core/SkColorSpace.h"
11 #include "include/gpu/graphite/Recorder.h"
12 #include "include/private/base/SkAlign.h"
13 #include "src/core/SkAutoPixmapStorage.h"
14 #include "src/core/SkCompressedDataUtils.h"
15 #include "src/core/SkMipmap.h"
16 #include "src/core/SkTraceEvent.h"
17 #include "src/gpu/DataUtils.h"
18 #include "src/gpu/graphite/Buffer.h"
19 #include "src/gpu/graphite/Caps.h"
20 #include "src/gpu/graphite/CommandBuffer.h"
21 #include "src/gpu/graphite/Log.h"
22 #include "src/gpu/graphite/RecorderPriv.h"
23 #include "src/gpu/graphite/ResourceProvider.h"
24 #include "src/gpu/graphite/Texture.h"
25 #include "src/gpu/graphite/TextureProxy.h"
26 #include "src/gpu/graphite/UploadBufferManager.h"
27
28 using namespace skia_private;
29
30 namespace skgpu::graphite {
31
32 UploadInstance::UploadInstance() = default;
33 UploadInstance::UploadInstance(UploadInstance&&) = default;
34 UploadInstance& UploadInstance::operator=(UploadInstance&&) = default;
35 UploadInstance::~UploadInstance() = default;
36
UploadInstance(const Buffer * buffer,size_t bytesPerPixel,sk_sp<TextureProxy> textureProxy,std::unique_ptr<ConditionalUploadContext> condContext)37 UploadInstance::UploadInstance(const Buffer* buffer,
38 size_t bytesPerPixel,
39 sk_sp<TextureProxy> textureProxy,
40 std::unique_ptr<ConditionalUploadContext> condContext)
41 : fBuffer(buffer)
42 , fBytesPerPixel(bytesPerPixel)
43 , fTextureProxy(textureProxy)
44 , fConditionalContext(std::move(condContext)) {}
45
46 // Returns total buffer size to allocate, and required offset alignment of that allocation.
47 // Updates 'levelOffsetsAndRowBytes' with offsets relative to start of the allocation, as well as
48 // the aligned destination rowBytes for each level.
compute_combined_buffer_size(const Caps * caps,int mipLevelCount,size_t bytesPerBlock,const SkISize & baseDimensions,SkTextureCompressionType compressionType,TArray<std::pair<size_t,size_t>> * levelOffsetsAndRowBytes)49 std::pair<size_t, size_t> compute_combined_buffer_size(
50 const Caps* caps,
51 int mipLevelCount,
52 size_t bytesPerBlock,
53 const SkISize& baseDimensions,
54 SkTextureCompressionType compressionType,
55 TArray<std::pair<size_t, size_t>>* levelOffsetsAndRowBytes) {
56 SkASSERT(levelOffsetsAndRowBytes && levelOffsetsAndRowBytes->empty());
57 SkASSERT(mipLevelCount >= 1);
58
59 SkISize compressedBlockDimensions = CompressedDimensionsInBlocks(compressionType,
60 baseDimensions);
61
62 size_t minTransferBufferAlignment =
63 std::max(bytesPerBlock, caps->requiredTransferBufferAlignment());
64 size_t alignedBytesPerRow =
65 caps->getAlignedTextureDataRowBytes(compressedBlockDimensions.width() * bytesPerBlock);
66
67 levelOffsetsAndRowBytes->push_back({0, alignedBytesPerRow});
68 size_t combinedBufferSize = SkAlignTo(alignedBytesPerRow * baseDimensions.height(),
69 minTransferBufferAlignment);
70 SkISize levelDimensions = baseDimensions;
71
72 for (int currentMipLevel = 1; currentMipLevel < mipLevelCount; ++currentMipLevel) {
73 levelDimensions = {std::max(1, levelDimensions.width() / 2),
74 std::max(1, levelDimensions.height() / 2)};
75 compressedBlockDimensions = CompressedDimensionsInBlocks(compressionType, levelDimensions);
76 alignedBytesPerRow = caps->getAlignedTextureDataRowBytes(
77 compressedBlockDimensions.width() * bytesPerBlock);
78 size_t alignedSize = SkAlignTo(alignedBytesPerRow * compressedBlockDimensions.height(),
79 minTransferBufferAlignment);
80 SkASSERT(combinedBufferSize % minTransferBufferAlignment == 0);
81
82 levelOffsetsAndRowBytes->push_back({combinedBufferSize, alignedBytesPerRow});
83 combinedBufferSize += alignedSize;
84 }
85
86 SkASSERT(levelOffsetsAndRowBytes->size() == mipLevelCount);
87 SkASSERT(combinedBufferSize % minTransferBufferAlignment == 0);
88 return {combinedBufferSize, minTransferBufferAlignment};
89 }
90
Make(Recorder * recorder,sk_sp<TextureProxy> textureProxy,const SkColorInfo & srcColorInfo,const SkColorInfo & dstColorInfo,SkSpan<const MipLevel> levels,const SkIRect & dstRect,std::unique_ptr<ConditionalUploadContext> condContext)91 UploadInstance UploadInstance::Make(Recorder* recorder,
92 sk_sp<TextureProxy> textureProxy,
93 const SkColorInfo& srcColorInfo,
94 const SkColorInfo& dstColorInfo,
95 SkSpan<const MipLevel> levels,
96 const SkIRect& dstRect,
97 std::unique_ptr<ConditionalUploadContext> condContext) {
98 const Caps* caps = recorder->priv().caps();
99 SkASSERT(caps->isTexturable(textureProxy->textureInfo()));
100 SkASSERT(caps->areColorTypeAndTextureInfoCompatible(dstColorInfo.colorType(),
101 textureProxy->textureInfo()));
102
103 unsigned int mipLevelCount = levels.size();
104 // The assumption is either that we have no mipmaps, or that our rect is the entire texture
105 SkASSERT(mipLevelCount == 1 || dstRect == SkIRect::MakeSize(textureProxy->dimensions()));
106
107 // We assume that if the texture has mip levels, we either upload to all the levels or just the
108 // first.
109 #ifdef SK_DEBUG
110 unsigned int numExpectedLevels = 1;
111 if (textureProxy->textureInfo().mipmapped() == Mipmapped::kYes) {
112 numExpectedLevels = SkMipmap::ComputeLevelCount(textureProxy->dimensions().width(),
113 textureProxy->dimensions().height()) + 1;
114 }
115 SkASSERT(mipLevelCount == 1 || mipLevelCount == numExpectedLevels);
116 #endif
117
118 if (dstRect.isEmpty()) {
119 return Invalid();
120 }
121
122 if (mipLevelCount == 1 && !levels[0].fPixels) {
123 return Invalid(); // no data to upload
124 }
125
126 for (unsigned int i = 0; i < mipLevelCount; ++i) {
127 // We do not allow any gaps in the mip data
128 if (!levels[i].fPixels) {
129 return Invalid();
130 }
131 }
132
133 SkColorType supportedColorType;
134 bool isRGB888Format;
135 std::tie(supportedColorType, isRGB888Format) =
136 caps->supportedWritePixelsColorType(dstColorInfo.colorType(),
137 textureProxy->textureInfo(),
138 srcColorInfo.colorType());
139 if (supportedColorType == kUnknown_SkColorType) {
140 return Invalid();
141 }
142
143 const size_t bpp = isRGB888Format ? 3 : SkColorTypeBytesPerPixel(supportedColorType);
144 TArray<std::pair<size_t, size_t>> levelOffsetsAndRowBytes(mipLevelCount);
145
146 auto [combinedBufferSize, minAlignment] = compute_combined_buffer_size(
147 caps,
148 mipLevelCount,
149 bpp,
150 dstRect.size(),
151 SkTextureCompressionType::kNone,
152 &levelOffsetsAndRowBytes);
153 SkASSERT(combinedBufferSize);
154
155 UploadBufferManager* bufferMgr = recorder->priv().uploadBufferManager();
156 auto [writer, bufferInfo] = bufferMgr->getTextureUploadWriter(combinedBufferSize, minAlignment);
157 if (!writer) {
158 SKGPU_LOG_W("Failed to get write-mapped buffer for texture upload of size %zu",
159 combinedBufferSize);
160 return Invalid();
161 }
162
163 UploadInstance upload{bufferInfo.fBuffer, bpp, std::move(textureProxy), std::move(condContext)};
164
165 // Fill in copy data
166 int32_t currentWidth = dstRect.width();
167 int32_t currentHeight = dstRect.height();
168 bool needsConversion = (srcColorInfo != dstColorInfo);
169 for (unsigned int currentMipLevel = 0; currentMipLevel < mipLevelCount; currentMipLevel++) {
170 const size_t trimRowBytes = currentWidth * bpp;
171 const size_t srcRowBytes = levels[currentMipLevel].fRowBytes;
172 const auto [mipOffset, dstRowBytes] = levelOffsetsAndRowBytes[currentMipLevel];
173
174 // copy data into the buffer, skipping any trailing bytes
175 const char* src = (const char*)levels[currentMipLevel].fPixels;
176
177 if (isRGB888Format) {
178 SkASSERT(supportedColorType == kRGB_888x_SkColorType &&
179 dstColorInfo.colorType() == kRGB_888x_SkColorType);
180 SkISize dims = {currentWidth, currentHeight};
181 SkImageInfo srcImageInfo = SkImageInfo::Make(dims, srcColorInfo);
182 SkImageInfo dstImageInfo = SkImageInfo::Make(dims, dstColorInfo);
183
184 const void* rgbConvertSrc = src;
185 size_t rgbSrcRowBytes = srcRowBytes;
186 SkAutoPixmapStorage temp;
187 if (needsConversion) {
188 temp.alloc(dstImageInfo);
189 SkAssertResult(SkConvertPixels(dstImageInfo,
190 temp.writable_addr(),
191 temp.rowBytes(),
192 srcImageInfo,
193 src,
194 srcRowBytes));
195 rgbConvertSrc = temp.addr();
196 rgbSrcRowBytes = temp.rowBytes();
197 }
198 writer.writeRGBFromRGBx(mipOffset,
199 rgbConvertSrc,
200 rgbSrcRowBytes,
201 dstRowBytes,
202 currentWidth,
203 currentHeight);
204 } else if (needsConversion) {
205 SkISize dims = {currentWidth, currentHeight};
206 SkImageInfo srcImageInfo = SkImageInfo::Make(dims, srcColorInfo);
207 SkImageInfo dstImageInfo = SkImageInfo::Make(dims, dstColorInfo);
208
209 writer.convertAndWrite(
210 mipOffset, srcImageInfo, src, srcRowBytes, dstImageInfo, dstRowBytes);
211 } else {
212 writer.write(mipOffset, src, srcRowBytes, dstRowBytes, trimRowBytes, currentHeight);
213 }
214
215 // For mipped data, the dstRect is always the full texture so we don't need to worry about
216 // modifying the TL coord as it will always be 0,0,for all levels.
217 upload.fCopyData.push_back({
218 /*fBufferOffset=*/bufferInfo.fOffset + mipOffset,
219 /*fBufferRowBytes=*/dstRowBytes,
220 /*fRect=*/SkIRect::MakeXYWH(dstRect.left(), dstRect.top(), currentWidth, currentHeight),
221 /*fMipmapLevel=*/currentMipLevel
222 });
223
224 currentWidth = std::max(1, currentWidth / 2);
225 currentHeight = std::max(1, currentHeight / 2);
226 }
227
228 ATRACE_ANDROID_FRAMEWORK("Upload %sTexture [%dx%d]",
229 mipLevelCount > 1 ? "MipMap " : "",
230 dstRect.width(), dstRect.height());
231
232 return upload;
233 }
234
MakeCompressed(Recorder * recorder,sk_sp<TextureProxy> textureProxy,const void * data,size_t dataSize)235 UploadInstance UploadInstance::MakeCompressed(Recorder* recorder,
236 sk_sp<TextureProxy> textureProxy,
237 const void* data,
238 size_t dataSize) {
239 if (!data) {
240 return Invalid(); // no data to upload
241 }
242
243 const TextureInfo& texInfo = textureProxy->textureInfo();
244
245 const Caps* caps = recorder->priv().caps();
246 SkASSERT(caps->isTexturable(texInfo));
247
248 SkTextureCompressionType compression = texInfo.compressionType();
249 if (compression == SkTextureCompressionType::kNone) {
250 return Invalid();
251 }
252
253 // Create a transfer buffer and fill with data.
254 const SkISize dimensions = textureProxy->dimensions();
255 skia_private::STArray<16, size_t> srcMipOffsets;
256 SkDEBUGCODE(size_t computedSize =) SkCompressedDataSize(compression,
257 dimensions,
258 &srcMipOffsets,
259 texInfo.mipmapped() == Mipmapped::kYes);
260 SkASSERT(computedSize == dataSize);
261
262 unsigned int mipLevelCount = srcMipOffsets.size();
263 size_t bytesPerBlock = SkCompressedBlockSize(compression);
264 TArray<std::pair<size_t, size_t>> levelOffsetsAndRowBytes(mipLevelCount);
265 auto [combinedBufferSize, minAlignment] = compute_combined_buffer_size(
266 caps,
267 mipLevelCount,
268 bytesPerBlock,
269 dimensions,
270 compression,
271 &levelOffsetsAndRowBytes);
272 SkASSERT(combinedBufferSize);
273
274 UploadBufferManager* bufferMgr = recorder->priv().uploadBufferManager();
275 auto [writer, bufferInfo] = bufferMgr->getTextureUploadWriter(combinedBufferSize, minAlignment);
276
277 std::vector<BufferTextureCopyData> copyData(mipLevelCount);
278
279 if (!bufferInfo.fBuffer) {
280 SKGPU_LOG_W("Failed to get write-mapped buffer for texture upload of size %zu",
281 combinedBufferSize);
282 return Invalid();
283 }
284
285 UploadInstance upload{bufferInfo.fBuffer, bytesPerBlock, std::move(textureProxy)};
286
287 // Fill in copy data
288 int32_t currentWidth = dimensions.width();
289 int32_t currentHeight = dimensions.height();
290 for (unsigned int currentMipLevel = 0; currentMipLevel < mipLevelCount; currentMipLevel++) {
291 SkISize blockDimensions = CompressedDimensionsInBlocks(compression,
292 {currentWidth, currentHeight});
293 int32_t blockHeight = blockDimensions.height();
294
295 const size_t trimRowBytes = CompressedRowBytes(compression, currentWidth);
296 const size_t srcRowBytes = trimRowBytes;
297 const auto [dstMipOffset, dstRowBytes] = levelOffsetsAndRowBytes[currentMipLevel];
298
299 // copy data into the buffer, skipping any trailing bytes
300 const void* src = SkTAddOffset<const void>(data, srcMipOffsets[currentMipLevel]);
301
302 writer.write(dstMipOffset, src, srcRowBytes, dstRowBytes, trimRowBytes, blockHeight);
303
304 int32_t copyWidth = currentWidth;
305 int32_t copyHeight = currentHeight;
306 if (caps->fullCompressedUploadSizeMustAlignToBlockDims()) {
307 SkISize oneBlockDims = CompressedDimensions(compression, {1, 1});
308 copyWidth = SkAlignTo(copyWidth, oneBlockDims.fWidth);
309 copyHeight = SkAlignTo(copyHeight, oneBlockDims.fHeight);
310 }
311
312 upload.fCopyData.push_back({
313 /*fBufferOffset=*/bufferInfo.fOffset + dstMipOffset,
314 /*fBufferRowBytes=*/dstRowBytes,
315 /*fRect=*/SkIRect::MakeXYWH(0, 0, copyWidth, copyHeight),
316 /*fMipLevel=*/currentMipLevel
317 });
318
319 currentWidth = std::max(1, currentWidth / 2);
320 currentHeight = std::max(1, currentHeight / 2);
321 }
322
323 ATRACE_ANDROID_FRAMEWORK("Upload Compressed %sTexture [%dx%d]",
324 mipLevelCount > 1 ? "MipMap " : "",
325 dimensions.width(),
326 dimensions.height());
327
328 return upload;
329 }
330
prepareResources(ResourceProvider * resourceProvider)331 bool UploadInstance::prepareResources(ResourceProvider* resourceProvider) {
332 // While most uploads are to already instantiated proxies (e.g. for client-created texture
333 // images) it is possible that writePixels() was issued as the first operation on a scratch
334 // Device, or that this is the first upload to the raster or text atlas proxies.
335 // TODO: Determine how to instantatiate textues in this case; atlas proxies shouldn't really be
336 // "scratch" because they aren't going to be reused for anything else in a Recording. At the
337 // same time, it could still go through the ScratchResourceManager and just never return them,
338 // which is no different from instantiating them directly with the ResourceProvider.
339 if (!TextureProxy::InstantiateIfNotLazy(resourceProvider, fTextureProxy.get())) {
340 SKGPU_LOG_E("Could not instantiate texture proxy for UploadTask!");
341 return false;
342 }
343 return true;
344 }
345
addCommand(Context * context,CommandBuffer * commandBuffer,Task::ReplayTargetData replayData) const346 Task::Status UploadInstance::addCommand(Context* context,
347 CommandBuffer* commandBuffer,
348 Task::ReplayTargetData replayData) const {
349 using Status = Task::Status;
350 SkASSERT(fTextureProxy && fTextureProxy->isInstantiated());
351
352 if (fConditionalContext && !fConditionalContext->needsUpload(context)) {
353 // Assume that if a conditional context says to dynamically not upload that another
354 // time through the tasks should try to upload again.
355 return Status::kSuccess;
356 }
357
358 if (fTextureProxy->texture() != replayData.fTarget) {
359 // The CommandBuffer doesn't take ownership of the upload buffer here; it's owned by
360 // UploadBufferManager, which will transfer ownership in transferToCommandBuffer.
361 if (!commandBuffer->copyBufferToTexture(fBuffer,
362 fTextureProxy->refTexture(),
363 fCopyData.data(),
364 fCopyData.size())) {
365 return Status::kFail;
366 }
367 } else {
368 // Here we assume that multiple copies in a single UploadInstance are always used for
369 // mipmaps of a single image, and that we won't ever upload to a replay target's mipmaps
370 // directly.
371 SkASSERT(fCopyData.size() == 1);
372 const BufferTextureCopyData& copyData = fCopyData[0];
373 SkIRect dstRect = copyData.fRect;
374 dstRect.offset(replayData.fTranslation);
375 SkIRect croppedDstRect = dstRect;
376
377 if (!replayData.fClip.isEmpty()) {
378 SkIRect dstClip = replayData.fClip;
379 dstClip.offset(replayData.fTranslation);
380 if (!croppedDstRect.intersect(dstClip)) {
381 // The replay clip can change on each insert, so subsequent replays may actually
382 // intersect the copy rect.
383 return Status::kSuccess;
384 }
385 }
386
387 if (!croppedDstRect.intersect(SkIRect::MakeSize(fTextureProxy->dimensions()))) {
388 // The replay translation can change on each insert, so subsequent replays may
389 // actually intersect the copy rect.
390 return Status::kSuccess;
391 }
392
393 BufferTextureCopyData transformedCopyData = copyData;
394 transformedCopyData.fBufferOffset +=
395 (croppedDstRect.y() - dstRect.y()) * copyData.fBufferRowBytes +
396 (croppedDstRect.x() - dstRect.x()) * fBytesPerPixel;
397 transformedCopyData.fRect = croppedDstRect;
398
399 if (!commandBuffer->copyBufferToTexture(fBuffer,
400 fTextureProxy->refTexture(),
401 &transformedCopyData, 1)) {
402 return Status::kFail;
403 }
404 }
405
406 // The conditional context will return false if the upload should not happen anymore. If there's
407 // no context assume that the upload should always be executed on replay.
408 if (!fConditionalContext || fConditionalContext->uploadSubmitted()) {
409 return Status::kSuccess;
410 } else {
411 return Status::kDiscard;
412 }
413 }
414
415 //---------------------------------------------------------------------------
416
recordUpload(Recorder * recorder,sk_sp<TextureProxy> textureProxy,const SkColorInfo & srcColorInfo,const SkColorInfo & dstColorInfo,SkSpan<const MipLevel> levels,const SkIRect & dstRect,std::unique_ptr<ConditionalUploadContext> condContext)417 bool UploadList::recordUpload(Recorder* recorder,
418 sk_sp<TextureProxy> textureProxy,
419 const SkColorInfo& srcColorInfo,
420 const SkColorInfo& dstColorInfo,
421 SkSpan<const MipLevel> levels,
422 const SkIRect& dstRect,
423 std::unique_ptr<ConditionalUploadContext> condContext) {
424 UploadInstance instance = UploadInstance::Make(recorder, std::move(textureProxy),
425 srcColorInfo, dstColorInfo,
426 levels, dstRect, std::move(condContext));
427 if (!instance.isValid()) {
428 return false;
429 }
430
431 fInstances.emplace_back(std::move(instance));
432 return true;
433 }
434
435 //---------------------------------------------------------------------------
436
Make(UploadList * uploadList)437 sk_sp<UploadTask> UploadTask::Make(UploadList* uploadList) {
438 SkASSERT(uploadList);
439 if (!uploadList->size()) {
440 return nullptr;
441 }
442 return sk_sp<UploadTask>(new UploadTask(std::move(uploadList->fInstances)));
443 }
444
Make(UploadInstance instance)445 sk_sp<UploadTask> UploadTask::Make(UploadInstance instance) {
446 if (!instance.isValid()) {
447 return nullptr;
448 }
449 return sk_sp<UploadTask>(new UploadTask(std::move(instance)));
450 }
451
UploadTask(skia_private::TArray<UploadInstance> && instances)452 UploadTask::UploadTask(skia_private::TArray<UploadInstance>&& instances)
453 : fInstances(std::move(instances)) {}
454
UploadTask(UploadInstance instance)455 UploadTask::UploadTask(UploadInstance instance) {
456 fInstances.emplace_back(std::move(instance));
457 }
458
~UploadTask()459 UploadTask::~UploadTask() {}
460
prepareResources(ResourceProvider * resourceProvider,ScratchResourceManager *,const RuntimeEffectDictionary *)461 Task::Status UploadTask::prepareResources(ResourceProvider* resourceProvider,
462 ScratchResourceManager*,
463 const RuntimeEffectDictionary*) {
464 for (int i = 0; i < fInstances.size(); ++i) {
465 // No upload should be invalidated before prepareResources() is called.
466 SkASSERT(fInstances[i].isValid());
467 if (!fInstances[i].prepareResources(resourceProvider)) {
468 return Status::kFail;
469 }
470 }
471
472 return Status::kSuccess;
473 }
474
addCommands(Context * context,CommandBuffer * commandBuffer,ReplayTargetData replayData)475 Task::Status UploadTask::addCommands(Context* context,
476 CommandBuffer* commandBuffer,
477 ReplayTargetData replayData) {
478 int discardCount = 0;
479 for (int i = 0; i < fInstances.size(); ++i) {
480 if (!fInstances[i].isValid()) {
481 discardCount++;
482 continue;
483 }
484 Status status = fInstances[i].addCommand(context, commandBuffer, replayData);
485 if (status == Status::kFail) {
486 return Status::kFail;
487 } else if (status == Status::kDiscard) {
488 fInstances[i] = UploadInstance::Invalid();
489 discardCount++;
490 }
491 }
492
493 if (discardCount == fInstances.size()) {
494 return Status::kDiscard;
495 } else {
496 return Status::kSuccess;
497 }
498 }
499
500 } // namespace skgpu::graphite
501