/* * Copyright 2021 Google LLC * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "include/gpu/graphite/Context.h" #include "include/core/SkColorSpace.h" #include "include/core/SkPathTypes.h" #include "include/core/SkTraceMemoryDump.h" #include "include/effects/SkRuntimeEffect.h" #include "include/gpu/graphite/BackendTexture.h" #include "include/gpu/graphite/PrecompileContext.h" #include "include/gpu/graphite/Recorder.h" #include "include/gpu/graphite/Recording.h" #include "include/gpu/graphite/Surface.h" #include "include/gpu/graphite/TextureInfo.h" #include "include/private/base/SkOnce.h" #include "src/base/SkRectMemcpy.h" #include "src/core/SkAutoPixmapStorage.h" #include "src/core/SkColorFilterPriv.h" #include "src/core/SkConvertPixels.h" #include "src/core/SkTraceEvent.h" #include "src/core/SkYUVMath.h" #include "src/gpu/RefCntedCallback.h" #include "src/gpu/graphite/AtlasProvider.h" #include "src/gpu/graphite/BufferManager.h" #include "src/gpu/graphite/Caps.h" #include "src/gpu/graphite/ClientMappedBufferManager.h" #include "src/gpu/graphite/CommandBuffer.h" #include "src/gpu/graphite/ContextPriv.h" #include "src/gpu/graphite/DrawAtlas.h" #include "src/gpu/graphite/GlobalCache.h" #include "src/gpu/graphite/GraphicsPipeline.h" #include "src/gpu/graphite/GraphicsPipelineDesc.h" #include "src/gpu/graphite/Image_Base_Graphite.h" #include "src/gpu/graphite/Image_Graphite.h" #include "src/gpu/graphite/KeyContext.h" #include "src/gpu/graphite/Log.h" #include "src/gpu/graphite/QueueManager.h" #include "src/gpu/graphite/RecorderPriv.h" #include "src/gpu/graphite/RecordingPriv.h" #include "src/gpu/graphite/Renderer.h" #include "src/gpu/graphite/RendererProvider.h" #include "src/gpu/graphite/ResourceProvider.h" #include "src/gpu/graphite/RuntimeEffectDictionary.h" #include "src/gpu/graphite/ShaderCodeDictionary.h" #include "src/gpu/graphite/SharedContext.h" #include "src/gpu/graphite/Surface_Graphite.h" #include "src/gpu/graphite/TextureProxyView.h" #include "src/gpu/graphite/TextureUtils.h" #include "src/gpu/graphite/task/CopyTask.h" #include "src/gpu/graphite/task/SynchronizeToCpuTask.h" #include "src/gpu/graphite/task/UploadTask.h" #include "src/image/SkSurface_Base.h" #include "src/sksl/SkSLGraphiteModules.h" #if defined(GPU_TEST_UTILS) #include "src/gpu/graphite/ContextOptionsPriv.h" #endif namespace skgpu::graphite { #define ASSERT_SINGLE_OWNER SKGPU_ASSERT_SINGLE_OWNER(this->singleOwner()) Context::ContextID Context::ContextID::Next() { static std::atomic nextID{1}; uint32_t id; do { id = nextID.fetch_add(1, std::memory_order_relaxed); } while (id == SK_InvalidUniqueID); return ContextID(id); } //-------------------------------------------------------------------------------------------------- Context::Context(sk_sp sharedContext, std::unique_ptr queueManager, const ContextOptions& options) : fSharedContext(std::move(sharedContext)) , fQueueManager(std::move(queueManager)) , fContextID(ContextID::Next()) { // We need to move the Graphite SkSL code into the central SkSL data loader at least once // (but preferrably only once) before we try to use it. We assume that there's no way to // use the SkSL code without making a context, so we initialize it here. static SkOnce once; once([] { SkSL::Loader::SetGraphiteModuleData(SkSL::Loader::GetGraphiteModules()); }); // We have to create this outside the initializer list because we need to pass in the Context's // SingleOwner object and it is declared last fResourceProvider = fSharedContext->makeResourceProvider(&fSingleOwner, SK_InvalidGenID, options.fGpuBudgetInBytes); fMappedBufferManager = std::make_unique(this->contextID()); #if defined(GPU_TEST_UTILS) if (options.fOptionsPriv) { fStoreContextRefInRecorder = options.fOptionsPriv->fStoreContextRefInRecorder; } #endif } Context::~Context() { #if defined(GPU_TEST_UTILS) SkAutoMutexExclusive lock(fTestingLock); for (auto& recorder : fTrackedRecorders) { recorder->priv().setContext(nullptr); } #endif } bool Context::finishInitialization() { SkASSERT(!fSharedContext->rendererProvider()); // Can only initialize once StaticBufferManager bufferManager{fResourceProvider.get(), fSharedContext->caps()}; std::unique_ptr renderers{ new RendererProvider(fSharedContext->caps(), &bufferManager)}; auto result = bufferManager.finalize(this, fQueueManager.get(), fSharedContext->globalCache()); if (result == StaticBufferManager::FinishResult::kFailure) { // If something went wrong filling out the static vertex buffers, any Renderer that would // use it will draw incorrectly, so it's better to fail the Context creation. return false; } if (result == StaticBufferManager::FinishResult::kSuccess && !fQueueManager->submitToGpu()) { SKGPU_LOG_W("Failed to submit initial command buffer for Context creation.\n"); return false; } // else result was kNoWork so skip submitting to the GPU fSharedContext->setRendererProvider(std::move(renderers)); return true; } BackendApi Context::backend() const { return fSharedContext->backend(); } std::unique_ptr Context::makeRecorder(const RecorderOptions& options) { ASSERT_SINGLE_OWNER // This is a client-owned Recorder so pass a null context so it creates its own ResourceProvider auto recorder = std::unique_ptr(new Recorder(fSharedContext, options, nullptr)); #if defined(GPU_TEST_UTILS) if (fStoreContextRefInRecorder) { recorder->priv().setContext(this); } #endif return recorder; } std::unique_ptr Context::makePrecompileContext() { ASSERT_SINGLE_OWNER return std::unique_ptr(new PrecompileContext(fSharedContext)); } std::unique_ptr Context::makeInternalRecorder() const { ASSERT_SINGLE_OWNER // Unlike makeRecorder(), this Recorder is meant to be short-lived and go // away before a Context public API function returns to the caller. As such // it shares the Context's resource provider (no separate budget) and does // not get tracked. The internal drawing performed with an internal recorder // should not require a client image provider. return std::unique_ptr(new Recorder(fSharedContext, {}, this)); } bool Context::insertRecording(const InsertRecordingInfo& info) { ASSERT_SINGLE_OWNER return fQueueManager->addRecording(info, this); } bool Context::submit(SyncToCpu syncToCpu) { ASSERT_SINGLE_OWNER if (syncToCpu == SyncToCpu::kYes && !fSharedContext->caps()->allowCpuSync()) { SKGPU_LOG_E("SyncToCpu::kYes not supported with ContextOptions::fNeverYieldToWebGPU. " "The parameter is ignored and no synchronization will occur."); syncToCpu = SyncToCpu::kNo; } bool success = fQueueManager->submitToGpu(); this->checkForFinishedWork(syncToCpu); return success; } bool Context::hasUnfinishedGpuWork() const { return fQueueManager->hasUnfinishedGpuWork(); } template struct Context::AsyncParams { const SrcPixels* fSrcImage; SkIRect fSrcRect; SkImageInfo fDstImageInfo; SkImage::ReadPixelsCallback* fCallback; SkImage::ReadPixelsContext fCallbackContext; template AsyncParams withNewSource(const S* newPixels, const SkIRect& newSrcRect) const { return AsyncParams{newPixels, newSrcRect, fDstImageInfo, fCallback, fCallbackContext}; } void fail() const { (*fCallback)(fCallbackContext, nullptr); } bool validate() const { if (!fSrcImage) { return false; } if (fSrcImage->isProtected()) { return false; } if (!SkIRect::MakeSize(fSrcImage->dimensions()).contains(fSrcRect)) { return false; } if (!SkImageInfoIsValid(fDstImageInfo)) { return false; } return true; } }; template void Context::asyncRescaleAndReadImpl(ReadFn Context::* asyncRead, SkImage::RescaleGamma rescaleGamma, SkImage::RescaleMode rescaleMode, const AsyncParams& params, ExtraArgs... extraParams) { if (!params.validate()) { return params.fail(); } if (params.fSrcRect.size() == params.fDstImageInfo.dimensions()) { // No need to rescale so do a direct readback return (this->*asyncRead)(/*recorder=*/nullptr, params, extraParams...); } // Make a recorder to collect the rescale drawing commands and the copy commands std::unique_ptr recorder = this->makeInternalRecorder(); sk_sp scaledImage = RescaleImage(recorder.get(), params.fSrcImage, params.fSrcRect, params.fDstImageInfo, rescaleGamma, rescaleMode); if (!scaledImage) { SKGPU_LOG_W("AsyncRead failed because rescaling failed"); return params.fail(); } (this->*asyncRead)(std::move(recorder), params.withNewSource(scaledImage.get(), params.fDstImageInfo.bounds()), extraParams...); } void Context::asyncRescaleAndReadPixels(const SkImage* src, const SkImageInfo& dstImageInfo, const SkIRect& srcRect, SkImage::RescaleGamma rescaleGamma, SkImage::RescaleMode rescaleMode, SkImage::ReadPixelsCallback callback, SkImage::ReadPixelsContext callbackContext) { this->asyncRescaleAndReadImpl(&Context::asyncReadPixels, rescaleGamma, rescaleMode, {src, srcRect, dstImageInfo, callback, callbackContext}); } void Context::asyncRescaleAndReadPixels(const SkSurface* src, const SkImageInfo& dstImageInfo, const SkIRect& srcRect, SkImage::RescaleGamma rescaleGamma, SkImage::RescaleMode rescaleMode, SkImage::ReadPixelsCallback callback, SkImage::ReadPixelsContext callbackContext) { sk_sp surfaceImage = SkSurfaces::AsImage(sk_ref_sp(src)); if (!surfaceImage) { // The source surface is not texturable, so the only supported readback is if there's // no rescaling if (src && asConstSB(src)->isGraphiteBacked() && srcRect.size() == dstImageInfo.dimensions()) { TextureProxy* proxy = static_cast(src)->backingTextureProxy(); return this->asyncReadTexture(/*recorder=*/nullptr, {proxy, srcRect, dstImageInfo, callback, callbackContext}, src->imageInfo().colorInfo()); } // else fall through and let asyncRescaleAndReadPixels() invoke the callback when it detects // the null image. } this->asyncRescaleAndReadPixels(surfaceImage.get(), dstImageInfo, srcRect, rescaleGamma, rescaleMode, callback, callbackContext); } void Context::asyncReadPixels(std::unique_ptr recorder, const AsyncParams& params) { TRACE_EVENT2("skia.gpu", TRACE_FUNC, "width", params.fSrcRect.width(), "height", params.fSrcRect.height()); SkASSERT(params.validate()); // all paths to here are already validated SkASSERT(params.fSrcRect.size() == params.fDstImageInfo.dimensions()); const Caps* caps = fSharedContext->caps(); TextureProxyView view = AsView(params.fSrcImage); if (!view || !caps->supportsReadPixels(view.proxy()->textureInfo())) { // This is either a YUVA image (null view) or the texture can't be read directly, so // perform a draw into a compatible texture format and/or flatten any YUVA planes to RGBA. if (!recorder) { recorder = this->makeInternalRecorder(); } sk_sp flattened = CopyAsDraw(recorder.get(), params.fSrcImage, params.fSrcRect, params.fDstImageInfo.colorInfo(), Budgeted::kYes, Mipmapped::kNo, SkBackingFit::kApprox, "AsyncReadPixelsFallbackTexture"); if (!flattened) { SKGPU_LOG_W("AsyncRead failed because copy-as-drawing into a readable format failed"); return params.fail(); } // Use the original fSrcRect and not flattened's size since it's approx-fit. return this->asyncReadPixels(std::move(recorder), params.withNewSource(flattened.get(), SkIRect::MakeSize(params.fSrcRect.size()))); } // Can copy directly from the image's texture this->asyncReadTexture(std::move(recorder), params.withNewSource(view.proxy(), params.fSrcRect), params.fSrcImage->imageInfo().colorInfo()); } void Context::asyncReadTexture(std::unique_ptr recorder, const AsyncParams& params, const SkColorInfo& srcColorInfo) { SkASSERT(params.fSrcRect.size() == params.fDstImageInfo.dimensions()); // We can get here directly from surface or testing-only read pixels, so re-validate if (!params.validate()) { return params.fail(); } PixelTransferResult transferResult = this->transferPixels(recorder.get(), params.fSrcImage, srcColorInfo, params.fDstImageInfo.colorInfo(), params.fSrcRect); if (!transferResult.fTransferBuffer) { // TODO: try to do a synchronous readPixels instead return params.fail(); } this->finalizeAsyncReadPixels(std::move(recorder), {&transferResult, 1}, params.fCallback, params.fCallbackContext); } void Context::asyncRescaleAndReadPixelsYUV420(const SkImage* src, SkYUVColorSpace yuvColorSpace, sk_sp dstColorSpace, const SkIRect& srcRect, const SkISize& dstSize, SkImage::RescaleGamma rescaleGamma, SkImage::RescaleMode rescaleMode, SkImage::ReadPixelsCallback callback, SkImage::ReadPixelsContext callbackContext) { // Use kOpaque alpha type to signal that we don't read back the alpha channel SkImageInfo dstImageInfo = SkImageInfo::Make(dstSize, kRGBA_8888_SkColorType, kOpaque_SkAlphaType, std::move(dstColorSpace)); this->asyncRescaleAndReadImpl(&Context::asyncReadPixelsYUV420, rescaleGamma, rescaleMode, {src, srcRect, dstImageInfo, callback, callbackContext}, yuvColorSpace); } void Context::asyncRescaleAndReadPixelsYUV420(const SkSurface* src, SkYUVColorSpace yuvColorSpace, sk_sp dstColorSpace, const SkIRect& srcRect, const SkISize& dstSize, SkImage::RescaleGamma rescaleGamma, SkImage::RescaleMode rescaleMode, SkImage::ReadPixelsCallback callback, SkImage::ReadPixelsContext callbackContext) { // YUV[A] readback requires the surface to be texturable since the plane conversion is performed // by draws. If AsImage() returns null, the image version of asyncRescaleAndReadback will // automatically fail. // TODO: Is it worth performing an extra copy from 'surface' into a texture in order to succeed? sk_sp surfaceImage = SkSurfaces::AsImage(sk_ref_sp(src)); this->asyncRescaleAndReadPixelsYUV420(surfaceImage.get(), yuvColorSpace, dstColorSpace, srcRect, dstSize, rescaleGamma, rescaleMode, callback, callbackContext); } void Context::asyncRescaleAndReadPixelsYUVA420(const SkImage* src, SkYUVColorSpace yuvColorSpace, sk_sp dstColorSpace, const SkIRect& srcRect, const SkISize& dstSize, SkImage::RescaleGamma rescaleGamma, SkImage::RescaleMode rescaleMode, SkImage::ReadPixelsCallback callback, SkImage::ReadPixelsContext callbackContext) { SkImageInfo dstImageInfo = SkImageInfo::Make(dstSize, kRGBA_8888_SkColorType, kPremul_SkAlphaType, std::move(dstColorSpace)); this->asyncRescaleAndReadImpl(&Context::asyncReadPixelsYUV420, rescaleGamma, rescaleMode, {src, srcRect, dstImageInfo, callback, callbackContext}, yuvColorSpace); } void Context::asyncRescaleAndReadPixelsYUVA420(const SkSurface* src, SkYUVColorSpace yuvColorSpace, sk_sp dstColorSpace, const SkIRect& srcRect, const SkISize& dstSize, SkImage::RescaleGamma rescaleGamma, SkImage::RescaleMode rescaleMode, SkImage::ReadPixelsCallback callback, SkImage::ReadPixelsContext callbackContext) { sk_sp surfaceImage = SkSurfaces::AsImage(sk_ref_sp(src)); this->asyncRescaleAndReadPixelsYUVA420(surfaceImage.get(), yuvColorSpace, dstColorSpace, srcRect, dstSize, rescaleGamma, rescaleMode, callback, callbackContext); } void Context::asyncReadPixelsYUV420(std::unique_ptr recorder, const AsyncParams& params, SkYUVColorSpace yuvColorSpace) { TRACE_EVENT2("skia.gpu", TRACE_FUNC, "width", params.fSrcRect.width(), "height", params.fSrcRect.height()); // This is only called by asyncRescaleAndReadImpl which already validates its parameters SkASSERT(params.validate()); SkASSERT(params.fSrcRect.size() == params.fDstImageInfo.dimensions()); // The planes are always extracted via drawing, so create the Recorder if there isn't one yet. if (!recorder) { recorder = this->makeInternalRecorder(); } // copyPlane renders the source image into an A8 image and sets up a transfer stored in 'result' auto copyPlane = [&](SkImageInfo planeInfo, std::string_view label, float rgb2yuv[20], const SkMatrix& texMatrix, PixelTransferResult* result) { sk_sp dstSurface = Surface::MakeScratch(recorder.get(), planeInfo, std::move(label), Budgeted::kYes, Mipmapped::kNo, SkBackingFit::kApprox); if (!dstSurface) { return false; } // Render the plane defined by rgb2yuv from srcImage into dstSurface SkPaint paint; const SkSamplingOptions sampling(SkFilterMode::kLinear, SkMipmapMode::kNone); sk_sp imgShader = params.fSrcImage->makeShader( SkTileMode::kClamp, SkTileMode::kClamp, sampling, texMatrix); paint.setShader(std::move(imgShader)); paint.setBlendMode(SkBlendMode::kSrc); if (rgb2yuv) { // NOTE: The dstSurface's color space is set to the requested RGB dstColorSpace, so // the rendered image is automatically converted to that RGB color space before the // RGB->YUV color filter is evaluated, putting the plane data into the alpha channel. paint.setColorFilter(SkColorFilters::Matrix(rgb2yuv)); } SkCanvas* canvas = dstSurface->getCanvas(); canvas->drawPaint(paint); // Manually flush the surface before transferPixels() is called to ensure the rendering // operations run before the CopyTextureToBuffer task. Flush(dstSurface); // Must use planeInfo.bounds() for srcRect since dstSurface is kApprox-fit. *result = this->transferPixels(recorder.get(), dstSurface->backingTextureProxy(), dstSurface->imageInfo().colorInfo(), planeInfo.colorInfo(), planeInfo.bounds()); return SkToBool(result->fTransferBuffer); }; // Set up draws and transfers. This interleaves the drawing to a plane and the copy to the // transfer buffer, which will allow the scratch A8 surface to be reused for each plane. // TODO: Use one transfer buffer for all three planes to reduce map/unmap cost? const bool readAlpha = params.fDstImageInfo.colorInfo().alphaType() != kOpaque_SkAlphaType; SkImageInfo yaInfo = params.fDstImageInfo.makeColorType(kAlpha_8_SkColorType) .makeAlphaType(kPremul_SkAlphaType); SkImageInfo uvInfo = yaInfo.makeWH(yaInfo.width()/2, yaInfo.height()/2); PixelTransferResult transfers[4]; float baseM[20]; SkColorMatrix_RGB2YUV(yuvColorSpace, baseM); SkMatrix texMatrix = SkMatrix::Translate(-params.fSrcRect.fLeft, -params.fSrcRect.fTop); // This matrix generates (r,g,b,a) = (0, 0, 0, y) float yM[20]; std::fill_n(yM, 15, 0.f); std::copy_n(baseM + 0, 5, yM + 15); if (!copyPlane(yaInfo, "AsyncReadPixelsYPlane", yM, texMatrix, &transfers[0])) { return params.fail(); } // No matrix, straight copy of alpha channel SkASSERT(baseM[15] == 0 && baseM[16] == 0 && baseM[17] == 0 && baseM[18] == 1 && baseM[19] == 0); if (readAlpha && !copyPlane(yaInfo, "AsyncReadPixelsAPlane", nullptr, texMatrix, &transfers[3])) { return params.fail(); } // The UV planes are at half resolution compared to Y and A in 4:2:0 texMatrix.postScale(0.5f, 0.5f); // This matrix generates (r,g,b,a) = (0, 0, 0, u) float uM[20]; std::fill_n(uM, 15, 0.f); std::copy_n(baseM + 5, 5, uM + 15); if (!copyPlane(uvInfo, "AsyncReadPixelsUPlane", uM, texMatrix, &transfers[1])) { return params.fail(); } // This matrix generates (r,g,b,a) = (0, 0, 0, v) float vM[20]; std::fill_n(vM, 15, 0.f); std::copy_n(baseM + 10, 5, vM + 15); if (!copyPlane(uvInfo, "AsyncReadPixelsVPlane", vM, texMatrix, &transfers[2])) { return params.fail(); } this->finalizeAsyncReadPixels(std::move(recorder), {transfers, readAlpha ? 4 : 3}, params.fCallback, params.fCallbackContext); } void Context::finalizeAsyncReadPixels(std::unique_ptr recorder, SkSpan transferResults, SkImage::ReadPixelsCallback callback, SkImage::ReadPixelsContext callbackContext) { // If the async readback work required a Recorder, insert the recording with all of the // accumulated work (which includes any copies). Otherwise, for pure copy readbacks, // transferPixels() already added the tasks directly to the QueueManager. if (recorder) { std::unique_ptr recording = recorder->snap(); if (!recording) { callback(callbackContext, nullptr); return; } InsertRecordingInfo recordingInfo; recordingInfo.fRecording = recording.get(); if (!this->insertRecording(recordingInfo)) { callback(callbackContext, nullptr); return; } } // Set up FinishContext and add transfer commands to queue struct AsyncReadFinishContext { SkImage::ReadPixelsCallback* fClientCallback; SkImage::ReadPixelsContext fClientContext; ClientMappedBufferManager* fMappedBufferManager; std::array fTransferResults; }; auto finishContext = std::make_unique(); finishContext->fClientCallback = callback; finishContext->fClientContext = callbackContext; finishContext->fMappedBufferManager = fMappedBufferManager.get(); SkASSERT(transferResults.size() <= std::size(finishContext->fTransferResults)); skia_private::STArray<4, sk_sp> buffersToAsyncMap; for (size_t i = 0; i < transferResults.size(); ++i) { finishContext->fTransferResults[i] = std::move(transferResults[i]); if (fSharedContext->caps()->bufferMapsAreAsync()) { buffersToAsyncMap.push_back(finishContext->fTransferResults[i].fTransferBuffer); } } InsertFinishInfo info; info.fFinishedContext = finishContext.release(); info.fFinishedProc = [](GpuFinishedContext c, CallbackResult status) { std::unique_ptr context( reinterpret_cast(c)); using AsyncReadResult = skgpu::TAsyncReadResult; ClientMappedBufferManager* manager = context->fMappedBufferManager; std::unique_ptr result; if (status == CallbackResult::kSuccess) { result = std::make_unique(manager->ownerID()); } for (const auto& r : context->fTransferResults) { if (!r.fTransferBuffer) { break; } if (result && !result->addTransferResult(r, r.fSize, r.fRowBytes, manager)) { result.reset(); } // If we didn't get this buffer into the mapped buffer manager then make sure it gets // unmapped if it has a pending or completed async map. if (!result && r.fTransferBuffer->isUnmappable()) { r.fTransferBuffer->unmap(); } } (*context->fClientCallback)(context->fClientContext, std::move(result)); }; // If addFinishInfo() fails, it invokes the finish callback automatically, which handles all the // required clean up for us, just log an error message. The buffers will never be mapped and // thus don't need an unmap. if (!fQueueManager->addFinishInfo(info, fResourceProvider.get(), buffersToAsyncMap)) { SKGPU_LOG_E("Failed to register finish callbacks for asyncReadPixels."); return; } } Context::PixelTransferResult Context::transferPixels(Recorder* recorder, const TextureProxy* srcProxy, const SkColorInfo& srcColorInfo, const SkColorInfo& dstColorInfo, const SkIRect& srcRect) { SkASSERT(SkIRect::MakeSize(srcProxy->dimensions()).contains(srcRect)); SkASSERT(SkColorInfoIsValid(dstColorInfo)); const Caps* caps = fSharedContext->caps(); if (!srcProxy || !caps->supportsReadPixels(srcProxy->textureInfo())) { return {}; } const SkColorType srcColorType = srcColorInfo.colorType(); SkColorType supportedColorType; bool isRGB888Format; std::tie(supportedColorType, isRGB888Format) = caps->supportedReadPixelsColorType(srcColorType, srcProxy->textureInfo(), dstColorInfo.colorType()); if (supportedColorType == kUnknown_SkColorType) { return {}; } // Fail if read color type does not have all of dstCT's color channels and those missing color // channels are in the src. uint32_t dstChannels = SkColorTypeChannelFlags(dstColorInfo.colorType()); uint32_t legalReadChannels = SkColorTypeChannelFlags(supportedColorType); uint32_t srcChannels = SkColorTypeChannelFlags(srcColorType); if ((~legalReadChannels & dstChannels) & srcChannels) { return {}; } int bpp = isRGB888Format ? 3 : SkColorTypeBytesPerPixel(supportedColorType); size_t rowBytes = caps->getAlignedTextureDataRowBytes(bpp * srcRect.width()); size_t size = SkAlignTo(rowBytes * srcRect.height(), caps->requiredTransferBufferAlignment()); sk_sp buffer = fResourceProvider->findOrCreateBuffer( size, BufferType::kXferGpuToCpu, AccessPattern::kHostVisible, "TransferToCpu"); if (!buffer) { return {}; } // Set up copy task. Since we always use a new buffer the offset can be 0 and we don't need to // worry about aligning it to the required transfer buffer alignment. sk_sp copyTask = CopyTextureToBufferTask::Make(sk_ref_sp(srcProxy), srcRect, buffer, /*bufferOffset=*/0, rowBytes); const bool addTasksDirectly = !SkToBool(recorder); Protected contextIsProtected = fSharedContext->isProtected(); if (!copyTask || (addTasksDirectly && !fQueueManager->addTask(copyTask.get(), this, contextIsProtected))) { return {}; } else if (!addTasksDirectly) { // Add the task to the Recorder instead of the QueueManager if that's been required for // collecting tasks to prepare the copied textures. recorder->priv().add(std::move(copyTask)); } sk_sp syncTask = SynchronizeToCpuTask::Make(buffer); if (!syncTask || (addTasksDirectly && !fQueueManager->addTask(syncTask.get(), this, contextIsProtected))) { return {}; } else if (!addTasksDirectly) { recorder->priv().add(std::move(syncTask)); } PixelTransferResult result; result.fTransferBuffer = std::move(buffer); result.fSize = srcRect.size(); // srcColorInfo describes the texture; readColorInfo describes the result of the copy-to-buffer, // which may be different; dstColorInfo is what we have to transform it into when invoking the // async callbacks. SkColorInfo readColorInfo = srcColorInfo.makeColorType(supportedColorType); if (readColorInfo != dstColorInfo || isRGB888Format) { SkISize dims = srcRect.size(); SkImageInfo srcInfo = SkImageInfo::Make(dims, readColorInfo); SkImageInfo dstInfo = SkImageInfo::Make(dims, dstColorInfo); result.fRowBytes = dstInfo.minRowBytes(); result.fPixelConverter = [dstInfo, srcInfo, rowBytes, isRGB888Format]( void* dst, const void* src) { SkAutoPixmapStorage temp; size_t srcRowBytes = rowBytes; if (isRGB888Format) { temp.alloc(srcInfo); size_t tRowBytes = temp.rowBytes(); auto* sRow = reinterpret_cast(src); auto* tRow = reinterpret_cast(temp.writable_addr()); for (int y = 0; y < srcInfo.height(); ++y, sRow += srcRowBytes, tRow += tRowBytes) { for (int x = 0; x < srcInfo.width(); ++x) { auto s = sRow + x*3; auto t = tRow + x*sizeof(uint32_t); memcpy(t, s, 3); t[3] = static_cast(0xFF); } } src = temp.addr(); srcRowBytes = tRowBytes; } SkAssertResult(SkConvertPixels(dstInfo, dst, dstInfo.minRowBytes(), srcInfo, src, srcRowBytes)); }; } else { result.fRowBytes = rowBytes; } return result; } void Context::checkForFinishedWork(SyncToCpu syncToCpu) { ASSERT_SINGLE_OWNER fQueueManager->checkForFinishedWork(syncToCpu); fMappedBufferManager->process(); } void Context::checkAsyncWorkCompletion() { this->checkForFinishedWork(SyncToCpu::kNo); } void Context::deleteBackendTexture(const BackendTexture& texture) { ASSERT_SINGLE_OWNER if (!texture.isValid() || texture.backend() != this->backend()) { return; } fResourceProvider->deleteBackendTexture(texture); } void Context::freeGpuResources() { ASSERT_SINGLE_OWNER this->checkAsyncWorkCompletion(); fResourceProvider->freeGpuResources(); } void Context::performDeferredCleanup(std::chrono::milliseconds msNotUsed) { ASSERT_SINGLE_OWNER this->checkAsyncWorkCompletion(); auto purgeTime = skgpu::StdSteadyClock::now() - msNotUsed; fResourceProvider->purgeResourcesNotUsedSince(purgeTime); } size_t Context::currentBudgetedBytes() const { ASSERT_SINGLE_OWNER return fResourceProvider->getResourceCacheCurrentBudgetedBytes(); } size_t Context::currentPurgeableBytes() const { ASSERT_SINGLE_OWNER return fResourceProvider->getResourceCacheCurrentPurgeableBytes(); } size_t Context::maxBudgetedBytes() const { ASSERT_SINGLE_OWNER return fResourceProvider->getResourceCacheLimit(); } void Context::setMaxBudgetedBytes(size_t bytes) { ASSERT_SINGLE_OWNER return fResourceProvider->setResourceCacheLimit(bytes); } void Context::dumpMemoryStatistics(SkTraceMemoryDump* traceMemoryDump) const { ASSERT_SINGLE_OWNER fResourceProvider->dumpMemoryStatistics(traceMemoryDump); // TODO: What is the graphite equivalent for the text blob cache and how do we print out its // used bytes here (see Ganesh implementation). } bool Context::isDeviceLost() const { return fSharedContext->isDeviceLost(); } int Context::maxTextureSize() const { return fSharedContext->caps()->maxTextureSize(); } bool Context::supportsProtectedContent() const { return fSharedContext->isProtected() == Protected::kYes; } GpuStatsFlags Context::supportedGpuStats() const { return fSharedContext->caps()->supportedGpuStats(); } /////////////////////////////////////////////////////////////////////////////////// #if defined(GPU_TEST_UTILS) void Context::deregisterRecorder(const Recorder* recorder) { SkAutoMutexExclusive lock(fTestingLock); for (auto it = fTrackedRecorders.begin(); it != fTrackedRecorders.end(); it++) { if (*it == recorder) { fTrackedRecorders.erase(it); return; } } } bool ContextPriv::readPixels(const SkPixmap& pm, const TextureProxy* textureProxy, const SkImageInfo& srcImageInfo, int srcX, int srcY) { auto rect = SkIRect::MakeXYWH(srcX, srcY, pm.width(), pm.height()); struct AsyncContext { bool fCalled = false; std::unique_ptr fResult; } asyncContext; auto asyncCallback = [](void* c, std::unique_ptr out) { auto context = static_cast(c); context->fResult = std::move(out); context->fCalled = true; }; const SkColorInfo& srcColorInfo = srcImageInfo.colorInfo(); // This is roughly equivalent to the logic taken in asyncRescaleAndRead(SkSurface) to either // try the image-based readback (with copy-as-draw fallbacks) or read the texture directly // if it supports reading. if (!fContext->fSharedContext->caps()->supportsReadPixels(textureProxy->textureInfo())) { // Since this is a synchronous testing-only API, callers should have flushed any pending // work that modifies this texture proxy already. This means we don't have to worry about // re-wrapping the proxy in a new Image (that wouldn't tbe connected to any Device, etc.). sk_sp image{new Image(TextureProxyView(sk_ref_sp(textureProxy)), srcColorInfo)}; Context::AsyncParams params {image.get(), rect, pm.info(), asyncCallback, &asyncContext}; if (!params.validate()) { params.fail(); } else { fContext->asyncReadPixels(/*recorder=*/nullptr, params); } } else { fContext->asyncReadTexture(/*recorder=*/nullptr, {textureProxy, rect, pm.info(), asyncCallback, &asyncContext}, srcImageInfo.colorInfo()); } if (fContext->fSharedContext->caps()->allowCpuSync()) { fContext->submit(SyncToCpu::kYes); } else { fContext->submit(SyncToCpu::kNo); if (fContext->fSharedContext->backend() == BackendApi::kDawn) { while (!asyncContext.fCalled) { fContext->fSharedContext->deviceTick(fContext); } } else { SK_ABORT("Only Dawn supports non-syncing contexts."); } } SkASSERT(asyncContext.fCalled); if (!asyncContext.fResult) { return false; } SkRectMemcpy(pm.writable_addr(), pm.rowBytes(), asyncContext.fResult->data(0), asyncContext.fResult->rowBytes(0), pm.info().minRowBytes(), pm.height()); return true; } bool ContextPriv::supportsPathRendererStrategy(PathRendererStrategy strategy) { AtlasProvider::PathAtlasFlagsBitMask pathAtlasFlags = AtlasProvider::QueryPathAtlasSupport(this->caps()); switch (strategy) { case PathRendererStrategy::kDefault: return true; case PathRendererStrategy::kComputeAnalyticAA: case PathRendererStrategy::kComputeMSAA16: case PathRendererStrategy::kComputeMSAA8: return SkToBool(pathAtlasFlags & AtlasProvider::PathAtlasFlags::kCompute); case PathRendererStrategy::kRasterAA: return SkToBool(pathAtlasFlags & AtlasProvider::PathAtlasFlags::kRaster); case PathRendererStrategy::kTessellation: return true; } return false; } #endif // GPU_TEST_UTILS /////////////////////////////////////////////////////////////////////////////////// std::unique_ptr ContextCtorAccessor::MakeContext( sk_sp sharedContext, std::unique_ptr queueManager, const ContextOptions& options) { auto context = std::unique_ptr(new Context(std::move(sharedContext), std::move(queueManager), options)); if (context && context->finishInitialization()) { return context; } else { return nullptr; } } } // namespace skgpu::graphite