From a7914d3a8e3b3256e8465dd5e0e98500cfbb3053 Mon Sep 17 00:00:00 2001 From: Michael Ludwig Date: Fri, 14 Sep 2018 09:47:21 -0400 Subject: [PATCH] Implement textured gradients Also clones SkGradientBitmapCache into GrGradientBitmapCache in the gpu/gradients folder. But after cleaning up the old gradient code, SkGradientBitmapCache will go away and SkGradientShader will have no reference to the bitmap cache or support for building bitmaps. The "new" GrGradientBitmapCache has been updated to hide the thread safety responsibilities and gradient bitmap generation code that had originally been a part of SkGradientShader. Bug: skia: Change-Id: Ida134c6437c866439fac44fa453d09a6a11549e7 Reviewed-on: https://skia-review.googlesource.com/150917 Commit-Queue: Michael Ludwig Reviewed-by: Brian Salomon --- gn/gpu.gni | 4 + gn/sksl.gni | 1 + src/gpu/GrProcessor.h | 1 + src/gpu/gradients/GrGradientBitmapCache.cpp | 249 ++++++++++++++++++ src/gpu/gradients/GrGradientBitmapCache.h | 62 +++++ src/gpu/gradients/GrGradientShader.cpp | 52 +++- .../gradients/GrTextureGradientColorizer.cpp | 58 ++++ .../gradients/GrTextureGradientColorizer.fp | 18 ++ .../gradients/GrTextureGradientColorizer.h | 39 +++ 9 files changed, 476 insertions(+), 8 deletions(-) create mode 100644 src/gpu/gradients/GrGradientBitmapCache.cpp create mode 100644 src/gpu/gradients/GrGradientBitmapCache.h create mode 100644 src/gpu/gradients/GrTextureGradientColorizer.cpp create mode 100644 src/gpu/gradients/GrTextureGradientColorizer.fp create mode 100644 src/gpu/gradients/GrTextureGradientColorizer.h diff --git a/gn/gpu.gni b/gn/gpu.gni index ffb5255e38..873f01e7ad 100644 --- a/gn/gpu.gni +++ b/gn/gpu.gni @@ -405,6 +405,8 @@ skia_gpu_sources = [ "$_src/gpu/gradients/GrDualIntervalGradientColorizer.h", "$_src/gpu/gradients/GrSingleIntervalGradientColorizer.cpp", "$_src/gpu/gradients/GrSingleIntervalGradientColorizer.h", + "$_src/gpu/gradients/GrTextureGradientColorizer.cpp", + "$_src/gpu/gradients/GrTextureGradientColorizer.h", "$_src/gpu/gradients/GrLinearGradientLayout.cpp", "$_src/gpu/gradients/GrLinearGradientLayout.h", "$_src/gpu/gradients/GrRadialGradientLayout.cpp", @@ -417,6 +419,8 @@ skia_gpu_sources = [ "$_src/gpu/gradients/GrClampedGradientEffect.h", "$_src/gpu/gradients/GrTiledGradientEffect.cpp", "$_src/gpu/gradients/GrTiledGradientEffect.h", + "$_src/gpu/gradients/GrGradientBitmapCache.cpp", + "$_src/gpu/gradients/GrGradientBitmapCache.h", "$_src/gpu/gradients/GrGradientShader.cpp", "$_src/gpu/gradients/GrGradientShader.h", diff --git a/gn/sksl.gni b/gn/sksl.gni index 0b76915b2d..764c946881 100644 --- a/gn/sksl.gni +++ b/gn/sksl.gni @@ -48,6 +48,7 @@ skia_gpu_processor_sources = [ "$_src/gpu/effects/GrYUVtoRGBEffect.fp", "$_src/gpu/gradients/GrDualIntervalGradientColorizer.fp", "$_src/gpu/gradients/GrSingleIntervalGradientColorizer.fp", + "$_src/gpu/gradients/GrTextureGradientColorizer.fp", "$_src/gpu/gradients/GrLinearGradientLayout.fp", "$_src/gpu/gradients/GrRadialGradientLayout.fp", "$_src/gpu/gradients/GrSweepGradientLayout.fp", diff --git a/src/gpu/GrProcessor.h b/src/gpu/GrProcessor.h index ed6d2b77fa..a7a03b763d 100644 --- a/src/gpu/GrProcessor.h +++ b/src/gpu/GrProcessor.h @@ -140,6 +140,7 @@ public: kGrSweepGradient_ClassID, kGrSweepGradientLayout_ClassID, kGrTextureDomainEffect_ClassID, + kGrTextureGradientColorizer_ClassID, kGrTiledGradientEffect_ClassID, kGrTwoPointConicalGradientLayout_ClassID, kGrUnpremulInputFragmentProcessor_ClassID, diff --git a/src/gpu/gradients/GrGradientBitmapCache.cpp b/src/gpu/gradients/GrGradientBitmapCache.cpp new file mode 100644 index 0000000000..459c13aba6 --- /dev/null +++ b/src/gpu/gradients/GrGradientBitmapCache.cpp @@ -0,0 +1,249 @@ +/* + * 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 "SkPM4fPriv.h" + +#include + +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 GrColor4f* colors, const SkScalar* positions, + int count, SkColorType colorType, SkBitmap* bitmap) { + SkHalf* pixelsF16 = reinterpret_cast(bitmap->getPixels()); + uint32_t* pixels32 = reinterpret_cast(bitmap->getPixels()); + + typedef std::function 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].fRGBA), + c1 = Sk4f::Load(colors[i ].fRGBA); + + Sk4f step = Sk4f(1.0f / static_cast(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 GrColor4f* colors, const SkScalar* positions, + int count, SkColorType colorType, SkAlphaType alphaType, SkBitmap* bitmap) { + // build our key: [numColors + colors[] + positions[] + alphaType + colorType ] + static_assert(sizeof(GrColor4f) % sizeof(int32_t) == 0, ""); + const int colorsAsIntCount = count * sizeof(GrColor4f) / 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(GrColor4f)); + buffer += colorsAsIntCount; + if (count > 2) { + for (int i = 1; i < count; i++) { + *buffer++ = SkFloat2Bits(positions[i]); + } + } + *buffer++ = static_cast(alphaType); + *buffer++ = static_cast(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 diff --git a/src/gpu/gradients/GrGradientBitmapCache.h b/src/gpu/gradients/GrGradientBitmapCache.h new file mode 100644 index 0000000000..a451504f8f --- /dev/null +++ b/src/gpu/gradients/GrGradientBitmapCache.h @@ -0,0 +1,62 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#ifndef GrGradientBitmapCache_DEFINED +#define GrGradientBitmapCache_DEFINED + +#include "SkBitmap.h" +#include "SkMutex.h" +#include "SkNoncopyable.h" +#include "GrColor.h" + +class GrGradientBitmapCache : SkNoncopyable { +public: + GrGradientBitmapCache(int maxEntries, int resolution); + ~GrGradientBitmapCache(); + + // Assumes colors are compatible with the specified alphaType (e.g. if it's premul then colors + // are already premultiplied). Thread safe. + void getGradient(const GrColor4f* colors, const SkScalar* positions, int count, + SkColorType colorType, SkAlphaType alphaType, SkBitmap* bitmap); + +private: + SkMutex fMutex; + + int fEntryCount; + const int fMaxEntries; + const int fResolution; + + struct Entry; + mutable Entry* fHead; + mutable Entry* fTail; + + inline Entry* release(Entry*) const; + inline void attachToHead(Entry*) const; + + bool find(const void* buffer, size_t len, SkBitmap*) const; + void add(const void* buffer, size_t len, const SkBitmap&); + + void fillGradient(const GrColor4f* colors, const SkScalar* positions, int count, + SkColorType colorType, SkBitmap* bitmap); + +#ifdef SK_DEBUG + void validate() const; +#else + void validate() const {} +#endif + + class AutoValidate : SkNoncopyable { + public: + AutoValidate(const GrGradientBitmapCache* bc) : fBC(bc) { bc->validate(); } + ~AutoValidate() { fBC->validate(); } + private: + const GrGradientBitmapCache* fBC; + }; +}; + +#endif diff --git a/src/gpu/gradients/GrGradientShader.cpp b/src/gpu/gradients/GrGradientShader.cpp index 6e676460c4..7e140259ba 100644 --- a/src/gpu/gradients/GrGradientShader.cpp +++ b/src/gpu/gradients/GrGradientShader.cpp @@ -17,15 +17,52 @@ #include "GrDualIntervalGradientColorizer.h" #include "GrSingleIntervalGradientColorizer.h" +#include "GrTextureGradientColorizer.h" +#include "GrGradientBitmapCache.h" -#include "SkGradientShaderPriv.h" +#include "SkGr.h" #include "GrColor.h" +#include "GrContext.h" +#include "GrContextPriv.h" + +// Each cache entry costs 1K or 2K of RAM. Each bitmap will be 1x256 at either 32bpp or 64bpp. +static const int kMaxNumCachedGradientBitmaps = 32; +static const int kGradientTextureSize = 256; + +// NOTE: signature takes raw pointers to the color/pos arrays and a count to make it easy for +// MakeColorizer to transparently take care of hard stops at the end points of the gradient. +static std::unique_ptr make_textured_colorizer(const GrColor4f* colors, + const SkScalar* positions, int count, bool premul, const GrFPArgs& args) { + static GrGradientBitmapCache gCache(kMaxNumCachedGradientBitmaps, kGradientTextureSize); + + // Use 8888 or F16, depending on the destination config. + // TODO: Use 1010102 for opaque gradients, at least if destination is 1010102? + SkColorType colorType = kRGBA_8888_SkColorType; + if (kLow_GrSLPrecision != GrSLSamplerPrecision(args.fDstColorSpaceInfo->config()) && + args.fContext->contextPriv().caps()->isConfigTexturable(kRGBA_half_GrPixelConfig)) { + colorType = kRGBA_F16_SkColorType; + } + SkAlphaType alphaType = premul ? kPremul_SkAlphaType : kUnpremul_SkAlphaType; + + SkBitmap bitmap; + gCache.getGradient(colors, positions, count, colorType, alphaType, &bitmap); + SkASSERT(1 == bitmap.height() && SkIsPow2(bitmap.width())); + SkASSERT(bitmap.isImmutable()); + + sk_sp proxy = GrMakeCachedBitmapProxy( + args.fContext->contextPriv().proxyProvider(), bitmap); + if (proxy == nullptr) { + SkDebugf("Gradient won't draw. Could not create texture."); + return nullptr; + } + + return GrTextureGradientColorizer::Make(std::move(proxy)); +} // Analyze the shader's color stops and positions and chooses an appropriate colorizer to represent // the gradient. static std::unique_ptr make_colorizer(const GrColor4f* colors, - const SkScalar* positions, - int count) { + const SkScalar* positions, int count, bool premul, const GrFPArgs& args) { // If there are hard stops at the beginning or end, the first and/or last color should be // ignored by the colorizer since it should only be used in a clamped border color. By detecting // and removing these stops at the beginning, it makes optimizing the remaining color stops @@ -34,8 +71,7 @@ static std::unique_ptr make_colorizer(const GrColor4f* colo // SkGradientShaderBase guarantees that pos[0] == 0 by adding a dummy bool bottomHardStop = SkScalarNearlyEqual(positions[0], positions[1]); // The same is true for pos[end] == 1 - bool topHardStop = SkScalarNearlyEqual(positions[count - 2], - positions[count - 1]); + bool topHardStop = SkScalarNearlyEqual(positions[count - 2], positions[count - 1]); int offset = 0; if (bottomHardStop) { @@ -63,7 +99,7 @@ static std::unique_ptr make_colorizer(const GrColor4f* colo positions[offset + 1]); } - return nullptr; + return make_textured_colorizer(colors + offset, positions + offset, count, premul, args); } // Combines the colorizer and layout with an appropriately configured master effect based on the @@ -106,8 +142,8 @@ static std::unique_ptr make_gradient(const SkGradientShader } // All gradients are colorized the same way, regardless of layout - std::unique_ptr colorizer = make_colorizer(colors.get(), positions, - shader.fColorCount); + std::unique_ptr colorizer = make_colorizer( + colors.get(), positions, shader.fColorCount, inputPremul, args); if (colorizer == nullptr) { return nullptr; } diff --git a/src/gpu/gradients/GrTextureGradientColorizer.cpp b/src/gpu/gradients/GrTextureGradientColorizer.cpp new file mode 100644 index 0000000000..f7fdfa4f00 --- /dev/null +++ b/src/gpu/gradients/GrTextureGradientColorizer.cpp @@ -0,0 +1,58 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrTextureGradientColorizer.fp; do not modify. + **************************************************************************************************/ +#include "GrTextureGradientColorizer.h" +#include "glsl/GrGLSLFragmentProcessor.h" +#include "glsl/GrGLSLFragmentShaderBuilder.h" +#include "glsl/GrGLSLProgramBuilder.h" +#include "GrTexture.h" +#include "SkSLCPP.h" +#include "SkSLUtil.h" +class GrGLSLTextureGradientColorizer : public GrGLSLFragmentProcessor { +public: + GrGLSLTextureGradientColorizer() {} + void emitCode(EmitArgs& args) override { + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + const GrTextureGradientColorizer& _outer = args.fFp.cast(); + (void)_outer; + fragBuilder->codeAppendf( + "half2 coord = half2(%s.x, 0.5);\n%s = texture(%s, float2(coord)).%s;\n", + args.fInputColor, args.fOutputColor, + fragBuilder->getProgramBuilder()->samplerVariable(args.fTexSamplers[0]).c_str(), + fragBuilder->getProgramBuilder()->samplerSwizzle(args.fTexSamplers[0]).c_str()); + } + +private: + void onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& _proc) override {} +}; +GrGLSLFragmentProcessor* GrTextureGradientColorizer::onCreateGLSLInstance() const { + return new GrGLSLTextureGradientColorizer(); +} +void GrTextureGradientColorizer::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const {} +bool GrTextureGradientColorizer::onIsEqual(const GrFragmentProcessor& other) const { + const GrTextureGradientColorizer& that = other.cast(); + (void)that; + if (fGradient != that.fGradient) return false; + return true; +} +GrTextureGradientColorizer::GrTextureGradientColorizer(const GrTextureGradientColorizer& src) + : INHERITED(kGrTextureGradientColorizer_ClassID, src.optimizationFlags()) + , fGradient(src.fGradient) { + this->setTextureSamplerCnt(1); +} +std::unique_ptr GrTextureGradientColorizer::clone() const { + return std::unique_ptr(new GrTextureGradientColorizer(*this)); +} +const GrFragmentProcessor::TextureSampler& GrTextureGradientColorizer::onTextureSampler( + int index) const { + return IthTextureSampler(index, fGradient); +} diff --git a/src/gpu/gradients/GrTextureGradientColorizer.fp b/src/gpu/gradients/GrTextureGradientColorizer.fp new file mode 100644 index 0000000000..c4eaf8287e --- /dev/null +++ b/src/gpu/gradients/GrTextureGradientColorizer.fp @@ -0,0 +1,18 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +// Should have height = 1px, horizontal axis represents t = 0 to 1 +in uniform sampler2D gradient; + +@samplerParams(gradient) { + GrSamplerState::ClampBilerp() +} + +void main() { + half2 coord = half2(sk_InColor.x, 0.5); + sk_OutColor = texture(gradient, coord); +} diff --git a/src/gpu/gradients/GrTextureGradientColorizer.h b/src/gpu/gradients/GrTextureGradientColorizer.h new file mode 100644 index 0000000000..9f39247da2 --- /dev/null +++ b/src/gpu/gradients/GrTextureGradientColorizer.h @@ -0,0 +1,39 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/************************************************************************************************** + *** This file was autogenerated from GrTextureGradientColorizer.fp; do not modify. + **************************************************************************************************/ +#ifndef GrTextureGradientColorizer_DEFINED +#define GrTextureGradientColorizer_DEFINED +#include "SkTypes.h" +#include "GrFragmentProcessor.h" +#include "GrCoordTransform.h" +class GrTextureGradientColorizer : public GrFragmentProcessor { +public: + static std::unique_ptr Make(sk_sp gradient) { + return std::unique_ptr(new GrTextureGradientColorizer(gradient)); + } + GrTextureGradientColorizer(const GrTextureGradientColorizer& src); + std::unique_ptr clone() const override; + const char* name() const override { return "TextureGradientColorizer"; } + +private: + GrTextureGradientColorizer(sk_sp gradient) + : INHERITED(kGrTextureGradientColorizer_ClassID, kNone_OptimizationFlags) + , fGradient(std::move(gradient), GrSamplerState::ClampBilerp()) { + this->setTextureSamplerCnt(1); + } + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; + bool onIsEqual(const GrFragmentProcessor&) const override; + const TextureSampler& onTextureSampler(int) const override; + GR_DECLARE_FRAGMENT_PROCESSOR_TEST + TextureSampler fGradient; + typedef GrFragmentProcessor INHERITED; +}; +#endif