/*
* 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 "GrResourceProvider.h"
#include "GrBackendSemaphore.h"
#include "GrBuffer.h"
#include "GrCaps.h"
#include "GrContext.h"
#include "GrContextPriv.h"
#include "GrGpu.h"
#include "GrPath.h"
#include "GrPathRendering.h"
#include "GrProxyProvider.h"
#include "GrRenderTargetPriv.h"
#include "GrResourceCache.h"
#include "GrResourceKey.h"
#include "GrSemaphore.h"
#include "GrStencilAttachment.h"
#include "GrTexturePriv.h"
#include "../private/GrSingleOwner.h"
#include "SkGr.h"
#include "SkMathPriv.h"
GR_DECLARE_STATIC_UNIQUE_KEY(gQuadIndexBufferKey);
const uint32_t GrResourceProvider::kMinScratchTextureSize = 16;
#ifdef SK_DISABLE_EXPLICIT_GPU_RESOURCE_ALLOCATION
static const bool kDefaultExplicitlyAllocateGPUResources = false;
#else
static const bool kDefaultExplicitlyAllocateGPUResources = true;
#endif
#define ASSERT_SINGLE_OWNER \
SkDEBUGCODE(GrSingleOwner::AutoEnforce debug_SingleOwner(fSingleOwner);)
GrResourceProvider::GrResourceProvider(GrGpu* gpu, GrResourceCache* cache, GrSingleOwner* owner,
GrContextOptions::Enable explicitlyAllocateGPUResources)
: fCache(cache)
, fGpu(gpu)
#ifdef SK_DEBUG
, fSingleOwner(owner)
#endif
{
if (GrContextOptions::Enable::kNo == explicitlyAllocateGPUResources) {
fExplicitlyAllocateGPUResources = false;
} else if (GrContextOptions::Enable::kYes == explicitlyAllocateGPUResources) {
fExplicitlyAllocateGPUResources = true;
} else {
fExplicitlyAllocateGPUResources = kDefaultExplicitlyAllocateGPUResources;
}
fCaps = sk_ref_sp(fGpu->caps());
GR_DEFINE_STATIC_UNIQUE_KEY(gQuadIndexBufferKey);
fQuadIndexBufferKey = gQuadIndexBufferKey;
}
sk_sp<GrTexture> GrResourceProvider::createTexture(const GrSurfaceDesc& desc, SkBudgeted budgeted,
const GrMipLevel texels[], int mipLevelCount,
SkDestinationSurfaceColorMode mipColorMode) {
ASSERT_SINGLE_OWNER
SkASSERT(mipLevelCount > 0);
if (this->isAbandoned()) {
return nullptr;
}
GrMipMapped mipMapped = mipLevelCount > 1 ? GrMipMapped::kYes : GrMipMapped::kNo;
if (!fCaps->validateSurfaceDesc(desc, mipMapped)) {
return nullptr;
}
sk_sp<GrTexture> tex(fGpu->createTexture(desc, budgeted, texels, mipLevelCount));
if (tex) {
tex->texturePriv().setMipColorMode(mipColorMode);
}
return tex;
}
sk_sp<GrTexture> GrResourceProvider::getExactScratch(const GrSurfaceDesc& desc,
SkBudgeted budgeted, uint32_t flags) {
sk_sp<GrTexture> tex(this->refScratchTexture(desc, flags));
if (tex && SkBudgeted::kNo == budgeted) {
tex->resourcePriv().makeUnbudgeted();
}
return tex;
}
sk_sp<GrTexture> GrResourceProvider::createTexture(const GrSurfaceDesc& desc,
SkBudgeted budgeted,
SkBackingFit fit,
const GrMipLevel& mipLevel) {
ASSERT_SINGLE_OWNER
if (this->isAbandoned()) {
return nullptr;
}
if (!mipLevel.fPixels) {
return nullptr;
}
if (!fCaps->validateSurfaceDesc(desc, GrMipMapped::kNo)) {
return nullptr;
}
GrContext* context = fGpu->getContext();
GrProxyProvider* proxyProvider = context->contextPriv().proxyProvider();
SkColorType colorType;
if (GrPixelConfigToColorType(desc.fConfig, &colorType)) {
// DDL TODO: remove this use of createInstantiatedProxy and convert it to a testing-only
// method.
sk_sp<GrTextureProxy> proxy = proxyProvider->createInstantiatedProxy(desc,
fit,
budgeted);
if (!proxy) {
return nullptr;
}
// We use an ephemeral surface context to do the write pixels. Here it isn't clear what
// color space to tag it with. That's ok because GrSurfaceContext::writePixels doesn't
// do any color space conversions. Though, that is likely to change. However, if the
// pixel config is sRGB then the passed color space here must have sRGB gamma or
// GrSurfaceContext creation fails.
sk_sp<SkColorSpace> colorSpace;
if (GrPixelConfigIsSRGB(desc.fConfig)) {
colorSpace = SkColorSpace::MakeSRGB();
}
auto srcInfo = SkImageInfo::Make(desc.fWidth, desc.fHeight, colorType,
kUnknown_SkAlphaType, colorSpace);
sk_sp<GrSurfaceContext> sContext = context->contextPriv().makeWrappedSurfaceContext(
std::move(proxy), std::move(colorSpace));
if (!sContext) {
return nullptr;
}
SkAssertResult(sContext->writePixels(srcInfo, mipLevel.fPixels, mipLevel.fRowBytes, 0, 0));
return sk_ref_sp(sContext->asTextureProxy()->priv().peekTexture());
} else {
return fGpu->createTexture(desc, budgeted, &mipLevel, 1);
}
}
sk_sp<GrTexture> GrResourceProvider::createTexture(const GrSurfaceDesc& desc, SkBudgeted budgeted,
uint32_t flags) {
ASSERT_SINGLE_OWNER
if (this->isAbandoned()) {
return nullptr;
}
if (!fCaps->validateSurfaceDesc(desc, GrMipMapped::kNo)) {
return nullptr;
}
sk_sp<GrTexture> tex = this->getExactScratch(desc, budgeted, flags);
if (tex) {
return tex;
}
return fGpu->createTexture(desc, budgeted);
}
sk_sp<GrTexture> GrResourceProvider::createApproxTexture(const GrSurfaceDesc& desc,
uint32_t flags) {
ASSERT_SINGLE_OWNER
SkASSERT(0 == flags || kNoPendingIO_Flag == flags);
if (this->isAbandoned()) {
return nullptr;
}
if (!fCaps->validateSurfaceDesc(desc, GrMipMapped::kNo)) {
return nullptr;
}
if (auto tex = this->refScratchTexture(desc, flags)) {
return tex;
}
SkTCopyOnFirstWrite<GrSurfaceDesc> copyDesc(desc);
// bin by pow2 with a reasonable min
if (!SkToBool(desc.fFlags & kPerformInitialClear_GrSurfaceFlag) &&
(fGpu->caps()->reuseScratchTextures() || (desc.fFlags & kRenderTarget_GrSurfaceFlag))) {
GrSurfaceDesc* wdesc = copyDesc.writable();
wdesc->fWidth = SkTMax(kMinScratchTextureSize, GrNextPow2(desc.fWidth));
wdesc->fHeight = SkTMax(kMinScratchTextureSize, GrNextPow2(desc.fHeight));
}
if (auto tex = this->refScratchTexture(*copyDesc, flags)) {
return tex;
}
return fGpu->createTexture(*copyDesc, SkBudgeted::kYes);
}
sk_sp<GrTexture> GrResourceProvider::refScratchTexture(const GrSurfaceDesc& desc,
uint32_t flags) {
ASSERT_SINGLE_OWNER
SkASSERT(!this->isAbandoned());
SkASSERT(fCaps->validateSurfaceDesc(desc, 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 (!SkToBool(desc.fFlags & kPerformInitialClear_GrSurfaceFlag) &&
(fGpu->caps()->reuseScratchTextures() || (desc.fFlags & kRenderTarget_GrSurfaceFlag))) {
GrScratchKey key;
GrTexturePriv::ComputeScratchKey(desc, &key);
uint32_t scratchFlags = 0;
if (kNoPendingIO_Flag & flags) {
scratchFlags = GrResourceCache::kRequireNoPendingIO_ScratchFlag;
} else if (!(desc.fFlags & kRenderTarget_GrSurfaceFlag)) {
// 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::kPreferNoPendingIO_ScratchFlag;
}
GrGpuResource* resource = fCache->findAndRefScratchResource(key,
GrSurface::WorstCaseSize(desc),
scratchFlags);
if (resource) {
GrSurface* surface = static_cast<GrSurface*>(resource);
return sk_sp<GrTexture>(surface->asTexture());
}
}
return nullptr;
}
sk_sp<GrTexture> GrResourceProvider::wrapBackendTexture(const GrBackendTexture& tex,
GrWrapOwnership ownership) {
ASSERT_SINGLE_OWNER
if (this->isAbandoned()) {
return nullptr;
}
return fGpu->wrapBackendTexture(tex, ownership);
}
sk_sp<GrTexture> GrResourceProvider::wrapRenderableBackendTexture(const GrBackendTexture& tex,
int sampleCnt,
GrWrapOwnership ownership) {
ASSERT_SINGLE_OWNER
if (this->isAbandoned()) {
return nullptr;
}
return fGpu->wrapRenderableBackendTexture(tex, sampleCnt, ownership);
}
sk_sp<GrRenderTarget> GrResourceProvider::wrapBackendRenderTarget(
const GrBackendRenderTarget& backendRT)
{
ASSERT_SINGLE_OWNER
return this->isAbandoned() ? nullptr : fGpu->wrapBackendRenderTarget(backendRT);
}
void GrResourceProvider::assignUniqueKeyToResource(const GrUniqueKey& key,
GrGpuResource* resource) {
ASSERT_SINGLE_OWNER
if (this->isAbandoned() || !resource) {
return;
}
resource->resourcePriv().setUniqueKey(key);
}
sk_sp<GrGpuResource> GrResourceProvider::findResourceByUniqueKey(const GrUniqueKey& key) {
ASSERT_SINGLE_OWNER
return this->isAbandoned() ? nullptr
: sk_sp<GrGpuResource>(fCache->findAndRefUniqueResource(key));
}
sk_sp<const GrBuffer> GrResourceProvider::findOrMakeStaticBuffer(GrBufferType intendedType,
size_t size,
const void* data,
const GrUniqueKey& key) {
if (auto buffer = this->findByUniqueKey<GrBuffer>(key)) {
return buffer;
}
if (auto buffer = this->createBuffer(size, intendedType, kStatic_GrAccessPattern, 0,
data)) {
// We shouldn't bin and/or cachestatic buffers.
SkASSERT(buffer->sizeInBytes() == size);
SkASSERT(!buffer->resourcePriv().getScratchKey().isValid());
SkASSERT(!buffer->resourcePriv().hasPendingIO_debugOnly());
buffer->resourcePriv().setUniqueKey(key);
return sk_sp<const GrBuffer>(buffer);
}
return nullptr;
}
sk_sp<const GrBuffer> 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<GrBuffer> buffer(this->createBuffer(bufferSize, kIndex_GrBufferType,
kStatic_GrAccessPattern, kNoPendingIO_Flag));
if (!buffer) {
return nullptr;
}
uint16_t* data = (uint16_t*) buffer->map();
SkAutoTArray<uint16_t> 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();
}
this->assignUniqueKeyToResource(key, buffer.get());
return std::move(buffer);
}
static constexpr int kMaxQuads = 1 << 12; // max possible: (1 << 14) - 1;
sk_sp<const GrBuffer> 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, fQuadIndexBufferKey);
}
int GrResourceProvider::QuadCountOfQuadBuffer() { return kMaxQuads; }
sk_sp<GrPath> 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<GrPathRange> GrResourceProvider::createPathRange(GrPathRange::PathGenerator* gen,
const GrStyle& style) {
if (this->isAbandoned()) {
return nullptr;
}
SkASSERT(this->gpu()->pathRendering());
return this->gpu()->pathRendering()->createPathRange(gen, style);
}
sk_sp<GrPathRange> GrResourceProvider::createGlyphs(const SkTypeface* tf,
const SkScalerContextEffects& effects,
const SkDescriptor* desc,
const GrStyle& style) {
SkASSERT(this->gpu()->pathRendering());
return this->gpu()->pathRendering()->createGlyphs(tf, effects, desc, style);
}
GrBuffer* GrResourceProvider::createBuffer(size_t size, GrBufferType intendedType,
GrAccessPattern accessPattern, uint32_t flags,
const void* data) {
if (this->isAbandoned()) {
return nullptr;
}
if (kDynamic_GrAccessPattern != accessPattern) {
return this->gpu()->createBuffer(size, intendedType, accessPattern, data);
}
if (!(flags & kRequireGpuMemory_Flag) &&
this->gpu()->caps()->preferClientSideDynamicBuffers() &&
GrBufferTypeIsVertexOrIndex(intendedType) &&
kDynamic_GrAccessPattern == accessPattern) {
return GrBuffer::CreateCPUBacked(this->gpu(), size, intendedType, 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;
GrBuffer::ComputeScratchKeyForDynamicVBO(allocSize, intendedType, &key);
uint32_t scratchFlags = 0;
if (flags & kNoPendingIO_Flag) {
scratchFlags = GrResourceCache::kRequireNoPendingIO_ScratchFlag;
} else {
scratchFlags = GrResourceCache::kPreferNoPendingIO_ScratchFlag;
}
GrBuffer* buffer = static_cast<GrBuffer*>(
this->cache()->findAndRefScratchResource(key, allocSize, scratchFlags));
if (!buffer) {
buffer = this->gpu()->createBuffer(allocSize, intendedType, kDynamic_GrAccessPattern);
if (!buffer) {
return nullptr;
}
}
if (data) {
buffer->updateData(data, size);
}
SkASSERT(!buffer->isCPUBacked()); // We should only cache real VBOs.
return buffer;
}
bool GrResourceProvider::attachStencilAttachment(GrRenderTarget* rt) {
SkASSERT(rt);
if (rt->renderTargetPriv().getStencilAttachment()) {
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
SkDEBUGCODE(bool newStencil = false;)
GrStencilAttachment::ComputeSharedStencilAttachmentKey(width, height,
rt->numStencilSamples(), &sbKey);
auto stencil = this->findByUniqueKey<GrStencilAttachment>(sbKey);
if (!stencil) {
// Need to try and create a new stencil
stencil.reset(this->gpu()->createStencilAttachmentForRenderTarget(rt, width, height));
if (stencil) {
this->assignUniqueKeyToResource(sbKey, stencil.get());
SkDEBUGCODE(newStencil = true;)
}
}
if (rt->renderTargetPriv().attachStencilAttachment(std::move(stencil))) {
#ifdef SK_DEBUG
// Fill the SB with an inappropriate value. opLists that use the
// SB should clear it properly.
if (newStencil) {
SkASSERT(rt->renderTargetPriv().getStencilAttachment()->isDirty());
this->gpu()->clearStencil(rt, 0xFFFF);
SkASSERT(rt->renderTargetPriv().getStencilAttachment()->isDirty());
}
#endif
}
}
return SkToBool(rt->renderTargetPriv().getStencilAttachment());
}
sk_sp<GrRenderTarget> GrResourceProvider::wrapBackendTextureAsRenderTarget(
const GrBackendTexture& tex, int sampleCnt)
{
if (this->isAbandoned()) {
return nullptr;
}
return fGpu->wrapBackendTextureAsRenderTarget(tex, sampleCnt);
}
sk_sp<GrSemaphore> SK_WARN_UNUSED_RESULT GrResourceProvider::makeSemaphore(bool isOwned) {
return fGpu->makeSemaphore(isOwned);
}
sk_sp<GrSemaphore> GrResourceProvider::wrapBackendSemaphore(const GrBackendSemaphore& semaphore,
SemaphoreWrapType wrapType,
GrWrapOwnership ownership) {
ASSERT_SINGLE_OWNER
return this->isAbandoned() ? nullptr : fGpu->wrapBackendSemaphore(semaphore,
wrapType,
ownership);
}
void GrResourceProvider::takeOwnershipOfSemaphore(sk_sp<GrSemaphore> semaphore) {
semaphore->resetGpu(fGpu);
}
void GrResourceProvider::releaseOwnershipOfSemaphore(sk_sp<GrSemaphore> semaphore) {
semaphore->resetGpu(nullptr);
}