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