/* * Copyright 2015 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/GrResourceProvider.h" #include "include/gpu/GrBackendSemaphore.h" #include "include/gpu/GrContext.h" #include "include/private/GrResourceKey.h" #include "include/private/GrSingleOwner.h" #include "src/core/SkConvertPixels.h" #include "src/core/SkMathPriv.h" #include "src/gpu/GrCaps.h" #include "src/gpu/GrContextPriv.h" #include "src/gpu/GrGpu.h" #include "src/gpu/GrGpuBuffer.h" #include "src/gpu/GrPath.h" #include "src/gpu/GrPathRendering.h" #include "src/gpu/GrProxyProvider.h" #include "src/gpu/GrRenderTargetPriv.h" #include "src/gpu/GrResourceCache.h" #include "src/gpu/GrSemaphore.h" #include "src/gpu/GrStencilAttachment.h" #include "src/gpu/GrTexturePriv.h" #include "src/gpu/SkGr.h" const uint32_t GrResourceProvider::kMinScratchTextureSize = 16; #define ASSERT_SINGLE_OWNER \ SkDEBUGCODE(GrSingleOwner::AutoEnforce debug_SingleOwner(fSingleOwner);) GrResourceProvider::GrResourceProvider(GrGpu* gpu, GrResourceCache* cache, GrSingleOwner* owner) : fCache(cache) , fGpu(gpu) #ifdef SK_DEBUG , fSingleOwner(owner) #endif { fCaps = sk_ref_sp(fGpu->caps()); } // Ensures the row bytes are populated (not 0) and makes a copy to a temporary // to make the row bytes tight if necessary. Returns false if the input row bytes are invalid. static bool prepare_level(const GrMipLevel& inLevel, size_t bpp, int w, int h, bool rowBytesSupport, bool mustInitializeAllLevels, GrMipLevel* outLevel, std::unique_ptr* data) { size_t minRB = w * bpp; if (!inLevel.fPixels) { if (mustInitializeAllLevels) { data->reset(new char[minRB * h]()); outLevel->fPixels = data->get(); outLevel->fRowBytes = minRB; } else { outLevel->fPixels = nullptr; outLevel->fRowBytes = 0; } return true; } size_t actualRB = inLevel.fRowBytes ? inLevel.fRowBytes : minRB; if (actualRB < minRB) { return false; } if (actualRB == minRB || rowBytesSupport) { outLevel->fRowBytes = actualRB; outLevel->fPixels = inLevel.fPixels; } else { data->reset(new char[minRB * h]); outLevel->fPixels = data->get(); outLevel->fRowBytes = minRB; SkRectMemcpy(data->get(), outLevel->fRowBytes, inLevel.fPixels, inLevel.fRowBytes, minRB, h); } return true; } sk_sp GrResourceProvider::createTexture(const GrSurfaceDesc& desc, GrRenderable renderable, int renderTargetSampleCnt, SkBudgeted budgeted, GrProtected isProtected, const GrMipLevel texels[], int mipLevelCount) { ASSERT_SINGLE_OWNER SkASSERT(mipLevelCount > 0); if (this->isAbandoned()) { return nullptr; } GrMipMapped mipMapped = mipLevelCount > 1 ? GrMipMapped::kYes : GrMipMapped::kNo; if (!fCaps->validateSurfaceDesc(desc, renderable, renderTargetSampleCnt, mipMapped)) { return nullptr; } bool mustInitializeAllLevels = this->caps()->createTextureMustSpecifyAllLevels(); bool rowBytesSupport = this->caps()->writePixelsRowBytesSupport(); SkAutoSTMalloc<14, GrMipLevel> tmpTexels; SkAutoSTArray<14, std::unique_ptr> tmpDatas; if (mipLevelCount > 0 && texels) { tmpTexels.reset(mipLevelCount); tmpDatas.reset(mipLevelCount); int w = desc.fWidth; int h = desc.fHeight; size_t bpp = GrBytesPerPixel(desc.fConfig); for (int i = 0; i < mipLevelCount; ++i) { if (!prepare_level(texels[i], bpp, w, h, rowBytesSupport, mustInitializeAllLevels, &tmpTexels[i], &tmpDatas[i])) { return nullptr; } w = std::max(w / 2, 1); h = std::max(h / 2, 1); } } return fGpu->createTexture(desc, renderable, renderTargetSampleCnt, budgeted, isProtected, tmpTexels.get(), mipLevelCount); } sk_sp GrResourceProvider::getExactScratch(const GrSurfaceDesc& desc, GrRenderable renderable, int renderTargetSampleCnt, SkBudgeted budgeted, GrProtected isProtected, Flags flags) { sk_sp tex( this->refScratchTexture(desc, renderable, renderTargetSampleCnt, isProtected, flags)); if (tex && SkBudgeted::kNo == budgeted) { tex->resourcePriv().makeUnbudgeted(); } return tex; } sk_sp GrResourceProvider::createTexture(const GrSurfaceDesc& desc, GrRenderable renderable, int renderTargetSampleCnt, SkBudgeted budgeted, SkBackingFit fit, GrProtected isProtected, const GrMipLevel& mipLevel, Flags flags) { ASSERT_SINGLE_OWNER if (this->isAbandoned()) { return nullptr; } if (!mipLevel.fPixels) { return nullptr; } if (!fCaps->validateSurfaceDesc(desc, renderable, renderTargetSampleCnt, GrMipMapped::kNo)) { return nullptr; } GrContext* context = fGpu->getContext(); GrProxyProvider* proxyProvider = context->priv().proxyProvider(); bool mustInitialize = this->caps()->createTextureMustSpecifyAllLevels(); bool rowBytesSupport = this->caps()->writePixelsRowBytesSupport(); size_t bpp = GrBytesPerPixel(desc.fConfig); std::unique_ptr tmpData; GrMipLevel tmpLevel; if (!prepare_level(mipLevel, bpp, desc.fWidth, desc.fHeight, rowBytesSupport, mustInitialize, &tmpLevel, &tmpData)) { return nullptr; } GrColorType colorType = GrPixelConfigToColorType(desc.fConfig); sk_sp tex = (SkBackingFit::kApprox == fit) ? this->createApproxTexture(desc, renderable, renderTargetSampleCnt, isProtected, flags) : this->createTexture(desc, renderable, renderTargetSampleCnt, budgeted, isProtected, flags); if (!tex) { return nullptr; } sk_sp proxy = proxyProvider->createWrapped(tex, kTopLeft_GrSurfaceOrigin); if (!proxy) { return nullptr; } // Here we don't really know the alpha type of the data we want to upload. All we really // care about is that it is not converted. So we use the same alpha type for the data // and the surface context. static constexpr auto kAlphaType = kPremul_SkAlphaType; sk_sp sContext = context->priv().makeWrappedSurfaceContext(std::move(proxy), colorType, kAlphaType); if (!sContext) { return nullptr; } GrPixelInfo srcInfo(colorType, kAlphaType, nullptr, desc.fWidth, desc.fHeight); SkAssertResult(sContext->writePixels(srcInfo, tmpLevel.fPixels, tmpLevel.fRowBytes, {0, 0})); return tex; } sk_sp GrResourceProvider::createCompressedTexture(int width, int height, SkImage::CompressionType compression, SkBudgeted budgeted, SkData* data) { ASSERT_SINGLE_OWNER if (this->isAbandoned()) { return nullptr; } return fGpu->createCompressedTexture(width, height, compression, budgeted, data->data(), data->size()); } sk_sp GrResourceProvider::createTexture(const GrSurfaceDesc& desc, GrRenderable renderable, int renderTargetSampleCnt, SkBudgeted budgeted, GrProtected isProtected, Flags flags) { ASSERT_SINGLE_OWNER if (this->isAbandoned()) { return nullptr; } if (!fCaps->validateSurfaceDesc(desc, renderable, renderTargetSampleCnt, GrMipMapped::kNo)) { return nullptr; } // Compressed textures are read-only so they don't support re-use for scratch. if (!GrPixelConfigIsCompressed(desc.fConfig)) { sk_sp tex = this->getExactScratch(desc, renderable, renderTargetSampleCnt, budgeted, isProtected, flags); if (tex) { return tex; } } if (fCaps->createTextureMustSpecifyAllLevels()) { size_t rowBytes = GrBytesPerPixel(desc.fConfig) * desc.fWidth; size_t size = rowBytes * desc.fHeight; std::unique_ptr zeros(new char[size]()); GrMipLevel level; level.fRowBytes = rowBytes; level.fPixels = zeros.get(); return fGpu->createTexture(desc, renderable, renderTargetSampleCnt, budgeted, isProtected, &level, 1); } return fGpu->createTexture(desc, renderable, renderTargetSampleCnt, budgeted, isProtected); } // Map 'value' to a larger multiple of 2. Values <= 'kMagicTol' will pop up to // the next power of 2. Those above 'kMagicTol' will only go up half the floor power of 2. uint32_t GrResourceProvider::MakeApprox(uint32_t value) { static const int kMagicTol = 1024; value = SkTMax(kMinScratchTextureSize, value); if (SkIsPow2(value)) { return value; } uint32_t ceilPow2 = GrNextPow2(value); if (value <= kMagicTol) { return ceilPow2; } uint32_t floorPow2 = ceilPow2 >> 1; uint32_t mid = floorPow2 + (floorPow2 >> 1); if (value <= mid) { return mid; } return ceilPow2; } sk_sp GrResourceProvider::createApproxTexture(const GrSurfaceDesc& desc, GrRenderable renderable, int renderTargetSampleCnt, GrProtected isProtected, Flags flags) { ASSERT_SINGLE_OWNER SkASSERT(Flags::kNone == flags || Flags::kNoPendingIO == flags); if (this->isAbandoned()) { return nullptr; } // Currently we don't recycle compressed textures as scratch. if (GrPixelConfigIsCompressed(desc.fConfig)) { return nullptr; } if (!fCaps->validateSurfaceDesc(desc, renderable, renderTargetSampleCnt, GrMipMapped::kNo)) { return nullptr; } if (auto tex = this->refScratchTexture(desc, renderable, renderTargetSampleCnt, isProtected, flags)) { return tex; } SkTCopyOnFirstWrite copyDesc(desc); // bin by some multiple or power of 2 with a reasonable min if (fGpu->caps()->reuseScratchTextures() || renderable == GrRenderable::kYes) { GrSurfaceDesc* wdesc = copyDesc.writable(); wdesc->fWidth = MakeApprox(wdesc->fWidth); wdesc->fHeight = MakeApprox(wdesc->fHeight); } if (auto tex = this->refScratchTexture(*copyDesc, renderable, renderTargetSampleCnt, isProtected, flags)) { return tex; } if (this->caps()->createTextureMustSpecifyAllLevels()) { size_t rowBytes = GrBytesPerPixel(copyDesc->fConfig) * copyDesc->fWidth; size_t size = rowBytes * copyDesc->fHeight; std::unique_ptr zeros(new char[size]()); GrMipLevel level; level.fRowBytes = rowBytes; level.fPixels = zeros.get(); return fGpu->createTexture(*copyDesc, renderable, renderTargetSampleCnt, SkBudgeted::kYes, isProtected, &level, 1); } return fGpu->createTexture(*copyDesc, renderable, renderTargetSampleCnt, SkBudgeted::kYes, isProtected); } sk_sp GrResourceProvider::refScratchTexture(const GrSurfaceDesc& desc, GrRenderable renderable, int renderTargetSampleCnt, GrProtected isProtected, Flags flags) { ASSERT_SINGLE_OWNER SkASSERT(!this->isAbandoned()); SkASSERT(!GrPixelConfigIsCompressed(desc.fConfig)); SkASSERT(fCaps->validateSurfaceDesc(desc, renderable, renderTargetSampleCnt, GrMipMapped::kNo)); // We could make initial clears work with scratch textures but it is a rare case so we just opt // to fall back to making a new texture. if (fGpu->caps()->reuseScratchTextures() || renderable == GrRenderable::kYes) { GrScratchKey key; GrTexturePriv::ComputeScratchKey(desc, renderable, renderTargetSampleCnt, &key); auto scratchFlags = GrResourceCache::ScratchFlags::kNone; if (Flags::kNoPendingIO & flags) { scratchFlags |= GrResourceCache::ScratchFlags::kRequireNoPendingIO; } else if (renderable == GrRenderable::kNo) { // If it is not a render target then it will most likely be populated by // writePixels() which will trigger a flush if the texture has pending IO. scratchFlags |= GrResourceCache::ScratchFlags::kPreferNoPendingIO; } GrGpuResource* resource = fCache->findAndRefScratchResource( key, GrSurface::WorstCaseSize(desc, renderable, renderTargetSampleCnt), scratchFlags); if (resource) { fGpu->stats()->incNumScratchTexturesReused(); GrSurface* surface = static_cast(resource); return sk_sp(surface->asTexture()); } } return nullptr; } sk_sp GrResourceProvider::wrapBackendTexture(const GrBackendTexture& tex, GrColorType colorType, GrWrapOwnership ownership, GrWrapCacheable cacheable, GrIOType ioType) { ASSERT_SINGLE_OWNER if (this->isAbandoned()) { return nullptr; } return fGpu->wrapBackendTexture(tex, colorType, ownership, cacheable, ioType); } sk_sp GrResourceProvider::wrapRenderableBackendTexture(const GrBackendTexture& tex, int sampleCnt, GrColorType colorType, GrWrapOwnership ownership, GrWrapCacheable cacheable) { ASSERT_SINGLE_OWNER if (this->isAbandoned()) { return nullptr; } return fGpu->wrapRenderableBackendTexture(tex, sampleCnt, colorType, ownership, cacheable); } sk_sp GrResourceProvider::wrapBackendRenderTarget( const GrBackendRenderTarget& backendRT, GrColorType colorType) { ASSERT_SINGLE_OWNER return this->isAbandoned() ? nullptr : fGpu->wrapBackendRenderTarget(backendRT, colorType); } sk_sp GrResourceProvider::wrapVulkanSecondaryCBAsRenderTarget( const SkImageInfo& imageInfo, const GrVkDrawableInfo& vkInfo) { ASSERT_SINGLE_OWNER return this->isAbandoned() ? nullptr : fGpu->wrapVulkanSecondaryCBAsRenderTarget(imageInfo, vkInfo); } void GrResourceProvider::assignUniqueKeyToResource(const GrUniqueKey& key, GrGpuResource* resource) { ASSERT_SINGLE_OWNER if (this->isAbandoned() || !resource) { return; } resource->resourcePriv().setUniqueKey(key); } sk_sp GrResourceProvider::findResourceByUniqueKey(const GrUniqueKey& key) { ASSERT_SINGLE_OWNER return this->isAbandoned() ? nullptr : sk_sp(fCache->findAndRefUniqueResource(key)); } sk_sp GrResourceProvider::findOrMakeStaticBuffer(GrGpuBufferType intendedType, size_t size, const void* data, const GrUniqueKey& key) { if (auto buffer = this->findByUniqueKey(key)) { return std::move(buffer); } if (auto buffer = this->createBuffer(size, intendedType, kStatic_GrAccessPattern, data)) { // We shouldn't bin and/or cache static buffers. SkASSERT(buffer->size() == size); SkASSERT(!buffer->resourcePriv().getScratchKey().isValid()); SkASSERT(!buffer->resourcePriv().hasPendingIO_debugOnly()); buffer->resourcePriv().setUniqueKey(key); return sk_sp(buffer); } return nullptr; } sk_sp GrResourceProvider::createPatternedIndexBuffer(const uint16_t* pattern, int patternSize, int reps, int vertCount, const GrUniqueKey* key) { size_t bufferSize = patternSize * reps * sizeof(uint16_t); // This is typically used in GrMeshDrawOps, so we assume kNoPendingIO. sk_sp buffer( this->createBuffer(bufferSize, GrGpuBufferType::kIndex, kStatic_GrAccessPattern)); if (!buffer) { return nullptr; } uint16_t* data = (uint16_t*) buffer->map(); SkAutoTArray temp; if (!data) { temp.reset(reps * patternSize); data = temp.get(); } for (int i = 0; i < reps; ++i) { int baseIdx = i * patternSize; uint16_t baseVert = (uint16_t)(i * vertCount); for (int j = 0; j < patternSize; ++j) { data[baseIdx+j] = baseVert + pattern[j]; } } if (temp.get()) { if (!buffer->updateData(data, bufferSize)) { return nullptr; } } else { buffer->unmap(); } if (key) { SkASSERT(key->isValid()); this->assignUniqueKeyToResource(*key, buffer.get()); } return std::move(buffer); } static constexpr int kMaxQuads = 1 << 12; // max possible: (1 << 14) - 1; sk_sp GrResourceProvider::createQuadIndexBuffer() { GR_STATIC_ASSERT(4 * kMaxQuads <= 65535); static const uint16_t kPattern[] = { 0, 1, 2, 2, 1, 3 }; return this->createPatternedIndexBuffer(kPattern, 6, kMaxQuads, 4, nullptr); } int GrResourceProvider::QuadCountOfQuadBuffer() { return kMaxQuads; } sk_sp GrResourceProvider::createPath(const SkPath& path, const GrStyle& style) { if (this->isAbandoned()) { return nullptr; } SkASSERT(this->gpu()->pathRendering()); return this->gpu()->pathRendering()->createPath(path, style); } sk_sp GrResourceProvider::createBuffer(size_t size, GrGpuBufferType intendedType, GrAccessPattern accessPattern, const void* data) { if (this->isAbandoned()) { return nullptr; } if (kDynamic_GrAccessPattern != accessPattern) { return this->gpu()->createBuffer(size, intendedType, accessPattern, data); } // bin by pow2 with a reasonable min static const size_t MIN_SIZE = 1 << 12; size_t allocSize = SkTMax(MIN_SIZE, GrNextSizePow2(size)); GrScratchKey key; GrGpuBuffer::ComputeScratchKeyForDynamicVBO(allocSize, intendedType, &key); auto buffer = sk_sp(static_cast(this->cache()->findAndRefScratchResource( key, allocSize, GrResourceCache::ScratchFlags::kNone))); if (!buffer) { buffer = this->gpu()->createBuffer(allocSize, intendedType, kDynamic_GrAccessPattern); if (!buffer) { return nullptr; } } if (data) { buffer->updateData(data, size); } return buffer; } bool GrResourceProvider::attachStencilAttachment(GrRenderTarget* rt, int minStencilSampleCount) { SkASSERT(rt); GrStencilAttachment* stencil = rt->renderTargetPriv().getStencilAttachment(); if (stencil && stencil->numSamples() >= minStencilSampleCount) { return true; } if (!rt->wasDestroyed() && rt->canAttemptStencilAttachment()) { GrUniqueKey sbKey; int width = rt->width(); int height = rt->height(); #if 0 if (this->caps()->oversizedStencilSupport()) { width = SkNextPow2(width); height = SkNextPow2(height); } #endif GrStencilAttachment::ComputeSharedStencilAttachmentKey( width, height, minStencilSampleCount, &sbKey); auto stencil = this->findByUniqueKey(sbKey); if (!stencil) { // Need to try and create a new stencil stencil.reset(this->gpu()->createStencilAttachmentForRenderTarget( rt, width, height, minStencilSampleCount)); if (!stencil) { return false; } this->assignUniqueKeyToResource(sbKey, stencil.get()); } rt->renderTargetPriv().attachStencilAttachment(std::move(stencil)); } if (GrStencilAttachment* stencil = rt->renderTargetPriv().getStencilAttachment()) { return stencil->numSamples() >= minStencilSampleCount; } return false; } sk_sp GrResourceProvider::wrapBackendTextureAsRenderTarget( const GrBackendTexture& tex, int sampleCnt, GrColorType colorType) { if (this->isAbandoned()) { return nullptr; } return fGpu->wrapBackendTextureAsRenderTarget(tex, sampleCnt, colorType); } sk_sp SK_WARN_UNUSED_RESULT GrResourceProvider::makeSemaphore(bool isOwned) { return fGpu->makeSemaphore(isOwned); } sk_sp GrResourceProvider::wrapBackendSemaphore(const GrBackendSemaphore& semaphore, SemaphoreWrapType wrapType, GrWrapOwnership ownership) { ASSERT_SINGLE_OWNER return this->isAbandoned() ? nullptr : fGpu->wrapBackendSemaphore(semaphore, wrapType, ownership); }