/* * 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 "SkBitmap.h" #include "SkBitmapCache.h" #include "SkImage_Base.h" #include "SkImageCacherator.h" #include "SkMallocPixelRef.h" #include "SkNextID.h" #include "SkPixelRef.h" #include "SkResourceCache.h" #if SK_SUPPORT_GPU #include "GrContext.h" #include "GrGpuResourcePriv.h" #include "GrImageIDTextureAdjuster.h" #include "GrResourceKey.h" #include "GrTextureParams.h" #include "GrYUVProvider.h" #include "SkGr.h" #include "SkGrPriv.h" #endif SkImageCacherator* SkImageCacherator::NewFromGenerator(SkImageGenerator* gen, const SkIRect* subset) { if (!gen) { return nullptr; } // We are required to take ownership of gen, regardless of if we return a cacherator or not SkAutoTDelete genHolder(gen); const SkImageInfo& info = gen->getInfo(); if (info.isEmpty()) { return nullptr; } uint32_t uniqueID = gen->uniqueID(); const SkIRect bounds = SkIRect::MakeWH(info.width(), info.height()); if (subset) { if (!bounds.contains(*subset)) { return nullptr; } if (*subset != bounds) { // we need a different uniqueID since we really are a subset of the raw generator uniqueID = SkNextID::ImageID(); } } else { subset = &bounds; } // Now that we know we can hand-off the generator (to be owned by the cacherator) we can // release our holder. (we DONT want to delete it here anymore) genHolder.release(); return new SkImageCacherator(gen, gen->getInfo().makeWH(subset->width(), subset->height()), SkIPoint::Make(subset->x(), subset->y()), uniqueID); } SkImageCacherator::SkImageCacherator(SkImageGenerator* gen, const SkImageInfo& info, const SkIPoint& origin, uint32_t uniqueID) : fNotThreadSafeGenerator(gen) , fInfo(info) , fOrigin(origin) , fUniqueID(uniqueID) {} SkData* SkImageCacherator::refEncoded(GrContext* ctx) { ScopedGenerator generator(this); return generator->refEncodedData(ctx); } static bool check_output_bitmap(const SkBitmap& bitmap, uint32_t expectedID) { SkASSERT(bitmap.getGenerationID() == expectedID); SkASSERT(bitmap.isImmutable()); SkASSERT(bitmap.getPixels()); return true; } // Note, this returns a new, mutable, bitmap, with a new genID. // If you want the immutable bitmap with the same ID as our cacherator, call tryLockAsBitmap() // bool SkImageCacherator::generateBitmap(SkBitmap* bitmap) { SkBitmap::Allocator* allocator = SkResourceCache::GetAllocator(); ScopedGenerator generator(this); const SkImageInfo& genInfo = generator->getInfo(); if (fInfo.dimensions() == genInfo.dimensions()) { SkASSERT(fOrigin.x() == 0 && fOrigin.y() == 0); // fast-case, no copy needed return generator->tryGenerateBitmap(bitmap, fInfo, allocator); } else { // need to handle subsetting, so we first generate the full size version, and then // "read" from it to get our subset. See https://bug.skia.org/4213 SkBitmap full; if (!generator->tryGenerateBitmap(&full, genInfo, allocator)) { return false; } if (!bitmap->tryAllocPixels(fInfo, nullptr, full.getColorTable())) { return false; } return full.readPixels(bitmap->info(), bitmap->getPixels(), bitmap->rowBytes(), fOrigin.x(), fOrigin.y()); } } bool SkImageCacherator::directGeneratePixels(const SkImageInfo& info, void* pixels, size_t rb, int srcX, int srcY) { ScopedGenerator generator(this); const SkImageInfo& genInfo = generator->getInfo(); // Currently generators do not natively handle subsets, so check that first. if (srcX || srcY || genInfo.width() != info.width() || genInfo.height() != info.height()) { return false; } return generator->getPixels(info, pixels, rb); } ////////////////////////////////////////////////////////////////////////////////////////////////// bool SkImageCacherator::lockAsBitmapOnlyIfAlreadyCached(SkBitmap* bitmap) { return SkBitmapCache::Find(fUniqueID, bitmap) && check_output_bitmap(*bitmap, fUniqueID); } bool SkImageCacherator::tryLockAsBitmap(SkBitmap* bitmap, const SkImage* client, SkImage::CachingHint chint) { if (this->lockAsBitmapOnlyIfAlreadyCached(bitmap)) { return true; } if (!this->generateBitmap(bitmap)) { return false; } bitmap->pixelRef()->setImmutableWithID(fUniqueID); if (SkImage::kAllow_CachingHint == chint) { SkBitmapCache::Add(fUniqueID, *bitmap); if (client) { as_IB(client)->notifyAddedToCache(); } } return true; } bool SkImageCacherator::lockAsBitmap(SkBitmap* bitmap, const SkImage* client, SkImage::CachingHint chint) { if (this->tryLockAsBitmap(bitmap, client, chint)) { return check_output_bitmap(*bitmap, fUniqueID); } #if SK_SUPPORT_GPU // Try to get a texture and read it back to raster (and then cache that with our ID) SkAutoTUnref tex; { ScopedGenerator generator(this); SkIRect subset = SkIRect::MakeXYWH(fOrigin.x(), fOrigin.y(), fInfo.width(), fInfo.height()); tex.reset(generator->generateTexture(nullptr, &subset)); } if (!tex) { bitmap->reset(); return false; } if (!bitmap->tryAllocPixels(fInfo)) { bitmap->reset(); return false; } const uint32_t pixelOpsFlags = 0; if (!tex->readPixels(0, 0, bitmap->width(), bitmap->height(), SkImageInfo2GrPixelConfig(fInfo), bitmap->getPixels(), bitmap->rowBytes(), pixelOpsFlags)) { bitmap->reset(); return false; } bitmap->pixelRef()->setImmutableWithID(fUniqueID); if (SkImage::kAllow_CachingHint == chint) { SkBitmapCache::Add(fUniqueID, *bitmap); if (client) { as_IB(client)->notifyAddedToCache(); } } return check_output_bitmap(*bitmap, fUniqueID); #else return false; #endif } ////////////////////////////////////////////////////////////////////////////////////////////////// #if SK_SUPPORT_GPU static GrTexture* load_compressed_into_texture(GrContext* ctx, SkData* data, GrSurfaceDesc desc) { const void* rawStart; GrPixelConfig config = GrIsCompressedTextureDataSupported(ctx, data, desc.fWidth, desc.fHeight, &rawStart); if (kUnknown_GrPixelConfig == config) { return nullptr; } desc.fConfig = config; return ctx->textureProvider()->createTexture(desc, SkBudgeted::kYes, rawStart, 0); } class Generator_GrYUVProvider : public GrYUVProvider { SkImageGenerator* fGen; public: Generator_GrYUVProvider(SkImageGenerator* gen) : fGen(gen) {} uint32_t onGetID() override { return fGen->uniqueID(); } bool onQueryYUV8(SkYUVSizeInfo* sizeInfo, SkYUVColorSpace* colorSpace) const override { return fGen->queryYUV8(sizeInfo, colorSpace); } bool onGetYUV8Planes(const SkYUVSizeInfo& sizeInfo, void* planes[3]) override { return fGen->getYUV8Planes(sizeInfo, planes); } }; static GrTexture* set_key_and_return(GrTexture* tex, const GrUniqueKey& key) { if (key.isValid()) { tex->resourcePriv().setUniqueKey(key); } return tex; } /* * We have a 5 ways to try to return a texture (in sorted order) * * 1. Check the cache for a pre-existing one * 2. Ask the generator to natively create one * 3. Ask the generator to return a compressed form that the GPU might support * 4. Ask the generator to return YUV planes, which the GPU can convert * 5. Ask the generator to return RGB(A) data, which the GPU can convert */ GrTexture* SkImageCacherator::lockTexture(GrContext* ctx, const GrUniqueKey& key, const SkImage* client, SkImage::CachingHint chint, bool willBeMipped) { // Values representing the various texture lock paths we can take. Used for logging the path // taken to a histogram. enum LockTexturePath { kFailure_LockTexturePath, kPreExisting_LockTexturePath, kNative_LockTexturePath, kCompressed_LockTexturePath, kYUV_LockTexturePath, kRGBA_LockTexturePath, }; enum { kLockTexturePathCount = kRGBA_LockTexturePath + 1 }; // 1. Check the cache for a pre-existing one if (key.isValid()) { if (GrTexture* tex = ctx->textureProvider()->findAndRefTextureByUniqueKey(key)) { SK_HISTOGRAM_ENUMERATION("LockTexturePath", kPreExisting_LockTexturePath, kLockTexturePathCount); return tex; } } // 2. Ask the generator to natively create one { ScopedGenerator generator(this); SkIRect subset = SkIRect::MakeXYWH(fOrigin.x(), fOrigin.y(), fInfo.width(), fInfo.height()); if (GrTexture* tex = generator->generateTexture(ctx, &subset)) { SK_HISTOGRAM_ENUMERATION("LockTexturePath", kNative_LockTexturePath, kLockTexturePathCount); return set_key_and_return(tex, key); } } const GrSurfaceDesc desc = GrImageInfoToSurfaceDesc(fInfo); // 3. Ask the generator to return a compressed form that the GPU might support SkAutoTUnref data(this->refEncoded(ctx)); if (data) { GrTexture* tex = load_compressed_into_texture(ctx, data, desc); if (tex) { SK_HISTOGRAM_ENUMERATION("LockTexturePath", kCompressed_LockTexturePath, kLockTexturePathCount); return set_key_and_return(tex, key); } } // 4. Ask the generator to return YUV planes, which the GPU can convert { ScopedGenerator generator(this); Generator_GrYUVProvider provider(generator); GrTexture* tex = provider.refAsTexture(ctx, desc, true); if (tex) { SK_HISTOGRAM_ENUMERATION("LockTexturePath", kYUV_LockTexturePath, kLockTexturePathCount); return set_key_and_return(tex, key); } } // 5. Ask the generator to return RGB(A) data, which the GPU can convert SkBitmap bitmap; if (this->tryLockAsBitmap(&bitmap, client, chint)) { GrTexture* tex = nullptr; // disable mipmapping until we generate anisotropic mipmap levels willBeMipped = false; if (willBeMipped) { tex = GrGenerateMipMapsAndUploadToTexture(ctx, bitmap); } else { tex = GrUploadBitmapToTexture(ctx, bitmap); } if (tex) { SK_HISTOGRAM_ENUMERATION("LockTexturePath", kRGBA_LockTexturePath, kLockTexturePathCount); return set_key_and_return(tex, key); } } SK_HISTOGRAM_ENUMERATION("LockTexturePath", kFailure_LockTexturePath, kLockTexturePathCount); return nullptr; } /////////////////////////////////////////////////////////////////////////////////////////////////// GrTexture* SkImageCacherator::lockAsTexture(GrContext* ctx, const GrTextureParams& params, const SkImage* client, SkImage::CachingHint chint) { if (!ctx) { return nullptr; } return GrImageTextureMaker(ctx, this, client, chint).refTextureForParams(params); } #else GrTexture* SkImageCacherator::lockAsTexture(GrContext* ctx, const GrTextureParams&, const SkImage* client, SkImage::CachingHint) { return nullptr; } #endif