| /* | |
| * 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 "src/gpu/gradients/GrGradientBitmapCache.h" | |
| #include "include/private/SkFloatBits.h" | |
| #include "include/private/SkHalf.h" | |
| #include "include/private/SkMalloc.h" | |
| #include "include/private/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 = std::min(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 | |
| SkAutoMutexExclusive 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 |