create SkBitmapProvider to abstract images and bitmaps

BUG=skia:

Review URL: https://codereview.chromium.org/1340223003
This commit is contained in:
reed 2015-09-15 12:38:12 -07:00 committed by Commit bot
parent 5598b63cd2
commit 98ed7b6d01
5 changed files with 232 additions and 75 deletions

View File

@ -6,6 +6,7 @@
*/
#include "SkBitmapCache.h"
#include "SkImage.h"
#include "SkResourceCache.h"
#include "SkMipMap.h"
#include "SkPixelRef.h"
@ -43,6 +44,41 @@ static SkIRect get_bounds_from_bitmap(const SkBitmap& bm) {
return SkIRect::MakeXYWH(origin.fX, origin.fY, bm.width(), bm.height());
}
/**
* This function finds the bounds of the image. Today this is just the entire bounds,
* but in the future we may support subsets within an image, in which case this should
* return that subset (see get_bounds_from_bitmap).
*/
static SkIRect get_bounds_from_image(const SkImage* image) {
return SkIRect::MakeWH(image->width(), image->height());
}
SkBitmapCacheDesc SkBitmapCacheDesc::Make(const SkBitmap& bm, int width, int height) {
SkBitmapCacheDesc desc;
desc.fImageID = bm.getGenerationID();
desc.fWidth = width;
desc.fHeight = height;
desc.fBounds = get_bounds_from_bitmap(bm);
return desc;
}
SkBitmapCacheDesc SkBitmapCacheDesc::Make(const SkBitmap& bm) {
return Make(bm, bm.width(), bm.height());
}
SkBitmapCacheDesc SkBitmapCacheDesc::Make(const SkImage* image, int width, int height) {
SkBitmapCacheDesc desc;
desc.fImageID = image->uniqueID();
desc.fWidth = width;
desc.fHeight = height;
desc.fBounds = get_bounds_from_image(image);
return desc;
}
SkBitmapCacheDesc SkBitmapCacheDesc::Make(const SkImage* image) {
return Make(image, image->width(), image->height());
}
namespace {
static unsigned gBitmapKeyNamespaceLabel;
@ -54,7 +90,17 @@ public:
, fHeight(height)
, fBounds(bounds)
{
this->init(&gBitmapKeyNamespaceLabel, SkMakeResourceCacheSharedIDForBitmap(genID),
this->init(&gBitmapKeyNamespaceLabel, SkMakeResourceCacheSharedIDForBitmap(fGenID),
sizeof(fGenID) + sizeof(fWidth) + sizeof(fHeight) + sizeof(fBounds));
}
BitmapKey(const SkBitmapCacheDesc& desc)
: fGenID(desc.fImageID)
, fWidth(desc.fWidth)
, fHeight(desc.fHeight)
, fBounds(desc.fBounds)
{
this->init(&gBitmapKeyNamespaceLabel, SkMakeResourceCacheSharedIDForBitmap(fGenID),
sizeof(fGenID) + sizeof(fWidth) + sizeof(fHeight) + sizeof(fBounds));
}
@ -80,6 +126,15 @@ struct BitmapRec : public SkResourceCache::Rec {
#endif
}
BitmapRec(const SkBitmapCacheDesc& desc, const SkBitmap& result)
: fKey(desc)
, fBitmap(result)
{
#ifdef TRACE_NEW_BITMAP_CACHE_RECS
fKey.dump();
#endif
}
const Key& getKey() const override { return fKey; }
size_t bytesUsed() const override { return sizeof(fKey) + fBitmap.getSize(); }
@ -106,28 +161,25 @@ private:
#define CHECK_LOCAL(localCache, localName, globalName, ...) \
((localCache) ? localCache->localName(__VA_ARGS__) : SkResourceCache::globalName(__VA_ARGS__))
bool SkBitmapCache::FindWH(const SkBitmap& src, int width, int height, SkBitmap* result,
bool SkBitmapCache::FindWH(const SkBitmapCacheDesc& desc, SkBitmap* result,
SkResourceCache* localCache) {
if (0 == width || 0 == height) {
if (0 == desc.fWidth || 0 == desc.fHeight) {
// degenerate
return false;
}
return CHECK_LOCAL(localCache, find, Find, BitmapKey(desc), BitmapRec::Finder, result);
}
bool SkBitmapCache::AddWH(const SkBitmapCacheDesc& desc, const SkBitmap& result,
SkResourceCache* localCache) {
if (0 == desc.fWidth || 0 == desc.fHeight) {
// degenerate, and the key we use for mipmaps
return false;
}
BitmapKey key(src.getGenerationID(), width, height, get_bounds_from_bitmap(src));
return CHECK_LOCAL(localCache, find, Find, key, BitmapRec::Finder, result);
}
void SkBitmapCache::AddWH(const SkBitmap& src, int width, int height,
const SkBitmap& result, SkResourceCache* localCache) {
if (0 == width || 0 == height) {
// degenerate, and the key we use for mipmaps
return;
}
SkASSERT(result.isImmutable());
BitmapRec* rec = new BitmapRec(src.getGenerationID(), width, height,
get_bounds_from_bitmap(src), result);
BitmapRec* rec = new BitmapRec(desc, result);
CHECK_LOCAL(localCache, add, Add, rec);
src.pixelRef()->notifyAddedToCache();
return true;
}
bool SkBitmapCache::Find(uint32_t genID, const SkIRect& subset, SkBitmap* result,
@ -226,8 +278,10 @@ private:
};
}
const SkMipMap* SkMipMapCache::FindAndRef(const SkBitmap& src, SkResourceCache* localCache) {
MipMapKey key(src.getGenerationID(), get_bounds_from_bitmap(src));
const SkMipMap* SkMipMapCache::FindAndRef(const SkBitmapCacheDesc& desc,
SkResourceCache* localCache) {
// Note: we ignore width/height from desc, just need id and bounds
MipMapKey key(desc.fImageID, desc.fBounds);
const SkMipMap* result;
if (!CHECK_LOCAL(localCache, find, Find, key, MipMapRec::Finder, &result)) {

View File

@ -11,6 +11,7 @@
#include "SkScalar.h"
#include "SkBitmap.h"
class SkImage;
class SkResourceCache;
class SkMipMap;
@ -18,6 +19,18 @@ uint64_t SkMakeResourceCacheSharedIDForBitmap(uint32_t bitmapGenID);
void SkNotifyBitmapGenIDIsStale(uint32_t bitmapGenID);
struct SkBitmapCacheDesc {
uint32_t fImageID;
int32_t fWidth;
int32_t fHeight;
SkIRect fBounds;
static SkBitmapCacheDesc Make(const SkBitmap&, int width, int height);
static SkBitmapCacheDesc Make(const SkBitmap&);
static SkBitmapCacheDesc Make(const SkImage*, int width, int height);
static SkBitmapCacheDesc Make(const SkImage*);
};
class SkBitmapCache {
public:
/**
@ -27,16 +40,16 @@ public:
static SkBitmap::Allocator* GetAllocator();
/**
* Search based on the src bitmap and scaled width/height. If found, returns true and
* Search based on the desc. If found, returns true and
* result will be set to the matching bitmap with its pixels already locked.
*/
static bool FindWH(const SkBitmap& src, int width, int height, SkBitmap* result,
static bool FindWH(const SkBitmapCacheDesc&, SkBitmap* result,
SkResourceCache* localCache = nullptr);
/*
* result must be marked isImmutable()
*/
static void AddWH(const SkBitmap& src, int width, int height, const SkBitmap& result,
static bool AddWH(const SkBitmapCacheDesc&, const SkBitmap& result,
SkResourceCache* localCache = nullptr);
/**
@ -60,7 +73,8 @@ public:
class SkMipMapCache {
public:
static const SkMipMap* FindAndRef(const SkBitmap& src, SkResourceCache* localCache = nullptr);
static const SkMipMap* FindAndRef(const SkBitmapCacheDesc&,
SkResourceCache* localCache = nullptr);
static const SkMipMap* AddAndRef(const SkBitmap& src, SkResourceCache* localCache = nullptr);
};

View File

@ -7,7 +7,9 @@
#include "SkBitmap.h"
#include "SkBitmapController.h"
#include "SkImage_Base.h"
#include "SkMatrix.h"
#include "SkPixelRef.h"
#include "SkTemplates.h"
// RESIZE_LANCZOS3 is another good option, but chrome prefers mitchell at the moment
@ -15,36 +17,85 @@
///////////////////////////////////////////////////////////////////////////////////////////////////
static bool valid_for_drawing(const SkBitmap& bm) {
if (0 == bm.width() || 0 == bm.height()) {
return false; // nothing to draw
}
if (nullptr == bm.pixelRef()) {
return false; // no pixels to read
}
if (bm.getTexture()) {
// we can handle texture (ugh) since lockPixels will perform a read-back
return true;
}
if (kIndex_8_SkColorType == bm.colorType()) {
SkAutoLockPixels alp(bm); // but we need to call it before getColorTable() is safe.
if (!bm.getColorTable()) {
int SkBitmapProvider::width() const {
return fImage ? fImage->width() : fBitmap.width();
}
int SkBitmapProvider::height() const {
return fImage ? fImage->height() : fBitmap.height();
}
uint32_t SkBitmapProvider::getID() const {
return fImage ? fImage->uniqueID() : fBitmap.getGenerationID();
}
bool SkBitmapProvider::validForDrawing() const {
if (!fImage) {
if (0 == fBitmap.width() || 0 == fBitmap.height()) {
return false;
}
if (nullptr == fBitmap.pixelRef()) {
return false; // no pixels to read
}
if (fBitmap.getTexture()) {
// we can handle texture (ugh) since lockPixels will perform a read-back
return true;
}
if (kIndex_8_SkColorType == fBitmap.colorType()) {
SkAutoLockPixels alp(fBitmap); // but we need to call it before getColorTable() is safe.
if (!fBitmap.getColorTable()) {
return false;
}
}
}
return true;
}
SkBitmapController::State* SkBitmapController::requestBitmap(const SkBitmap& bm,
SkImageInfo SkBitmapProvider::info() const {
if (fImage) {
SkAlphaType at = fImage->isOpaque() ? kOpaque_SkAlphaType : kPremul_SkAlphaType;
return SkImageInfo::MakeN32(fImage->width(), fImage->height(), at);
} else {
return fBitmap.info();
}
}
SkBitmapCacheDesc SkBitmapProvider::makeCacheDesc(int w, int h) const {
return fImage ? SkBitmapCacheDesc::Make(fImage, w, h) : SkBitmapCacheDesc::Make(fBitmap, w, h);
}
SkBitmapCacheDesc SkBitmapProvider::makeCacheDesc() const {
return fImage ? SkBitmapCacheDesc::Make(fImage) : SkBitmapCacheDesc::Make(fBitmap);
}
void SkBitmapProvider::notifyAddedToCache() const {
if (fImage) {
// TODO
} else {
fBitmap.pixelRef()->notifyAddedToCache();
}
}
bool SkBitmapProvider::asBitmap(SkBitmap* bm) const {
if (fImage) {
return as_IB(fImage)->getROPixels(bm);
} else {
*bm = fBitmap;
return true;
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
SkBitmapController::State* SkBitmapController::requestBitmap(const SkBitmapProvider& provider,
const SkMatrix& inv,
SkFilterQuality quality,
void* storage, size_t storageSize) {
if (!valid_for_drawing(bm)) {
if (!provider.validForDrawing()) {
return nullptr;
}
State* state = this->onRequestBitmap(bm, inv, quality, storage, storageSize);
State* state = this->onRequestBitmap(provider, inv, quality, storage, storageSize);
if (state) {
if (nullptr == state->fPixmap.addr()) {
SkInPlaceDeleteCheck(state, storage);
@ -63,19 +114,19 @@ SkBitmapController::State* SkBitmapController::requestBitmap(const SkBitmap& bm,
class SkDefaultBitmapControllerState : public SkBitmapController::State {
public:
SkDefaultBitmapControllerState(const SkBitmap& src, const SkMatrix& inv, SkFilterQuality qual);
SkDefaultBitmapControllerState(const SkBitmapProvider&, const SkMatrix& inv, SkFilterQuality);
private:
SkBitmap fResultBitmap;
SkAutoTUnref<const SkMipMap> fCurrMip;
bool processHQRequest(const SkBitmap& orig);
bool processMediumRequest(const SkBitmap& orig);
bool processHQRequest(const SkBitmapProvider&);
bool processMediumRequest(const SkBitmapProvider&);
};
// Check to see that the size of the bitmap that would be produced by
// scaling by the given inverted matrix is less than the maximum allowed.
static inline bool cache_size_okay(const SkBitmap& bm, const SkMatrix& invMat) {
static inline bool cache_size_okay(const SkBitmapProvider& provider, const SkMatrix& invMat) {
size_t maximumAllocation = SkResourceCache::GetEffectiveSingleAllocationByteLimit();
if (0 == maximumAllocation) {
return true;
@ -83,7 +134,7 @@ static inline bool cache_size_okay(const SkBitmap& bm, const SkMatrix& invMat) {
// float matrixScaleFactor = 1.0 / (invMat.scaleX * invMat.scaleY);
// return ((origBitmapSize * matrixScaleFactor) < maximumAllocationSize);
// Skip the division step:
const size_t size = bm.info().getSafeSize(bm.info().minRowBytes());
const size_t size = provider.info().getSafeSize(provider.info().minRowBytes());
return size < (maximumAllocation * invMat.getScaleX() * invMat.getScaleY());
}
@ -91,7 +142,7 @@ static inline bool cache_size_okay(const SkBitmap& bm, const SkMatrix& invMat) {
* High quality is implemented by performing up-right scale-only filtering and then
* using bilerp for any remaining transformations.
*/
bool SkDefaultBitmapControllerState::processHQRequest(const SkBitmap& origBitmap) {
bool SkDefaultBitmapControllerState::processHQRequest(const SkBitmapProvider& provider) {
if (fQuality != kHigh_SkFilterQuality) {
return false;
}
@ -100,7 +151,7 @@ bool SkDefaultBitmapControllerState::processHQRequest(const SkBitmap& origBitmap
// to a valid bitmap. If we succeed, we will set this to Low instead.
fQuality = kMedium_SkFilterQuality;
if (kN32_SkColorType != origBitmap.colorType() || !cache_size_okay(origBitmap, fInvMatrix) ||
if (kN32_SkColorType != provider.info().colorType() || !cache_size_okay(provider, fInvMatrix) ||
fInvMatrix.hasPerspective())
{
return false; // can't handle the reqeust
@ -120,12 +171,17 @@ bool SkDefaultBitmapControllerState::processHQRequest(const SkBitmap& origBitmap
return false; // no need for HQ
}
const int dstW = SkScalarRoundToScalar(origBitmap.width() / invScaleX);
const int dstH = SkScalarRoundToScalar(origBitmap.height() / invScaleY);
if (!SkBitmapCache::FindWH(origBitmap, dstW, dstH, &fResultBitmap)) {
const int dstW = SkScalarRoundToScalar(provider.width() / invScaleX);
const int dstH = SkScalarRoundToScalar(provider.height() / invScaleY);
const SkBitmapCacheDesc desc = provider.makeCacheDesc(dstW, dstH);
if (!SkBitmapCache::FindWH(desc, &fResultBitmap)) {
SkBitmap orig;
if (!provider.asBitmap(&orig)) {
return false;
}
SkAutoPixmapUnlock src;
if (!origBitmap.requestLock(&src)) {
if (!orig.requestLock(&src)) {
return false;
}
if (!SkBitmapScaler::Resize(&fResultBitmap, src.pixmap(), kHQ_RESIZE_METHOD,
@ -135,13 +191,15 @@ bool SkDefaultBitmapControllerState::processHQRequest(const SkBitmap& origBitmap
SkASSERT(fResultBitmap.getPixels());
fResultBitmap.setImmutable();
SkBitmapCache::AddWH(origBitmap, dstW, dstH, fResultBitmap);
if (SkBitmapCache::AddWH(desc, fResultBitmap)) {
provider.notifyAddedToCache();
}
}
SkASSERT(fResultBitmap.getPixels());
fInvMatrix.postScale(SkIntToScalar(dstW) / origBitmap.width(),
SkIntToScalar(dstH) / origBitmap.height());
fInvMatrix.postScale(SkIntToScalar(dstW) / provider.width(),
SkIntToScalar(dstH) / provider.height());
fQuality = kLow_SkFilterQuality;
return true;
}
@ -150,7 +208,7 @@ bool SkDefaultBitmapControllerState::processHQRequest(const SkBitmap& origBitmap
* Modulo internal errors, this should always succeed *if* the matrix is downscaling
* (in this case, we have the inverse, so it succeeds if fInvMatrix is upscaling)
*/
bool SkDefaultBitmapControllerState::processMediumRequest(const SkBitmap& origBitmap) {
bool SkDefaultBitmapControllerState::processMediumRequest(const SkBitmapProvider& provider) {
SkASSERT(fQuality <= kMedium_SkFilterQuality);
if (fQuality != kMedium_SkFilterQuality) {
return false;
@ -167,9 +225,13 @@ bool SkDefaultBitmapControllerState::processMediumRequest(const SkBitmap& origBi
SkScalar invScale = SkScalarSqrt(invScaleSize.width() * invScaleSize.height());
if (invScale > SK_Scalar1) {
fCurrMip.reset(SkMipMapCache::FindAndRef(origBitmap));
fCurrMip.reset(SkMipMapCache::FindAndRef(provider.makeCacheDesc()));
if (nullptr == fCurrMip.get()) {
fCurrMip.reset(SkMipMapCache::AddAndRef(origBitmap));
SkBitmap orig;
if (!provider.asBitmap(&orig)) {
return false;
}
fCurrMip.reset(SkMipMapCache::AddAndRef(orig));
if (nullptr == fCurrMip.get()) {
return false;
}
@ -185,7 +247,7 @@ bool SkDefaultBitmapControllerState::processMediumRequest(const SkBitmap& origBi
SkScalar invScaleFixup = level.fScale;
fInvMatrix.postScale(invScaleFixup, invScaleFixup);
const SkImageInfo info = origBitmap.info().makeWH(level.fWidth, level.fHeight);
const SkImageInfo info = provider.info().makeWH(level.fWidth, level.fHeight);
// todo: if we could wrap the fCurrMip in a pixelref, then we could just install
// that here, and not need to explicitly track it ourselves.
return fResultBitmap.installPixels(info, level.fPixels, level.fRowBytes);
@ -197,16 +259,16 @@ bool SkDefaultBitmapControllerState::processMediumRequest(const SkBitmap& origBi
return false;
}
SkDefaultBitmapControllerState::SkDefaultBitmapControllerState(const SkBitmap& src,
SkDefaultBitmapControllerState::SkDefaultBitmapControllerState(const SkBitmapProvider& provider,
const SkMatrix& inv,
SkFilterQuality qual) {
fInvMatrix = inv;
fQuality = qual;
if (this->processHQRequest(src) || this->processMediumRequest(src)) {
if (this->processHQRequest(provider) || this->processMediumRequest(provider)) {
SkASSERT(fResultBitmap.getPixels());
} else {
fResultBitmap = src;
(void)provider.asBitmap(&fResultBitmap);
fResultBitmap.lockPixels();
// lock may fail to give us pixels
}
@ -218,7 +280,7 @@ SkDefaultBitmapControllerState::SkDefaultBitmapControllerState(const SkBitmap& s
fResultBitmap.getColorTable());
}
SkBitmapController::State* SkDefaultBitmapController::onRequestBitmap(const SkBitmap& bm,
SkBitmapController::State* SkDefaultBitmapController::onRequestBitmap(const SkBitmapProvider& bm,
const SkMatrix& inverse,
SkFilterQuality quality,
void* storage, size_t size) {

View File

@ -9,9 +9,36 @@
#define SkBitmapController_DEFINED
#include "SkBitmap.h"
#include "SkBitmapCache.h"
#include "SkFilterQuality.h"
#include "SkImage.h"
#include "SkMatrix.h"
class SkBitmapProvider {
public:
SkBitmapProvider(const SkBitmap& bm) : fBitmap(bm) {}
SkBitmapProvider(const SkImage* img) : fImage(SkRef(img)) {}
int width() const;
int height() const;
uint32_t getID() const;
bool validForDrawing() const;
SkImageInfo info() const;
SkBitmapCacheDesc makeCacheDesc(int w, int h) const;
SkBitmapCacheDesc makeCacheDesc() const;
void notifyAddedToCache() const;
// Only call this if you're sure you need the bits, since it make be expensive
// ... cause a decode and cache, or gpu-readback
bool asBitmap(SkBitmap*) const;
private:
SkBitmap fBitmap;
SkAutoTUnref<const SkImage> fImage;
};
/**
* Handles request to scale, filter, and lock a bitmap to be rasterized.
*/
@ -36,15 +63,15 @@ public:
virtual ~SkBitmapController() {}
State* requestBitmap(const SkBitmap&, const SkMatrix& inverse, SkFilterQuality,
State* requestBitmap(const SkBitmapProvider&, const SkMatrix& inverse, SkFilterQuality,
void* storage, size_t storageSize);
State* requestBitmap(const SkBitmap& bm, const SkMatrix& inverse, SkFilterQuality quality) {
return this->requestBitmap(bm, inverse, quality, nullptr, 0);
State* requestBitmap(const SkBitmapProvider& bp, const SkMatrix& inv, SkFilterQuality quality) {
return this->requestBitmap(bp, inv, quality, nullptr, 0);
}
protected:
virtual State* onRequestBitmap(const SkBitmap&, const SkMatrix& inverse, SkFilterQuality,
virtual State* onRequestBitmap(const SkBitmapProvider&, const SkMatrix& inv, SkFilterQuality,
void* storage, size_t storageSize) = 0;
};
@ -55,7 +82,7 @@ public:
SkDefaultBitmapController() {}
protected:
State* onRequestBitmap(const SkBitmap&, const SkMatrix& inverse, SkFilterQuality,
State* onRequestBitmap(const SkBitmapProvider&, const SkMatrix& inverse, SkFilterQuality,
void* storage, size_t storageSize) override;
};

View File

@ -23,7 +23,7 @@ static bool is_in_scaled_image_cache(const SkBitmap& orig,
SkBitmap scaled;
int width = SkScalarRoundToInt(orig.width() * xScale);
int height = SkScalarRoundToInt(orig.height() * yScale);
return SkBitmapCache::FindWH(orig, width, height, &scaled);
return SkBitmapCache::FindWH(SkBitmapCacheDesc::Make(orig, width, height), &scaled);
}
// Draw a scaled bitmap, then return true if it has been cached.
@ -153,14 +153,14 @@ static void test_mipmapcache(skiatest::Reporter* reporter, SkResourceCache* cach
src.allocN32Pixels(5, 5);
src.setImmutable();
const SkMipMap* mipmap = SkMipMapCache::FindAndRef(src, cache);
const SkMipMap* mipmap = SkMipMapCache::FindAndRef(SkBitmapCacheDesc::Make(src), cache);
REPORTER_ASSERT(reporter, nullptr == mipmap);
mipmap = SkMipMapCache::AddAndRef(src, cache);
REPORTER_ASSERT(reporter, mipmap);
{
const SkMipMap* mm = SkMipMapCache::FindAndRef(src, cache);
const SkMipMap* mm = SkMipMapCache::FindAndRef(SkBitmapCacheDesc::Make(src), cache);
REPORTER_ASSERT(reporter, mm);
REPORTER_ASSERT(reporter, mm == mipmap);
mm->unref();
@ -174,7 +174,7 @@ static void test_mipmapcache(skiatest::Reporter* reporter, SkResourceCache* cach
check_data(reporter, mipmap, 1, kInCache, kNotLocked);
// find us again
mipmap = SkMipMapCache::FindAndRef(src, cache);
mipmap = SkMipMapCache::FindAndRef(SkBitmapCacheDesc::Make(src), cache);
check_data(reporter, mipmap, 2, kInCache, kLocked);
cache->purgeAll();
@ -193,7 +193,7 @@ static void test_mipmap_notify(skiatest::Reporter* reporter, SkResourceCache* ca
}
for (int i = 0; i < N; ++i) {
const SkMipMap* mipmap = SkMipMapCache::FindAndRef(src[i], cache);
const SkMipMap* mipmap = SkMipMapCache::FindAndRef(SkBitmapCacheDesc::Make(src[i]), cache);
if (cache) {
// if cache is null, we're working on the global cache, and other threads might purge
// it, making this check fragile.
@ -203,7 +203,7 @@ static void test_mipmap_notify(skiatest::Reporter* reporter, SkResourceCache* ca
src[i].reset(); // delete the underlying pixelref, which *should* remove us from the cache
mipmap = SkMipMapCache::FindAndRef(src[i], cache);
mipmap = SkMipMapCache::FindAndRef(SkBitmapCacheDesc::Make(src[i]), cache);
REPORTER_ASSERT(reporter, !mipmap);
}
}