xref: /aosp_15_r20/external/skia/src/gpu/graphite/task/UploadTask.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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