| /* | |
| * 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/SkAlphaType.h" | |
| #include "include/core/SkBlendMode.h" | |
| #include "include/core/SkCanvas.h" | |
| #include "include/core/SkColorFilter.h" | |
| #include "include/core/SkColorSpace.h" | |
| #include "include/core/SkColorType.h" | |
| #include "include/core/SkImage.h" | |
| #include "include/core/SkImageInfo.h" | |
| #include "include/core/SkMatrix.h" | |
| #include "include/core/SkPaint.h" | |
| #include "include/core/SkPixmap.h" | |
| #include "include/core/SkRect.h" | |
| #include "include/core/SkRefCnt.h" | |
| #include "include/core/SkSamplingOptions.h" | |
| #include "include/core/SkShader.h" | |
| #include "include/core/SkSize.h" | |
| #include "include/core/SkSurface.h" | |
| #include "include/core/SkTileMode.h" | |
| #include "include/core/SkTypes.h" | |
| #include "include/gpu/GpuTypes.h" | |
| #include "include/gpu/MutableTextureState.h" | |
| #include "include/gpu/graphite/BackendTexture.h" | |
| #include "include/gpu/graphite/ContextOptions.h" | |
| #include "include/gpu/graphite/GraphiteTypes.h" | |
| #include "include/gpu/graphite/PrecompileContext.h" | |
| #include "include/gpu/graphite/Recorder.h" | |
| #include "include/gpu/graphite/Surface.h" | |
| #include "include/private/SingleOwner.h" | |
| #include "include/private/SkAlign.h" | |
| #include "include/private/SkAttributes.h" | |
| #include "include/private/SkEnumBitMask.h" | |
| #include "include/private/SkLog.h" | |
| #include "include/private/SkMutex.h" | |
| #include "include/private/SkOnce.h" | |
| #include "include/private/SkTArray.h" | |
| #include "include/private/SkTo.h" | |
| #include "src/capture/SkCapture.h" | |
| #include "src/capture/SkCaptureManager.h" | |
| #include "src/core/SkAutoPixmapStorage.h" | |
| #include "src/core/SkColorSpaceXformSteps.h" | |
| #include "src/core/SkConvertPixels.h" | |
| #include "src/core/SkImageInfoPriv.h" | |
| #include "src/core/SkRectMemcpy.h" | |
| #include "src/core/SkSafeMath.h" | |
| #include "src/core/SkTraceEvent.h" | |
| #include "src/core/SkYUVMath.h" | |
| #include "src/gpu/AsyncReadTypes.h" | |
| #include "src/gpu/GlobalResourceStats.h" | |
| #include "src/gpu/GpuTypesPriv.h" | |
| #include "src/gpu/SkBackingFit.h" | |
| #include "src/gpu/graphite/AtlasProvider.h" | |
| #include "src/gpu/graphite/Buffer.h" | |
| #include "src/gpu/graphite/BufferManager.h" | |
| #include "src/gpu/graphite/Caps.h" | |
| #include "src/gpu/graphite/ClientMappedBufferManager.h" | |
| #include "src/gpu/graphite/ContextPriv.h" | |
| #include "src/gpu/graphite/GlobalCache.h" | |
| #include "src/gpu/graphite/Image_Graphite.h" | |
| #include "src/gpu/graphite/QueueManager.h" | |
| #include "src/gpu/graphite/RecorderPriv.h" | |
| #include "src/gpu/graphite/RecordingPriv.h" | |
| #include "src/gpu/graphite/RendererProvider.h" | |
| #include "src/gpu/graphite/ResourceProvider.h" | |
| #include "src/gpu/graphite/ResourceTypes.h" | |
| #include "src/gpu/graphite/RuntimeEffectDictionary.h" | |
| #include "src/gpu/graphite/SharedContext.h" | |
| #include "src/gpu/graphite/Surface_Graphite.h" | |
| #include "src/gpu/graphite/Texture.h" | |
| #include "src/gpu/graphite/TextureFormatXferFn.h" | |
| #include "src/gpu/graphite/TextureInfoPriv.h" | |
| #include "src/gpu/graphite/TextureProxy.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/Task.h" | |
| #include "src/image/SkSurface_Base.h" | |
| #include "src/sksl/SkSLGraphiteModules.h" | |
| #include <algorithm> | |
| #include <array> | |
| #include <atomic> | |
| #include <chrono> | |
| #include <cstdint> | |
| #include <cstring> | |
| #include <forward_list> | |
| #include <functional> | |
| #include <memory> | |
| #include <optional> | |
| #include <string_view> | |
| #include <tuple> | |
| #include <utility> | |
| #include <vector> | |
| #if defined(GPU_TEST_UTILS) | |
| #include "src/gpu/graphite/ContextOptionsPriv.h" | |
| #endif | |
| class SkTraceMemoryDump; | |
| namespace skgpu::graphite { class Recording; } | |
| namespace skgpu::graphite { | |
| #if defined(GPU_TEST_UTILS) | |
| namespace { | |
| bool gDisableSmallSizeReadCPUFallback = false; | |
| } // namespace | |
| void ContextPriv::disableSmallSizeReadCPUFallback_ForTesting(bool disable) { | |
| gDisableSmallSizeReadCPUFallback = disable; | |
| } | |
| #endif | |
| #define ASSERT_SINGLE_OWNER SKGPU_ASSERT_SINGLE_OWNER(this->singleOwner()) | |
| Context::ContextID Context::ContextID::Next() { | |
| static std::atomic<uint32_t> 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> sharedContext, | |
| std::unique_ptr<QueueManager> 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. | |
| SkSL::Loader::LoadGraphiteModules(); | |
| // 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<ClientMappedBufferManager>(this->contextID()); | |
| #if defined(GPU_TEST_UTILS) | |
| if (options.fOptionsPriv) { | |
| fStoreContextRefInRecorder = options.fOptionsPriv->fStoreContextRefInRecorder; | |
| } | |
| #endif | |
| fSharedContext->globalCache()->setPipelineCallback(options.fPipelineCallbackContext, | |
| options.fPipelineCachingCallback, | |
| options.fPipelineCallback); | |
| if (options.fEnableCapture) { | |
| fSharedContext->setCaptureManager(sk_make_sp<SkCaptureManager>()); | |
| } | |
| fPersistentPipelineStorage = options.fPersistentPipelineStorage; | |
| } | |
| Context::~Context() { | |
| // The PipelineManager uses the Context's SkExecutor but it, and the SharedContext, | |
| // could be kept alive after this call via a PrecompileContext. In order to make the | |
| // usage lifetime of the executor manageable, remove the PipelineManager's usage | |
| // of the executor here. This means that if any PrecompileContext's outlive their | |
| // generating Context they will revert to serial, in-line compilation. | |
| // | |
| // A side effect of terminating threaded compilation here is that any threaded | |
| // tasks (that rely on the SharedContext's existence) are cleared out. | |
| // | |
| // Note that, because this is happening on the main thread, the PipelineManager should not | |
| // be waiting to resolve any Pipelines (in resolveHandle) so we shouldn't deadlock. | |
| fSharedContext->pipelineManager()->shutDown(); | |
| #if defined(GPU_TEST_UTILS) | |
| SkAutoMutexExclusive lock(fTestingLock); | |
| for (auto& recorder : fTrackedRecorders) { | |
| recorder->priv().setContext(nullptr); | |
| } | |
| #endif | |
| } | |
| Context::PixelTransferResult::PixelTransferResult() = default; | |
| Context::PixelTransferResult::PixelTransferResult(const PixelTransferResult&) = default; | |
| Context::PixelTransferResult::PixelTransferResult(PixelTransferResult&&) = default; | |
| Context::PixelTransferResult& Context::PixelTransferResult::operator=(const PixelTransferResult&) = default; | |
| Context::PixelTransferResult::~PixelTransferResult() = default; | |
| bool Context::finishInitialization() { | |
| SkASSERT(!fSharedContext->rendererProvider()); // Can only initialize once | |
| if (!fSharedContext->globalCache()->initializeDynamicSamplers(fResourceProvider.get(), | |
| fSharedContext->caps())) { | |
| return false; | |
| } | |
| StaticBufferManager bufferManager{fResourceProvider.get(), fSharedContext->caps()}; | |
| std::unique_ptr<RendererProvider> 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(/*submitInfo=*/{})) { | |
| SKIA_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<Recorder> 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<Recorder>(new Recorder(fSharedContext, options, nullptr)); | |
| #if defined(GPU_TEST_UTILS) | |
| if (fStoreContextRefInRecorder) { | |
| recorder->priv().setContext(this); | |
| } | |
| #endif | |
| return recorder; | |
| } | |
| std::unique_ptr<PrecompileContext> Context::makePrecompileContext() { | |
| ASSERT_SINGLE_OWNER | |
| return std::unique_ptr<PrecompileContext>(new PrecompileContext(fSharedContext)); | |
| } | |
| std::unique_ptr<Recorder> 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. | |
| // | |
| // Explicitly overrides fRequiresOrderedRecordings to false so that these Recorders do not | |
| // inherit any global policy from the ContextOptions. Since they will only produce one Recording | |
| // there's no need to require subsequent recordings be ordered. | |
| RecorderOptions options = {}; | |
| options.fRequireOrderedRecordings = false; | |
| return std::unique_ptr<Recorder>(new Recorder(fSharedContext, options, this)); | |
| } | |
| InsertStatus Context::insertRecording(const InsertRecordingInfo& info) { | |
| ASSERT_SINGLE_OWNER | |
| if (fSharedContext->captureManager() && | |
| fSharedContext->captureManager()->isCurrentlyCapturing() && | |
| info.fRecording) { | |
| fSharedContext->captureManager()->onInsertRecording( | |
| info.fRecording->priv().capturedPictures() | |
| ); | |
| } | |
| return fQueueManager->addRecording(info, this); | |
| } | |
| bool Context::submit(SubmitInfo submitInfo) { | |
| ASSERT_SINGLE_OWNER | |
| if (submitInfo.fSync == SyncToCpu::kYes && !fSharedContext->caps()->allowCpuSync()) { | |
| SKIA_LOG_E("SyncToCpu::kYes not supported with ContextOptions::fNeverYieldToWebGPU. " | |
| "The parameter is ignored and no synchronization will occur."); | |
| submitInfo.fSync = SyncToCpu::kNo; | |
| } | |
| bool success = fQueueManager->submitToGpu(submitInfo); | |
| this->checkForFinishedWork(submitInfo.fSync); | |
| return success; | |
| } | |
| bool Context::hasUnfinishedGpuWork() const { return fQueueManager->hasUnfinishedGpuWork(); } | |
| bool Context::hasPendingGPUWork() const { return fQueueManager->hasPendingGPUWork(); } | |
| template <typename SrcPixels> | |
| struct Context::AsyncParams { | |
| const SrcPixels* fSrcImage; | |
| SkIRect fSrcRect; | |
| SkImageInfo fDstImageInfo; | |
| SkImage::ReadPixelsCallback* fCallback; | |
| SkImage::ReadPixelsContext fCallbackContext; | |
| template <typename S> | |
| AsyncParams<S> withNewSource(const S* newPixels, const SkIRect& newSrcRect) const { | |
| return AsyncParams<S>{newPixels, newSrcRect, | |
| fDstImageInfo, fCallback, fCallbackContext}; | |
| } | |
| void fail() const { | |
| (*fCallback)(fCallbackContext, nullptr); | |
| } | |
| bool validate() const { | |
| if (!fSrcImage) { | |
| return false; | |
| } | |
| // SkImage::isProtected() -> bool, TextureProxyView::isProtected() -> Protected enum. | |
| // The explicit cast makes this function work for both template instantiations since | |
| // the Protected enum is backed by a bool. | |
| if ((bool) fSrcImage->isProtected()) { | |
| return false; | |
| } | |
| if (!SkIRect::MakeSize(fSrcImage->dimensions()).contains(fSrcRect)) { | |
| return false; | |
| } | |
| if (!SkImageInfoIsValid(fDstImageInfo)) { | |
| return false; | |
| } | |
| return true; | |
| } | |
| }; | |
| template <typename ReadFn, typename... ExtraArgs> | |
| void Context::asyncRescaleAndReadImpl(ReadFn Context::* asyncRead, | |
| SkImage::RescaleGamma rescaleGamma, | |
| SkImage::RescaleMode rescaleMode, | |
| const AsyncParams<SkImage>& params, | |
| ExtraArgs... extraParams) { | |
| if (!params.validate() || !as_IB(params.fSrcImage)->isGraphiteBacked()) { | |
| 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> recorder = this->makeInternalRecorder(); | |
| sk_sp<SkImage> scaledImage = RescaleImage(recorder.get(), | |
| static_cast<const Image_Base*>(params.fSrcImage), | |
| params.fSrcRect, | |
| params.fDstImageInfo, | |
| rescaleGamma, | |
| rescaleMode); | |
| if (!scaledImage) { | |
| SKIA_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<SkImage> 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()) { | |
| TextureProxyView view = static_cast<const Surface*>(src)->target(); | |
| const TextureInfo& texInfo = view.proxy()->textureInfo(); | |
| const TextureFormat format = TextureInfoPriv::ViewFormat(texInfo); | |
| const SkColorInfo& srcColorInfo = src->imageInfo().colorInfo(); | |
| const SkColorInfo& dstColorInfo = dstImageInfo.colorInfo(); | |
| SkColorSpaceXformSteps csSteps{srcColorInfo, dstColorInfo}; | |
| auto xferFn = TextureFormatXferFn::MakeGpuToCpu(format, | |
| view.swizzle(), | |
| csSteps, | |
| dstColorInfo.colorType()); | |
| if (!xferFn) { | |
| callback(callbackContext, nullptr); | |
| return; | |
| } | |
| return this->asyncReadTexture( | |
| /*recorder=*/nullptr, | |
| {&view, srcRect, dstImageInfo, callback, callbackContext}, | |
| *xferFn); | |
| } | |
| // 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> recorder, | |
| const AsyncParams<SkImage>& 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); | |
| SkColorInfo srcColorInfo = params.fSrcImage->imageInfo().colorInfo(); | |
| const SkColorInfo& dstColorInfo = params.fDstImageInfo.colorInfo(); | |
| SkIRect srcRect = params.fSrcRect; | |
| std::optional<TextureFormatXferFn> xferFn; | |
| if (view) { | |
| const TextureInfo& texInfo = view.proxy()->textureInfo(); | |
| const TextureFormat format = TextureInfoPriv::ViewFormat(texInfo); | |
| SkColorSpaceXformSteps csSteps{srcColorInfo, dstColorInfo}; | |
| xferFn = TextureFormatXferFn::MakeGpuToCpu(format, | |
| view.swizzle(), | |
| csSteps, | |
| dstColorInfo.colorType()); | |
| } | |
| const bool requireConversion = !view || !caps->isCopyableSrc(view.proxy()->textureInfo()); | |
| const bool smallRead = | |
| #if defined(GPU_TEST_UTILS) | |
| !gDisableSmallSizeReadCPUFallback && | |
| #endif | |
| (srcRect.size().area() < 64 * 64); | |
| const bool tryGpuConversion = | |
| // For small reads, CPU conversion might be faster than setting up the | |
| // GPU pass. So only do GPU conversion for non-small reads. | |
| !smallRead && | |
| view && | |
| ( | |
| #if !defined(SK_LEGACY_GRAPHITE_READ_PIXELS_BOTTOM_LEFT_BEHAVIOR) | |
| // Flip if the image is bottom left | |
| view.origin() == Origin::kBottomLeft || | |
| #endif | |
| // Try GPU conversion if the transfer function is not identity | |
| (xferFn && !xferFn->isIdentity())); | |
| if (requireConversion || tryGpuConversion) { | |
| if (!recorder) { | |
| recorder = this->makeInternalRecorder(); | |
| } | |
| sk_sp<SkImage> converted = CopyAsDraw(recorder.get(), | |
| /*drawContext=*/nullptr, | |
| params.fSrcImage, | |
| srcRect, | |
| dstColorInfo, | |
| Budgeted::kYes, | |
| Mipmapped::kNo, | |
| SkBackingFit::kApprox, | |
| "AsyncReadPixelsConversionTexture"); | |
| if (converted) { | |
| view = AsView(converted); | |
| srcColorInfo = converted->imageInfo().colorInfo(); | |
| srcRect = SkIRect::MakeSize(srcRect.size()); | |
| // The GPU draw converted the pixels to converted's color info (target color space | |
| // and alpha type). However, the backing texture format may not natively match | |
| // dstColorInfo's channel ordering (e.g. TF::kRGB10_A2 for kBGRA_1010102 on Dawn). | |
| // Query a transfer function for the remaining format/swizzle conversion. | |
| const TextureInfo& texInfo = view.proxy()->textureInfo(); | |
| const TextureFormat format = TextureInfoPriv::ViewFormat(texInfo); | |
| SkColorSpaceXformSteps csSteps{srcColorInfo, dstColorInfo}; | |
| xferFn = TextureFormatXferFn::MakeGpuToCpu(format, | |
| view.swizzle(), | |
| csSteps, | |
| dstColorInfo.colorType()); | |
| } else if (requireConversion) { | |
| SKIA_LOG_W("AsyncRead failed because copy-as-drawing into a readable format failed"); | |
| return params.fail(); | |
| } | |
| // else it couldn't be rendered so apply the GPU-optional conversions on the CPU | |
| // instead | |
| } | |
| if (!xferFn) { | |
| return params.fail(); | |
| } | |
| this->asyncReadTexture(std::move(recorder), | |
| params.withNewSource(&view, srcRect), | |
| *xferFn); | |
| } | |
| void Context::asyncReadTexture(std::unique_ptr<Recorder> recorder, | |
| const AsyncParams<TextureProxyView>& params, | |
| TextureFormatXferFn xferFn) { | |
| 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, | |
| params.fDstImageInfo.colorInfo(), | |
| params.fSrcRect, | |
| xferFn); | |
| 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<SkColorSpace> 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<SkColorSpace> 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<SkImage> 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<SkColorSpace> 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<SkColorSpace> dstColorSpace, | |
| const SkIRect& srcRect, | |
| const SkISize& dstSize, | |
| SkImage::RescaleGamma rescaleGamma, | |
| SkImage::RescaleMode rescaleMode, | |
| SkImage::ReadPixelsCallback callback, | |
| SkImage::ReadPixelsContext callbackContext) { | |
| sk_sp<SkImage> 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> recorder, | |
| const AsyncParams<SkImage>& 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<Surface> dstSurface = Surface::MakeScratch(recorder.get(), | |
| planeInfo, | |
| 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<SkShader> 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); | |
| auto identityXfer = TextureFormatXferFn::MakeIdentity( | |
| dstSurface->target().proxy()->format()); | |
| if (!identityXfer) { | |
| return false; | |
| } | |
| // Must use planeInfo.bounds() for srcRect since dstSurface is kApprox-fit. | |
| *result = this->transferPixels(recorder.get(), | |
| dstSurface->target(), | |
| planeInfo.colorInfo(), | |
| planeInfo.bounds(), | |
| *identityXfer); | |
| 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); | |
| SK_UNSAFE_TODO(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); | |
| SK_UNSAFE_TODO(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); | |
| SK_UNSAFE_TODO(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 ? 4u : 3u}, | |
| params.fCallback, | |
| params.fCallbackContext); | |
| } | |
| void Context::finalizeAsyncReadPixels(std::unique_ptr<Recorder> recorder, | |
| SkSpan<PixelTransferResult> 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> 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<PixelTransferResult, 4> fTransferResults; | |
| }; | |
| auto finishContext = std::make_unique<AsyncReadFinishContext>(); | |
| finishContext->fClientCallback = callback; | |
| finishContext->fClientContext = callbackContext; | |
| finishContext->fMappedBufferManager = fMappedBufferManager.get(); | |
| SkASSERT(transferResults.size() <= std::size(finishContext->fTransferResults)); | |
| skia_private::STArray<4, sk_sp<Buffer>> 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<const AsyncReadFinishContext> context( | |
| reinterpret_cast<const AsyncReadFinishContext*>(c)); | |
| using AsyncReadResult = skgpu::TAsyncReadResult<Buffer, ContextID, PixelTransferResult>; | |
| ClientMappedBufferManager* manager = context->fMappedBufferManager; | |
| std::unique_ptr<AsyncReadResult> result; | |
| if (status == CallbackResult::kSuccess) { | |
| result = std::make_unique<AsyncReadResult>(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)) { | |
| SKIA_LOG_E("Failed to register finish callbacks for asyncReadPixels."); | |
| return; | |
| } | |
| } | |
| Context::PixelTransferResult Context::transferPixels(Recorder* recorder, | |
| const TextureProxyView& srcView, | |
| const SkColorInfo& dstColorInfo, | |
| const SkIRect& srcRect, | |
| const TextureFormatXferFn& cpuXferFn) { | |
| SkASSERT(SkIRect::MakeSize(srcView.dimensions()).contains(srcRect)); | |
| SkASSERT(SkColorInfoIsValid(dstColorInfo)); | |
| const Caps* caps = fSharedContext->caps(); | |
| if (!srcView || !caps->isCopyableSrc(srcView.proxy()->textureInfo())) { | |
| return {}; | |
| } | |
| const TextureInfo& texInfo = srcView.proxy()->textureInfo(); | |
| const TextureFormat format = TextureInfoPriv::ViewFormat(texInfo); | |
| SkSafeMath safe; | |
| int bpp = TextureFormatBytesPerBlock(format); | |
| size_t rowBytes = caps->getAlignedTextureDataRowBytes(safe.mul(bpp, srcRect.width()), bpp); | |
| size_t size = safe.alignUp(safe.mul(rowBytes, srcRect.height()), | |
| caps->requiredTransferBufferAlignment()); | |
| if (!safe.ok() || rowBytes == 0 || size == 0) { | |
| return {}; | |
| } | |
| sk_sp<Buffer> buffer = fResourceProvider->findOrCreateNonShareableBuffer( | |
| size, BufferType::kXferGpuToCpu, AccessPattern::kHostVisible, "TransferToCpu"); | |
| if (!buffer) { | |
| return {}; | |
| } | |
| #if defined(SK_LEGACY_GRAPHITE_READ_PIXELS_BOTTOM_LEFT_BEHAVIOR) | |
| bool flipY = false; | |
| #else | |
| const bool flipY = (srcView.origin() == Origin::kBottomLeft); | |
| #endif | |
| SkIRect copyRect = srcRect; | |
| if (flipY) { | |
| int h = srcView.dimensions().height(); | |
| copyRect = SkIRect::MakeLTRB(srcRect.fLeft, h - srcRect.fBottom, | |
| srcRect.fRight, h - srcRect.fTop); | |
| } | |
| // 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<CopyTextureToBufferTask> copyTask = CopyTextureToBufferTask::Make(srcView.refProxy(), | |
| copyRect, | |
| 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<SynchronizeToCpuTask> 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(); | |
| if (cpuXferFn.isIdentity() && !flipY) { | |
| result.fRowBytes = rowBytes; | |
| } else { | |
| SkImageInfo dstInfo = SkImageInfo::Make(srcRect.size(), dstColorInfo); | |
| result.fRowBytes = dstInfo.minRowBytes(); | |
| // TODO(b/553467540): do flipping in TextureFormatXferFn::run | |
| result.fPixelConverter = [cpuXferFn, dstInfo, rowBytes, flipY](void* dst, | |
| const void* src) { | |
| if (flipY) { | |
| for (int y = 0; y < dstInfo.height(); ++y) { | |
| const auto* srcRow = SK_UNSAFE_TODO(static_cast<const char*>(src) + | |
| (dstInfo.height() - 1 - y) * rowBytes); | |
| auto* dstRow = | |
| SK_UNSAFE_TODO(static_cast<char*>(dst) + y * dstInfo.minRowBytes()); | |
| cpuXferFn.run(dstInfo.width(), 1, | |
| srcRow, rowBytes, | |
| dstRow, dstInfo.minRowBytes()); | |
| } | |
| } else { | |
| cpuXferFn.run(dstInfo.width(), dstInfo.height(), | |
| src, rowBytes, | |
| dst, dstInfo.minRowBytes()); | |
| } | |
| }; | |
| } | |
| return result; | |
| } | |
| void Context::checkForFinishedWork(SyncToCpu syncToCpu) { | |
| ASSERT_SINGLE_OWNER | |
| fQueueManager->checkForFinishedWork(syncToCpu); | |
| fMappedBufferManager->process(); | |
| // Process the return queue periodically to make sure it doesn't get too big | |
| fResourceProvider->forceProcessReturnedResources(); | |
| fSharedContext->forceProcessReturnedResources(); | |
| GlobalResourceStats::TraceStatsSummary(); | |
| } | |
| 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(); | |
| fSharedContext->freeGpuResources(); | |
| } | |
| void Context::performDeferredCleanup( | |
| std::chrono::milliseconds msNotUsed, | |
| std::optional<std::chrono::microseconds> microsMaxPurgingDur) { | |
| ASSERT_SINGLE_OWNER | |
| this->checkAsyncWorkCompletion(); | |
| auto purgeTime = skgpu::StdSteadyClock::now() - msNotUsed; | |
| // We do not ever limit the time required to purge resources from the threadsafe resource cache | |
| // because it holds objects which should be basically trivial to delete. Eventually, these | |
| // resources will be moved into a global threadsafe resource cache anyhow. | |
| fSharedContext->purgeResourcesNotUsedSince(purgeTime); | |
| fResourceProvider->purgeResourcesNotUsedSince(purgeTime, microsMaxPurgingDur); | |
| } | |
| size_t Context::currentBudgetedBytes() const { | |
| ASSERT_SINGLE_OWNER | |
| SkASSERT(fSharedContext->getResourceCacheCurrentBudgetedBytes() == 0); | |
| return fResourceProvider->getResourceCacheCurrentBudgetedBytes(); | |
| } | |
| size_t Context::currentPurgeableBytes() const { | |
| ASSERT_SINGLE_OWNER | |
| SkASSERT(fSharedContext->getResourceCacheCurrentPurgeableBytes() == 0); | |
| return fResourceProvider->getResourceCacheCurrentPurgeableBytes(); | |
| } | |
| size_t Context::maxBudgetedBytes() const { | |
| ASSERT_SINGLE_OWNER | |
| SkASSERT(fSharedContext->getResourceCacheLimit() == SharedContext::kThreadedSafeResourceBudget); | |
| 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); | |
| fSharedContext->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(); | |
| } | |
| void Context::syncPipelineData(size_t maxSize) { | |
| ASSERT_SINGLE_OWNER | |
| if (fPersistentPipelineStorage) { | |
| fSharedContext->syncPipelineData(fPersistentPipelineStorage, maxSize); | |
| } | |
| } | |
| void Context::startCapture() { | |
| if (fSharedContext->captureManager()) { | |
| fSharedContext->captureManager()->toggleCapture(true); | |
| } | |
| } | |
| sk_sp<SkCapture> Context::endCapture() { | |
| if (fSharedContext->captureManager()) { | |
| fSharedContext->captureManager()->toggleCapture(false); | |
| return fSharedContext->captureManager()->getLastCapture(); | |
| } | |
| return nullptr; | |
| } | |
| bool Context::setMutableState(const BackendTexture& texture, | |
| const skgpu::MutableTextureState& state) { | |
| if (!texture.isValid() || texture.backend() != this->backend()) { | |
| SKIA_LOG_E("Context::setMutableState: Invalid or non-Graphite BackendTexture"); | |
| return false; | |
| } | |
| if (!state.isValid() || state.backend() != this->backend()) { | |
| SKIA_LOG_E("Context::setMutableState: Invalid or non-Graphite MutableTextureState"); | |
| return false; | |
| } | |
| if (!fQueueManager->prepareBackendTextureForStateUpdate(texture, &state, | |
| fResourceProvider.get())) { | |
| SKIA_LOG_E("Context::setMutableState: Failed to perform state update for backend texture"); | |
| return false; | |
| } | |
| return true; | |
| } | |
| /////////////////////////////////////////////////////////////////////////////////// | |
| #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 TextureProxyView& srcView, | |
| 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<const SkImage::AsyncReadResult> fResult; | |
| } asyncContext; | |
| auto asyncCallback = [](void* c, std::unique_ptr<const SkImage::AsyncReadResult> out) { | |
| auto context = static_cast<AsyncContext*>(c); | |
| context->fResult = std::move(out); | |
| context->fCalled = true; | |
| }; | |
| const SkColorInfo& srcColorInfo = srcImageInfo.colorInfo(); | |
| const Caps* caps = fContext->fSharedContext->caps(); | |
| const TextureInfo& texInfo = srcView.proxy()->textureInfo(); | |
| // Prefer the image-based readback (with copy-as-draw fallbacks in asyncReadPixels) when | |
| // texturable so that GPU conversions are attempted. If not texturable but copyable, | |
| // read the texture directly via asyncReadTexture (CPU conversion). | |
| if (caps->isTexturable(texInfo)) { | |
| // 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 be connected to any Device, etc.). | |
| sk_sp<SkImage> image{new Image(srcView, srcColorInfo)}; | |
| Context::AsyncParams<SkImage> params{image.get(), rect, pm.info(), | |
| asyncCallback, &asyncContext}; | |
| if (!params.validate()) { | |
| params.fail(); | |
| } else { | |
| fContext->asyncReadPixels(/*recorder=*/nullptr, params); | |
| } | |
| } else if (caps->isCopyableSrc(texInfo)) { | |
| const TextureFormat format = srcView.proxy()->format(); | |
| const SkColorInfo& dstColorInfo = pm.info().colorInfo(); | |
| SkColorSpaceXformSteps csSteps{srcColorInfo, dstColorInfo}; | |
| auto xferFn = TextureFormatXferFn::MakeGpuToCpu(format, | |
| srcView.swizzle(), | |
| csSteps, | |
| dstColorInfo.colorType()); | |
| if (!xferFn) { | |
| return false; | |
| } | |
| fContext->asyncReadTexture(/*recorder=*/nullptr, | |
| {&srcView, rect, pm.info(), asyncCallback, &asyncContext}, | |
| *xferFn); | |
| } else { | |
| return false; | |
| } | |
| 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; | |
| } | |
| #endif // GPU_TEST_UTILS | |
| /////////////////////////////////////////////////////////////////////////////////// | |
| std::unique_ptr<Context> ContextCtorAccessor::MakeContext( | |
| sk_sp<SharedContext> sharedContext, | |
| std::unique_ptr<QueueManager> queueManager, | |
| const ContextOptions& options) { | |
| auto context = std::unique_ptr<Context>(new Context(std::move(sharedContext), | |
| std::move(queueManager), | |
| options)); | |
| if (context && context->finishInitialization()) { | |
| return context; | |
| } else { | |
| return nullptr; | |
| } | |
| } | |
| } // namespace skgpu::graphite |