Support for color-spaces with multi-stop (texture) gradients
Texture is F16 linear, unless that's not supported. In that
case, we pack down to sRGB.
Added more test patches to the gamut GM with many stops,
to test this case. Now they render correctly.
BUG=skia:
GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2343253002
Review-Url: https://codereview.chromium.org/2343253002
diff --git a/gm/gamut.cpp b/gm/gamut.cpp
index 595e36b..26231c5 100644
--- a/gm/gamut.cpp
+++ b/gm/gamut.cpp
@@ -58,9 +58,10 @@
};
struct GradientCellRenderer : public CellRenderer {
- GradientCellRenderer(SkColor colorOne, SkColor colorTwo) {
+ GradientCellRenderer(SkColor colorOne, SkColor colorTwo, bool manyStops) {
fColors[0] = colorOne;
fColors[1] = colorTwo;
+ fManyStops = manyStops;
}
void draw(SkCanvas* canvas) override {
SkPoint points[2] = {
@@ -68,8 +69,16 @@
SkPoint::Make(0, gScalarSize)
};
SkPaint paint;
- paint.setShader(SkGradientShader::MakeLinear(points, fColors, nullptr, 2,
- SkShader::kClamp_TileMode));
+ if (fManyStops) {
+ SkColor colors[4] ={
+ fColors[0], fColors[0], fColors[1], fColors[1]
+ };
+ paint.setShader(SkGradientShader::MakeLinear(points, colors, nullptr, 4,
+ SkShader::kClamp_TileMode));
+ } else {
+ paint.setShader(SkGradientShader::MakeLinear(points, fColors, nullptr, 2,
+ SkShader::kClamp_TileMode));
+ }
canvas->drawPaint(paint);
}
const char* label() override {
@@ -77,6 +86,7 @@
}
protected:
SkColor fColors[2];
+ bool fManyStops;
};
struct VerticesCellRenderer : public CellRenderer {
@@ -198,9 +208,16 @@
renderers.push_back(new BitmapCellRenderer(SK_ColorGREEN, kHigh_SkFilterQuality, 0.5f));
// Various gradients involving sRGB primaries and white/black
- renderers.push_back(new GradientCellRenderer(SK_ColorRED, SK_ColorGREEN));
- renderers.push_back(new GradientCellRenderer(SK_ColorGREEN, SK_ColorBLACK));
- renderers.push_back(new GradientCellRenderer(SK_ColorGREEN, SK_ColorWHITE));
+
+ // First with just two stops (implemented with uniforms on GPU)
+ renderers.push_back(new GradientCellRenderer(SK_ColorRED, SK_ColorGREEN, false));
+ renderers.push_back(new GradientCellRenderer(SK_ColorGREEN, SK_ColorBLACK, false));
+ renderers.push_back(new GradientCellRenderer(SK_ColorGREEN, SK_ColorWHITE, false));
+
+ // ... and then with four stops (implemented with textures on GPU)
+ renderers.push_back(new GradientCellRenderer(SK_ColorRED, SK_ColorGREEN, true));
+ renderers.push_back(new GradientCellRenderer(SK_ColorGREEN, SK_ColorBLACK, true));
+ renderers.push_back(new GradientCellRenderer(SK_ColorGREEN, SK_ColorWHITE, true));
// Vertex colors
renderers.push_back(new VerticesCellRenderer(SK_ColorRED, SK_ColorRED));
diff --git a/src/effects/gradients/SkGradientShader.cpp b/src/effects/gradients/SkGradientShader.cpp
index 4f174d7..27f6136 100644
--- a/src/effects/gradients/SkGradientShader.cpp
+++ b/src/effects/gradients/SkGradientShader.cpp
@@ -7,6 +7,7 @@
#include "Sk4fLinearGradient.h"
#include "SkGradientShaderPriv.h"
+#include "SkHalf.h"
#include "SkLinearGradient.h"
#include "SkRadialGradient.h"
#include "SkTwoPointConicalGradient.h"
@@ -544,6 +545,62 @@
}
}
+void SkGradientShaderBase::initLinearBitmap(SkBitmap* bitmap) const {
+ const bool interpInPremul = SkToBool(fGradFlags &
+ SkGradientShader::kInterpolateColorsInPremul_Flag);
+ bitmap->lockPixels();
+ SkHalf* pixelsF16 = reinterpret_cast<SkHalf*>(bitmap->getPixels());
+ uint32_t* pixelsS32 = 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 writeS32Pixel = [&](const Sk4f& c, int index) {
+ pixelsS32[index] = Sk4f_toS32(c);
+ };
+
+ pixelWriteFn_t writeSizedPixel =
+ (kRGBA_F16_SkColorType == bitmap->colorType()) ? writeF16Pixel : writeS32Pixel;
+ pixelWriteFn_t writeUnpremulPixel = [&](const Sk4f& c, int index) {
+ writeSizedPixel(c * Sk4f(c[3], c[3], c[3], 1.0f), index);
+ };
+
+ pixelWriteFn_t writePixel = interpInPremul ? writeSizedPixel : writeUnpremulPixel;
+
+ int prevIndex = 0;
+ for (int i = 1; i < fColorCount; i++) {
+ int nextIndex = (fColorCount == 2) ? (kCache32Count - 1)
+ : SkFixedToFFFF(fRecs[i].fPos) >> kCache32Shift;
+ SkASSERT(nextIndex < kCache32Count);
+
+ if (nextIndex > prevIndex) {
+ Sk4f c0 = Sk4f::Load(fOrigColors4f[i - 1].vec());
+ Sk4f c1 = Sk4f::Load(fOrigColors4f[i].vec());
+ if (interpInPremul) {
+ c0 = c0 * Sk4f(c0[3], c0[3], c0[3], 1.0f);
+ c1 = c1 * Sk4f(c1[3], c1[3], c1[3], 1.0f);
+ }
+
+ 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 == kCache32Count - 1);
+ bitmap->unlockPixels();
+}
+
/*
* The gradient holds a cache for the most recent value of alpha. Successive
* callers with the same alpha value will share the same cache.
@@ -570,13 +627,13 @@
* colors and positions. Note: we don't try to flatten the fMapper, so if one
* is present, we skip the cache for now.
*/
-void SkGradientShaderBase::getGradientTableBitmap(SkBitmap* bitmap) const {
- // our caller assumes no external alpha, so we ensure that our cache is
- // built with 0xFF
+void SkGradientShaderBase::getGradientTableBitmap(SkBitmap* bitmap,
+ GradientBitmapType bitmapType) const {
+ // our caller assumes no external alpha, so we ensure that our cache is built with 0xFF
SkAutoTUnref<GradientShaderCache> cache(this->refCache(0xFF, true));
- // build our key: [numColors + colors[] + {positions[]} + flags ]
- int count = 1 + fColorCount + 1;
+ // build our key: [numColors + colors[] + {positions[]} + flags + colorType ]
+ int count = 1 + fColorCount + 1 + 1;
if (fColorCount > 2) {
count += fColorCount - 1; // fRecs[].fPos
}
@@ -593,12 +650,13 @@
}
}
*buffer++ = fGradFlags;
+ *buffer++ = static_cast<int32_t>(bitmapType);
SkASSERT(buffer - storage.get() == count);
///////////////////////////////////
static SkGradientBitmapCache* gCache;
- // each cache cost 1K of RAM, since each bitmap will be 1x256 at 32bpp
+ // each cache cost 1K or 2K of RAM, since each bitmap will be 1x256 at either 32bpp or 64bpp
static const int MAX_NUM_CACHED_GRADIENT_BITMAPS = 32;
SkAutoMutexAcquire ama(gGradientCacheMutex);
@@ -608,11 +666,35 @@
size_t size = count * sizeof(int32_t);
if (!gCache->find(storage.get(), size, bitmap)) {
- // force our cahce32pixelref to be built
- (void)cache->getCache32();
- bitmap->setInfo(SkImageInfo::MakeN32Premul(kCache32Count, 1));
- bitmap->setPixelRef(cache->getCache32PixelRef());
+ if (GradientBitmapType::kLegacy == bitmapType) {
+ // force our cache32pixelref to be built
+ (void)cache->getCache32();
+ bitmap->setInfo(SkImageInfo::MakeN32Premul(kCache32Count, 1));
+ bitmap->setPixelRef(cache->getCache32PixelRef());
+ } else {
+ // For these cases we use the bitmap cache, but not the GradientShaderCache. So just
+ // allocate and populate the bitmap's data directly.
+ SkImageInfo info;
+ switch (bitmapType) {
+ case GradientBitmapType::kSRGB:
+ info = SkImageInfo::Make(kCache32Count, 1, kRGBA_8888_SkColorType,
+ kPremul_SkAlphaType,
+ SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named));
+ break;
+ case GradientBitmapType::kHalfFloat:
+ info = SkImageInfo::Make(kCache32Count, 1, kRGBA_F16_SkColorType,
+ kPremul_SkAlphaType,
+ SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named)
+ ->makeLinearGamma());
+ break;
+ default:
+ SkFAIL("Unexpected bitmap type");
+ return;
+ }
+ bitmap->allocPixels(info);
+ this->initLinearBitmap(bitmap);
+ }
gCache->add(storage.get(), size, *bitmap);
}
}
@@ -902,6 +984,7 @@
#include "GrInvariantOutput.h"
#include "GrTextureStripAtlas.h"
#include "gl/GrGLContext.h"
+#include "glsl/GrGLSLColorSpaceXformHelper.h"
#include "glsl/GrGLSLFragmentShaderBuilder.h"
#include "glsl/GrGLSLProgramDataManager.h"
#include "glsl/GrGLSLUniformHandler.h"
@@ -1113,6 +1196,9 @@
pdman.set1f(fFSYUni, yCoord);
fCachedYCoord = yCoord;
}
+ if (SkToBool(e.fColorSpaceXform)) {
+ pdman.setSkMatrix44(fColorSpaceXformUni, e.fColorSpaceXform->srcToDst());
+ }
break;
}
}
@@ -1150,6 +1236,8 @@
}
#endif
+ key |= GrColorSpaceXform::XformKey(e.fColorSpaceXform.get()) << kReservedBits;
+
return key;
}
@@ -1331,11 +1419,15 @@
}
case kTexture_ColorType: {
+ GrGLSLColorSpaceXformHelper colorSpaceHelper(uniformHandler, ge.fColorSpaceXform.get(),
+ &fColorSpaceXformUni);
+
const char* fsyuni = uniformHandler->getUniformCStr(fFSYUni);
fragBuilder->codeAppendf("vec2 coord = vec2(%s, %s);", gradientTValue, fsyuni);
fragBuilder->codeAppendf("%s = ", outputColor);
- fragBuilder->appendTextureLookupAndModulate(inputColor, texSamplers[0], "coord");
+ fragBuilder->appendTextureLookupAndModulate(inputColor, texSamplers[0], "coord",
+ kVec2f_GrSLType, &colorSpaceHelper);
fragBuilder->codeAppend(";");
break;
@@ -1351,12 +1443,12 @@
fIsOpaque = shader.isOpaque();
fColorType = this->determineColorType(shader);
+ fColorSpaceXform = std::move(args.fColorSpaceXform);
if (kTexture_ColorType != fColorType) {
SkASSERT(shader.fOrigColors && shader.fOrigColors4f);
if (args.fGammaCorrect) {
fColors4f = SkTDArray<SkColor4f>(shader.fOrigColors4f, shader.fColorCount);
- fColorSpaceXform = std::move(args.fColorSpaceXform);
} else {
fColors = SkTDArray<SkColor>(shader.fOrigColors, shader.fColorCount);
}
@@ -1397,8 +1489,22 @@
// effect key.
fPremulType = kBeforeInterp_PremulType;
+ SkGradientShaderBase::GradientBitmapType bitmapType =
+ SkGradientShaderBase::GradientBitmapType::kLegacy;
+ if (args.fGammaCorrect) {
+ // Try to use F16 if we can
+ if (args.fContext->caps()->isConfigTexturable(kRGBA_half_GrPixelConfig)) {
+ bitmapType = SkGradientShaderBase::GradientBitmapType::kHalfFloat;
+ } else if (args.fContext->caps()->isConfigTexturable(kSRGBA_8888_GrPixelConfig)) {
+ bitmapType = SkGradientShaderBase::GradientBitmapType::kSRGB;
+ } else {
+ // This should never happen, but just fall back to legacy behavior
+ SkDEBUGFAIL("Requesting a gamma-correct gradient FP without F16 or sRGB");
+ }
+ }
+
SkBitmap bitmap;
- shader.getGradientTableBitmap(&bitmap);
+ shader.getGradientTableBitmap(&bitmap, bitmapType);
GrTextureStripAtlas::Desc desc;
desc.fWidth = bitmap.width();
diff --git a/src/effects/gradients/SkGradientShaderPriv.h b/src/effects/gradients/SkGradientShaderPriv.h
index aeb1301..d8d50e8 100644
--- a/src/effects/gradients/SkGradientShaderPriv.h
+++ b/src/effects/gradients/SkGradientShaderPriv.h
@@ -175,7 +175,13 @@
bool isOpaque() const override;
- void getGradientTableBitmap(SkBitmap*) const;
+ enum class GradientBitmapType : uint8_t {
+ kLegacy,
+ kSRGB,
+ kHalfFloat,
+ };
+
+ void getGradientTableBitmap(SkBitmap*, GradientBitmapType bitmapType) const;
enum {
/// Seems like enough for visual accuracy. TODO: if pos[] deserves
@@ -214,6 +220,9 @@
bool onAsLuminanceColor(SkColor*) const override;
+
+ void initLinearBitmap(SkBitmap* bitmap) const;
+
/*
* Takes in pointers to gradient color and Rec info as colorSrc and recSrc respectively.
* Count is the number of colors in the gradient
@@ -490,6 +499,7 @@
SkScalar fCachedYCoord;
GrGLSLProgramDataManager::UniformHandle fColorsUni;
GrGLSLProgramDataManager::UniformHandle fFSYUni;
+ GrGLSLProgramDataManager::UniformHandle fColorSpaceXformUni;
typedef GrGLSLFragmentProcessor INHERITED;
};