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