/* * Copyright 2018 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "GrGradientBitmapCache.h" #include "SkMalloc.h" #include "SkFloatBits.h" #include "SkHalf.h" #include "SkTemplates.h" #include <functional> struct GrGradientBitmapCache::Entry { Entry* fPrev; Entry* fNext; void* fBuffer; size_t fSize; SkBitmap fBitmap; Entry(const void* buffer, size_t size, const SkBitmap& bm) : fPrev(nullptr), fNext(nullptr), fBitmap(bm) { fBuffer = sk_malloc_throw(size); fSize = size; memcpy(fBuffer, buffer, size); } ~Entry() { sk_free(fBuffer); } bool equals(const void* buffer, size_t size) const { return (fSize == size) && !memcmp(fBuffer, buffer, size); } }; GrGradientBitmapCache::GrGradientBitmapCache(int max, int res) : fMaxEntries(max) , fResolution(res) { fEntryCount = 0; fHead = fTail = nullptr; this->validate(); } GrGradientBitmapCache::~GrGradientBitmapCache() { this->validate(); Entry* entry = fHead; while (entry) { Entry* next = entry->fNext; delete entry; entry = next; } } GrGradientBitmapCache::Entry* GrGradientBitmapCache::release(Entry* entry) const { if (entry->fPrev) { SkASSERT(fHead != entry); entry->fPrev->fNext = entry->fNext; } else { SkASSERT(fHead == entry); fHead = entry->fNext; } if (entry->fNext) { SkASSERT(fTail != entry); entry->fNext->fPrev = entry->fPrev; } else { SkASSERT(fTail == entry); fTail = entry->fPrev; } return entry; } void GrGradientBitmapCache::attachToHead(Entry* entry) const { entry->fPrev = nullptr; entry->fNext = fHead; if (fHead) { fHead->fPrev = entry; } else { fTail = entry; } fHead = entry; } bool GrGradientBitmapCache::find(const void* buffer, size_t size, SkBitmap* bm) const { AutoValidate av(this); Entry* entry = fHead; while (entry) { if (entry->equals(buffer, size)) { if (bm) { *bm = entry->fBitmap; } // move to the head of our list, so we purge it last this->release(entry); this->attachToHead(entry); return true; } entry = entry->fNext; } return false; } void GrGradientBitmapCache::add(const void* buffer, size_t len, const SkBitmap& bm) { AutoValidate av(this); if (fEntryCount == fMaxEntries) { SkASSERT(fTail); delete this->release(fTail); fEntryCount -= 1; } Entry* entry = new Entry(buffer, len, bm); this->attachToHead(entry); fEntryCount += 1; } /////////////////////////////////////////////////////////////////////////////// void GrGradientBitmapCache::fillGradient(const SkPMColor4f* colors, const SkScalar* positions, int count, SkColorType colorType, SkBitmap* bitmap) { SkHalf* pixelsF16 = reinterpret_cast<SkHalf*>(bitmap->getPixels()); uint32_t* pixels32 = reinterpret_cast<uint32_t*>(bitmap->getPixels()); typedef std::function<void(const Sk4f&, int)> pixelWriteFn_t; pixelWriteFn_t writeF16Pixel = [&](const Sk4f& x, int index) { Sk4h c = SkFloatToHalf_finite_ftz(x); pixelsF16[4*index+0] = c[0]; pixelsF16[4*index+1] = c[1]; pixelsF16[4*index+2] = c[2]; pixelsF16[4*index+3] = c[3]; }; pixelWriteFn_t write8888Pixel = [&](const Sk4f& c, int index) { pixels32[index] = Sk4f_toL32(c); }; pixelWriteFn_t writePixel = (colorType == kRGBA_F16_SkColorType) ? writeF16Pixel : write8888Pixel; int prevIndex = 0; for (int i = 1; i < count; i++) { // Historically, stops have been mapped to [0, 256], with 256 then nudged to the next // smaller value, then truncate for the texture index. This seems to produce the best // results for some common distributions, so we preserve the behavior. int nextIndex = SkTMin(positions[i] * fResolution, SkIntToScalar(fResolution - 1)); if (nextIndex > prevIndex) { Sk4f c0 = Sk4f::Load(colors[i - 1].vec()), c1 = Sk4f::Load(colors[i ].vec()); Sk4f step = Sk4f(1.0f / static_cast<float>(nextIndex - prevIndex)); Sk4f delta = (c1 - c0) * step; for (int curIndex = prevIndex; curIndex <= nextIndex; ++curIndex) { writePixel(c0, curIndex); c0 += delta; } } prevIndex = nextIndex; } SkASSERT(prevIndex == fResolution - 1); } void GrGradientBitmapCache::getGradient(const SkPMColor4f* colors, const SkScalar* positions, int count, SkColorType colorType, SkAlphaType alphaType, SkBitmap* bitmap) { // build our key: [numColors + colors[] + positions[] + alphaType + colorType ] static_assert(sizeof(SkPMColor4f) % sizeof(int32_t) == 0, ""); const int colorsAsIntCount = count * sizeof(SkPMColor4f) / sizeof(int32_t); int keyCount = 1 + colorsAsIntCount + 1 + 1; if (count > 2) { keyCount += count - 1; } SkAutoSTMalloc<64, int32_t> storage(keyCount); int32_t* buffer = storage.get(); *buffer++ = count; memcpy(buffer, colors, count * sizeof(SkPMColor4f)); buffer += colorsAsIntCount; if (count > 2) { for (int i = 1; i < count; i++) { *buffer++ = SkFloat2Bits(positions[i]); } } *buffer++ = static_cast<int32_t>(alphaType); *buffer++ = static_cast<int32_t>(colorType); SkASSERT(buffer - storage.get() == keyCount); /////////////////////////////////// // acquire lock for checking/adding to cache SkAutoExclusive ama(fMutex); size_t size = keyCount * sizeof(int32_t); if (!this->find(storage.get(), size, bitmap)) { SkImageInfo info = SkImageInfo::Make(fResolution, 1, colorType, alphaType); bitmap->allocPixels(info); GrGradientBitmapCache::fillGradient(colors, positions, count, colorType, bitmap); bitmap->setImmutable(); this->add(storage.get(), size, *bitmap); } } /////////////////////////////////////////////////////////////////////////////// #ifdef SK_DEBUG void GrGradientBitmapCache::validate() const { SkASSERT(fEntryCount >= 0 && fEntryCount <= fMaxEntries); if (fEntryCount > 0) { SkASSERT(nullptr == fHead->fPrev); SkASSERT(nullptr == fTail->fNext); if (fEntryCount == 1) { SkASSERT(fHead == fTail); } else { SkASSERT(fHead != fTail); } Entry* entry = fHead; int count = 0; while (entry) { count += 1; entry = entry->fNext; } SkASSERT(count == fEntryCount); entry = fTail; while (entry) { count -= 1; entry = entry->fPrev; } SkASSERT(0 == count); } else { SkASSERT(nullptr == fHead); SkASSERT(nullptr == fTail); } } #endif