/* * Copyright 2016 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "src/gpu/ganesh/GrSurfaceProxy.h" #include "include/core/SkAlphaType.h" #include "include/core/SkColorSpace.h" #include "include/core/SkPoint.h" #include "include/gpu/GpuTypes.h" #include "include/gpu/ganesh/GrRecordingContext.h" #include "src/gpu/SkBackingFit.h" #include "src/gpu/Swizzle.h" #include "src/gpu/ganesh/GrCaps.h" #include "src/gpu/ganesh/GrGpuResourcePriv.h" #include "src/gpu/ganesh/GrImageInfo.h" #include "src/gpu/ganesh/GrRecordingContextPriv.h" #include "src/gpu/ganesh/GrRenderTargetProxy.h" #include "src/gpu/ganesh/GrRenderTask.h" #include "src/gpu/ganesh/GrResourceProvider.h" #include "src/gpu/ganesh/GrSurface.h" #include "src/gpu/ganesh/GrSurfaceProxyPriv.h" #include "src/gpu/ganesh/GrSurfaceProxyView.h" #include "src/gpu/ganesh/GrTexture.h" #include "src/gpu/ganesh/GrTextureProxy.h" #include "src/gpu/ganesh/SurfaceContext.h" #include "src/gpu/ganesh/SurfaceFillContext.h" #include #ifdef SK_DEBUG #include "include/gpu/ganesh/GrDirectContext.h" #include "src/gpu/ganesh/GrDirectContextPriv.h" static bool is_valid_lazy(const SkISize& dimensions, SkBackingFit fit) { // A "fully" lazy proxy's width and height are not known until instantiation time. // So fully lazy proxies are created with width and height < 0. Regular lazy proxies must be // created with positive widths and heights. The width and height are set to 0 only after a // failed instantiation. The former must be "approximate" fit while the latter can be either. return ((dimensions.fWidth < 0 && dimensions.fHeight < 0 && SkBackingFit::kApprox == fit) || (dimensions.fWidth > 0 && dimensions.fHeight > 0)); } static bool is_valid_non_lazy(SkISize dimensions) { return dimensions.fWidth > 0 && dimensions.fHeight > 0; } #endif GrSurfaceProxy::LazyCallbackResult::LazyCallbackResult(sk_sp surf, bool releaseCallback, LazyInstantiationKeyMode mode) : fSurface(std::move(surf)), fKeyMode(mode), fReleaseCallback(releaseCallback) {} GrSurfaceProxy::LazyCallbackResult::LazyCallbackResult(sk_sp tex) : LazyCallbackResult(sk_sp(std::move(tex))) {} // Deferred version GrSurfaceProxy::GrSurfaceProxy(const GrBackendFormat& format, SkISize dimensions, SkBackingFit fit, skgpu::Budgeted budgeted, GrProtected isProtected, GrInternalSurfaceFlags surfaceFlags, UseAllocator useAllocator, std::string_view label) : fSurfaceFlags(surfaceFlags) , fFormat(format) , fDimensions(dimensions) , fFit(fit) , fBudgeted(budgeted) , fUseAllocator(useAllocator) , fIsProtected(isProtected) , fLabel(label) { SkASSERT(fFormat.isValid()); SkASSERT(is_valid_non_lazy(dimensions)); } // Lazy-callback version GrSurfaceProxy::GrSurfaceProxy(LazyInstantiateCallback&& callback, const GrBackendFormat& format, SkISize dimensions, SkBackingFit fit, skgpu::Budgeted budgeted, GrProtected isProtected, GrInternalSurfaceFlags surfaceFlags, UseAllocator useAllocator, std::string_view label) : fSurfaceFlags(surfaceFlags) , fFormat(format) , fDimensions(dimensions) , fFit(fit) , fBudgeted(budgeted) , fUseAllocator(useAllocator) , fLazyInstantiateCallback(std::move(callback)) , fIsProtected(isProtected) , fLabel(label) { SkASSERT(fFormat.isValid()); SkASSERT(fLazyInstantiateCallback); SkASSERT(is_valid_lazy(dimensions, fit)); } // Wrapped version GrSurfaceProxy::GrSurfaceProxy(sk_sp surface, SkBackingFit fit, UseAllocator useAllocator) : fTarget(std::move(surface)) , fSurfaceFlags(fTarget->flags()) , fFormat(fTarget->backendFormat()) , fDimensions(fTarget->dimensions()) , fFit(fit) , fBudgeted(fTarget->resourcePriv().budgetedType() == GrBudgetedType::kBudgeted ? skgpu::Budgeted::kYes : skgpu::Budgeted::kNo) , fUseAllocator(useAllocator) , fUniqueID(fTarget->uniqueID()) // Note: converting from unique resource ID to a proxy ID! , fIsProtected(fTarget->isProtected() ? GrProtected::kYes : GrProtected::kNo) , fLabel(fTarget->getLabel()) { SkASSERT(fFormat.isValid()); } GrSurfaceProxy::~GrSurfaceProxy() { } sk_sp GrSurfaceProxy::createSurfaceImpl(GrResourceProvider* resourceProvider, int sampleCnt, GrRenderable renderable, skgpu::Mipmapped mipmapped) const { SkASSERT(mipmapped == skgpu::Mipmapped::kNo || fFit == SkBackingFit::kExact); SkASSERT(!this->isLazy()); SkASSERT(!fTarget); sk_sp surface; if (SkBackingFit::kApprox == fFit) { surface = resourceProvider->createApproxTexture(fDimensions, fFormat, fFormat.textureType(), renderable, sampleCnt, fIsProtected, fLabel); } else { surface = resourceProvider->createTexture(fDimensions, fFormat, fFormat.textureType(), renderable, sampleCnt, mipmapped, fBudgeted, fIsProtected, fLabel); } if (!surface) { return nullptr; } return surface; } bool GrSurfaceProxy::canSkipResourceAllocator() const { if (fUseAllocator == UseAllocator::kNo) { // Usually an atlas or onFlush proxy return true; } auto peek = this->peekSurface(); if (!peek) { return false; } // If this resource is already allocated and not recyclable then the resource allocator does // not need to do anything with it. return !peek->resourcePriv().getScratchKey().isValid(); } void GrSurfaceProxy::assign(sk_sp surface) { SkASSERT(!fTarget && surface); SkDEBUGCODE(this->validateSurface(surface.get());) fTarget = std::move(surface); #ifdef SK_DEBUG if (this->asRenderTargetProxy()) { SkASSERT(fTarget->asRenderTarget()); } // In order to give DDL users some flexibility in the destination of there DDLs, // a DDL's target proxy can be more conservative (and thus require less memory) // than the actual GrSurface used to fulfill it. if (!this->isDDLTarget() && kInvalidGpuMemorySize != this->getRawGpuMemorySize_debugOnly()) { // TODO(11373): Can this check be exact? SkASSERT(fTarget->gpuMemorySize() <= this->getRawGpuMemorySize_debugOnly()); } #endif } bool GrSurfaceProxy::instantiateImpl(GrResourceProvider* resourceProvider, int sampleCnt, GrRenderable renderable, skgpu::Mipmapped mipmapped, const skgpu::UniqueKey* uniqueKey) { SkASSERT(!this->isLazy()); if (fTarget) { if (uniqueKey && uniqueKey->isValid()) { SkASSERT(fTarget->getUniqueKey().isValid() && fTarget->getUniqueKey() == *uniqueKey); } return true; } sk_sp surface = this->createSurfaceImpl(resourceProvider, sampleCnt, renderable, mipmapped); if (!surface) { return false; } // If there was an invalidation message pending for this key, we might have just processed it, // causing the key (stored on this proxy) to become invalid. if (uniqueKey && uniqueKey->isValid()) { resourceProvider->assignUniqueKeyToResource(*uniqueKey, surface.get()); } this->assign(std::move(surface)); return true; } void GrSurfaceProxy::deinstantiate() { SkASSERT(this->isInstantiated()); fTarget = nullptr; } void GrSurfaceProxy::computeScratchKey(const GrCaps& caps, skgpu::ScratchKey* key) const { SkASSERT(!this->isFullyLazy()); GrRenderable renderable = GrRenderable::kNo; int sampleCount = 1; if (const auto* rtp = this->asRenderTargetProxy()) { renderable = GrRenderable::kYes; sampleCount = rtp->numSamples(); } const GrTextureProxy* tp = this->asTextureProxy(); skgpu::Mipmapped mipmapped = skgpu::Mipmapped::kNo; if (tp) { mipmapped = tp->mipmapped(); } GrTexture::ComputeScratchKey(caps, this->backendFormat(), this->backingStoreDimensions(), renderable, sampleCount, mipmapped, fIsProtected, key); } SkISize GrSurfaceProxy::backingStoreDimensions() const { SkASSERT(!this->isFullyLazy()); if (fTarget) { return fTarget->dimensions(); } if (SkBackingFit::kExact == fFit) { return fDimensions; } return skgpu::GetApproxSize(fDimensions); } bool GrSurfaceProxy::isFunctionallyExact() const { SkASSERT(!this->isFullyLazy()); return fFit == SkBackingFit::kExact || fDimensions == skgpu::GetApproxSize(fDimensions); } bool GrSurfaceProxy::isFormatCompressed(const GrCaps* caps) const { return caps->isFormatCompressed(this->backendFormat()); } #ifdef SK_DEBUG void GrSurfaceProxy::validate(GrContext_Base* context) const { if (fTarget) { SkASSERT(fTarget->getContext()->priv().matches(context)); } } #endif sk_sp GrSurfaceProxy::Copy(GrRecordingContext* rContext, sk_sp src, GrSurfaceOrigin origin, skgpu::Mipmapped mipmapped, SkIRect srcRect, SkBackingFit fit, skgpu::Budgeted budgeted, std::string_view label, RectsMustMatch rectsMustMatch, sk_sp* outTask) { SkASSERT(!src->isFullyLazy()); int width; int height; SkIPoint dstPoint; if (rectsMustMatch == RectsMustMatch::kYes) { width = src->width(); height = src->height(); dstPoint = {srcRect.fLeft, srcRect.fTop}; } else { width = srcRect.width(); height = srcRect.height(); dstPoint = {0, 0}; } if (!srcRect.intersect(SkIRect::MakeSize(src->dimensions()))) { return {}; } auto format = src->backendFormat().makeTexture2D(); SkASSERT(format.isValid()); if (src->backendFormat().textureType() != GrTextureType::kExternal) { GrImageInfo info(GrColorType::kUnknown, kUnknown_SkAlphaType, nullptr, {width, height}); auto dstContext = rContext->priv().makeSC(info, format, label, fit, origin, GrRenderable::kNo, 1, mipmapped, src->isProtected(), budgeted); sk_sp copyTask; if (dstContext && (copyTask = dstContext->copy(src, srcRect, dstPoint))) { if (outTask) { *outTask = std::move(copyTask); } return dstContext->asSurfaceProxyRef(); } } if (src->asTextureProxy()) { auto dstContext = rContext->priv().makeSFC(kUnknown_SkAlphaType, nullptr, {width, height}, fit, format, 1, mipmapped, src->isProtected(), skgpu::Swizzle::RGBA(), skgpu::Swizzle::RGBA(), origin, budgeted, label); GrSurfaceProxyView view(std::move(src), origin, skgpu::Swizzle::RGBA()); if (dstContext && dstContext->blitTexture(std::move(view), srcRect, dstPoint)) { if (outTask) { *outTask = dstContext->refRenderTask(); } return dstContext->asSurfaceProxyRef(); } } // Can't use backend copies or draws. return nullptr; } sk_sp GrSurfaceProxy::Copy(GrRecordingContext* context, sk_sp src, GrSurfaceOrigin origin, skgpu::Mipmapped mipmapped, SkBackingFit fit, skgpu::Budgeted budgeted, std::string_view label, sk_sp* outTask) { SkASSERT(!src->isFullyLazy()); auto rect = SkIRect::MakeSize(src->dimensions()); return Copy(context, std::move(src), origin, mipmapped, rect, fit, budgeted, label, RectsMustMatch::kNo, outTask); } #if defined(GPU_TEST_UTILS) int32_t GrSurfaceProxy::testingOnly_getBackingRefCnt() const { if (fTarget) { return fTarget->testingOnly_getRefCnt(); } return -1; // no backing GrSurface } GrInternalSurfaceFlags GrSurfaceProxy::testingOnly_getFlags() const { return fSurfaceFlags; } SkString GrSurfaceProxy::dump() const { SkString tmp; tmp.appendf("proxyID: %u - surfaceID: %u", this->uniqueID().asUInt(), this->peekSurface() ? this->peekSurface()->uniqueID().asUInt() : -1); return tmp; } #endif void GrSurfaceProxyPriv::exactify() { SkASSERT(!fProxy->isFullyLazy()); if (this->isExact()) { return; } // The kApprox case. Setting the proxy's width & height to the backing-store width & height // could have side-effects going forward, since we're obliterating the area of interest // information. This is only used by SkSpecialImage when it's determined that sampling will // not access beyond the safe known region (the current value of fProxy->fDimensions). If // the proxy is instantiated, update the proxy's dimensions to match. Otherwise update them // to the backing-store dimensions. SkASSERT(SkBackingFit::kApprox == fProxy->fFit); fProxy->fDimensions = fProxy->fTarget ? fProxy->fTarget->dimensions() : fProxy->backingStoreDimensions(); fProxy->fFit = SkBackingFit::kExact; } bool GrSurfaceProxyPriv::doLazyInstantiation(GrResourceProvider* resourceProvider) { SkASSERT(fProxy->isLazy()); sk_sp surface; if (const auto& uniqueKey = fProxy->getUniqueKey(); uniqueKey.isValid()) { // First try to reattach to a cached version if the proxy is uniquely keyed surface = resourceProvider->findByUniqueKey(uniqueKey); } bool syncKey = true; bool releaseCallback = false; if (!surface) { auto result = fProxy->fLazyInstantiateCallback(resourceProvider, fProxy->callbackDesc()); surface = std::move(result.fSurface); syncKey = result.fKeyMode == GrSurfaceProxy::LazyInstantiationKeyMode::kSynced; releaseCallback = surface && result.fReleaseCallback; } if (!surface) { fProxy->fDimensions.setEmpty(); return false; } if (fProxy->isFullyLazy()) { // This was a fully lazy proxy. We need to fill in the width & height. For partially // lazy proxies we must preserve the original width & height since that indicates // the content area. fProxy->fDimensions = surface->dimensions(); } SkASSERT(fProxy->width() <= surface->width()); SkASSERT(fProxy->height() <= surface->height()); if (GrTextureProxy* texProxy = fProxy->asTextureProxy()) { texProxy->setTargetKeySync(syncKey); if (syncKey) { const skgpu::UniqueKey& key = texProxy->getUniqueKey(); if (key.isValid()) { if (!surface->asTexture()->getUniqueKey().isValid()) { // If 'surface' is newly created, attach the unique key resourceProvider->assignUniqueKeyToResource(key, surface.get()); } else { // otherwise we had better have reattached to a cached version SkASSERT(surface->asTexture()->getUniqueKey() == key); } } else { SkASSERT(!surface->getUniqueKey().isValid()); } } } this->assign(std::move(surface)); if (releaseCallback) { fProxy->fLazyInstantiateCallback = nullptr; } return true; } #ifdef SK_DEBUG void GrSurfaceProxy::validateSurface(const GrSurface* surface) { SkASSERTF(surface->backendFormat() == fFormat, "%s != %s", surface->backendFormat().toStr().c_str(), fFormat.toStr().c_str()); this->onValidateSurface(surface); } #endif