/* * Copyright 2011 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "GrBackendSemaphore.h" #include "GrContext.h" #include "GrClip.h" #include "GrContextOptions.h" #include "GrContextPriv.h" #include "GrDrawingManager.h" #include "GrGpu.h" #include "GrProxyProvider.h" #include "GrRenderTargetContext.h" #include "GrRenderTargetProxy.h" #include "GrResourceCache.h" #include "GrResourceProvider.h" #include "GrSemaphore.h" #include "GrSoftwarePathRenderer.h" #include "GrSurfaceContext.h" #include "GrSurfacePriv.h" #include "GrSurfaceProxyPriv.h" #include "GrTexture.h" #include "GrTextureContext.h" #include "GrTracing.h" #include "SkConvertPixels.h" #include "SkDeferredDisplayList.h" #include "SkGr.h" #include "SkImageInfoPriv.h" #include "SkJSONWriter.h" #include "SkMakeUnique.h" #include "SkTaskGroup.h" #include "SkUnPreMultiplyPriv.h" #include "effects/GrConfigConversionEffect.h" #include "text/GrTextBlobCache.h" #include "gl/GrGLGpu.h" #include "mock/GrMockGpu.h" #ifdef SK_METAL #include "mtl/GrMtlTrampoline.h" #endif #ifdef SK_VULKAN #include "vk/GrVkGpu.h" #endif #define ASSERT_OWNED_PROXY(P) \ SkASSERT(!(P) || !((P)->priv().peekTexture()) || (P)->priv().peekTexture()->getContext() == this) #define ASSERT_OWNED_PROXY_PRIV(P) \ SkASSERT(!(P) || !((P)->priv().peekTexture()) || (P)->priv().peekTexture()->getContext() == fContext) #define ASSERT_OWNED_RESOURCE(R) SkASSERT(!(R) || (R)->getContext() == this) #define ASSERT_SINGLE_OWNER \ SkDEBUGCODE(GrSingleOwner::AutoEnforce debug_SingleOwner(&fSingleOwner);) #define ASSERT_SINGLE_OWNER_PRIV \ SkDEBUGCODE(GrSingleOwner::AutoEnforce debug_SingleOwner(&fContext->fSingleOwner);) #define RETURN_IF_ABANDONED if (fDrawingManager->wasAbandoned()) { return; } #define RETURN_IF_ABANDONED_PRIV if (fContext->fDrawingManager->wasAbandoned()) { return; } #define RETURN_FALSE_IF_ABANDONED if (fDrawingManager->wasAbandoned()) { return false; } #define RETURN_FALSE_IF_ABANDONED_PRIV if (fContext->fDrawingManager->wasAbandoned()) { return false; } #define RETURN_NULL_IF_ABANDONED if (fDrawingManager->wasAbandoned()) { return nullptr; } //////////////////////////////////////////////////////////////////////////////// class SK_API GrDirectContext : public GrContext { public: GrDirectContext(GrBackend backend) : INHERITED(backend) { } protected: private: typedef GrContext INHERITED; }; class SK_API GrDDLContext : public GrContext { public: GrDDLContext(GrContextThreadSafeProxy* proxy) : INHERITED(proxy) {} protected: private: typedef GrContext INHERITED; }; GrContext* GrContext::Create(GrBackend backend, GrBackendContext backendContext) { GrContextOptions defaultOptions; return Create(backend, backendContext, defaultOptions); } GrContext* GrContext::Create(GrBackend backend, GrBackendContext backendContext, const GrContextOptions& options) { sk_sp context(new GrDirectContext(backend)); context->fGpu = GrGpu::Make(backend, backendContext, options, context.get()); if (!context->fGpu) { return nullptr; } if (!context->init(options)) { return nullptr; } return context.release(); } sk_sp GrContext::MakeGL(sk_sp interface) { GrContextOptions defaultOptions; return MakeGL(std::move(interface), defaultOptions); } sk_sp GrContext::MakeGL(sk_sp interface, const GrContextOptions& options) { sk_sp context(new GrDirectContext(kOpenGL_GrBackend)); context->fGpu = GrGLGpu::Make(std::move(interface), options, context.get()); if (!context->fGpu) { return nullptr; } if (!context->init(options)) { return nullptr; } return context; } sk_sp GrContext::MakeGL(const GrGLInterface* interface) { return MakeGL(sk_ref_sp(interface)); } sk_sp GrContext::MakeGL(const GrGLInterface* interface, const GrContextOptions& options) { return MakeGL(sk_ref_sp(interface), options); } sk_sp GrContext::MakeMock(const GrMockOptions* mockOptions) { GrContextOptions defaultOptions; return MakeMock(mockOptions, defaultOptions); } sk_sp GrContext::MakeMock(const GrMockOptions* mockOptions, const GrContextOptions& options) { sk_sp context(new GrDirectContext(kMock_GrBackend)); context->fGpu = GrMockGpu::Make(mockOptions, options, context.get()); if (!context->fGpu) { return nullptr; } if (!context->init(options)) { return nullptr; } return context; } #ifdef SK_VULKAN sk_sp GrContext::MakeVulkan(sk_sp backendContext) { GrContextOptions defaultOptions; return MakeVulkan(std::move(backendContext), defaultOptions); } sk_sp GrContext::MakeVulkan(sk_sp backendContext, const GrContextOptions& options) { sk_sp context(new GrDirectContext(kVulkan_GrBackend)); context->fGpu = GrVkGpu::Make(std::move(backendContext), options, context.get()); if (!context->fGpu) { return nullptr; } if (!context->init(options)) { return nullptr; } return context; } #endif #ifdef SK_METAL sk_sp GrContext::MakeMetal(void* device, void* queue) { GrContextOptions defaultOptions; return MakeMetal(device, queue, defaultOptions); } sk_sp GrContext::MakeMetal(void* device, void* queue, const GrContextOptions& options) { sk_sp context(new GrContext(kMetal_GrBackend)); context->fGpu = GrMtlTrampoline::MakeGpu(context.get(), options, device, queue); if (!context->fGpu) { return nullptr; } if (!context->init(options)) { return nullptr; } return context; } #endif static int32_t gNextID = 1; static int32_t next_id() { int32_t id; do { id = sk_atomic_inc(&gNextID); } while (id == SK_InvalidGenID); return id; } sk_sp GrContextPriv::MakeDDL(GrContextThreadSafeProxy* proxy) { sk_sp context(new GrDDLContext(proxy)); // Note: we aren't creating a Gpu here. This causes the resource provider & cache to // also not be created if (!context->init(proxy->fOptions)) { return nullptr; } return context; } GrContext::GrContext(GrBackend backend) : fUniqueID(next_id()) , fBackend(backend) { fResourceCache = nullptr; fResourceProvider = nullptr; fProxyProvider = nullptr; fAtlasGlyphCache = nullptr; } GrContext::GrContext(GrContextThreadSafeProxy* proxy) : fCaps(proxy->fCaps) , fUniqueID(proxy->fContextUniqueID) , fBackend(proxy->fBackend) { fResourceCache = nullptr; fResourceProvider = nullptr; fProxyProvider = nullptr; fAtlasGlyphCache = nullptr; } bool GrContext::init(const GrContextOptions& options) { ASSERT_SINGLE_OWNER if (fGpu) { fCaps = fGpu->refCaps(); fResourceCache = new GrResourceCache(fCaps.get(), fUniqueID); fResourceProvider = new GrResourceProvider(fGpu.get(), fResourceCache, &fSingleOwner, options.fExplicitlyAllocateGPUResources); } fProxyProvider = new GrProxyProvider(fResourceProvider, fResourceCache, fCaps, &fSingleOwner); if (fResourceCache) { fResourceCache->setProxyProvider(fProxyProvider); } // DDL TODO: we need to think through how the task group & persistent cache // get passed on to/shared between all the DDLRecorders created with this context. fThreadSafeProxy.reset(new GrContextThreadSafeProxy(fCaps, this->uniqueID(), fBackend, options)); fDisableGpuYUVConversion = options.fDisableGpuYUVConversion; fSharpenMipmappedTextures = options.fSharpenMipmappedTextures; fDidTestPMConversions = false; GrPathRendererChain::Options prcOptions; prcOptions.fAllowPathMaskCaching = options.fAllowPathMaskCaching; #if GR_TEST_UTILS prcOptions.fGpuPathRenderers = options.fGpuPathRenderers; #endif if (options.fDisableDistanceFieldPaths) { prcOptions.fGpuPathRenderers &= ~GpuPathRenderers::kSmall; } if (!fResourceCache) { // DDL TODO: remove this crippling of the path renderer chain // Disable the small path renderer bc of the proxies in the atlas. They need to be // unified when the opLists are added back to the destination drawing manager. prcOptions.fGpuPathRenderers &= ~GpuPathRenderers::kSmall; } GrAtlasTextContext::Options atlasTextContextOptions; atlasTextContextOptions.fMaxDistanceFieldFontSize = options.fGlyphsAsPathsFontSize; atlasTextContextOptions.fMinDistanceFieldFontSize = options.fMinDistanceFieldFontSize; atlasTextContextOptions.fDistanceFieldVerticesAlwaysHaveW = false; #if SK_SUPPORT_ATLAS_TEXT if (GrContextOptions::Enable::kYes == options.fDistanceFieldGlyphVerticesAlwaysHaveW) { atlasTextContextOptions.fDistanceFieldVerticesAlwaysHaveW = true; } #endif fDrawingManager.reset(new GrDrawingManager(this, prcOptions, atlasTextContextOptions, &fSingleOwner, options.fSortRenderTargets)); GrDrawOpAtlas::AllowMultitexturing allowMultitexturing; if (GrContextOptions::Enable::kNo == options.fAllowMultipleGlyphCacheTextures || // multitexturing supported only if range can represent the index + texcoords fully !(fCaps->shaderCaps()->floatIs32Bits() || fCaps->shaderCaps()->integerSupport())) { allowMultitexturing = GrDrawOpAtlas::AllowMultitexturing::kNo; } else { allowMultitexturing = GrDrawOpAtlas::AllowMultitexturing::kYes; } fAtlasGlyphCache = new GrAtlasGlyphCache(fProxyProvider, options.fGlyphCacheTextureMaximumBytes, allowMultitexturing); this->contextPriv().addOnFlushCallbackObject(fAtlasGlyphCache); fTextBlobCache.reset(new GrTextBlobCache(TextBlobCacheOverBudgetCB, this, this->uniqueID(), SkToBool(fGpu))); if (options.fExecutor) { fTaskGroup = skstd::make_unique(*options.fExecutor); } fPersistentCache = options.fPersistentCache; return true; } GrContext::~GrContext() { ASSERT_SINGLE_OWNER if (fGpu) { this->flush(); } if (fDrawingManager) { fDrawingManager->cleanup(); } for (int i = 0; i < fCleanUpData.count(); ++i) { (*fCleanUpData[i].fFunc)(this, fCleanUpData[i].fInfo); } delete fResourceProvider; delete fResourceCache; delete fProxyProvider; delete fAtlasGlyphCache; } sk_sp GrContext::threadSafeProxy() { return fThreadSafeProxy; } SkSurfaceCharacterization GrContextThreadSafeProxy::createCharacterization( size_t cacheMaxResourceBytes, const SkImageInfo& ii, const GrBackendFormat& backendFormat, int sampleCnt, GrSurfaceOrigin origin, const SkSurfaceProps& surfaceProps, bool isMipMapped) { if (!backendFormat.isValid()) { return SkSurfaceCharacterization(); // return an invalid characterization } // We're assuming GrFSAAType::kMixedSamples will never be specified via this code path GrFSAAType FSAAType = sampleCnt > 1 ? GrFSAAType::kUnifiedMSAA : GrFSAAType::kNone; if (!fCaps->mipMapSupport()) { isMipMapped = false; } GrPixelConfig config = kUnknown_GrPixelConfig; if (!fCaps->getConfigFromBackendFormat(backendFormat, ii.colorType(), &config)) { return SkSurfaceCharacterization(); // return an invalid characterization } // This surface characterization factory assumes that the resulting characterization is // textureable. if (!fCaps->isConfigTexturable(config)) { return SkSurfaceCharacterization(); // return an invalid characterization } return SkSurfaceCharacterization(sk_ref_sp(this), cacheMaxResourceBytes, origin, ii.width(), ii.height(), config, FSAAType, sampleCnt, SkSurfaceCharacterization::Textureable(true), SkSurfaceCharacterization::MipMapped(isMipMapped), ii.refColorSpace(), surfaceProps); } void GrContext::abandonContext() { ASSERT_SINGLE_OWNER fProxyProvider->abandon(); fResourceProvider->abandon(); // Need to abandon the drawing manager first so all the render targets // will be released/forgotten before they too are abandoned. fDrawingManager->abandon(); // abandon first to so destructors // don't try to free the resources in the API. fResourceCache->abandonAll(); fGpu->disconnect(GrGpu::DisconnectType::kAbandon); fAtlasGlyphCache->freeAll(); fTextBlobCache->freeAll(); } void GrContext::releaseResourcesAndAbandonContext() { ASSERT_SINGLE_OWNER fProxyProvider->abandon(); fResourceProvider->abandon(); // Need to abandon the drawing manager first so all the render targets // will be released/forgotten before they too are abandoned. fDrawingManager->abandon(); // Release all resources in the backend 3D API. fResourceCache->releaseAll(); fGpu->disconnect(GrGpu::DisconnectType::kCleanup); fAtlasGlyphCache->freeAll(); fTextBlobCache->freeAll(); } void GrContext::resetContext(uint32_t state) { ASSERT_SINGLE_OWNER fGpu->markContextDirty(state); } void GrContext::freeGpuResources() { ASSERT_SINGLE_OWNER this->flush(); fAtlasGlyphCache->freeAll(); fDrawingManager->freeGpuResources(); fResourceCache->purgeAllUnlocked(); } void GrContext::performDeferredCleanup(std::chrono::milliseconds msNotUsed) { ASSERT_SINGLE_OWNER fResourceCache->purgeAsNeeded(); fResourceCache->purgeResourcesNotUsedSince(GrStdSteadyClock::now() - msNotUsed); fTextBlobCache->purgeStaleBlobs(); } void GrContext::purgeUnlockedResources(size_t bytesToPurge, bool preferScratchResources) { ASSERT_SINGLE_OWNER fResourceCache->purgeUnlockedResources(bytesToPurge, preferScratchResources); } void GrContext::getResourceCacheUsage(int* resourceCount, size_t* resourceBytes) const { ASSERT_SINGLE_OWNER if (resourceCount) { *resourceCount = fResourceCache->getBudgetedResourceCount(); } if (resourceBytes) { *resourceBytes = fResourceCache->getBudgetedResourceBytes(); } } size_t GrContext::getResourceCachePurgeableBytes() const { ASSERT_SINGLE_OWNER return fResourceCache->getPurgeableBytes(); } //////////////////////////////////////////////////////////////////////////////// bool GrContext::colorTypeSupportedAsImage(SkColorType colorType) const { GrPixelConfig config = SkImageInfo2GrPixelConfig(colorType, nullptr, *this->caps()); return this->caps()->isConfigTexturable(config); } int GrContext::maxSurfaceSampleCountForColorType(SkColorType colorType) const { GrPixelConfig config = SkImageInfo2GrPixelConfig(colorType, nullptr, *this->caps()); return this->caps()->maxRenderTargetSampleCount(config); } //////////////////////////////////////////////////////////////////////////////// void GrContext::TextBlobCacheOverBudgetCB(void* data) { SkASSERT(data); // TextBlobs are drawn at the SkGpuDevice level, therefore they cannot rely on // GrRenderTargetContext to perform a necessary flush. The solution is to move drawText calls // to below the GrContext level, but this is not trivial because they call drawPath on // SkGpuDevice. GrContext* context = reinterpret_cast(data); context->flush(); } //////////////////////////////////////////////////////////////////////////////// void GrContext::flush() { ASSERT_SINGLE_OWNER RETURN_IF_ABANDONED fDrawingManager->flush(nullptr); } GrSemaphoresSubmitted GrContext::flushAndSignalSemaphores(int numSemaphores, GrBackendSemaphore signalSemaphores[]) { ASSERT_SINGLE_OWNER if (fDrawingManager->wasAbandoned()) { return GrSemaphoresSubmitted::kNo; } return fDrawingManager->flush(nullptr, numSemaphores, signalSemaphores); } void GrContextPriv::flush(GrSurfaceProxy* proxy) { ASSERT_SINGLE_OWNER_PRIV RETURN_IF_ABANDONED_PRIV ASSERT_OWNED_PROXY_PRIV(proxy); fContext->fDrawingManager->flush(proxy); } bool sw_convert_to_premul(GrColorType srcColorType, int width, int height, size_t inRowBytes, const void* inPixels, size_t outRowBytes, void* outPixels) { SkColorType colorType = GrColorTypeToSkColorType(srcColorType); if (kUnknown_SkColorType == colorType || 4 != SkColorTypeBytesPerPixel(colorType)) { return false; } for (int y = 0; y < height; y++) { SkOpts::RGBA_to_rgbA((uint32_t*) outPixels, inPixels, width); outPixels = SkTAddOffset(outPixels, outRowBytes); inPixels = SkTAddOffset(inPixels, inRowBytes); } return true; } // TODO: This will be removed when GrSurfaceContexts are aware of their color types. // (skbug.com/6718) static bool valid_premul_config(GrPixelConfig config) { switch (config) { case kUnknown_GrPixelConfig: return false; case kAlpha_8_GrPixelConfig: return false; case kGray_8_GrPixelConfig: return false; case kRGB_565_GrPixelConfig: return false; case kRGBA_4444_GrPixelConfig: return true; case kRGBA_8888_GrPixelConfig: return true; case kBGRA_8888_GrPixelConfig: return true; case kSRGBA_8888_GrPixelConfig: return true; case kSBGRA_8888_GrPixelConfig: return true; case kRGBA_float_GrPixelConfig: return true; case kRG_float_GrPixelConfig: return false; case kAlpha_half_GrPixelConfig: return false; case kRGBA_half_GrPixelConfig: return true; case kAlpha_8_as_Alpha_GrPixelConfig: return false; case kAlpha_8_as_Red_GrPixelConfig: return false; case kAlpha_half_as_Red_GrPixelConfig: return false; case kGray_8_as_Lum_GrPixelConfig: return false; case kGray_8_as_Red_GrPixelConfig: return false; } SK_ABORT("Invalid GrPixelConfig"); return false; } static bool valid_premul_color_type(GrColorType ct) { switch (ct) { case GrColorType::kUnknown: return false; case GrColorType::kAlpha_8: return false; case GrColorType::kRGB_565: return false; case GrColorType::kABGR_4444: return true; case GrColorType::kRGBA_8888: return true; case GrColorType::kBGRA_8888: return true; case GrColorType::kGray_8: return false; case GrColorType::kAlpha_F16: return false; case GrColorType::kRGBA_F16: return true; case GrColorType::kRG_F32: return false; case GrColorType::kRGBA_F32: return true; } SK_ABORT("Invalid GrColorType"); return false; } static bool valid_pixel_conversion(GrColorType cpuColorType, GrPixelConfig gpuConfig, bool premulConversion) { // We only allow premul <-> unpremul conversions for some formats if (premulConversion && (!valid_premul_color_type(cpuColorType) || !valid_premul_config(gpuConfig))) { return false; } return true; } static bool pm_upm_must_round_trip(GrColorType cpuColorType, const SkColorSpace* cpuColorSpace) { return !cpuColorSpace && (GrColorType::kRGBA_8888 == cpuColorType || GrColorType::kBGRA_8888 == cpuColorType); } // TODO: This will be removed when GrSurfaceContexts are aware of their color types. // (skbug.com/6718) static bool pm_upm_must_round_trip(GrPixelConfig surfaceConfig, const SkColorSpace* surfaceColorSpace) { return !surfaceColorSpace && (kRGBA_8888_GrPixelConfig == surfaceConfig || kBGRA_8888_GrPixelConfig == surfaceConfig); } static GrSRGBConversion determine_write_pixels_srgb_conversion(GrColorType srcColorType, const SkColorSpace* srcColorSpace, GrSRGBEncoded dstSRGBEncoded, const SkColorSpace* dstColorSpace, const GrCaps& caps) { // No support for sRGB-encoded alpha. if (GrColorTypeIsAlphaOnly(srcColorType)) { return GrSRGBConversion::kNone; } // No conversions without GPU support for sRGB. (Legacy mode) if (!caps.srgbSupport()) { return GrSRGBConversion::kNone; } // If the GrSurfaceContext has no color space then it is in legacy mode. if (!dstColorSpace) { return GrSRGBConversion::kNone; } bool srcColorSpaceIsSRGB = srcColorSpace && srcColorSpace->gammaCloseToSRGB(); bool dstColorSpaceIsSRGB = dstColorSpace->gammaCloseToSRGB(); // For now we are assuming that if color space of the dst does not have sRGB gamma then the // texture format is not sRGB encoded and vice versa. Note that we already checked for "legacy" // mode being forced on by caps above. This may change in the future. We will then have to // perform shader based conversions. SkASSERT(dstColorSpaceIsSRGB == (GrSRGBEncoded::kYes == dstSRGBEncoded)); if (srcColorSpaceIsSRGB == dstColorSpaceIsSRGB) { return GrSRGBConversion::kNone; } return srcColorSpaceIsSRGB ? GrSRGBConversion::kSRGBToLinear : GrSRGBConversion::kLinearToSRGB; } static GrSRGBConversion determine_read_pixels_srgb_conversion(GrSRGBEncoded srcSRGBEncoded, const SkColorSpace* srcColorSpace, GrColorType dstColorType, const SkColorSpace* dstColorSpace, const GrCaps& caps) { // This is symmetrical with the write version. switch (determine_write_pixels_srgb_conversion(dstColorType, dstColorSpace, srcSRGBEncoded, srcColorSpace, caps)) { case GrSRGBConversion::kNone: return GrSRGBConversion::kNone; case GrSRGBConversion::kLinearToSRGB: return GrSRGBConversion::kSRGBToLinear; case GrSRGBConversion::kSRGBToLinear: return GrSRGBConversion::kLinearToSRGB; } return GrSRGBConversion::kNone; } bool GrContextPriv::writeSurfacePixels(GrSurfaceContext* dst, int left, int top, int width, int height, GrColorType srcColorType, SkColorSpace* srcColorSpace, const void* buffer, size_t rowBytes, uint32_t pixelOpsFlags) { // TODO: Color space conversion ASSERT_SINGLE_OWNER_PRIV RETURN_FALSE_IF_ABANDONED_PRIV SkASSERT(dst); ASSERT_OWNED_PROXY_PRIV(dst->asSurfaceProxy()); GR_CREATE_TRACE_MARKER_CONTEXT("GrContextPriv", "writeSurfacePixels", fContext); if (!dst->asSurfaceProxy()->instantiate(this->resourceProvider())) { return false; } GrSurfaceProxy* dstProxy = dst->asSurfaceProxy(); GrSurface* dstSurface = dstProxy->priv().peekSurface(); // The src is unpremul but the dst is premul -> premul the src before or as part of the write const bool premul = SkToBool(kUnpremul_PixelOpsFlag & pixelOpsFlags); if (!valid_pixel_conversion(srcColorType, dstProxy->config(), premul)) { return false; } // We need to guarantee round-trip conversion if we are reading and writing 8888 non-sRGB data, // without any color spaces attached, and the caller wants us to premul. bool useConfigConversionEffect = premul && pm_upm_must_round_trip(srcColorType, srcColorSpace) && pm_upm_must_round_trip(dstProxy->config(), dst->colorSpaceInfo().colorSpace()); // Are we going to try to premul as part of a draw? For the non-legacy case, we always allow // this. GrConfigConversionEffect fails on some GPUs, so only allow this if it works perfectly. bool premulOnGpu = premul && (!useConfigConversionEffect || fContext->validPMUPMConversionExists()); // Trim the params here so that if we wind up making a temporary surface it can be as small as // necessary and because GrGpu::getWritePixelsInfo requires it. if (!GrSurfacePriv::AdjustWritePixelParams(dstSurface->width(), dstSurface->height(), GrColorTypeBytesPerPixel(srcColorType), &left, &top, &width, &height, &buffer, &rowBytes)) { return false; } GrGpu::DrawPreference drawPreference = premulOnGpu ? GrGpu::kCallerPrefersDraw_DrawPreference : GrGpu::kNoDraw_DrawPreference; GrGpu::WritePixelTempDrawInfo tempDrawInfo; GrSRGBConversion srgbConversion = determine_write_pixels_srgb_conversion( srcColorType, srcColorSpace, GrPixelConfigIsSRGBEncoded(dstProxy->config()), dst->colorSpaceInfo().colorSpace(), *fContext->caps()); if (!fContext->fGpu->getWritePixelsInfo(dstSurface, dstProxy->origin(), width, height, srcColorType, srgbConversion, &drawPreference, &tempDrawInfo)) { return false; } if (!(kDontFlush_PixelOpsFlag & pixelOpsFlags) && dstSurface->surfacePriv().hasPendingIO()) { this->flush(nullptr); // MDB TODO: tighten this } sk_sp tempProxy; if (GrGpu::kNoDraw_DrawPreference != drawPreference) { tempProxy = this->proxyProvider()->createProxy(tempDrawInfo.fTempSurfaceDesc, SkBackingFit::kApprox, SkBudgeted::kYes); if (!tempProxy && GrGpu::kRequireDraw_DrawPreference == drawPreference) { return false; } } // temp buffer for doing sw premul conversion, if needed. SkAutoSTMalloc<128 * 128, uint32_t> tmpPixels(0); // We need to do sw premul if we were unable to create a RT for drawing, or if we can't do the // premul on the GPU if (premul && (!tempProxy || !premulOnGpu)) { size_t tmpRowBytes = 4 * width; tmpPixels.reset(width * height); if (!sw_convert_to_premul(srcColorType, width, height, rowBytes, buffer, tmpRowBytes, tmpPixels.get())) { return false; } rowBytes = tmpRowBytes; buffer = tmpPixels.get(); } if (tempProxy) { auto fp = GrSimpleTextureEffect::Make(tempProxy, SkMatrix::I()); if (premulOnGpu) { fp = fContext->createUPMToPMEffect(std::move(fp), useConfigConversionEffect); } fp = GrFragmentProcessor::SwizzleOutput(std::move(fp), tempDrawInfo.fSwizzle); if (!fp) { return false; } if (!tempProxy->instantiate(this->resourceProvider())) { return false; } GrTexture* texture = tempProxy->priv().peekTexture(); if (tempProxy->priv().hasPendingIO()) { this->flush(tempProxy.get()); } if (!fContext->fGpu->writePixels(texture, tempProxy->origin(), 0, 0, width, height, tempDrawInfo.fWriteColorType, buffer, rowBytes)) { return false; } tempProxy = nullptr; SkMatrix matrix; matrix.setTranslate(SkIntToScalar(left), SkIntToScalar(top)); GrRenderTargetContext* renderTargetContext = dst->asRenderTargetContext(); if (!renderTargetContext) { return false; } GrPaint paint; paint.addColorFragmentProcessor(std::move(fp)); paint.setPorterDuffXPFactory(SkBlendMode::kSrc); paint.setAllowSRGBInputs(dst->colorSpaceInfo().isGammaCorrect() || GrPixelConfigIsSRGB(dst->colorSpaceInfo().config())); SkRect rect = SkRect::MakeWH(SkIntToScalar(width), SkIntToScalar(height)); renderTargetContext->drawRect(GrNoClip(), std::move(paint), GrAA::kNo, matrix, rect, nullptr); if (kFlushWrites_PixelOp & pixelOpsFlags) { this->flushSurfaceWrites(renderTargetContext->asRenderTargetProxy()); } } else { return fContext->fGpu->writePixels(dstSurface, dstProxy->origin(), left, top, width, height, srcColorType, buffer, rowBytes); } return true; } bool GrContextPriv::readSurfacePixels(GrSurfaceContext* src, int left, int top, int width, int height, GrColorType dstColorType, SkColorSpace* dstColorSpace, void* buffer, size_t rowBytes, uint32_t flags) { // TODO: Color space conversion ASSERT_SINGLE_OWNER_PRIV RETURN_FALSE_IF_ABANDONED_PRIV SkASSERT(src); ASSERT_OWNED_PROXY_PRIV(src->asSurfaceProxy()); GR_CREATE_TRACE_MARKER_CONTEXT("GrContextPriv", "readSurfacePixels", fContext); // MDB TODO: delay this instantiation until later in the method if (!src->asSurfaceProxy()->instantiate(this->resourceProvider())) { return false; } GrSurfaceProxy* srcProxy = src->asSurfaceProxy(); GrSurface* srcSurface = srcProxy->priv().peekSurface(); // The src is premul but the dst is unpremul -> unpremul the src after or as part of the read bool unpremul = SkToBool(kUnpremul_PixelOpsFlag & flags); if (!valid_pixel_conversion(dstColorType, srcProxy->config(), unpremul)) { return false; } // We need to guarantee round-trip conversion if we are reading and writing 8888 non-sRGB data, // without any color spaces attached, and the caller wants us to unpremul. bool useConfigConversionEffect = unpremul && pm_upm_must_round_trip(srcProxy->config(), src->colorSpaceInfo().colorSpace()) && pm_upm_must_round_trip(dstColorType, dstColorSpace); // Are we going to try to unpremul as part of a draw? For the non-legacy case, we always allow // this. GrConfigConversionEffect fails on some GPUs, so only allow this if it works perfectly. bool unpremulOnGpu = unpremul && (!useConfigConversionEffect || fContext->validPMUPMConversionExists()); // Adjust the params so that if we wind up using an intermediate surface we've already done // all the trimming and the temporary can be the min size required. if (!GrSurfacePriv::AdjustReadPixelParams(srcSurface->width(), srcSurface->height(), GrColorTypeBytesPerPixel(dstColorType), &left, &top, &width, &height, &buffer, &rowBytes)) { return false; } GrGpu::DrawPreference drawPreference = unpremulOnGpu ? GrGpu::kCallerPrefersDraw_DrawPreference : GrGpu::kNoDraw_DrawPreference; GrGpu::ReadPixelTempDrawInfo tempDrawInfo; GrSRGBConversion srgbConversion = determine_read_pixels_srgb_conversion( GrPixelConfigIsSRGBEncoded(srcProxy->config()), src->colorSpaceInfo().colorSpace(), dstColorType, dstColorSpace, *fContext->caps()); if (!fContext->fGpu->getReadPixelsInfo(srcSurface, srcProxy->origin(), width, height, rowBytes, dstColorType, srgbConversion, &drawPreference, &tempDrawInfo)) { return false; } if (!(kDontFlush_PixelOpsFlag & flags) && srcSurface->surfacePriv().hasPendingWrite()) { this->flush(nullptr); // MDB TODO: tighten this } sk_sp proxyToRead = src->asSurfaceProxyRef(); bool didTempDraw = false; if (GrGpu::kNoDraw_DrawPreference != drawPreference) { if (SkBackingFit::kExact == tempDrawInfo.fTempSurfaceFit) { // We only respect this when the entire src is being read. Otherwise we can trigger too // many odd ball texture sizes and trash the cache. if (width != srcSurface->width() || height != srcSurface->height()) { tempDrawInfo.fTempSurfaceFit= SkBackingFit::kApprox; } } // TODO: Need to decide the semantics of this function for color spaces. Do we support // conversion to a passed-in color space? For now, specifying nullptr means that this // path will do no conversion, so it will match the behavior of the non-draw path. For // now we simply infer an sRGB color space if the config is sRGB in order to avoid an // illegal combination. sk_sp colorSpace; if (GrPixelConfigIsSRGB(tempDrawInfo.fTempSurfaceDesc.fConfig)) { colorSpace = SkColorSpace::MakeSRGB(); } sk_sp tempRTC = fContext->makeDeferredRenderTargetContext(tempDrawInfo.fTempSurfaceFit, tempDrawInfo.fTempSurfaceDesc.fWidth, tempDrawInfo.fTempSurfaceDesc.fHeight, tempDrawInfo.fTempSurfaceDesc.fConfig, std::move(colorSpace), tempDrawInfo.fTempSurfaceDesc.fSampleCnt, GrMipMapped::kNo, tempDrawInfo.fTempSurfaceDesc.fOrigin); if (tempRTC) { SkMatrix textureMatrix = SkMatrix::MakeTrans(SkIntToScalar(left), SkIntToScalar(top)); sk_sp proxy = src->asTextureProxyRef(); auto fp = GrSimpleTextureEffect::Make(std::move(proxy), textureMatrix); if (unpremulOnGpu) { fp = fContext->createPMToUPMEffect(std::move(fp), useConfigConversionEffect); // We no longer need to do this on CPU after the read back. unpremul = false; } fp = GrFragmentProcessor::SwizzleOutput(std::move(fp), tempDrawInfo.fSwizzle); if (!fp) { return false; } GrPaint paint; paint.addColorFragmentProcessor(std::move(fp)); paint.setPorterDuffXPFactory(SkBlendMode::kSrc); paint.setAllowSRGBInputs(true); SkRect rect = SkRect::MakeWH(SkIntToScalar(width), SkIntToScalar(height)); tempRTC->drawRect(GrNoClip(), std::move(paint), GrAA::kNo, SkMatrix::I(), rect, nullptr); proxyToRead = tempRTC->asTextureProxyRef(); left = 0; top = 0; didTempDraw = true; } } if (!proxyToRead) { return false; } if (GrGpu::kRequireDraw_DrawPreference == drawPreference && !didTempDraw) { return false; } GrColorType colorTypeToRead = dstColorType; if (didTempDraw) { this->flushSurfaceWrites(proxyToRead.get()); colorTypeToRead = tempDrawInfo.fReadColorType; } if (!proxyToRead->instantiate(this->resourceProvider())) { return false; } GrSurface* surfaceToRead = proxyToRead->priv().peekSurface(); if (!fContext->fGpu->readPixels(surfaceToRead, proxyToRead->origin(), left, top, width, height, colorTypeToRead, buffer, rowBytes)) { return false; } // Perform umpremul conversion if we weren't able to perform it as a draw. if (unpremul) { SkColorType colorType = GrColorTypeToSkColorType(dstColorType); if (kUnknown_SkColorType == colorType || 4 != SkColorTypeBytesPerPixel(colorType)) { return false; } for (int y = 0; y < height; y++) { SkUnpremultiplyRow((uint32_t*) buffer, (const uint32_t*) buffer, width); buffer = SkTAddOffset(buffer, rowBytes); } } return true; } void GrContextPriv::prepareSurfaceForExternalIO(GrSurfaceProxy* proxy) { ASSERT_SINGLE_OWNER_PRIV RETURN_IF_ABANDONED_PRIV SkASSERT(proxy); ASSERT_OWNED_PROXY_PRIV(proxy); fContext->fDrawingManager->prepareSurfaceForExternalIO(proxy, 0, nullptr); } void GrContextPriv::flushSurfaceWrites(GrSurfaceProxy* proxy) { ASSERT_SINGLE_OWNER_PRIV RETURN_IF_ABANDONED_PRIV SkASSERT(proxy); ASSERT_OWNED_PROXY_PRIV(proxy); if (proxy->priv().hasPendingWrite()) { this->flush(proxy); } } void GrContextPriv::flushSurfaceIO(GrSurfaceProxy* proxy) { ASSERT_SINGLE_OWNER_PRIV RETURN_IF_ABANDONED_PRIV SkASSERT(proxy); ASSERT_OWNED_PROXY_PRIV(proxy); if (proxy->priv().hasPendingIO()) { this->flush(proxy); } } //////////////////////////////////////////////////////////////////////////////// sk_sp GrContextPriv::makeWrappedSurfaceContext(sk_sp proxy, sk_sp colorSpace, const SkSurfaceProps* props) { ASSERT_SINGLE_OWNER_PRIV // sRGB pixel configs may only be used with near-sRGB gamma color spaces. if (GrPixelConfigIsSRGB(proxy->config())) { if (!colorSpace || !colorSpace->gammaCloseToSRGB()) { return nullptr; } } if (proxy->asRenderTargetProxy()) { return this->drawingManager()->makeRenderTargetContext(std::move(proxy), std::move(colorSpace), props); } else { SkASSERT(proxy->asTextureProxy()); SkASSERT(!props); return this->drawingManager()->makeTextureContext(std::move(proxy), std::move(colorSpace)); } } sk_sp GrContextPriv::makeDeferredSurfaceContext(const GrSurfaceDesc& dstDesc, GrMipMapped mipMapped, SkBackingFit fit, SkBudgeted isDstBudgeted, sk_sp colorSpace, const SkSurfaceProps* props) { sk_sp proxy; if (GrMipMapped::kNo == mipMapped) { proxy = this->proxyProvider()->createProxy(dstDesc, fit, isDstBudgeted); } else { SkASSERT(SkBackingFit::kExact == fit); proxy = this->proxyProvider()->createMipMapProxy(dstDesc, isDstBudgeted); } if (!proxy) { return nullptr; } return this->makeWrappedSurfaceContext(std::move(proxy), std::move(colorSpace), props); } sk_sp GrContextPriv::makeBackendTextureContext(const GrBackendTexture& tex, GrSurfaceOrigin origin, sk_sp colorSpace) { ASSERT_SINGLE_OWNER_PRIV sk_sp proxy = this->proxyProvider()->createWrappedTextureProxy(tex, origin); if (!proxy) { return nullptr; } return this->drawingManager()->makeTextureContext(std::move(proxy), std::move(colorSpace)); } sk_sp GrContextPriv::makeBackendTextureRenderTargetContext( const GrBackendTexture& tex, GrSurfaceOrigin origin, int sampleCnt, sk_sp colorSpace, const SkSurfaceProps* props) { ASSERT_SINGLE_OWNER_PRIV SkASSERT(sampleCnt > 0); sk_sp proxy(this->proxyProvider()->createWrappedTextureProxy(tex, origin, sampleCnt)); if (!proxy) { return nullptr; } return this->drawingManager()->makeRenderTargetContext(std::move(proxy), std::move(colorSpace), props); } sk_sp GrContextPriv::makeBackendRenderTargetRenderTargetContext( const GrBackendRenderTarget& backendRT, GrSurfaceOrigin origin, sk_sp colorSpace, const SkSurfaceProps* surfaceProps) { ASSERT_SINGLE_OWNER_PRIV sk_sp proxy = this->proxyProvider()->createWrappedRenderTargetProxy(backendRT, origin); if (!proxy) { return nullptr; } return this->drawingManager()->makeRenderTargetContext(std::move(proxy), std::move(colorSpace), surfaceProps); } sk_sp GrContextPriv::makeBackendTextureAsRenderTargetRenderTargetContext( const GrBackendTexture& tex, GrSurfaceOrigin origin, int sampleCnt, sk_sp colorSpace, const SkSurfaceProps* props) { ASSERT_SINGLE_OWNER_PRIV SkASSERT(sampleCnt > 0); sk_sp proxy(this->proxyProvider()->createWrappedRenderTargetProxy(tex, origin, sampleCnt)); if (!proxy) { return nullptr; } return this->drawingManager()->makeRenderTargetContext(std::move(proxy), std::move(colorSpace), props); } void GrContextPriv::addOnFlushCallbackObject(GrOnFlushCallbackObject* onFlushCBObject) { fContext->fDrawingManager->addOnFlushCallbackObject(onFlushCBObject); } void GrContextPriv::moveOpListsToDDL(SkDeferredDisplayList* ddl) { fContext->fDrawingManager->moveOpListsToDDL(ddl); } void GrContextPriv::copyOpListsFromDDL(const SkDeferredDisplayList* ddl, GrRenderTargetProxy* newDest) { fContext->fDrawingManager->copyOpListsFromDDL(ddl, newDest); } static inline GrPixelConfig GrPixelConfigFallback(GrPixelConfig config) { switch (config) { case kAlpha_8_GrPixelConfig: case kRGB_565_GrPixelConfig: case kRGBA_4444_GrPixelConfig: case kBGRA_8888_GrPixelConfig: return kRGBA_8888_GrPixelConfig; case kSBGRA_8888_GrPixelConfig: return kSRGBA_8888_GrPixelConfig; case kAlpha_half_GrPixelConfig: return kRGBA_half_GrPixelConfig; default: return kUnknown_GrPixelConfig; } } sk_sp GrContext::makeDeferredRenderTargetContextWithFallback( SkBackingFit fit, int width, int height, GrPixelConfig config, sk_sp colorSpace, int sampleCnt, GrMipMapped mipMapped, GrSurfaceOrigin origin, const SkSurfaceProps* surfaceProps, SkBudgeted budgeted) { SkASSERT(sampleCnt > 0); if (0 == this->caps()->getRenderTargetSampleCount(sampleCnt, config)) { config = GrPixelConfigFallback(config); } return this->makeDeferredRenderTargetContext(fit, width, height, config, std::move(colorSpace), sampleCnt, mipMapped, origin, surfaceProps, budgeted); } sk_sp GrContext::makeDeferredRenderTargetContext( SkBackingFit fit, int width, int height, GrPixelConfig config, sk_sp colorSpace, int sampleCnt, GrMipMapped mipMapped, GrSurfaceOrigin origin, const SkSurfaceProps* surfaceProps, SkBudgeted budgeted) { SkASSERT(sampleCnt > 0); if (this->abandoned()) { return nullptr; } GrSurfaceDesc desc; desc.fFlags = kRenderTarget_GrSurfaceFlag; desc.fOrigin = origin; desc.fWidth = width; desc.fHeight = height; desc.fConfig = config; desc.fSampleCnt = sampleCnt; sk_sp rtp; if (GrMipMapped::kNo == mipMapped) { rtp = fProxyProvider->createProxy(desc, fit, budgeted); } else { rtp = fProxyProvider->createMipMapProxy(desc, budgeted); } if (!rtp) { return nullptr; } sk_sp renderTargetContext( fDrawingManager->makeRenderTargetContext(std::move(rtp), std::move(colorSpace), surfaceProps)); if (!renderTargetContext) { return nullptr; } renderTargetContext->discard(); return renderTargetContext; } bool GrContext::abandoned() const { ASSERT_SINGLE_OWNER return fDrawingManager->wasAbandoned(); } std::unique_ptr GrContext::createPMToUPMEffect( std::unique_ptr fp, bool useConfigConversionEffect) { ASSERT_SINGLE_OWNER // We have specialized effects that guarantee round-trip conversion for some formats if (useConfigConversionEffect) { // We should have already called this->validPMUPMConversionExists() in this case SkASSERT(fDidTestPMConversions); // ...and it should have succeeded SkASSERT(this->validPMUPMConversionExists()); return GrConfigConversionEffect::Make(std::move(fp), PMConversion::kToUnpremul); } else { // For everything else (sRGB, half-float, etc...), it doesn't make sense to try and // explicitly round the results. Just do the obvious, naive thing in the shader. return GrFragmentProcessor::UnpremulOutput(std::move(fp)); } } std::unique_ptr GrContext::createUPMToPMEffect( std::unique_ptr fp, bool useConfigConversionEffect) { ASSERT_SINGLE_OWNER // We have specialized effects that guarantee round-trip conversion for these formats if (useConfigConversionEffect) { // We should have already called this->validPMUPMConversionExists() in this case SkASSERT(fDidTestPMConversions); // ...and it should have succeeded SkASSERT(this->validPMUPMConversionExists()); return GrConfigConversionEffect::Make(std::move(fp), PMConversion::kToPremul); } else { // For everything else (sRGB, half-float, etc...), it doesn't make sense to try and // explicitly round the results. Just do the obvious, naive thing in the shader. return GrFragmentProcessor::PremulOutput(std::move(fp)); } } bool GrContext::validPMUPMConversionExists() { ASSERT_SINGLE_OWNER if (!fDidTestPMConversions) { fPMUPMConversionsRoundTrip = GrConfigConversionEffect::TestForPreservingPMConversions(this); fDidTestPMConversions = true; } // The PM<->UPM tests fail or succeed together so we only need to check one. return fPMUPMConversionsRoundTrip; } ////////////////////////////////////////////////////////////////////////////// // DDL TODO: remove 'maxResources' void GrContext::getResourceCacheLimits(int* maxResources, size_t* maxResourceBytes) const { ASSERT_SINGLE_OWNER if (maxResources) { *maxResources = fResourceCache->getMaxResourceCount(); } if (maxResourceBytes) { *maxResourceBytes = fResourceCache->getMaxResourceBytes(); } } void GrContext::setResourceCacheLimits(int maxResources, size_t maxResourceBytes) { ASSERT_SINGLE_OWNER fResourceCache->setLimits(maxResources, maxResourceBytes); } ////////////////////////////////////////////////////////////////////////////// void GrContext::dumpMemoryStatistics(SkTraceMemoryDump* traceMemoryDump) const { ASSERT_SINGLE_OWNER fResourceCache->dumpMemoryStatistics(traceMemoryDump); } ////////////////////////////////////////////////////////////////////////////// SkString GrContext::dump() const { SkDynamicMemoryWStream stream; SkJSONWriter writer(&stream, SkJSONWriter::Mode::kPretty); writer.beginObject(); static const char* kBackendStr[] = { "Metal", "OpenGL", "Vulkan", "Mock", }; GR_STATIC_ASSERT(0 == kMetal_GrBackend); GR_STATIC_ASSERT(1 == kOpenGL_GrBackend); GR_STATIC_ASSERT(2 == kVulkan_GrBackend); GR_STATIC_ASSERT(3 == kMock_GrBackend); writer.appendString("backend", kBackendStr[fBackend]); writer.appendName("caps"); fCaps->dumpJSON(&writer); writer.appendName("gpu"); fGpu->dumpJSON(&writer); // Flush JSON to the memory stream writer.endObject(); writer.flush(); // Null terminate the JSON data in the memory stream stream.write8(0); // Allocate a string big enough to hold all the data, then copy out of the stream SkString result(stream.bytesWritten()); stream.copyToAndReset(result.writable_str()); return result; }