diff options
author | Florin Malita <fmalita@chromium.org> | 2017-05-30 16:39:47 -0400 |
---|---|---|
committer | Skia Commit-Bot <skia-commit-bot@chromium.org> | 2017-05-30 21:01:46 +0000 |
commit | 5edba45dca995baed5e66dfaaa7859132e716314 (patch) | |
tree | 0333422088e6ed781db5c6e2cf4ed330b69a0957 /src/shaders | |
parent | 64790a3714467300848971aa153aca8cea91cf7b (diff) |
[Reland] Relocate shaders to own dir
Consolidate all shader impls under src/shaders/.
(reland of https://skia-review.googlesource.com/c/17927/)
Change-Id: I7918bdc1aafe842ed194412ba95b9ae53a2ec1d7
Reviewed-on: https://skia-review.googlesource.com/18146
Reviewed-by: Florin Malita <fmalita@chromium.org>
Commit-Queue: Florin Malita <fmalita@chromium.org>
Diffstat (limited to 'src/shaders')
41 files changed, 12690 insertions, 0 deletions
diff --git a/src/shaders/SkBitmapProcShader.cpp b/src/shaders/SkBitmapProcShader.cpp new file mode 100644 index 0000000000..5410447d6d --- /dev/null +++ b/src/shaders/SkBitmapProcShader.cpp @@ -0,0 +1,206 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkBitmapProcShader.h" + +#include "SkArenaAlloc.h" +#include "SkBitmapProcState.h" +#include "SkBitmapProvider.h" +#include "SkXfermodePriv.h" + +static bool only_scale_and_translate(const SkMatrix& matrix) { + unsigned mask = SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask; + return (matrix.getType() & ~mask) == 0; +} + +class BitmapProcInfoContext : public SkShaderBase::Context { +public: + // The info has been allocated elsewhere, but we are responsible for calling its destructor. + BitmapProcInfoContext(const SkShaderBase& shader, const SkShaderBase::ContextRec& rec, + SkBitmapProcInfo* info) + : INHERITED(shader, rec) + , fInfo(info) + { + fFlags = 0; + if (fInfo->fPixmap.isOpaque() && (255 == this->getPaintAlpha())) { + fFlags |= SkShaderBase::kOpaqueAlpha_Flag; + } + + if (1 == fInfo->fPixmap.height() && only_scale_and_translate(this->getTotalInverse())) { + fFlags |= SkShaderBase::kConstInY32_Flag; + } + } + + uint32_t getFlags() const override { return fFlags; } + +private: + SkBitmapProcInfo* fInfo; + uint32_t fFlags; + + typedef SkShaderBase::Context INHERITED; +}; + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +class BitmapProcShaderContext : public BitmapProcInfoContext { +public: + BitmapProcShaderContext(const SkShaderBase& shader, const SkShaderBase::ContextRec& rec, + SkBitmapProcState* state) + : INHERITED(shader, rec, state) + , fState(state) + {} + + void shadeSpan(int x, int y, SkPMColor dstC[], int count) override { + const SkBitmapProcState& state = *fState; + if (state.getShaderProc32()) { + state.getShaderProc32()(&state, x, y, dstC, count); + return; + } + + const int BUF_MAX = 128; + uint32_t buffer[BUF_MAX]; + SkBitmapProcState::MatrixProc mproc = state.getMatrixProc(); + SkBitmapProcState::SampleProc32 sproc = state.getSampleProc32(); + const int max = state.maxCountForBufferSize(sizeof(buffer[0]) * BUF_MAX); + + SkASSERT(state.fPixmap.addr()); + + for (;;) { + int n = SkTMin(count, max); + SkASSERT(n > 0 && n < BUF_MAX*2); + mproc(state, buffer, n, x, y); + sproc(state, buffer, n, dstC); + + if ((count -= n) == 0) { + break; + } + SkASSERT(count > 0); + x += n; + dstC += n; + } + } + + ShadeProc asAShadeProc(void** ctx) override { + if (fState->getShaderProc32()) { + *ctx = fState; + return (ShadeProc)fState->getShaderProc32(); + } + return nullptr; + } + +private: + SkBitmapProcState* fState; + + typedef BitmapProcInfoContext INHERITED; +}; + +/////////////////////////////////////////////////////////////////////////////////////////////////// +#include "SkLinearBitmapPipeline.h" +#include "SkPM4f.h" + +class LinearPipelineContext : public BitmapProcInfoContext { +public: + LinearPipelineContext(const SkShaderBase& shader, const SkShaderBase::ContextRec& rec, + SkBitmapProcInfo* info, SkArenaAlloc* alloc) + : INHERITED(shader, rec, info), fAllocator{alloc} + { + // Save things off in case we need to build a blitter pipeline. + fSrcPixmap = info->fPixmap; + fAlpha = SkColorGetA(info->fPaintColor) / 255.0f; + fFilterQuality = info->fFilterQuality; + fMatrixTypeMask = info->fRealInvMatrix.getType(); + + fShaderPipeline = alloc->make<SkLinearBitmapPipeline>( + info->fRealInvMatrix, info->fFilterQuality, + info->fTileModeX, info->fTileModeY, + info->fPaintColor, + info->fPixmap, + fAllocator); + + // To implement the old shadeSpan entry-point, we need to efficiently convert our native + // floats into SkPMColor. The SkXfermode::D32Procs do exactly that. + // + fSrcModeProc = SkXfermode::GetD32Proc(SkBlendMode::kSrc, 0); + } + + void shadeSpan4f(int x, int y, SkPM4f dstC[], int count) override { + fShaderPipeline->shadeSpan4f(x, y, dstC, count); + } + + void shadeSpan(int x, int y, SkPMColor dstC[], int count) override { + const int N = 128; + SkPM4f tmp[N]; + + while (count > 0) { + const int n = SkTMin(count, N); + fShaderPipeline->shadeSpan4f(x, y, tmp, n); + fSrcModeProc(SkBlendMode::kSrc, dstC, tmp, n, nullptr); + dstC += n; + x += n; + count -= n; + } + } + +private: + // Store the allocator from the context creation incase we are asked to build a blitter. + SkArenaAlloc* fAllocator; + SkLinearBitmapPipeline* fShaderPipeline; + SkXfermode::D32Proc fSrcModeProc; + SkPixmap fSrcPixmap; + float fAlpha; + SkMatrix::TypeMask fMatrixTypeMask; + SkFilterQuality fFilterQuality; + + typedef BitmapProcInfoContext INHERITED; +}; + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +static bool choose_linear_pipeline(const SkShaderBase::ContextRec& rec, const SkImageInfo& srcInfo) { + // If we get here, we can reasonably use either context, respect the caller's preference + // + bool needsPremul = srcInfo.alphaType() == kUnpremul_SkAlphaType; + bool needsSwizzle = srcInfo.bytesPerPixel() == 4 && srcInfo.colorType() != kN32_SkColorType; + return SkShaderBase::ContextRec::kPM4f_DstType == rec.fPreferredDstType + || needsPremul || needsSwizzle; +} + +size_t SkBitmapProcLegacyShader::ContextSize(const ContextRec& rec, const SkImageInfo& srcInfo) { + size_t size0 = sizeof(BitmapProcShaderContext) + sizeof(SkBitmapProcState); + size_t size1 = sizeof(LinearPipelineContext) + sizeof(SkBitmapProcInfo); + size_t s = SkTMax(size0, size1); + return s; +} + +SkShaderBase::Context* SkBitmapProcLegacyShader::MakeContext( + const SkShaderBase& shader, TileMode tmx, TileMode tmy, + const SkBitmapProvider& provider, const ContextRec& rec, SkArenaAlloc* alloc) +{ + SkMatrix totalInverse; + // Do this first, so we know the matrix can be inverted. + if (!shader.computeTotalInverse(*rec.fMatrix, rec.fLocalMatrix, &totalInverse)) { + return nullptr; + } + + // Decide if we can/want to use the new linear pipeline + bool useLinearPipeline = choose_linear_pipeline(rec, provider.info()); + + if (useLinearPipeline) { + SkBitmapProcInfo* info = alloc->make<SkBitmapProcInfo>(provider, tmx, tmy); + if (!info->init(totalInverse, *rec.fPaint)) { + return nullptr; + } + + return alloc->make<LinearPipelineContext>(shader, rec, info, alloc); + } else { + SkBitmapProcState* state = alloc->make<SkBitmapProcState>(provider, tmx, tmy); + if (!state->setup(totalInverse, *rec.fPaint)) { + return nullptr; + } + return alloc->make<BitmapProcShaderContext>(shader, rec, state); + } +} diff --git a/src/shaders/SkBitmapProcShader.h b/src/shaders/SkBitmapProcShader.h new file mode 100644 index 0000000000..2a2599cb1d --- /dev/null +++ b/src/shaders/SkBitmapProcShader.h @@ -0,0 +1,26 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkBitmapProcShader_DEFINED +#define SkBitmapProcShader_DEFINED + +#include "SkImagePriv.h" +#include "SkShaderBase.h" + +class SkBitmapProvider; + +class SkBitmapProcLegacyShader : public SkShaderBase { +private: + friend class SkImageShader; + + static size_t ContextSize(const ContextRec&, const SkImageInfo& srcInfo); + static Context* MakeContext(const SkShaderBase&, TileMode tmx, TileMode tmy, + const SkBitmapProvider&, const ContextRec&, SkArenaAlloc* alloc); + + typedef SkShaderBase INHERITED; +}; + +#endif diff --git a/src/shaders/SkColorFilterShader.cpp b/src/shaders/SkColorFilterShader.cpp new file mode 100644 index 0000000000..4798422dfa --- /dev/null +++ b/src/shaders/SkColorFilterShader.cpp @@ -0,0 +1,138 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkArenaAlloc.h" +#include "SkColorFilterShader.h" +#include "SkColorSpaceXformer.h" +#include "SkReadBuffer.h" +#include "SkWriteBuffer.h" +#include "SkShader.h" +#include "SkString.h" + +#if SK_SUPPORT_GPU +#include "GrFragmentProcessor.h" +#endif + +SkColorFilterShader::SkColorFilterShader(sk_sp<SkShader> shader, sk_sp<SkColorFilter> filter) + : fShader(std::move(shader)) + , fFilter(std::move(filter)) +{ + SkASSERT(fShader); + SkASSERT(fFilter); +} + +sk_sp<SkFlattenable> SkColorFilterShader::CreateProc(SkReadBuffer& buffer) { + auto shader = buffer.readShader(); + auto filter = buffer.readColorFilter(); + if (!shader || !filter) { + return nullptr; + } + return sk_make_sp<SkColorFilterShader>(shader, filter); +} + +void SkColorFilterShader::flatten(SkWriteBuffer& buffer) const { + buffer.writeFlattenable(fShader.get()); + buffer.writeFlattenable(fFilter.get()); +} + +uint32_t SkColorFilterShader::FilterShaderContext::getFlags() const { + const SkColorFilterShader& filterShader = static_cast<const SkColorFilterShader&>(fShader); + + uint32_t shaderF = fShaderContext->getFlags(); + uint32_t filterF = filterShader.fFilter->getFlags(); + + // If the filter does not support a given feature, but sure to clear the corresponding flag + // in the shader flags. + // + if (!(filterF & SkColorFilter::kAlphaUnchanged_Flag)) { + shaderF &= ~kOpaqueAlpha_Flag; + } + return shaderF; +} + +SkShaderBase::Context* SkColorFilterShader::onMakeContext(const ContextRec& rec, + SkArenaAlloc* alloc) const { + auto* shaderContext = as_SB(fShader)->makeContext(rec, alloc); + if (nullptr == shaderContext) { + return nullptr; + } + return alloc->make<FilterShaderContext>(*this, shaderContext, rec); +} + +sk_sp<SkShader> SkColorFilterShader::onMakeColorSpace(SkColorSpaceXformer* xformer) const { + return xformer->apply(fShader.get())->makeWithColorFilter(xformer->apply(fFilter.get())); +} + +SkColorFilterShader::FilterShaderContext::FilterShaderContext( + const SkColorFilterShader& filterShader, + SkShaderBase::Context* shaderContext, + const ContextRec& rec) + : INHERITED(filterShader, rec) + , fShaderContext(shaderContext) +{} + +void SkColorFilterShader::FilterShaderContext::shadeSpan(int x, int y, SkPMColor result[], + int count) { + const SkColorFilterShader& filterShader = static_cast<const SkColorFilterShader&>(fShader); + + fShaderContext->shadeSpan(x, y, result, count); + filterShader.fFilter->filterSpan(result, count, result); +} + +void SkColorFilterShader::FilterShaderContext::shadeSpan4f(int x, int y, SkPM4f result[], + int count) { + const SkColorFilterShader& filterShader = static_cast<const SkColorFilterShader&>(fShader); + + fShaderContext->shadeSpan4f(x, y, result, count); + filterShader.fFilter->filterSpan4f(result, count, result); +} + +#if SK_SUPPORT_GPU +///////////////////////////////////////////////////////////////////// + +sk_sp<GrFragmentProcessor> SkColorFilterShader::asFragmentProcessor(const AsFPArgs& args) const { + + sk_sp<GrFragmentProcessor> fp1(as_SB(fShader)->asFragmentProcessor(args)); + if (!fp1) { + return nullptr; + } + + sk_sp<GrFragmentProcessor> fp2(fFilter->asFragmentProcessor(args.fContext, + args.fDstColorSpace)); + if (!fp2) { + return fp1; + } + + sk_sp<GrFragmentProcessor> fpSeries[] = { std::move(fp1), std::move(fp2) }; + return GrFragmentProcessor::RunInSeries(fpSeries, 2); +} +#endif + +#ifndef SK_IGNORE_TO_STRING +void SkColorFilterShader::toString(SkString* str) const { + str->append("SkColorFilterShader: ("); + + str->append("Shader: "); + as_SB(fShader)->toString(str); + str->append(" Filter: "); + // TODO: add "fFilter->toString(str);" once SkColorFilter::toString is added + + this->INHERITED::toString(str); + + str->append(")"); +} +#endif + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +sk_sp<SkShader> SkShader::makeWithColorFilter(sk_sp<SkColorFilter> filter) const { + SkShader* base = const_cast<SkShader*>(this); + if (!filter) { + return sk_ref_sp(base); + } + return sk_make_sp<SkColorFilterShader>(sk_ref_sp(base), filter); +} diff --git a/src/shaders/SkColorFilterShader.h b/src/shaders/SkColorFilterShader.h new file mode 100644 index 0000000000..7f4202158a --- /dev/null +++ b/src/shaders/SkColorFilterShader.h @@ -0,0 +1,60 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkColorFilterShader_DEFINED +#define SkColorFilterShader_DEFINED + +#include "SkColorFilter.h" +#include "SkShaderBase.h" + +class SkArenaAlloc; + +class SkColorFilterShader : public SkShaderBase { +public: + SkColorFilterShader(sk_sp<SkShader> shader, sk_sp<SkColorFilter> filter); + +#if SK_SUPPORT_GPU + sk_sp<GrFragmentProcessor> asFragmentProcessor(const AsFPArgs&) const override; +#endif + + class FilterShaderContext : public Context { + public: + // Takes ownership of shaderContext and calls its destructor. + FilterShaderContext(const SkColorFilterShader&, SkShaderBase::Context*, const ContextRec&); + + uint32_t getFlags() const override; + + void shadeSpan(int x, int y, SkPMColor[], int count) override; + void shadeSpan4f(int x, int y, SkPM4f[], int count) override; + + void set3DMask(const SkMask* mask) override { + // forward to our proxy + fShaderContext->set3DMask(mask); + } + + private: + SkShaderBase::Context* fShaderContext; + + typedef Context INHERITED; + }; + + SK_TO_STRING_OVERRIDE() + SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkColorFilterShader) + +protected: + void flatten(SkWriteBuffer&) const override; + Context* onMakeContext(const ContextRec&, SkArenaAlloc* alloc) const override; + sk_sp<SkShader> onMakeColorSpace(SkColorSpaceXformer* xformer) const override; + +private: + sk_sp<SkShader> fShader; + sk_sp<SkColorFilter> fFilter; + + typedef SkShaderBase INHERITED; +}; + +#endif diff --git a/src/shaders/SkColorShader.cpp b/src/shaders/SkColorShader.cpp new file mode 100644 index 0000000000..7b6d0a9068 --- /dev/null +++ b/src/shaders/SkColorShader.cpp @@ -0,0 +1,274 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkArenaAlloc.h" +#include "SkColorShader.h" +#include "SkColorSpace.h" +#include "SkPM4fPriv.h" +#include "SkRasterPipeline.h" +#include "SkReadBuffer.h" +#include "SkUtils.h" + +SkColorShader::SkColorShader(SkColor c) : fColor(c) {} + +bool SkColorShader::isOpaque() const { + return SkColorGetA(fColor) == 255; +} + +sk_sp<SkFlattenable> SkColorShader::CreateProc(SkReadBuffer& buffer) { + return sk_make_sp<SkColorShader>(buffer.readColor()); +} + +void SkColorShader::flatten(SkWriteBuffer& buffer) const { + buffer.writeColor(fColor); +} + +uint32_t SkColorShader::ColorShaderContext::getFlags() const { + return fFlags; +} + +SkShaderBase::Context* SkColorShader::onMakeContext(const ContextRec& rec, + SkArenaAlloc* alloc) const { + return alloc->make<ColorShaderContext>(*this, rec); +} + +SkColorShader::ColorShaderContext::ColorShaderContext(const SkColorShader& shader, + const ContextRec& rec) + : INHERITED(shader, rec) +{ + SkColor color = shader.fColor; + unsigned a = SkAlphaMul(SkColorGetA(color), SkAlpha255To256(rec.fPaint->getAlpha())); + + unsigned r = SkColorGetR(color); + unsigned g = SkColorGetG(color); + unsigned b = SkColorGetB(color); + + if (a != 255) { + r = SkMulDiv255Round(r, a); + g = SkMulDiv255Round(g, a); + b = SkMulDiv255Round(b, a); + } + fPMColor = SkPackARGB32(a, r, g, b); + + SkColor4f c4 = SkColor4f::FromColor(shader.fColor); + c4.fA *= rec.fPaint->getAlpha() / 255.0f; + fPM4f = c4.premul(); + + fFlags = kConstInY32_Flag; + if (255 == a) { + fFlags |= kOpaqueAlpha_Flag; + } +} + +void SkColorShader::ColorShaderContext::shadeSpan(int x, int y, SkPMColor span[], int count) { + sk_memset32(span, fPMColor, count); +} + +void SkColorShader::ColorShaderContext::shadeSpanAlpha(int x, int y, uint8_t alpha[], int count) { + memset(alpha, SkGetPackedA32(fPMColor), count); +} + +void SkColorShader::ColorShaderContext::shadeSpan4f(int x, int y, SkPM4f span[], int count) { + for (int i = 0; i < count; ++i) { + span[i] = fPM4f; + } +} + +SkShader::GradientType SkColorShader::asAGradient(GradientInfo* info) const { + if (info) { + if (info->fColors && info->fColorCount >= 1) { + info->fColors[0] = fColor; + } + info->fColorCount = 1; + info->fTileMode = SkShader::kRepeat_TileMode; + } + return kColor_GradientType; +} + +#if SK_SUPPORT_GPU + +#include "SkGr.h" +#include "effects/GrConstColorProcessor.h" +sk_sp<GrFragmentProcessor> SkColorShader::asFragmentProcessor(const AsFPArgs& args) const { + GrColor4f color = SkColorToPremulGrColor4f(fColor, args.fDstColorSpace); + return GrConstColorProcessor::Make(color, GrConstColorProcessor::kModulateA_InputMode); +} + +#endif + +#ifndef SK_IGNORE_TO_STRING +void SkColorShader::toString(SkString* str) const { + str->append("SkColorShader: ("); + + str->append("Color: "); + str->appendHex(fColor); + + this->INHERITED::toString(str); + + str->append(")"); +} +#endif + +/////////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// + +static unsigned unit_to_byte(float unit) { + SkASSERT(unit >= 0 && unit <= 1); + return (unsigned)(unit * 255 + 0.5); +} + +static SkColor unit_to_skcolor(const SkColor4f& unit, SkColorSpace* cs) { + return SkColorSetARGB(unit_to_byte(unit.fA), unit_to_byte(unit.fR), + unit_to_byte(unit.fG), unit_to_byte(unit.fB)); +} + +SkColor4Shader::SkColor4Shader(const SkColor4f& color, sk_sp<SkColorSpace> space) + : fColorSpace(std::move(space)) + , fColor4(color) + , fCachedByteColor(unit_to_skcolor(color.pin(), space.get())) +{} + +sk_sp<SkFlattenable> SkColor4Shader::CreateProc(SkReadBuffer& buffer) { + SkColor4f color; + buffer.readColor4f(&color); + if (buffer.readBool()) { + // TODO how do we unflatten colorspaces + } + return SkShader::MakeColorShader(color, nullptr); +} + +void SkColor4Shader::flatten(SkWriteBuffer& buffer) const { + buffer.writeColor4f(fColor4); + buffer.writeBool(false); // TODO how do we flatten colorspaces? +} + +uint32_t SkColor4Shader::Color4Context::getFlags() const { + return fFlags; +} + +SkShaderBase::Context* SkColor4Shader::onMakeContext(const ContextRec& rec, + SkArenaAlloc* alloc) const { + return alloc->make<Color4Context>(*this, rec); +} + +SkColor4Shader::Color4Context::Color4Context(const SkColor4Shader& shader, + const ContextRec& rec) +: INHERITED(shader, rec) +{ + SkColor color = shader.fCachedByteColor; + unsigned a = SkAlphaMul(SkColorGetA(color), SkAlpha255To256(rec.fPaint->getAlpha())); + + unsigned r = SkColorGetR(color); + unsigned g = SkColorGetG(color); + unsigned b = SkColorGetB(color); + + if (a != 255) { + r = SkMulDiv255Round(r, a); + g = SkMulDiv255Round(g, a); + b = SkMulDiv255Round(b, a); + } + fPMColor = SkPackARGB32(a, r, g, b); + + SkColor4f c4 = shader.fColor4; + c4.fA *= rec.fPaint->getAlpha() * (1 / 255.0f); + fPM4f = c4.premul(); + + fFlags = kConstInY32_Flag; + if (255 == a) { + fFlags |= kOpaqueAlpha_Flag; + } +} + +void SkColor4Shader::Color4Context::shadeSpan(int x, int y, SkPMColor span[], int count) { + sk_memset32(span, fPMColor, count); +} + +void SkColor4Shader::Color4Context::shadeSpanAlpha(int x, int y, uint8_t alpha[], int count) { + memset(alpha, SkGetPackedA32(fPMColor), count); +} + +void SkColor4Shader::Color4Context::shadeSpan4f(int x, int y, SkPM4f span[], int count) { + for (int i = 0; i < count; ++i) { + span[i] = fPM4f; + } +} + +// TODO: do we need an updated version of this method for color4+colorspace? +SkShader::GradientType SkColor4Shader::asAGradient(GradientInfo* info) const { + if (info) { + if (info->fColors && info->fColorCount >= 1) { + info->fColors[0] = fCachedByteColor; + } + info->fColorCount = 1; + info->fTileMode = SkShader::kRepeat_TileMode; + } + return kColor_GradientType; +} + +#if SK_SUPPORT_GPU + +#include "SkGr.h" +#include "effects/GrConstColorProcessor.h" +#include "GrColorSpaceXform.h" +sk_sp<GrFragmentProcessor> SkColor4Shader::asFragmentProcessor(const AsFPArgs& args) const { + sk_sp<GrColorSpaceXform> colorSpaceXform = GrColorSpaceXform::Make(fColorSpace.get(), + args.fDstColorSpace); + GrColor4f color = GrColor4f::FromSkColor4f(fColor4); + if (colorSpaceXform) { + color = colorSpaceXform->apply(color); + } + return GrConstColorProcessor::Make(color.premul(), GrConstColorProcessor::kModulateA_InputMode); +} + +#endif + +#ifndef SK_IGNORE_TO_STRING +void SkColor4Shader::toString(SkString* str) const { + str->append("SkColor4Shader: ("); + + str->append("RGBA:"); + for (int i = 0; i < 4; ++i) { + str->appendf(" %g", fColor4.vec()[i]); + } + str->append(" )"); +} +#endif + +sk_sp<SkShader> SkColor4Shader::onMakeColorSpace(SkColorSpaceXformer* xformer) const { + return SkShader::MakeColorShader(xformer->apply(fCachedByteColor)); +} + +sk_sp<SkShader> SkShader::MakeColorShader(const SkColor4f& color, sk_sp<SkColorSpace> space) { + if (!SkScalarsAreFinite(color.vec(), 4)) { + return nullptr; + } + return sk_make_sp<SkColor4Shader>(color, std::move(space)); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +bool SkColorShader::onAppendStages(SkRasterPipeline* p, + SkColorSpace* dst, + SkArenaAlloc* scratch, + const SkMatrix&, + const SkPaint&, + const SkMatrix*) const { + auto color = scratch->make<SkPM4f>(SkPM4f_from_SkColor(fColor, dst)); + p->append(SkRasterPipeline::constant_color, color); + return true; +} + +bool SkColor4Shader::onAppendStages(SkRasterPipeline* p, + SkColorSpace* dst, + SkArenaAlloc* scratch, + const SkMatrix&, + const SkPaint&, + const SkMatrix*) const { + auto color = scratch->make<SkPM4f>(to_colorspace(fColor4, fColorSpace.get(), dst).premul()); + p->append(SkRasterPipeline::constant_color, color); + return true; +} diff --git a/src/shaders/SkColorShader.h b/src/shaders/SkColorShader.h new file mode 100644 index 0000000000..969064946e --- /dev/null +++ b/src/shaders/SkColorShader.h @@ -0,0 +1,136 @@ +/* + * Copyright 2007 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkColorShader_DEFINED +#define SkColorShader_DEFINED + +#include "SkColorSpaceXformer.h" +#include "SkShaderBase.h" +#include "SkPM4f.h" + +/** \class SkColorShader + A Shader that represents a single color. In general, this effect can be + accomplished by just using the color field on the paint, but if an + actual shader object is needed, this provides that feature. +*/ +class SkColorShader : public SkShaderBase { +public: + /** Create a ColorShader that ignores the color in the paint, and uses the + specified color. Note: like all shaders, at draw time the paint's alpha + will be respected, and is applied to the specified color. + */ + explicit SkColorShader(SkColor c); + + bool isOpaque() const override; + bool isConstant() const override { return true; } + + class ColorShaderContext : public Context { + public: + ColorShaderContext(const SkColorShader& shader, const ContextRec&); + + uint32_t getFlags() const override; + void shadeSpan(int x, int y, SkPMColor span[], int count) override; + void shadeSpanAlpha(int x, int y, uint8_t alpha[], int count) override; + void shadeSpan4f(int x, int y, SkPM4f[], int count) override; + + private: + SkPM4f fPM4f; + SkPMColor fPMColor; + uint32_t fFlags; + + typedef Context INHERITED; + }; + + GradientType asAGradient(GradientInfo* info) const override; + +#if SK_SUPPORT_GPU + sk_sp<GrFragmentProcessor> asFragmentProcessor(const AsFPArgs&) const override; +#endif + + SK_TO_STRING_OVERRIDE() + SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkColorShader) + +protected: + SkColorShader(SkReadBuffer&); + void flatten(SkWriteBuffer&) const override; + Context* onMakeContext(const ContextRec&, SkArenaAlloc* storage) const override; + + bool onAsLuminanceColor(SkColor* lum) const override { + *lum = fColor; + return true; + } + + bool onAppendStages(SkRasterPipeline*, SkColorSpace*, SkArenaAlloc*, + const SkMatrix& ctm, const SkPaint&, const SkMatrix*) const override; + + sk_sp<SkShader> onMakeColorSpace(SkColorSpaceXformer* xformer) const override { + return SkShader::MakeColorShader(xformer->apply(fColor)); + } + +private: + SkColor fColor; + + typedef SkShaderBase INHERITED; +}; + +class SkColor4Shader : public SkShaderBase { +public: + SkColor4Shader(const SkColor4f&, sk_sp<SkColorSpace>); + + bool isOpaque() const override { + return SkColorGetA(fCachedByteColor) == 255; + } + bool isConstant() const override { return true; } + + class Color4Context : public Context { + public: + Color4Context(const SkColor4Shader& shader, const ContextRec&); + + uint32_t getFlags() const override; + void shadeSpan(int x, int y, SkPMColor span[], int count) override; + void shadeSpanAlpha(int x, int y, uint8_t alpha[], int count) override; + void shadeSpan4f(int x, int y, SkPM4f[], int count) override; + + private: + SkPM4f fPM4f; + SkPMColor fPMColor; + uint32_t fFlags; + + typedef Context INHERITED; + }; + + GradientType asAGradient(GradientInfo* info) const override; + +#if SK_SUPPORT_GPU + sk_sp<GrFragmentProcessor> asFragmentProcessor(const AsFPArgs&) const override; +#endif + + SK_TO_STRING_OVERRIDE() + SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkColorShader) + +protected: + SkColor4Shader(SkReadBuffer&); + void flatten(SkWriteBuffer&) const override; + Context* onMakeContext(const ContextRec&, SkArenaAlloc*) const override; + bool onAsLuminanceColor(SkColor* lum) const override { + *lum = fCachedByteColor; + return true; + } + bool onAppendStages(SkRasterPipeline*, SkColorSpace*, SkArenaAlloc*, + const SkMatrix& ctm, const SkPaint&, const SkMatrix*) const override; + + sk_sp<SkShader> onMakeColorSpace(SkColorSpaceXformer* xformer) const override; + +private: + sk_sp<SkColorSpace> fColorSpace; + const SkColor4f fColor4; + const SkColor fCachedByteColor; + + typedef SkShaderBase INHERITED; +}; + +#endif diff --git a/src/shaders/SkComposeShader.cpp b/src/shaders/SkComposeShader.cpp new file mode 100644 index 0000000000..7735494291 --- /dev/null +++ b/src/shaders/SkComposeShader.cpp @@ -0,0 +1,311 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkArenaAlloc.h" +#include "SkBlendModePriv.h" +#include "SkComposeShader.h" +#include "SkColorFilter.h" +#include "SkColorPriv.h" +#include "SkColorShader.h" +#include "SkRasterPipeline.h" +#include "SkReadBuffer.h" +#include "SkWriteBuffer.h" +#include "SkString.h" +#include "../jumper/SkJumper.h" + +sk_sp<SkShader> SkShader::MakeComposeShader(sk_sp<SkShader> dst, sk_sp<SkShader> src, + SkBlendMode mode) { + if (!src || !dst) { + return nullptr; + } + if (SkBlendMode::kSrc == mode) { + return src; + } + if (SkBlendMode::kDst == mode) { + return dst; + } + return sk_sp<SkShader>(new SkComposeShader(std::move(dst), std::move(src), mode)); +} + +/////////////////////////////////////////////////////////////////////////////// + +class SkAutoAlphaRestore { +public: + SkAutoAlphaRestore(SkPaint* paint, uint8_t newAlpha) { + fAlpha = paint->getAlpha(); + fPaint = paint; + paint->setAlpha(newAlpha); + } + + ~SkAutoAlphaRestore() { + fPaint->setAlpha(fAlpha); + } +private: + SkPaint* fPaint; + uint8_t fAlpha; +}; +#define SkAutoAlphaRestore(...) SK_REQUIRE_LOCAL_VAR(SkAutoAlphaRestore) + +sk_sp<SkFlattenable> SkComposeShader::CreateProc(SkReadBuffer& buffer) { + sk_sp<SkShader> shaderA(buffer.readShader()); + sk_sp<SkShader> shaderB(buffer.readShader()); + SkBlendMode mode; + if (buffer.isVersionLT(SkReadBuffer::kXfermodeToBlendMode2_Version)) { + sk_sp<SkXfermode> xfer = buffer.readXfermode(); + mode = xfer ? xfer->blend() : SkBlendMode::kSrcOver; + } else { + mode = (SkBlendMode)buffer.read32(); + } + if (!shaderA || !shaderB) { + return nullptr; + } + return sk_make_sp<SkComposeShader>(std::move(shaderA), std::move(shaderB), mode); +} + +void SkComposeShader::flatten(SkWriteBuffer& buffer) const { + buffer.writeFlattenable(fShaderA.get()); + buffer.writeFlattenable(fShaderB.get()); + buffer.write32((int)fMode); +} + +SkShaderBase::Context* SkComposeShader::onMakeContext( + const ContextRec& rec, SkArenaAlloc* alloc) const +{ + // we preconcat our localMatrix (if any) with the device matrix + // before calling our sub-shaders + SkMatrix tmpM; + tmpM.setConcat(*rec.fMatrix, this->getLocalMatrix()); + + // Our sub-shaders need to see opaque, so by combining them we don't double-alphatize the + // result. ComposeShader itself will respect the alpha, and post-apply it after calling the + // sub-shaders. + SkPaint opaquePaint(*rec.fPaint); + opaquePaint.setAlpha(0xFF); + + ContextRec newRec(rec); + newRec.fMatrix = &tmpM; + newRec.fPaint = &opaquePaint; + + SkShaderBase::Context* contextA = as_SB(fShaderA)->makeContext(newRec, alloc); + SkShaderBase::Context* contextB = as_SB(fShaderB)->makeContext(newRec, alloc); + if (!contextA || !contextB) { + return nullptr; + } + + return alloc->make<ComposeShaderContext>(*this, rec, contextA, contextB); +} + +sk_sp<SkShader> SkComposeShader::onMakeColorSpace(SkColorSpaceXformer* xformer) const { + return SkShader::MakeComposeShader(xformer->apply(fShaderA.get()), + xformer->apply(fShaderB.get()), fMode); +} + +SkComposeShader::ComposeShaderContext::ComposeShaderContext( + const SkComposeShader& shader, const ContextRec& rec, + SkShaderBase::Context* contextA, SkShaderBase::Context* contextB) + : INHERITED(shader, rec) + , fShaderContextA(contextA) + , fShaderContextB(contextB) {} + +bool SkComposeShader::asACompose(ComposeRec* rec) const { + if (rec) { + rec->fShaderA = fShaderA.get(); + rec->fShaderB = fShaderB.get(); + rec->fBlendMode = fMode; + } + return true; +} + +bool SkComposeShader::isRasterPipelineOnly() const { + return as_SB(fShaderA)->isRasterPipelineOnly() || as_SB(fShaderB)->isRasterPipelineOnly(); +} + +bool SkComposeShader::onAppendStages(SkRasterPipeline* pipeline, SkColorSpace* dstCS, + SkArenaAlloc* alloc, const SkMatrix& ctm, + const SkPaint& paint, const SkMatrix* localM) const { + struct Storage { + float fXY[4 * SkJumper_kMaxStride]; + float fRGBA[4 * SkJumper_kMaxStride]; + float fAlpha; + }; + auto storage = alloc->make<Storage>(); + + // We need to save off device x,y (inputs to shader), since after calling fShaderA they + // will be smashed, and I'll need them again for fShaderB. store_rgba saves off 4 registers + // even though we only need to save r,g. + pipeline->append(SkRasterPipeline::store_rgba, storage->fXY); + if (!as_SB(fShaderB)->appendStages(pipeline, dstCS, alloc, ctm, paint, localM)) { // SRC + return false; + } + // This outputs r,g,b,a, which we'll need later when we apply the mode, but we save it off now + // since fShaderB will overwrite them. + pipeline->append(SkRasterPipeline::store_rgba, storage->fRGBA); + // Now we restore the device x,y for the next shader + pipeline->append(SkRasterPipeline::load_rgba, storage->fXY); + if (!as_SB(fShaderA)->appendStages(pipeline, dstCS, alloc, ctm, paint, localM)) { // DST + return false; + } + // We now have our logical 'dst' in r,g,b,a, but we need it in dr,dg,db,da for the mode + // so we have to shuttle them. If we had a stage the would load_into_dst, then we could + // reverse the two shader invocations, and avoid this move... + pipeline->append(SkRasterPipeline::move_src_dst); + pipeline->append(SkRasterPipeline::load_rgba, storage->fRGBA); + + // Idea: should time this, and see if it helps to have custom versions of the overflow modes + // that do their own clamping, avoiding the overhead of an extra stage. + SkBlendMode_AppendStages(fMode, pipeline); + if (SkBlendMode_CanOverflow(fMode)) { + pipeline->append(SkRasterPipeline::clamp_a); + } + return true; +} + +// larger is better (fewer times we have to loop), but we shouldn't +// take up too much stack-space (each element is 4 bytes) +#define TMP_COLOR_COUNT 64 + +void SkComposeShader::ComposeShaderContext::shadeSpan(int x, int y, SkPMColor result[], int count) { + auto* shaderContextA = fShaderContextA; + auto* shaderContextB = fShaderContextB; + SkBlendMode mode = static_cast<const SkComposeShader&>(fShader).fMode; + unsigned scale = SkAlpha255To256(this->getPaintAlpha()); + + SkPMColor tmp[TMP_COLOR_COUNT]; + + SkXfermode* xfer = SkXfermode::Peek(mode); + if (nullptr == xfer) { // implied SRC_OVER + // TODO: when we have a good test-case, should use SkBlitRow::Proc32 + // for these loops + do { + int n = count; + if (n > TMP_COLOR_COUNT) { + n = TMP_COLOR_COUNT; + } + + shaderContextA->shadeSpan(x, y, result, n); + shaderContextB->shadeSpan(x, y, tmp, n); + + if (256 == scale) { + for (int i = 0; i < n; i++) { + result[i] = SkPMSrcOver(tmp[i], result[i]); + } + } else { + for (int i = 0; i < n; i++) { + result[i] = SkAlphaMulQ(SkPMSrcOver(tmp[i], result[i]), + scale); + } + } + + result += n; + x += n; + count -= n; + } while (count > 0); + } else { // use mode for the composition + do { + int n = count; + if (n > TMP_COLOR_COUNT) { + n = TMP_COLOR_COUNT; + } + + shaderContextA->shadeSpan(x, y, result, n); + shaderContextB->shadeSpan(x, y, tmp, n); + xfer->xfer32(result, tmp, n, nullptr); + + if (256 != scale) { + for (int i = 0; i < n; i++) { + result[i] = SkAlphaMulQ(result[i], scale); + } + } + + result += n; + x += n; + count -= n; + } while (count > 0); + } +} + +void SkComposeShader::ComposeShaderContext::shadeSpan4f(int x, int y, SkPM4f result[], int count) { + auto* shaderContextA = fShaderContextA; + auto* shaderContextB = fShaderContextB; + SkBlendMode mode = static_cast<const SkComposeShader&>(fShader).fMode; + unsigned alpha = this->getPaintAlpha(); + Sk4f scale(alpha * (1.0f / 255)); + + SkPM4f tmp[TMP_COLOR_COUNT]; + + SkXfermodeProc4f xfer = SkXfermode::GetProc4f(mode); + do { + int n = SkTMin(count, TMP_COLOR_COUNT); + + shaderContextA->shadeSpan4f(x, y, result, n); + shaderContextB->shadeSpan4f(x, y, tmp, n); + if (255 == alpha) { + for (int i = 0; i < n; ++i) { + result[i] = xfer(tmp[i], result[i]); + } + } else { + for (int i = 0; i < n; ++i) { + (xfer(tmp[i], result[i]).to4f() * scale).store(result + i); + } + } + result += n; + x += n; + count -= n; + } while (count > 0); +} + +#if SK_SUPPORT_GPU + +#include "effects/GrConstColorProcessor.h" +#include "effects/GrXfermodeFragmentProcessor.h" + +///////////////////////////////////////////////////////////////////// + +sk_sp<GrFragmentProcessor> SkComposeShader::asFragmentProcessor(const AsFPArgs& args) const { + switch (fMode) { + case SkBlendMode::kClear: + return GrConstColorProcessor::Make(GrColor4f::TransparentBlack(), + GrConstColorProcessor::kIgnore_InputMode); + break; + case SkBlendMode::kSrc: + return as_SB(fShaderB)->asFragmentProcessor(args); + break; + case SkBlendMode::kDst: + return as_SB(fShaderA)->asFragmentProcessor(args); + break; + default: + sk_sp<GrFragmentProcessor> fpA(as_SB(fShaderA)->asFragmentProcessor(args)); + if (!fpA) { + return nullptr; + } + sk_sp<GrFragmentProcessor> fpB(as_SB(fShaderB)->asFragmentProcessor(args)); + if (!fpB) { + return nullptr; + } + return GrXfermodeFragmentProcessor::MakeFromTwoProcessors(std::move(fpB), + std::move(fpA), fMode); + } +} +#endif + +#ifndef SK_IGNORE_TO_STRING +void SkComposeShader::toString(SkString* str) const { + str->append("SkComposeShader: ("); + + str->append("ShaderA: "); + as_SB(fShaderA)->toString(str); + str->append(" ShaderB: "); + as_SB(fShaderB)->toString(str); + if (SkBlendMode::kSrcOver != fMode) { + str->appendf(" Xfermode: %s", SkXfermode::ModeName(fMode)); + } + + this->INHERITED::toString(str); + + str->append(")"); +} +#endif diff --git a/src/shaders/SkComposeShader.h b/src/shaders/SkComposeShader.h new file mode 100644 index 0000000000..c7bb4b9eb8 --- /dev/null +++ b/src/shaders/SkComposeShader.h @@ -0,0 +1,88 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkComposeShader_DEFINED +#define SkComposeShader_DEFINED + +#include "SkShaderBase.h" +#include "SkBlendMode.h" + +class SkColorSpacXformer; + +/////////////////////////////////////////////////////////////////////////////////////////// + +/** \class SkComposeShader + This subclass of shader returns the composition of two other shaders, combined by + a xfermode. +*/ +class SkComposeShader : public SkShaderBase { +public: + /** Create a new compose shader, given shaders A, B, and a combining xfermode mode. + When the xfermode is called, it will be given the result from shader A as its + "dst", and the result from shader B as its "src". + mode->xfer32(sA_result, sB_result, ...) + @param shaderA The colors from this shader are seen as the "dst" by the xfermode + @param shaderB The colors from this shader are seen as the "src" by the xfermode + @param mode The xfermode that combines the colors from the two shaders. If mode + is null, then SRC_OVER is assumed. + */ + SkComposeShader(sk_sp<SkShader> sA, sk_sp<SkShader> sB, SkBlendMode mode) + : fShaderA(std::move(sA)) + , fShaderB(std::move(sB)) + , fMode(mode) + {} + +#if SK_SUPPORT_GPU + sk_sp<GrFragmentProcessor> asFragmentProcessor(const AsFPArgs&) const override; +#endif + + class ComposeShaderContext : public Context { + public: + // When this object gets destroyed, it will call contextA and contextB's destructor + // but it will NOT free the memory. + ComposeShaderContext(const SkComposeShader&, const ContextRec&, + SkShaderBase::Context* contextA, SkShaderBase::Context* contextB); + + void shadeSpan(int x, int y, SkPMColor[], int count) override; + void shadeSpan4f(int x, int y, SkPM4f[], int count) override; + + private: + SkShaderBase::Context* fShaderContextA; + SkShaderBase::Context* fShaderContextB; + + typedef Context INHERITED; + }; + +#ifdef SK_DEBUG + SkShader* getShaderA() { return fShaderA.get(); } + SkShader* getShaderB() { return fShaderB.get(); } +#endif + + bool asACompose(ComposeRec* rec) const override; + + SK_TO_STRING_OVERRIDE() + SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkComposeShader) + +protected: + SkComposeShader(SkReadBuffer&); + void flatten(SkWriteBuffer&) const override; + Context* onMakeContext(const ContextRec&, SkArenaAlloc*) const override; + sk_sp<SkShader> onMakeColorSpace(SkColorSpaceXformer* xformer) const override; + bool onAppendStages(SkRasterPipeline*, SkColorSpace* dstCS, SkArenaAlloc*, + const SkMatrix&, const SkPaint&, const SkMatrix* localM) const override; + + bool isRasterPipelineOnly() const final; + +private: + sk_sp<SkShader> fShaderA; + sk_sp<SkShader> fShaderB; + SkBlendMode fMode; + + typedef SkShaderBase INHERITED; +}; + +#endif diff --git a/src/shaders/SkEmptyShader.h b/src/shaders/SkEmptyShader.h new file mode 100644 index 0000000000..d7187c4edf --- /dev/null +++ b/src/shaders/SkEmptyShader.h @@ -0,0 +1,41 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkEmptyShader_DEFINED +#define SkEmptyShader_DEFINED + +#include "SkShaderBase.h" + +// TODO: move this to private, as there is a public factory on SkShader + +/** + * \class SkEmptyShader + * A Shader that always draws nothing. Its createContext always returns nullptr. + */ +class SkEmptyShader : public SkShaderBase { +public: + SkEmptyShader() {} + + SK_TO_STRING_OVERRIDE() + SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkEmptyShader) + +protected: + Context* onMakeContext(const ContextRec&, SkArenaAlloc*) const override { + return nullptr; + } + + void flatten(SkWriteBuffer& buffer) const override { + // Do nothing. + // We just don't want to fall through to SkShader::flatten(), + // which will write data we don't care to serialize or decode. + } + +private: + typedef SkShaderBase INHERITED; +}; + +#endif diff --git a/src/shaders/SkImageShader.cpp b/src/shaders/SkImageShader.cpp new file mode 100644 index 0000000000..751300e951 --- /dev/null +++ b/src/shaders/SkImageShader.cpp @@ -0,0 +1,391 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkArenaAlloc.h" +#include "SkBitmapController.h" +#include "SkBitmapProcShader.h" +#include "SkBitmapProvider.h" +#include "SkColorTable.h" +#include "SkEmptyShader.h" +#include "SkImage_Base.h" +#include "SkImageShader.h" +#include "SkPM4fPriv.h" +#include "SkReadBuffer.h" +#include "SkWriteBuffer.h" +#include "../jumper/SkJumper.h" + +SkImageShader::SkImageShader(sk_sp<SkImage> img, TileMode tmx, TileMode tmy, const SkMatrix* matrix) + : INHERITED(matrix) + , fImage(std::move(img)) + , fTileModeX(tmx) + , fTileModeY(tmy) +{} + +sk_sp<SkFlattenable> SkImageShader::CreateProc(SkReadBuffer& buffer) { + const TileMode tx = (TileMode)buffer.readUInt(); + const TileMode ty = (TileMode)buffer.readUInt(); + SkMatrix matrix; + buffer.readMatrix(&matrix); + sk_sp<SkImage> img = buffer.readImage(); + if (!img) { + return nullptr; + } + return SkImageShader::Make(std::move(img), tx, ty, &matrix); +} + +void SkImageShader::flatten(SkWriteBuffer& buffer) const { + buffer.writeUInt(fTileModeX); + buffer.writeUInt(fTileModeY); + buffer.writeMatrix(this->getLocalMatrix()); + buffer.writeImage(fImage.get()); +} + +bool SkImageShader::isOpaque() const { + return fImage->isOpaque(); +} + +SkShaderBase::Context* SkImageShader::onMakeContext(const ContextRec& rec, + SkArenaAlloc* alloc) const { + return SkBitmapProcLegacyShader::MakeContext(*this, fTileModeX, fTileModeY, + SkBitmapProvider(fImage.get(), rec.fDstColorSpace), + rec, alloc); +} + +SkImage* SkImageShader::onIsAImage(SkMatrix* texM, TileMode xy[]) const { + if (texM) { + *texM = this->getLocalMatrix(); + } + if (xy) { + xy[0] = (TileMode)fTileModeX; + xy[1] = (TileMode)fTileModeY; + } + return const_cast<SkImage*>(fImage.get()); +} + +#ifdef SK_SUPPORT_LEGACY_SHADER_ISABITMAP +bool SkImageShader::onIsABitmap(SkBitmap* texture, SkMatrix* texM, TileMode xy[]) const { + const SkBitmap* bm = as_IB(fImage)->onPeekBitmap(); + if (!bm) { + return false; + } + + if (texture) { + *texture = *bm; + } + if (texM) { + *texM = this->getLocalMatrix(); + } + if (xy) { + xy[0] = (TileMode)fTileModeX; + xy[1] = (TileMode)fTileModeY; + } + return true; +} +#endif + +static bool bitmap_is_too_big(int w, int h) { + // SkBitmapProcShader stores bitmap coordinates in a 16bit buffer, as it + // communicates between its matrix-proc and its sampler-proc. Until we can + // widen that, we have to reject bitmaps that are larger. + // + static const int kMaxSize = 65535; + + return w > kMaxSize || h > kMaxSize; +} + +sk_sp<SkShader> SkImageShader::Make(sk_sp<SkImage> image, TileMode tx, TileMode ty, + const SkMatrix* localMatrix) { + if (!image || bitmap_is_too_big(image->width(), image->height())) { + return sk_make_sp<SkEmptyShader>(); + } else { + return sk_make_sp<SkImageShader>(image, tx, ty, localMatrix); + } +} + +#ifndef SK_IGNORE_TO_STRING +void SkImageShader::toString(SkString* str) const { + const char* gTileModeName[SkShader::kTileModeCount] = { + "clamp", "repeat", "mirror" + }; + + str->appendf("ImageShader: ((%s %s) ", gTileModeName[fTileModeX], gTileModeName[fTileModeY]); + fImage->toString(str); + this->INHERITED::toString(str); + str->append(")"); +} +#endif + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#if SK_SUPPORT_GPU + +#include "SkGr.h" +#include "GrContext.h" +#include "effects/GrSimpleTextureEffect.h" +#include "effects/GrBicubicEffect.h" +#include "effects/GrSimpleTextureEffect.h" + +sk_sp<GrFragmentProcessor> SkImageShader::asFragmentProcessor(const AsFPArgs& args) const { + + SkMatrix lmInverse; + if (!this->getLocalMatrix().invert(&lmInverse)) { + return nullptr; + } + if (args.fLocalMatrix) { + SkMatrix inv; + if (!args.fLocalMatrix->invert(&inv)) { + return nullptr; + } + lmInverse.postConcat(inv); + } + + SkShader::TileMode tm[] = { fTileModeX, fTileModeY }; + + // Must set wrap and filter on the sampler before requesting a texture. In two places below + // we check the matrix scale factors to determine how to interpret the filter quality setting. + // This completely ignores the complexity of the drawVertices case where explicit local coords + // are provided by the caller. + bool doBicubic; + GrSamplerParams::FilterMode textureFilterMode = + GrSkFilterQualityToGrFilterMode(args.fFilterQuality, *args.fViewMatrix, this->getLocalMatrix(), + &doBicubic); + GrSamplerParams params(tm, textureFilterMode); + sk_sp<SkColorSpace> texColorSpace; + SkScalar scaleAdjust[2] = { 1.0f, 1.0f }; + sk_sp<GrTextureProxy> proxy(as_IB(fImage)->asTextureProxyRef(args.fContext, params, + args.fDstColorSpace, + &texColorSpace, scaleAdjust)); + if (!proxy) { + return nullptr; + } + + bool isAlphaOnly = GrPixelConfigIsAlphaOnly(proxy->config()); + + lmInverse.postScale(scaleAdjust[0], scaleAdjust[1]); + + sk_sp<GrColorSpaceXform> colorSpaceXform = GrColorSpaceXform::Make(texColorSpace.get(), + args.fDstColorSpace); + sk_sp<GrFragmentProcessor> inner; + if (doBicubic) { + inner = GrBicubicEffect::Make(args.fContext->resourceProvider(), std::move(proxy), + std::move(colorSpaceXform), lmInverse, tm); + } else { + inner = GrSimpleTextureEffect::Make(args.fContext->resourceProvider(), std::move(proxy), + std::move(colorSpaceXform), lmInverse, params); + } + + if (isAlphaOnly) { + return inner; + } + return sk_sp<GrFragmentProcessor>(GrFragmentProcessor::MulOutputByInputAlpha(std::move(inner))); +} + +#endif + +/////////////////////////////////////////////////////////////////////////////////////////////////// +#include "SkImagePriv.h" + +sk_sp<SkShader> SkMakeBitmapShader(const SkBitmap& src, SkShader::TileMode tmx, + SkShader::TileMode tmy, const SkMatrix* localMatrix, + SkCopyPixelsMode cpm) { + return SkImageShader::Make(SkMakeImageFromRasterBitmap(src, cpm), + tmx, tmy, localMatrix); +} + +static sk_sp<SkFlattenable> SkBitmapProcShader_CreateProc(SkReadBuffer& buffer) { + SkMatrix lm; + buffer.readMatrix(&lm); + sk_sp<SkImage> image = buffer.readBitmapAsImage(); + SkShader::TileMode mx = (SkShader::TileMode)buffer.readUInt(); + SkShader::TileMode my = (SkShader::TileMode)buffer.readUInt(); + return image ? image->makeShader(mx, my, &lm) : nullptr; +} + +SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkShaderBase) +SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkImageShader) +SkFlattenable::Register("SkBitmapProcShader", SkBitmapProcShader_CreateProc, kSkShaderBase_Type); +SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END + + +bool SkImageShader::onAppendStages(SkRasterPipeline* p, SkColorSpace* dstCS, SkArenaAlloc* alloc, + const SkMatrix& ctm, const SkPaint& paint, + const SkMatrix* localM) const { + auto matrix = SkMatrix::Concat(ctm, this->getLocalMatrix()); + if (localM) { + matrix.preConcat(*localM); + } + + if (!matrix.invert(&matrix)) { + return false; + } + auto quality = paint.getFilterQuality(); + + SkBitmapProvider provider(fImage.get(), dstCS); + SkDefaultBitmapController controller(SkDefaultBitmapController::CanShadeHQ::kYes); + std::unique_ptr<SkBitmapController::State> state { + controller.requestBitmap(provider, matrix, quality) + }; + if (!state) { + return false; + } + + const SkPixmap& pm = state->pixmap(); + matrix = state->invMatrix(); + quality = state->quality(); + auto info = pm.info(); + + // When the matrix is just an integer translate, bilerp == nearest neighbor. + if (quality == kLow_SkFilterQuality && + matrix.getType() <= SkMatrix::kTranslate_Mask && + matrix.getTranslateX() == (int)matrix.getTranslateX() && + matrix.getTranslateY() == (int)matrix.getTranslateY()) { + quality = kNone_SkFilterQuality; + } + + // See skia:4649 and the GM image_scale_aligned. + if (quality == kNone_SkFilterQuality) { + if (matrix.getScaleX() >= 0) { + matrix.setTranslateX(nextafterf(matrix.getTranslateX(), + floorf(matrix.getTranslateX()))); + } + if (matrix.getScaleY() >= 0) { + matrix.setTranslateY(nextafterf(matrix.getTranslateY(), + floorf(matrix.getTranslateY()))); + } + } + + + struct MiscCtx { + std::unique_ptr<SkBitmapController::State> state; + SkColor4f paint_color; + float matrix[9]; + }; + auto misc = alloc->make<MiscCtx>(); + misc->state = std::move(state); // Extend lifetime to match the pipeline's. + misc->paint_color = SkColor4f_from_SkColor(paint.getColor(), dstCS); + if (matrix.asAffine(misc->matrix)) { + p->append(SkRasterPipeline::matrix_2x3, misc->matrix); + } else { + matrix.get9(misc->matrix); + p->append(SkRasterPipeline::matrix_perspective, misc->matrix); + } + + auto gather = alloc->make<SkJumper_GatherCtx>(); + gather->pixels = pm.addr(); + gather->ctable = pm.ctable() ? pm.ctable()->readColors() : nullptr; + gather->stride = pm.rowBytesAsPixels(); + + // Tiling stages (clamp_x, mirror_y, etc.) are inclusive of their limit, + // so we tick down our width and height by one float to make them exclusive. + auto ulp_before = [](float f) { + uint32_t bits; + memcpy(&bits, &f, 4); + bits--; + memcpy(&f, &bits, 4); + return f; + }; + auto limit_x = alloc->make<float>(ulp_before((float)pm. width())), + limit_y = alloc->make<float>(ulp_before((float)pm.height())); + + auto append_tiling_and_gather = [&] { + switch (fTileModeX) { + case kClamp_TileMode: p->append(SkRasterPipeline::clamp_x, limit_x); break; + case kMirror_TileMode: p->append(SkRasterPipeline::mirror_x, limit_x); break; + case kRepeat_TileMode: p->append(SkRasterPipeline::repeat_x, limit_x); break; + } + switch (fTileModeY) { + case kClamp_TileMode: p->append(SkRasterPipeline::clamp_y, limit_y); break; + case kMirror_TileMode: p->append(SkRasterPipeline::mirror_y, limit_y); break; + case kRepeat_TileMode: p->append(SkRasterPipeline::repeat_y, limit_y); break; + } + switch (info.colorType()) { + case kAlpha_8_SkColorType: p->append(SkRasterPipeline::gather_a8, gather); break; + case kIndex_8_SkColorType: p->append(SkRasterPipeline::gather_i8, gather); break; + case kGray_8_SkColorType: p->append(SkRasterPipeline::gather_g8, gather); break; + case kRGB_565_SkColorType: p->append(SkRasterPipeline::gather_565, gather); break; + case kARGB_4444_SkColorType: p->append(SkRasterPipeline::gather_4444, gather); break; + case kRGBA_8888_SkColorType: + case kBGRA_8888_SkColorType: p->append(SkRasterPipeline::gather_8888, gather); break; + case kRGBA_F16_SkColorType: p->append(SkRasterPipeline::gather_f16, gather); break; + default: SkASSERT(false); + } + if (info.gammaCloseToSRGB() && dstCS != nullptr) { + p->append_from_srgb(info.alphaType()); + } + }; + + SkJumper_SamplerCtx* sampler = nullptr; + if (quality != kNone_SkFilterQuality) { + sampler = alloc->make<SkJumper_SamplerCtx>(); + } + + auto sample = [&](SkRasterPipeline::StockStage setup_x, + SkRasterPipeline::StockStage setup_y) { + p->append(setup_x, sampler); + p->append(setup_y, sampler); + append_tiling_and_gather(); + p->append(SkRasterPipeline::accumulate, sampler); + }; + + if (quality == kNone_SkFilterQuality) { + append_tiling_and_gather(); + } else if (quality == kLow_SkFilterQuality) { + p->append(SkRasterPipeline::save_xy, sampler); + + sample(SkRasterPipeline::bilinear_nx, SkRasterPipeline::bilinear_ny); + sample(SkRasterPipeline::bilinear_px, SkRasterPipeline::bilinear_ny); + sample(SkRasterPipeline::bilinear_nx, SkRasterPipeline::bilinear_py); + sample(SkRasterPipeline::bilinear_px, SkRasterPipeline::bilinear_py); + + p->append(SkRasterPipeline::move_dst_src); + } else { + p->append(SkRasterPipeline::save_xy, sampler); + + sample(SkRasterPipeline::bicubic_n3x, SkRasterPipeline::bicubic_n3y); + sample(SkRasterPipeline::bicubic_n1x, SkRasterPipeline::bicubic_n3y); + sample(SkRasterPipeline::bicubic_p1x, SkRasterPipeline::bicubic_n3y); + sample(SkRasterPipeline::bicubic_p3x, SkRasterPipeline::bicubic_n3y); + + sample(SkRasterPipeline::bicubic_n3x, SkRasterPipeline::bicubic_n1y); + sample(SkRasterPipeline::bicubic_n1x, SkRasterPipeline::bicubic_n1y); + sample(SkRasterPipeline::bicubic_p1x, SkRasterPipeline::bicubic_n1y); + sample(SkRasterPipeline::bicubic_p3x, SkRasterPipeline::bicubic_n1y); + + sample(SkRasterPipeline::bicubic_n3x, SkRasterPipeline::bicubic_p1y); + sample(SkRasterPipeline::bicubic_n1x, SkRasterPipeline::bicubic_p1y); + sample(SkRasterPipeline::bicubic_p1x, SkRasterPipeline::bicubic_p1y); + sample(SkRasterPipeline::bicubic_p3x, SkRasterPipeline::bicubic_p1y); + + sample(SkRasterPipeline::bicubic_n3x, SkRasterPipeline::bicubic_p3y); + sample(SkRasterPipeline::bicubic_n1x, SkRasterPipeline::bicubic_p3y); + sample(SkRasterPipeline::bicubic_p1x, SkRasterPipeline::bicubic_p3y); + sample(SkRasterPipeline::bicubic_p3x, SkRasterPipeline::bicubic_p3y); + + p->append(SkRasterPipeline::move_dst_src); + } + + auto effective_color_type = [](SkColorType ct) { + return ct == kIndex_8_SkColorType ? kN32_SkColorType : ct; + }; + + if (effective_color_type(info.colorType()) == kBGRA_8888_SkColorType) { + p->append(SkRasterPipeline::swap_rb); + } + if (info.colorType() == kAlpha_8_SkColorType) { + p->append(SkRasterPipeline::set_rgb, &misc->paint_color); + } + if (info.colorType() == kAlpha_8_SkColorType || info.alphaType() == kUnpremul_SkAlphaType) { + p->append(SkRasterPipeline::premul); + } + if (quality > kLow_SkFilterQuality) { + // Bicubic filtering naturally produces out of range values on both sides. + p->append(SkRasterPipeline::clamp_0); + p->append(SkRasterPipeline::clamp_a); + } + append_gamut_transform(p, alloc, info.colorSpace(), dstCS, kPremul_SkAlphaType); + return true; +} diff --git a/src/shaders/SkImageShader.h b/src/shaders/SkImageShader.h new file mode 100644 index 0000000000..7be982c5c6 --- /dev/null +++ b/src/shaders/SkImageShader.h @@ -0,0 +1,58 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkImageShader_DEFINED +#define SkImageShader_DEFINED + +#include "SkBitmapProcShader.h" +#include "SkColorSpaceXformer.h" +#include "SkImage.h" +#include "SkShaderBase.h" + +class SkImageShader : public SkShaderBase { +public: + static sk_sp<SkShader> Make(sk_sp<SkImage>, TileMode tx, TileMode ty, + const SkMatrix* localMatrix); + + bool isOpaque() const override; + + SK_TO_STRING_OVERRIDE() + SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkImageShader) + +#if SK_SUPPORT_GPU + sk_sp<GrFragmentProcessor> asFragmentProcessor(const AsFPArgs&) const override; +#endif + + SkImageShader(sk_sp<SkImage>, TileMode tx, TileMode ty, const SkMatrix* localMatrix); + +protected: + void flatten(SkWriteBuffer&) const override; + Context* onMakeContext(const ContextRec&, SkArenaAlloc* storage) const override; +#ifdef SK_SUPPORT_LEGACY_SHADER_ISABITMAP + bool onIsABitmap(SkBitmap*, SkMatrix*, TileMode*) const override; +#endif + SkImage* onIsAImage(SkMatrix*, TileMode*) const override; + + bool onAppendStages(SkRasterPipeline*, SkColorSpace*, SkArenaAlloc*, + const SkMatrix& ctm, const SkPaint&, const SkMatrix*) const override; + + sk_sp<SkShader> onMakeColorSpace(SkColorSpaceXformer* xformer) const override { + return xformer->apply(fImage.get())->makeShader(fTileModeX, fTileModeY, + &this->getLocalMatrix()); + } + + sk_sp<SkImage> fImage; + const TileMode fTileModeX; + const TileMode fTileModeY; + +private: + friend class SkShaderBase; + + typedef SkShaderBase INHERITED; +}; + +#endif diff --git a/src/shaders/SkLightingShader.cpp b/src/shaders/SkLightingShader.cpp new file mode 100644 index 0000000000..cdfa528e1e --- /dev/null +++ b/src/shaders/SkLightingShader.cpp @@ -0,0 +1,488 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkArenaAlloc.h" +#include "SkBitmapProcShader.h" +#include "SkBitmapProcState.h" +#include "SkColor.h" +#include "SkColorSpaceXformer.h" +#include "SkEmptyShader.h" +#include "SkLightingShader.h" +#include "SkMathPriv.h" +#include "SkNormalSource.h" +#include "SkPoint3.h" +#include "SkReadBuffer.h" +#include "SkShaderBase.h" +#include "SkWriteBuffer.h" + +//////////////////////////////////////////////////////////////////////////// + +/* + SkLightingShader TODOs: + support different light types + support multiple lights + fix non-opaque diffuse textures + + To Test: + A8 diffuse textures + down & upsampled draws +*/ + + + +/** \class SkLightingShaderImpl + This subclass of shader applies lighting. +*/ +class SkLightingShaderImpl : public SkShaderBase { +public: + /** Create a new lighting shader that uses the provided normal map and + lights to light the diffuse bitmap. + @param diffuseShader the shader that provides the diffuse colors + @param normalSource the source of normals for lighting computation + @param lights the lights applied to the geometry + */ + SkLightingShaderImpl(sk_sp<SkShader> diffuseShader, + sk_sp<SkNormalSource> normalSource, + sk_sp<SkLights> lights) + : fDiffuseShader(std::move(diffuseShader)) + , fNormalSource(std::move(normalSource)) + , fLights(std::move(lights)) {} + + bool isOpaque() const override; + +#if SK_SUPPORT_GPU + sk_sp<GrFragmentProcessor> asFragmentProcessor(const AsFPArgs&) const override; +#endif + + class LightingShaderContext : public Context { + public: + // The context takes ownership of the context and provider. It will call their destructors + // and then indirectly free their memory by calling free() on heapAllocated + LightingShaderContext(const SkLightingShaderImpl&, const ContextRec&, + SkShaderBase::Context* diffuseContext, SkNormalSource::Provider*, + void* heapAllocated); + + void shadeSpan(int x, int y, SkPMColor[], int count) override; + + uint32_t getFlags() const override { return fFlags; } + + private: + SkShaderBase::Context* fDiffuseContext; + SkNormalSource::Provider* fNormalProvider; + SkColor fPaintColor; + uint32_t fFlags; + + typedef Context INHERITED; + }; + + SK_TO_STRING_OVERRIDE() + SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkLightingShaderImpl) + +protected: + void flatten(SkWriteBuffer&) const override; + Context* onMakeContext(const ContextRec&, SkArenaAlloc*) const override; + sk_sp<SkShader> onMakeColorSpace(SkColorSpaceXformer* xformer) const override; + +private: + sk_sp<SkShader> fDiffuseShader; + sk_sp<SkNormalSource> fNormalSource; + sk_sp<SkLights> fLights; + + friend class SkLightingShader; + + typedef SkShaderBase INHERITED; +}; + +//////////////////////////////////////////////////////////////////////////// + +#if SK_SUPPORT_GPU + +#include "GrCoordTransform.h" +#include "GrFragmentProcessor.h" +#include "glsl/GrGLSLFragmentProcessor.h" +#include "glsl/GrGLSLFragmentShaderBuilder.h" +#include "glsl/GrGLSLProgramDataManager.h" +#include "glsl/GrGLSLUniformHandler.h" +#include "SkGr.h" + +// This FP expects a premul'd color input for its diffuse color. Premul'ing of the paint's color is +// handled by the asFragmentProcessor() factory, but shaders providing diffuse color must output it +// premul'd. +class LightingFP : public GrFragmentProcessor { +public: + LightingFP(sk_sp<GrFragmentProcessor> normalFP, sk_sp<SkLights> lights) + : INHERITED(kPreservesOpaqueInput_OptimizationFlag) { + // fuse all ambient lights into a single one + fAmbientColor = lights->ambientLightColor(); + for (int i = 0; i < lights->numLights(); ++i) { + if (SkLights::Light::kDirectional_LightType == lights->light(i).type()) { + fDirectionalLights.push_back(lights->light(i)); + // TODO get the handle to the shadow map if there is one + } else { + SkDEBUGFAIL("Unimplemented Light Type passed to LightingFP"); + } + } + + this->registerChildProcessor(std::move(normalFP)); + this->initClassID<LightingFP>(); + } + + class GLSLLightingFP : public GrGLSLFragmentProcessor { + public: + GLSLLightingFP() { + fAmbientColor.fX = 0.0f; + } + + void emitCode(EmitArgs& args) override { + + GrGLSLFragmentBuilder* fragBuilder = args.fFragBuilder; + GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; + const LightingFP& lightingFP = args.fFp.cast<LightingFP>(); + + const char *lightDirsUniName = nullptr; + const char *lightColorsUniName = nullptr; + if (lightingFP.fDirectionalLights.count() != 0) { + fLightDirsUni = uniformHandler->addUniformArray( + kFragment_GrShaderFlag, + kVec3f_GrSLType, + kDefault_GrSLPrecision, + "LightDir", + lightingFP.fDirectionalLights.count(), + &lightDirsUniName); + fLightColorsUni = uniformHandler->addUniformArray( + kFragment_GrShaderFlag, + kVec3f_GrSLType, + kDefault_GrSLPrecision, + "LightColor", + lightingFP.fDirectionalLights.count(), + &lightColorsUniName); + } + + const char* ambientColorUniName = nullptr; + fAmbientColorUni = uniformHandler->addUniform(kFragment_GrShaderFlag, + kVec3f_GrSLType, kDefault_GrSLPrecision, + "AmbientColor", &ambientColorUniName); + + fragBuilder->codeAppendf("vec4 diffuseColor = %s;", args.fInputColor); + + SkString dstNormalName("dstNormal"); + this->emitChild(0, &dstNormalName, args); + + fragBuilder->codeAppendf("vec3 normal = %s.xyz;", dstNormalName.c_str()); + + fragBuilder->codeAppend( "vec3 result = vec3(0.0);"); + + // diffuse light + if (lightingFP.fDirectionalLights.count() != 0) { + fragBuilder->codeAppendf("for (int i = 0; i < %d; i++) {", + lightingFP.fDirectionalLights.count()); + // TODO: modulate the contribution from each light based on the shadow map + fragBuilder->codeAppendf(" float NdotL = clamp(dot(normal, %s[i]), 0.0, 1.0);", + lightDirsUniName); + fragBuilder->codeAppendf(" result += %s[i]*diffuseColor.rgb*NdotL;", + lightColorsUniName); + fragBuilder->codeAppend("}"); + } + + // ambient light + fragBuilder->codeAppendf("result += %s * diffuseColor.rgb;", ambientColorUniName); + + // Clamping to alpha (equivalent to an unpremul'd clamp to 1.0) + fragBuilder->codeAppendf("%s = vec4(clamp(result.rgb, 0.0, diffuseColor.a), " + "diffuseColor.a);", args.fOutputColor); + } + + static void GenKey(const GrProcessor& proc, const GrShaderCaps&, GrProcessorKeyBuilder* b) { + const LightingFP& lightingFP = proc.cast<LightingFP>(); + b->add32(lightingFP.fDirectionalLights.count()); + } + + protected: + void onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& proc) override { + const LightingFP& lightingFP = proc.cast<LightingFP>(); + + const SkTArray<SkLights::Light>& directionalLights = lightingFP.directionalLights(); + if (directionalLights != fDirectionalLights) { + SkTArray<SkColor3f> lightDirs(directionalLights.count()); + SkTArray<SkVector3> lightColors(directionalLights.count()); + for (const SkLights::Light& light : directionalLights) { + lightDirs.push_back(light.dir()); + lightColors.push_back(light.color()); + } + + pdman.set3fv(fLightDirsUni, directionalLights.count(), &(lightDirs[0].fX)); + pdman.set3fv(fLightColorsUni, directionalLights.count(), &(lightColors[0].fX)); + + fDirectionalLights = directionalLights; + } + + const SkColor3f& ambientColor = lightingFP.ambientColor(); + if (ambientColor != fAmbientColor) { + pdman.set3fv(fAmbientColorUni, 1, &ambientColor.fX); + fAmbientColor = ambientColor; + } + } + + private: + SkTArray<SkLights::Light> fDirectionalLights; + GrGLSLProgramDataManager::UniformHandle fLightDirsUni; + GrGLSLProgramDataManager::UniformHandle fLightColorsUni; + + SkColor3f fAmbientColor; + GrGLSLProgramDataManager::UniformHandle fAmbientColorUni; + }; + + void onGetGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override { + GLSLLightingFP::GenKey(*this, caps, b); + } + + const char* name() const override { return "LightingFP"; } + + const SkTArray<SkLights::Light>& directionalLights() const { return fDirectionalLights; } + const SkColor3f& ambientColor() const { return fAmbientColor; } + +private: + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override { return new GLSLLightingFP; } + + bool onIsEqual(const GrFragmentProcessor& proc) const override { + const LightingFP& lightingFP = proc.cast<LightingFP>(); + return fDirectionalLights == lightingFP.fDirectionalLights && + fAmbientColor == lightingFP.fAmbientColor; + } + + SkTArray<SkLights::Light> fDirectionalLights; + SkColor3f fAmbientColor; + + typedef GrFragmentProcessor INHERITED; +}; + +//////////////////////////////////////////////////////////////////////////// + +sk_sp<GrFragmentProcessor> SkLightingShaderImpl::asFragmentProcessor(const AsFPArgs& args) const { + sk_sp<GrFragmentProcessor> normalFP(fNormalSource->asFragmentProcessor(args)); + if (!normalFP) { + return nullptr; + } + + if (fDiffuseShader) { + sk_sp<GrFragmentProcessor> fpPipeline[] = { + as_SB(fDiffuseShader)->asFragmentProcessor(args), + sk_make_sp<LightingFP>(std::move(normalFP), fLights) + }; + if(!fpPipeline[0]) { + return nullptr; + } + + sk_sp<GrFragmentProcessor> innerLightFP = GrFragmentProcessor::RunInSeries(fpPipeline, 2); + // FP is wrapped because paint's alpha needs to be applied to output + return GrFragmentProcessor::MulOutputByInputAlpha(std::move(innerLightFP)); + } else { + // FP is wrapped because paint comes in unpremul'd to fragment shader, but LightingFP + // expects premul'd color. + return GrFragmentProcessor::PremulInput(sk_make_sp<LightingFP>(std::move(normalFP), + fLights)); + } +} + +#endif + +//////////////////////////////////////////////////////////////////////////// + +bool SkLightingShaderImpl::isOpaque() const { + return (fDiffuseShader ? fDiffuseShader->isOpaque() : false); +} + +SkLightingShaderImpl::LightingShaderContext::LightingShaderContext( + const SkLightingShaderImpl& shader, const ContextRec& rec, + SkShaderBase::Context* diffuseContext, SkNormalSource::Provider* normalProvider, + void* heapAllocated) + : INHERITED(shader, rec) + , fDiffuseContext(diffuseContext) + , fNormalProvider(normalProvider) { + bool isOpaque = shader.isOpaque(); + + // update fFlags + uint32_t flags = 0; + if (isOpaque && (255 == this->getPaintAlpha())) { + flags |= kOpaqueAlpha_Flag; + } + + fPaintColor = rec.fPaint->getColor(); + fFlags = flags; +} + +static inline SkPMColor convert(SkColor3f color, U8CPU a) { + if (color.fX <= 0.0f) { + color.fX = 0.0f; + } else if (color.fX >= 255.0f) { + color.fX = 255.0f; + } + + if (color.fY <= 0.0f) { + color.fY = 0.0f; + } else if (color.fY >= 255.0f) { + color.fY = 255.0f; + } + + if (color.fZ <= 0.0f) { + color.fZ = 0.0f; + } else if (color.fZ >= 255.0f) { + color.fZ = 255.0f; + } + + return SkPreMultiplyARGB(a, (int) color.fX, (int) color.fY, (int) color.fZ); +} + +// larger is better (fewer times we have to loop), but we shouldn't +// take up too much stack-space (each one here costs 16 bytes) +#define BUFFER_MAX 16 +void SkLightingShaderImpl::LightingShaderContext::shadeSpan(int x, int y, + SkPMColor result[], int count) { + const SkLightingShaderImpl& lightShader = static_cast<const SkLightingShaderImpl&>(fShader); + + SkPMColor diffuse[BUFFER_MAX]; + SkPoint3 normals[BUFFER_MAX]; + + SkColor diffColor = fPaintColor; + + do { + int n = SkTMin(count, BUFFER_MAX); + + fNormalProvider->fillScanLine(x, y, normals, n); + + if (fDiffuseContext) { + fDiffuseContext->shadeSpan(x, y, diffuse, n); + } + + for (int i = 0; i < n; ++i) { + if (fDiffuseContext) { + diffColor = SkUnPreMultiply::PMColorToColor(diffuse[i]); + } + + SkColor3f accum = SkColor3f::Make(0.0f, 0.0f, 0.0f); + + // Adding ambient light + accum.fX += lightShader.fLights->ambientLightColor().fX * SkColorGetR(diffColor); + accum.fY += lightShader.fLights->ambientLightColor().fY * SkColorGetG(diffColor); + accum.fZ += lightShader.fLights->ambientLightColor().fZ * SkColorGetB(diffColor); + + // This is all done in linear unpremul color space (each component 0..255.0f though) + for (int l = 0; l < lightShader.fLights->numLights(); ++l) { + const SkLights::Light& light = lightShader.fLights->light(l); + + SkScalar illuminanceScalingFactor = 1.0f; + + if (SkLights::Light::kDirectional_LightType == light.type()) { + illuminanceScalingFactor = normals[i].dot(light.dir()); + if (illuminanceScalingFactor < 0.0f) { + illuminanceScalingFactor = 0.0f; + } + } + + accum.fX += light.color().fX * SkColorGetR(diffColor) * illuminanceScalingFactor; + accum.fY += light.color().fY * SkColorGetG(diffColor) * illuminanceScalingFactor; + accum.fZ += light.color().fZ * SkColorGetB(diffColor) * illuminanceScalingFactor; + } + + // convert() premultiplies the accumulate color with alpha + result[i] = convert(accum, SkColorGetA(diffColor)); + } + + result += n; + x += n; + count -= n; + } while (count > 0); +} + +//////////////////////////////////////////////////////////////////////////// + +#ifndef SK_IGNORE_TO_STRING +void SkLightingShaderImpl::toString(SkString* str) const { + str->appendf("LightingShader: ()"); +} +#endif + +sk_sp<SkFlattenable> SkLightingShaderImpl::CreateProc(SkReadBuffer& buf) { + + // Discarding SkShader flattenable params + bool hasLocalMatrix = buf.readBool(); + SkAssertResult(!hasLocalMatrix); + + sk_sp<SkLights> lights = SkLights::MakeFromBuffer(buf); + + sk_sp<SkNormalSource> normalSource(buf.readFlattenable<SkNormalSource>()); + + bool hasDiffuse = buf.readBool(); + sk_sp<SkShader> diffuseShader = nullptr; + if (hasDiffuse) { + diffuseShader = buf.readFlattenable<SkShaderBase>(); + } + + return sk_make_sp<SkLightingShaderImpl>(std::move(diffuseShader), std::move(normalSource), + std::move(lights)); +} + +void SkLightingShaderImpl::flatten(SkWriteBuffer& buf) const { + this->INHERITED::flatten(buf); + + fLights->flatten(buf); + + buf.writeFlattenable(fNormalSource.get()); + buf.writeBool(fDiffuseShader); + if (fDiffuseShader) { + buf.writeFlattenable(fDiffuseShader.get()); + } +} + +SkShaderBase::Context* SkLightingShaderImpl::onMakeContext( + const ContextRec& rec, SkArenaAlloc* alloc) const +{ + SkShaderBase::Context *diffuseContext = nullptr; + if (fDiffuseShader) { + diffuseContext = as_SB(fDiffuseShader)->makeContext(rec, alloc); + if (!diffuseContext) { + return nullptr; + } + } + + SkNormalSource::Provider* normalProvider = fNormalSource->asProvider(rec, alloc); + if (!normalProvider) { + return nullptr; + } + + return alloc->make<LightingShaderContext>(*this, rec, diffuseContext, normalProvider, nullptr); +} + +sk_sp<SkShader> SkLightingShaderImpl::onMakeColorSpace(SkColorSpaceXformer* xformer) const { + sk_sp<SkShader> xformedDiffuseShader = + fDiffuseShader ? xformer->apply(fDiffuseShader.get()) : nullptr; + return SkLightingShader::Make(std::move(xformedDiffuseShader), fNormalSource, + fLights->makeColorSpace(xformer)); +} + +/////////////////////////////////////////////////////////////////////////////// + +sk_sp<SkShader> SkLightingShader::Make(sk_sp<SkShader> diffuseShader, + sk_sp<SkNormalSource> normalSource, + sk_sp<SkLights> lights) { + SkASSERT(lights); + if (!normalSource) { + normalSource = SkNormalSource::MakeFlat(); + } + + return sk_make_sp<SkLightingShaderImpl>(std::move(diffuseShader), std::move(normalSource), + std::move(lights)); +} + +/////////////////////////////////////////////////////////////////////////////// + +SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkLightingShader) + SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkLightingShaderImpl) +SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END + +/////////////////////////////////////////////////////////////////////////////// diff --git a/src/shaders/SkLightingShader.h b/src/shaders/SkLightingShader.h new file mode 100644 index 0000000000..aa90710aa4 --- /dev/null +++ b/src/shaders/SkLightingShader.h @@ -0,0 +1,39 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkLightingShader_DEFINED +#define SkLightingShader_DEFINED + +#include "SkLights.h" +#include "SkShader.h" + +class SkBitmap; +class SkMatrix; +class SkNormalSource; + +class SK_API SkLightingShader { +public: + /** Returns a shader that lights the shape, colored by the diffuseShader, using the + normals from normalSource, with the set of lights provided. + + @param diffuseShader the shader that provides the colors. If nullptr, uses the paint's + color. + @param normalSource the source for the shape's normals. If nullptr, assumes straight + up normals (<0,0,1>). + @param lights the lights applied to the normals + + The lighting equation is currently: + result = (LightColor * dot(Normal, LightDir) + AmbientColor) * DiffuseColor + + */ + static sk_sp<SkShader> Make(sk_sp<SkShader> diffuseShader, sk_sp<SkNormalSource> normalSource, + sk_sp<SkLights> lights); + + SK_DECLARE_FLATTENABLE_REGISTRAR_GROUP() +}; + +#endif diff --git a/src/shaders/SkLocalMatrixShader.cpp b/src/shaders/SkLocalMatrixShader.cpp new file mode 100644 index 0000000000..e21e4a84b7 --- /dev/null +++ b/src/shaders/SkLocalMatrixShader.cpp @@ -0,0 +1,110 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkLocalMatrixShader.h" + +#if SK_SUPPORT_GPU +#include "GrFragmentProcessor.h" +#endif + +#if SK_SUPPORT_GPU +sk_sp<GrFragmentProcessor> SkLocalMatrixShader::asFragmentProcessor(const AsFPArgs& args) const { + SkMatrix tmp = this->getLocalMatrix(); + if (args.fLocalMatrix) { + tmp.preConcat(*args.fLocalMatrix); + } + return as_SB(fProxyShader)->asFragmentProcessor(AsFPArgs( + args.fContext, args.fViewMatrix, &tmp, args.fFilterQuality, args.fDstColorSpace)); +} +#endif + +sk_sp<SkFlattenable> SkLocalMatrixShader::CreateProc(SkReadBuffer& buffer) { + SkMatrix lm; + buffer.readMatrix(&lm); + auto baseShader(buffer.readShader()); + if (!baseShader) { + return nullptr; + } + return baseShader->makeWithLocalMatrix(lm); +} + +void SkLocalMatrixShader::flatten(SkWriteBuffer& buffer) const { + buffer.writeMatrix(this->getLocalMatrix()); + buffer.writeFlattenable(fProxyShader.get()); +} + +SkShaderBase::Context* SkLocalMatrixShader::onMakeContext( + const ContextRec& rec, SkArenaAlloc* alloc) const +{ + ContextRec newRec(rec); + SkMatrix tmp; + if (rec.fLocalMatrix) { + tmp.setConcat(*rec.fLocalMatrix, this->getLocalMatrix()); + newRec.fLocalMatrix = &tmp; + } else { + newRec.fLocalMatrix = &this->getLocalMatrix(); + } + return as_SB(fProxyShader)->makeContext(newRec, alloc); +} + +SkImage* SkLocalMatrixShader::onIsAImage(SkMatrix* outMatrix, enum TileMode* mode) const { + SkMatrix imageMatrix; + SkImage* image = fProxyShader->isAImage(&imageMatrix, mode); + if (image && outMatrix) { + // Local matrix must be applied first so it is on the right side of the concat. + *outMatrix = SkMatrix::Concat(imageMatrix, this->getLocalMatrix()); + } + + return image; +} + +bool SkLocalMatrixShader::onAppendStages(SkRasterPipeline* p, + SkColorSpace* dst, + SkArenaAlloc* scratch, + const SkMatrix& ctm, + const SkPaint& paint, + const SkMatrix* localM) const { + SkMatrix tmp; + if (localM) { + tmp.setConcat(*localM, this->getLocalMatrix()); + } + return as_SB(fProxyShader)->appendStages(p, dst, scratch, ctm, paint, + localM ? &tmp : &this->getLocalMatrix()); +} + +#ifndef SK_IGNORE_TO_STRING +void SkLocalMatrixShader::toString(SkString* str) const { + str->append("SkLocalMatrixShader: ("); + + as_SB(fProxyShader)->toString(str); + + this->INHERITED::toString(str); + + str->append(")"); +} +#endif + +sk_sp<SkShader> SkShader::makeWithLocalMatrix(const SkMatrix& localMatrix) const { + if (localMatrix.isIdentity()) { + return sk_ref_sp(const_cast<SkShader*>(this)); + } + + const SkMatrix* lm = &localMatrix; + + sk_sp<SkShader> baseShader; + SkMatrix otherLocalMatrix; + sk_sp<SkShader> proxy(as_SB(this)->makeAsALocalMatrixShader(&otherLocalMatrix)); + if (proxy) { + otherLocalMatrix.preConcat(localMatrix); + lm = &otherLocalMatrix; + baseShader = proxy; + } else { + baseShader = sk_ref_sp(const_cast<SkShader*>(this)); + } + + return sk_make_sp<SkLocalMatrixShader>(std::move(baseShader), *lm); +} diff --git a/src/shaders/SkLocalMatrixShader.h b/src/shaders/SkLocalMatrixShader.h new file mode 100644 index 0000000000..4572e9fe2e --- /dev/null +++ b/src/shaders/SkLocalMatrixShader.h @@ -0,0 +1,75 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkLocalMatrixShader_DEFINED +#define SkLocalMatrixShader_DEFINED + +#include "SkShaderBase.h" +#include "SkReadBuffer.h" +#include "SkWriteBuffer.h" + +class GrFragmentProcessor; +class SkArenaAlloc; +class SkColorSpaceXformer; + +class SkLocalMatrixShader : public SkShaderBase { +public: + SkLocalMatrixShader(sk_sp<SkShader> proxy, const SkMatrix& localMatrix) + : INHERITED(&localMatrix) + , fProxyShader(std::move(proxy)) + {} + + GradientType asAGradient(GradientInfo* info) const override { + return fProxyShader->asAGradient(info); + } + +#if SK_SUPPORT_GPU + sk_sp<GrFragmentProcessor> asFragmentProcessor(const AsFPArgs&) const override; +#endif + + sk_sp<SkShader> makeAsALocalMatrixShader(SkMatrix* localMatrix) const override { + if (localMatrix) { + *localMatrix = this->getLocalMatrix(); + } + return fProxyShader; + } + + SK_TO_STRING_OVERRIDE() + SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkLocalMatrixShader) + +protected: + void flatten(SkWriteBuffer&) const override; + + Context* onMakeContext(const ContextRec&, SkArenaAlloc*) const override; + + SkImage* onIsAImage(SkMatrix* matrix, TileMode* mode) const override; + + bool onAppendStages(SkRasterPipeline*, SkColorSpace*, SkArenaAlloc*, + const SkMatrix&, const SkPaint&, const SkMatrix*) const override; + + sk_sp<SkShader> onMakeColorSpace(SkColorSpaceXformer* xformer) const override { + return as_SB(fProxyShader)->makeColorSpace(xformer)->makeWithLocalMatrix( + this->getLocalMatrix()); + } + +#ifdef SK_SUPPORT_LEGACY_SHADER_ISABITMAP + bool onIsABitmap(SkBitmap* bitmap, SkMatrix* matrix, TileMode* mode) const override { + return fProxyShader->isABitmap(bitmap, matrix, mode); + } +#endif + + bool isRasterPipelineOnly() const final { + return as_SB(fProxyShader)->isRasterPipelineOnly(); + } + +private: + sk_sp<SkShader> fProxyShader; + + typedef SkShaderBase INHERITED; +}; + +#endif diff --git a/src/shaders/SkPerlinNoiseShader.cpp b/src/shaders/SkPerlinNoiseShader.cpp new file mode 100644 index 0000000000..9d8d030002 --- /dev/null +++ b/src/shaders/SkPerlinNoiseShader.cpp @@ -0,0 +1,1496 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkPerlinNoiseShader.h" + +#include "SkArenaAlloc.h" +#include "SkDither.h" +#include "SkColorFilter.h" +#include "SkReadBuffer.h" +#include "SkWriteBuffer.h" +#include "SkShader.h" +#include "SkUnPreMultiply.h" +#include "SkString.h" + +#if SK_SUPPORT_GPU +#include "GrContext.h" +#include "GrCoordTransform.h" +#include "SkGr.h" +#include "effects/GrConstColorProcessor.h" +#include "glsl/GrGLSLFragmentProcessor.h" +#include "glsl/GrGLSLFragmentShaderBuilder.h" +#include "glsl/GrGLSLProgramDataManager.h" +#include "glsl/GrGLSLUniformHandler.h" +#endif + +static const int kBlockSize = 256; +static const int kBlockMask = kBlockSize - 1; +static const int kPerlinNoise = 4096; +static const int kRandMaximum = SK_MaxS32; // 2**31 - 1 + +static uint8_t improved_noise_permutations[] = { + 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, + 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, + 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, + 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, + 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, + 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, + 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, + 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, + 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, + 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, + 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, + 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, + 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, + 141, 128, 195, 78, 66, 215, 61, 156, 180, + 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, + 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, + 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, + 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, + 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, + 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, + 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, + 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, + 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, + 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, + 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, + 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, + 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, + 141, 128, 195, 78, 66, 215, 61, 156, 180 +}; + +class SkPerlinNoiseShaderImpl : public SkShaderBase { +public: + struct StitchData; + struct PaintingData; + + /** + * About the noise types : the difference between the first 2 is just minor tweaks to the + * algorithm, they're not 2 entirely different noises. The output looks different, but once the + * noise is generated in the [1, -1] range, the output is brought back in the [0, 1] range by + * doing : + * kFractalNoise_Type : noise * 0.5 + 0.5 + * kTurbulence_Type : abs(noise) + * Very little differences between the 2 types, although you can tell the difference visually. + * "Improved" is based on the Improved Perlin Noise algorithm described at + * http://mrl.nyu.edu/~perlin/noise/. It is quite distinct from the other two, and the noise is + * a 2D slice of a 3D noise texture. Minor changes to the Z coordinate will result in minor + * changes to the noise, making it suitable for animated noise. + */ + enum Type { + kFractalNoise_Type, + kTurbulence_Type, + kImprovedNoise_Type, + kFirstType = kFractalNoise_Type, + kLastType = kImprovedNoise_Type + }; + + SkPerlinNoiseShaderImpl(SkPerlinNoiseShaderImpl::Type type, SkScalar baseFrequencyX, + SkScalar baseFrequencyY, int numOctaves, SkScalar seed, + const SkISize* tileSize); + ~SkPerlinNoiseShaderImpl() override; + + + class PerlinNoiseShaderContext : public Context { + public: + PerlinNoiseShaderContext(const SkPerlinNoiseShaderImpl& shader, const ContextRec&); + ~PerlinNoiseShaderContext() override; + + void shadeSpan(int x, int y, SkPMColor[], int count) override; + + private: + SkPMColor shade(const SkPoint& point, StitchData& stitchData) const; + SkScalar calculateTurbulenceValueForPoint( + int channel, + StitchData& stitchData, const SkPoint& point) const; + SkScalar calculateImprovedNoiseValueForPoint(int channel, const SkPoint& point) const; + SkScalar noise2D(int channel, + const StitchData& stitchData, const SkPoint& noiseVector) const; + + SkMatrix fMatrix; + PaintingData* fPaintingData; + + typedef Context INHERITED; + }; + +#if SK_SUPPORT_GPU + sk_sp<GrFragmentProcessor> asFragmentProcessor(const AsFPArgs&) const override; +#endif + + SK_TO_STRING_OVERRIDE() + SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkPerlinNoiseShaderImpl) + +protected: + void flatten(SkWriteBuffer&) const override; + Context* onMakeContext(const ContextRec&, SkArenaAlloc*) const override; + +private: + const SkPerlinNoiseShaderImpl::Type fType; + const SkScalar fBaseFrequencyX; + const SkScalar fBaseFrequencyY; + const int fNumOctaves; + const SkScalar fSeed; + const SkISize fTileSize; + const bool fStitchTiles; + + friend class ::SkPerlinNoiseShader; + + typedef SkShaderBase INHERITED; +}; + +namespace { + +// noiseValue is the color component's value (or color) +// limitValue is the maximum perlin noise array index value allowed +// newValue is the current noise dimension (either width or height) +inline int checkNoise(int noiseValue, int limitValue, int newValue) { + // If the noise value would bring us out of bounds of the current noise array while we are + // stiching noise tiles together, wrap the noise around the current dimension of the noise to + // stay within the array bounds in a continuous fashion (so that tiling lines are not visible) + if (noiseValue >= limitValue) { + noiseValue -= newValue; + } + return noiseValue; +} + +inline SkScalar smoothCurve(SkScalar t) { + return t * t * (3 - 2 * t); +} + +} // end namespace + +struct SkPerlinNoiseShaderImpl::StitchData { + StitchData() + : fWidth(0) + , fWrapX(0) + , fHeight(0) + , fWrapY(0) + {} + + bool operator==(const StitchData& other) const { + return fWidth == other.fWidth && + fWrapX == other.fWrapX && + fHeight == other.fHeight && + fWrapY == other.fWrapY; + } + + int fWidth; // How much to subtract to wrap for stitching. + int fWrapX; // Minimum value to wrap. + int fHeight; + int fWrapY; +}; + +struct SkPerlinNoiseShaderImpl::PaintingData { + PaintingData(const SkISize& tileSize, SkScalar seed, + SkScalar baseFrequencyX, SkScalar baseFrequencyY, + const SkMatrix& matrix) + { + SkVector vec[2] = { + { SkScalarInvert(baseFrequencyX), SkScalarInvert(baseFrequencyY) }, + { SkIntToScalar(tileSize.fWidth), SkIntToScalar(tileSize.fHeight) }, + }; + matrix.mapVectors(vec, 2); + + fBaseFrequency.set(SkScalarInvert(vec[0].fX), SkScalarInvert(vec[0].fY)); + fTileSize.set(SkScalarRoundToInt(vec[1].fX), SkScalarRoundToInt(vec[1].fY)); + this->init(seed); + if (!fTileSize.isEmpty()) { + this->stitch(); + } + +#if SK_SUPPORT_GPU + fPermutationsBitmap.setInfo(SkImageInfo::MakeA8(kBlockSize, 1)); + fPermutationsBitmap.setPixels(fLatticeSelector); + + fNoiseBitmap.setInfo(SkImageInfo::MakeN32Premul(kBlockSize, 4)); + fNoiseBitmap.setPixels(fNoise[0][0]); + + fImprovedPermutationsBitmap.setInfo(SkImageInfo::MakeA8(256, 1)); + fImprovedPermutationsBitmap.setPixels(improved_noise_permutations); + + fGradientBitmap.setInfo(SkImageInfo::MakeN32Premul(16, 1)); + static uint8_t gradients[] = { 2, 2, 1, 0, + 0, 2, 1, 0, + 2, 0, 1, 0, + 0, 0, 1, 0, + 2, 1, 2, 0, + 0, 1, 2, 0, + 2, 1, 0, 0, + 0, 1, 0, 0, + 1, 2, 2, 0, + 1, 0, 2, 0, + 1, 2, 0, 0, + 1, 0, 0, 0, + 2, 2, 1, 0, + 1, 0, 2, 0, + 0, 2, 1, 0, + 1, 0, 0, 0 }; + fGradientBitmap.setPixels(gradients); +#endif + } + + int fSeed; + uint8_t fLatticeSelector[kBlockSize]; + uint16_t fNoise[4][kBlockSize][2]; + SkPoint fGradient[4][kBlockSize]; + SkISize fTileSize; + SkVector fBaseFrequency; + StitchData fStitchDataInit; + +private: + +#if SK_SUPPORT_GPU + SkBitmap fPermutationsBitmap; + SkBitmap fNoiseBitmap; + SkBitmap fImprovedPermutationsBitmap; + SkBitmap fGradientBitmap; +#endif + + inline int random() { + static const int gRandAmplitude = 16807; // 7**5; primitive root of m + static const int gRandQ = 127773; // m / a + static const int gRandR = 2836; // m % a + + int result = gRandAmplitude * (fSeed % gRandQ) - gRandR * (fSeed / gRandQ); + if (result <= 0) + result += kRandMaximum; + fSeed = result; + return result; + } + + // Only called once. Could be part of the constructor. + void init(SkScalar seed) + { + static const SkScalar gInvBlockSizef = SkScalarInvert(SkIntToScalar(kBlockSize)); + + // According to the SVG spec, we must truncate (not round) the seed value. + fSeed = SkScalarTruncToInt(seed); + // The seed value clamp to the range [1, kRandMaximum - 1]. + if (fSeed <= 0) { + fSeed = -(fSeed % (kRandMaximum - 1)) + 1; + } + if (fSeed > kRandMaximum - 1) { + fSeed = kRandMaximum - 1; + } + for (int channel = 0; channel < 4; ++channel) { + for (int i = 0; i < kBlockSize; ++i) { + fLatticeSelector[i] = i; + fNoise[channel][i][0] = (random() % (2 * kBlockSize)); + fNoise[channel][i][1] = (random() % (2 * kBlockSize)); + } + } + for (int i = kBlockSize - 1; i > 0; --i) { + int k = fLatticeSelector[i]; + int j = random() % kBlockSize; + SkASSERT(j >= 0); + SkASSERT(j < kBlockSize); + fLatticeSelector[i] = fLatticeSelector[j]; + fLatticeSelector[j] = k; + } + + // Perform the permutations now + { + // Copy noise data + uint16_t noise[4][kBlockSize][2]; + for (int i = 0; i < kBlockSize; ++i) { + for (int channel = 0; channel < 4; ++channel) { + for (int j = 0; j < 2; ++j) { + noise[channel][i][j] = fNoise[channel][i][j]; + } + } + } + // Do permutations on noise data + for (int i = 0; i < kBlockSize; ++i) { + for (int channel = 0; channel < 4; ++channel) { + for (int j = 0; j < 2; ++j) { + fNoise[channel][i][j] = noise[channel][fLatticeSelector[i]][j]; + } + } + } + } + + // Half of the largest possible value for 16 bit unsigned int + static const SkScalar gHalfMax16bits = 32767.5f; + + // Compute gradients from permutated noise data + for (int channel = 0; channel < 4; ++channel) { + for (int i = 0; i < kBlockSize; ++i) { + fGradient[channel][i] = SkPoint::Make( + (fNoise[channel][i][0] - kBlockSize) * gInvBlockSizef, + (fNoise[channel][i][1] - kBlockSize) * gInvBlockSizef); + fGradient[channel][i].normalize(); + // Put the normalized gradient back into the noise data + fNoise[channel][i][0] = SkScalarRoundToInt( + (fGradient[channel][i].fX + 1) * gHalfMax16bits); + fNoise[channel][i][1] = SkScalarRoundToInt( + (fGradient[channel][i].fY + 1) * gHalfMax16bits); + } + } + } + + // Only called once. Could be part of the constructor. + void stitch() { + SkScalar tileWidth = SkIntToScalar(fTileSize.width()); + SkScalar tileHeight = SkIntToScalar(fTileSize.height()); + SkASSERT(tileWidth > 0 && tileHeight > 0); + // When stitching tiled turbulence, the frequencies must be adjusted + // so that the tile borders will be continuous. + if (fBaseFrequency.fX) { + SkScalar lowFrequencx = + SkScalarFloorToScalar(tileWidth * fBaseFrequency.fX) / tileWidth; + SkScalar highFrequencx = + SkScalarCeilToScalar(tileWidth * fBaseFrequency.fX) / tileWidth; + // BaseFrequency should be non-negative according to the standard. + if (fBaseFrequency.fX / lowFrequencx < highFrequencx / fBaseFrequency.fX) { + fBaseFrequency.fX = lowFrequencx; + } else { + fBaseFrequency.fX = highFrequencx; + } + } + if (fBaseFrequency.fY) { + SkScalar lowFrequency = + SkScalarFloorToScalar(tileHeight * fBaseFrequency.fY) / tileHeight; + SkScalar highFrequency = + SkScalarCeilToScalar(tileHeight * fBaseFrequency.fY) / tileHeight; + if (fBaseFrequency.fY / lowFrequency < highFrequency / fBaseFrequency.fY) { + fBaseFrequency.fY = lowFrequency; + } else { + fBaseFrequency.fY = highFrequency; + } + } + // Set up TurbulenceInitial stitch values. + fStitchDataInit.fWidth = + SkScalarRoundToInt(tileWidth * fBaseFrequency.fX); + fStitchDataInit.fWrapX = kPerlinNoise + fStitchDataInit.fWidth; + fStitchDataInit.fHeight = + SkScalarRoundToInt(tileHeight * fBaseFrequency.fY); + fStitchDataInit.fWrapY = kPerlinNoise + fStitchDataInit.fHeight; + } + +public: + +#if SK_SUPPORT_GPU + const SkBitmap& getPermutationsBitmap() const { return fPermutationsBitmap; } + + const SkBitmap& getNoiseBitmap() const { return fNoiseBitmap; } + + const SkBitmap& getImprovedPermutationsBitmap() const { return fImprovedPermutationsBitmap; } + + const SkBitmap& getGradientBitmap() const { return fGradientBitmap; } +#endif +}; + +SkPerlinNoiseShaderImpl::SkPerlinNoiseShaderImpl(SkPerlinNoiseShaderImpl::Type type, + SkScalar baseFrequencyX, + SkScalar baseFrequencyY, + int numOctaves, + SkScalar seed, + const SkISize* tileSize) + : fType(type) + , fBaseFrequencyX(baseFrequencyX) + , fBaseFrequencyY(baseFrequencyY) + , fNumOctaves(numOctaves > 255 ? 255 : numOctaves/*[0,255] octaves allowed*/) + , fSeed(seed) + , fTileSize(nullptr == tileSize ? SkISize::Make(0, 0) : *tileSize) + , fStitchTiles(!fTileSize.isEmpty()) +{ + SkASSERT(numOctaves >= 0 && numOctaves < 256); +} + +SkPerlinNoiseShaderImpl::~SkPerlinNoiseShaderImpl() { +} + +sk_sp<SkFlattenable> SkPerlinNoiseShaderImpl::CreateProc(SkReadBuffer& buffer) { + Type type = (Type)buffer.readInt(); + SkScalar freqX = buffer.readScalar(); + SkScalar freqY = buffer.readScalar(); + int octaves = buffer.readInt(); + SkScalar seed = buffer.readScalar(); + SkISize tileSize; + tileSize.fWidth = buffer.readInt(); + tileSize.fHeight = buffer.readInt(); + + switch (type) { + case kFractalNoise_Type: + return SkPerlinNoiseShader::MakeFractalNoise(freqX, freqY, octaves, seed, &tileSize); + case kTurbulence_Type: + return SkPerlinNoiseShader::MakeTurbulence(freqX, freqY, octaves, seed, &tileSize); + case kImprovedNoise_Type: + return SkPerlinNoiseShader::MakeImprovedNoise(freqX, freqY, octaves, seed); + default: + return nullptr; + } +} + +void SkPerlinNoiseShaderImpl::flatten(SkWriteBuffer& buffer) const { + buffer.writeInt((int) fType); + buffer.writeScalar(fBaseFrequencyX); + buffer.writeScalar(fBaseFrequencyY); + buffer.writeInt(fNumOctaves); + buffer.writeScalar(fSeed); + buffer.writeInt(fTileSize.fWidth); + buffer.writeInt(fTileSize.fHeight); +} + +SkScalar SkPerlinNoiseShaderImpl::PerlinNoiseShaderContext::noise2D( + int channel, const StitchData& stitchData, const SkPoint& noiseVector) const { + struct Noise { + int noisePositionIntegerValue; + int nextNoisePositionIntegerValue; + SkScalar noisePositionFractionValue; + Noise(SkScalar component) + { + SkScalar position = component + kPerlinNoise; + noisePositionIntegerValue = SkScalarFloorToInt(position); + noisePositionFractionValue = position - SkIntToScalar(noisePositionIntegerValue); + nextNoisePositionIntegerValue = noisePositionIntegerValue + 1; + } + }; + Noise noiseX(noiseVector.x()); + Noise noiseY(noiseVector.y()); + SkScalar u, v; + const SkPerlinNoiseShaderImpl& perlinNoiseShader = static_cast<const SkPerlinNoiseShaderImpl&>(fShader); + // If stitching, adjust lattice points accordingly. + if (perlinNoiseShader.fStitchTiles) { + noiseX.noisePositionIntegerValue = + checkNoise(noiseX.noisePositionIntegerValue, stitchData.fWrapX, stitchData.fWidth); + noiseY.noisePositionIntegerValue = + checkNoise(noiseY.noisePositionIntegerValue, stitchData.fWrapY, stitchData.fHeight); + noiseX.nextNoisePositionIntegerValue = + checkNoise(noiseX.nextNoisePositionIntegerValue, stitchData.fWrapX, stitchData.fWidth); + noiseY.nextNoisePositionIntegerValue = + checkNoise(noiseY.nextNoisePositionIntegerValue, stitchData.fWrapY, stitchData.fHeight); + } + noiseX.noisePositionIntegerValue &= kBlockMask; + noiseY.noisePositionIntegerValue &= kBlockMask; + noiseX.nextNoisePositionIntegerValue &= kBlockMask; + noiseY.nextNoisePositionIntegerValue &= kBlockMask; + int i = + fPaintingData->fLatticeSelector[noiseX.noisePositionIntegerValue]; + int j = + fPaintingData->fLatticeSelector[noiseX.nextNoisePositionIntegerValue]; + int b00 = (i + noiseY.noisePositionIntegerValue) & kBlockMask; + int b10 = (j + noiseY.noisePositionIntegerValue) & kBlockMask; + int b01 = (i + noiseY.nextNoisePositionIntegerValue) & kBlockMask; + int b11 = (j + noiseY.nextNoisePositionIntegerValue) & kBlockMask; + SkScalar sx = smoothCurve(noiseX.noisePositionFractionValue); + SkScalar sy = smoothCurve(noiseY.noisePositionFractionValue); + + if (sx < 0 || sy < 0 || sx > 1 || sy > 1) { + return 0; // Check for pathological inputs. + } + + // This is taken 1:1 from SVG spec: http://www.w3.org/TR/SVG11/filters.html#feTurbulenceElement + SkPoint fractionValue = SkPoint::Make(noiseX.noisePositionFractionValue, + noiseY.noisePositionFractionValue); // Offset (0,0) + u = fPaintingData->fGradient[channel][b00].dot(fractionValue); + fractionValue.fX -= SK_Scalar1; // Offset (-1,0) + v = fPaintingData->fGradient[channel][b10].dot(fractionValue); + SkScalar a = SkScalarInterp(u, v, sx); + fractionValue.fY -= SK_Scalar1; // Offset (-1,-1) + v = fPaintingData->fGradient[channel][b11].dot(fractionValue); + fractionValue.fX = noiseX.noisePositionFractionValue; // Offset (0,-1) + u = fPaintingData->fGradient[channel][b01].dot(fractionValue); + SkScalar b = SkScalarInterp(u, v, sx); + return SkScalarInterp(a, b, sy); +} + +SkScalar SkPerlinNoiseShaderImpl::PerlinNoiseShaderContext::calculateTurbulenceValueForPoint( + int channel, StitchData& stitchData, const SkPoint& point) const { + const SkPerlinNoiseShaderImpl& perlinNoiseShader = static_cast<const SkPerlinNoiseShaderImpl&>(fShader); + if (perlinNoiseShader.fStitchTiles) { + // Set up TurbulenceInitial stitch values. + stitchData = fPaintingData->fStitchDataInit; + } + SkScalar turbulenceFunctionResult = 0; + SkPoint noiseVector(SkPoint::Make(point.x() * fPaintingData->fBaseFrequency.fX, + point.y() * fPaintingData->fBaseFrequency.fY)); + SkScalar ratio = SK_Scalar1; + for (int octave = 0; octave < perlinNoiseShader.fNumOctaves; ++octave) { + SkScalar noise = noise2D(channel, stitchData, noiseVector); + SkScalar numer = (perlinNoiseShader.fType == kFractalNoise_Type) ? + noise : SkScalarAbs(noise); + turbulenceFunctionResult += numer / ratio; + noiseVector.fX *= 2; + noiseVector.fY *= 2; + ratio *= 2; + if (perlinNoiseShader.fStitchTiles) { + // Update stitch values + stitchData.fWidth *= 2; + stitchData.fWrapX = stitchData.fWidth + kPerlinNoise; + stitchData.fHeight *= 2; + stitchData.fWrapY = stitchData.fHeight + kPerlinNoise; + } + } + + // The value of turbulenceFunctionResult comes from ((turbulenceFunctionResult) + 1) / 2 + // by fractalNoise and (turbulenceFunctionResult) by turbulence. + if (perlinNoiseShader.fType == kFractalNoise_Type) { + turbulenceFunctionResult = SkScalarHalf(turbulenceFunctionResult + 1); + } + + if (channel == 3) { // Scale alpha by paint value + turbulenceFunctionResult *= SkIntToScalar(getPaintAlpha()) / 255; + } + + // Clamp result + return SkScalarPin(turbulenceFunctionResult, 0, SK_Scalar1); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Improved Perlin Noise based on Java implementation found at http://mrl.nyu.edu/~perlin/noise/ +static SkScalar fade(SkScalar t) { + return t * t * t * (t * (t * 6 - 15) + 10); +} + +static SkScalar lerp(SkScalar t, SkScalar a, SkScalar b) { + return a + t * (b - a); +} + +static SkScalar grad(int hash, SkScalar x, SkScalar y, SkScalar z) { + int h = hash & 15; + SkScalar u = h < 8 ? x : y; + SkScalar v = h < 4 ? y : h == 12 || h == 14 ? x : z; + return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v); +} + +SkScalar SkPerlinNoiseShaderImpl::PerlinNoiseShaderContext::calculateImprovedNoiseValueForPoint( + int channel, const SkPoint& point) const { + const SkPerlinNoiseShaderImpl& perlinNoiseShader = static_cast<const SkPerlinNoiseShaderImpl&>(fShader); + SkScalar x = point.fX * perlinNoiseShader.fBaseFrequencyX; + SkScalar y = point.fY * perlinNoiseShader.fBaseFrequencyY; + // z offset between different channels, chosen arbitrarily + static const SkScalar CHANNEL_DELTA = 1000.0f; + SkScalar z = channel * CHANNEL_DELTA + perlinNoiseShader.fSeed; + SkScalar result = 0; + SkScalar ratio = SK_Scalar1; + for (int i = 0; i < perlinNoiseShader.fNumOctaves; i++) { + int X = SkScalarFloorToInt(x) & 255; + int Y = SkScalarFloorToInt(y) & 255; + int Z = SkScalarFloorToInt(z) & 255; + SkScalar px = x - SkScalarFloorToScalar(x); + SkScalar py = y - SkScalarFloorToScalar(y); + SkScalar pz = z - SkScalarFloorToScalar(z); + SkScalar u = fade(px); + SkScalar v = fade(py); + SkScalar w = fade(pz); + uint8_t* permutations = improved_noise_permutations; + int A = permutations[X] + Y; + int AA = permutations[A] + Z; + int AB = permutations[A + 1] + Z; + int B = permutations[X + 1] + Y; + int BA = permutations[B] + Z; + int BB = permutations[B + 1] + Z; + result += lerp(w, lerp(v, lerp(u, grad(permutations[AA ], px , py , pz ), + grad(permutations[BA ], px - 1, py , pz )), + lerp(u, grad(permutations[AB ], px , py - 1, pz ), + grad(permutations[BB ], px - 1, py - 1, pz ))), + lerp(v, lerp(u, grad(permutations[AA + 1], px , py , pz - 1), + grad(permutations[BA + 1], px - 1, py , pz - 1)), + lerp(u, grad(permutations[AB + 1], px , py - 1, pz - 1), + grad(permutations[BB + 1], px - 1, py - 1, pz - 1)))) / + ratio; + x *= 2; + y *= 2; + ratio *= 2; + } + result = SkScalarClampMax((result + 1.0f) / 2.0f, 1.0f); + return result; +} +//////////////////////////////////////////////////////////////////////////////////////////////////// + +SkPMColor SkPerlinNoiseShaderImpl::PerlinNoiseShaderContext::shade( + const SkPoint& point, StitchData& stitchData) const { + const SkPerlinNoiseShaderImpl& perlinNoiseShader = static_cast<const SkPerlinNoiseShaderImpl&>(fShader); + SkPoint newPoint; + fMatrix.mapPoints(&newPoint, &point, 1); + newPoint.fX = SkScalarRoundToScalar(newPoint.fX); + newPoint.fY = SkScalarRoundToScalar(newPoint.fY); + + U8CPU rgba[4]; + for (int channel = 3; channel >= 0; --channel) { + SkScalar value; + if (perlinNoiseShader.fType == kImprovedNoise_Type) { + value = calculateImprovedNoiseValueForPoint(channel, newPoint); + } + else { + value = calculateTurbulenceValueForPoint(channel, stitchData, newPoint); + } + rgba[channel] = SkScalarFloorToInt(255 * value); + } + return SkPreMultiplyARGB(rgba[3], rgba[0], rgba[1], rgba[2]); +} + +SkShaderBase::Context* SkPerlinNoiseShaderImpl::onMakeContext(const ContextRec& rec, + SkArenaAlloc* alloc) const { + return alloc->make<PerlinNoiseShaderContext>(*this, rec); +} + +SkPerlinNoiseShaderImpl::PerlinNoiseShaderContext::PerlinNoiseShaderContext( + const SkPerlinNoiseShaderImpl& shader, const ContextRec& rec) + : INHERITED(shader, rec) +{ + SkMatrix newMatrix = *rec.fMatrix; + newMatrix.preConcat(shader.getLocalMatrix()); + if (rec.fLocalMatrix) { + newMatrix.preConcat(*rec.fLocalMatrix); + } + // This (1,1) translation is due to WebKit's 1 based coordinates for the noise + // (as opposed to 0 based, usually). The same adjustment is in the setData() function. + fMatrix.setTranslate(-newMatrix.getTranslateX() + SK_Scalar1, -newMatrix.getTranslateY() + SK_Scalar1); + fPaintingData = new PaintingData(shader.fTileSize, shader.fSeed, shader.fBaseFrequencyX, + shader.fBaseFrequencyY, newMatrix); +} + +SkPerlinNoiseShaderImpl::PerlinNoiseShaderContext::~PerlinNoiseShaderContext() { delete fPaintingData; } + +void SkPerlinNoiseShaderImpl::PerlinNoiseShaderContext::shadeSpan( + int x, int y, SkPMColor result[], int count) { + SkPoint point = SkPoint::Make(SkIntToScalar(x), SkIntToScalar(y)); + StitchData stitchData; + for (int i = 0; i < count; ++i) { + result[i] = shade(point, stitchData); + point.fX += SK_Scalar1; + } +} + +///////////////////////////////////////////////////////////////////// + +#if SK_SUPPORT_GPU + +class GrGLPerlinNoise : public GrGLSLFragmentProcessor { +public: + void emitCode(EmitArgs&) override; + + static inline void GenKey(const GrProcessor&, const GrShaderCaps&, GrProcessorKeyBuilder* b); + +protected: + void onSetData(const GrGLSLProgramDataManager&, const GrFragmentProcessor&) override; + +private: + GrGLSLProgramDataManager::UniformHandle fStitchDataUni; + GrGLSLProgramDataManager::UniformHandle fBaseFrequencyUni; + + typedef GrGLSLFragmentProcessor INHERITED; +}; + +///////////////////////////////////////////////////////////////////// + +class GrPerlinNoise2Effect : public GrFragmentProcessor { +public: + static sk_sp<GrFragmentProcessor> Make(GrResourceProvider* resourceProvider, + SkPerlinNoiseShaderImpl::Type type, + int numOctaves, bool stitchTiles, + SkPerlinNoiseShaderImpl::PaintingData* paintingData, + sk_sp<GrTextureProxy> permutationsProxy, + sk_sp<GrTextureProxy> noiseProxy, + const SkMatrix& matrix) { + return sk_sp<GrFragmentProcessor>( + new GrPerlinNoise2Effect(resourceProvider, type, numOctaves, stitchTiles, paintingData, + std::move(permutationsProxy), std::move(noiseProxy), matrix)); + } + + ~GrPerlinNoise2Effect() override { delete fPaintingData; } + + const char* name() const override { return "PerlinNoise"; } + + const SkPerlinNoiseShaderImpl::StitchData& stitchData() const { return fPaintingData->fStitchDataInit; } + + SkPerlinNoiseShaderImpl::Type type() const { return fType; } + bool stitchTiles() const { return fStitchTiles; } + const SkVector& baseFrequency() const { return fPaintingData->fBaseFrequency; } + int numOctaves() const { return fNumOctaves; } + const SkMatrix& matrix() const { return fCoordTransform.getMatrix(); } + +private: + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override { + return new GrGLPerlinNoise; + } + + virtual void onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const override { + GrGLPerlinNoise::GenKey(*this, caps, b); + } + + bool onIsEqual(const GrFragmentProcessor& sBase) const override { + const GrPerlinNoise2Effect& s = sBase.cast<GrPerlinNoise2Effect>(); + return fType == s.fType && + fPaintingData->fBaseFrequency == s.fPaintingData->fBaseFrequency && + fNumOctaves == s.fNumOctaves && + fStitchTiles == s.fStitchTiles && + fPaintingData->fStitchDataInit == s.fPaintingData->fStitchDataInit; + } + + GrPerlinNoise2Effect(GrResourceProvider* resourceProvider, + SkPerlinNoiseShaderImpl::Type type, int numOctaves, bool stitchTiles, + SkPerlinNoiseShaderImpl::PaintingData* paintingData, + sk_sp<GrTextureProxy> permutationsProxy, + sk_sp<GrTextureProxy> noiseProxy, + const SkMatrix& matrix) + : INHERITED(kNone_OptimizationFlags) + , fType(type) + , fNumOctaves(numOctaves) + , fStitchTiles(stitchTiles) + , fPermutationsSampler(resourceProvider, std::move(permutationsProxy)) + , fNoiseSampler(resourceProvider, std::move(noiseProxy)) + , fPaintingData(paintingData) { + this->initClassID<GrPerlinNoise2Effect>(); + this->addTextureSampler(&fPermutationsSampler); + this->addTextureSampler(&fNoiseSampler); + fCoordTransform.reset(matrix); + this->addCoordTransform(&fCoordTransform); + } + + GR_DECLARE_FRAGMENT_PROCESSOR_TEST; + + SkPerlinNoiseShaderImpl::Type fType; + GrCoordTransform fCoordTransform; + int fNumOctaves; + bool fStitchTiles; + TextureSampler fPermutationsSampler; + TextureSampler fNoiseSampler; + SkPerlinNoiseShaderImpl::PaintingData* fPaintingData; + + typedef GrFragmentProcessor INHERITED; +}; + +///////////////////////////////////////////////////////////////////// +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrPerlinNoise2Effect); + +#if GR_TEST_UTILS +sk_sp<GrFragmentProcessor> GrPerlinNoise2Effect::TestCreate(GrProcessorTestData* d) { + int numOctaves = d->fRandom->nextRangeU(2, 10); + bool stitchTiles = d->fRandom->nextBool(); + SkScalar seed = SkIntToScalar(d->fRandom->nextU()); + SkISize tileSize = SkISize::Make(d->fRandom->nextRangeU(4, 4096), + d->fRandom->nextRangeU(4, 4096)); + SkScalar baseFrequencyX = d->fRandom->nextRangeScalar(0.01f, + 0.99f); + SkScalar baseFrequencyY = d->fRandom->nextRangeScalar(0.01f, + 0.99f); + + sk_sp<SkShader> shader(d->fRandom->nextBool() ? + SkPerlinNoiseShader::MakeFractalNoise(baseFrequencyX, baseFrequencyY, numOctaves, seed, + stitchTiles ? &tileSize : nullptr) : + SkPerlinNoiseShader::MakeTurbulence(baseFrequencyX, baseFrequencyY, numOctaves, seed, + stitchTiles ? &tileSize : nullptr)); + + GrTest::TestAsFPArgs asFPArgs(d); + return as_SB(shader)->asFragmentProcessor(asFPArgs.args()); +} +#endif + +void GrGLPerlinNoise::emitCode(EmitArgs& args) { + const GrPerlinNoise2Effect& pne = args.fFp.cast<GrPerlinNoise2Effect>(); + + GrGLSLFragmentBuilder* fragBuilder = args.fFragBuilder; + GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; + SkString vCoords = fragBuilder->ensureCoords2D(args.fTransformedCoords[0]); + + fBaseFrequencyUni = uniformHandler->addUniform(kFragment_GrShaderFlag, + kVec2f_GrSLType, kDefault_GrSLPrecision, + "baseFrequency"); + const char* baseFrequencyUni = uniformHandler->getUniformCStr(fBaseFrequencyUni); + + const char* stitchDataUni = nullptr; + if (pne.stitchTiles()) { + fStitchDataUni = uniformHandler->addUniform(kFragment_GrShaderFlag, + kVec2f_GrSLType, kDefault_GrSLPrecision, + "stitchData"); + stitchDataUni = uniformHandler->getUniformCStr(fStitchDataUni); + } + + // There are 4 lines, so the center of each line is 1/8, 3/8, 5/8 and 7/8 + const char* chanCoordR = "0.125"; + const char* chanCoordG = "0.375"; + const char* chanCoordB = "0.625"; + const char* chanCoordA = "0.875"; + const char* chanCoord = "chanCoord"; + const char* stitchData = "stitchData"; + const char* ratio = "ratio"; + const char* noiseVec = "noiseVec"; + const char* noiseSmooth = "noiseSmooth"; + const char* floorVal = "floorVal"; + const char* fractVal = "fractVal"; + const char* uv = "uv"; + const char* ab = "ab"; + const char* latticeIdx = "latticeIdx"; + const char* bcoords = "bcoords"; + const char* lattice = "lattice"; + const char* inc8bit = "0.00390625"; // 1.0 / 256.0 + // This is the math to convert the two 16bit integer packed into rgba 8 bit input into a + // [-1,1] vector and perform a dot product between that vector and the provided vector. + const char* dotLattice = "dot(((%s.ga + %s.rb * vec2(%s)) * vec2(2.0) - vec2(1.0)), %s);"; + + // Add noise function + static const GrShaderVar gPerlinNoiseArgs[] = { + GrShaderVar(chanCoord, kFloat_GrSLType), + GrShaderVar(noiseVec, kVec2f_GrSLType) + }; + + static const GrShaderVar gPerlinNoiseStitchArgs[] = { + GrShaderVar(chanCoord, kFloat_GrSLType), + GrShaderVar(noiseVec, kVec2f_GrSLType), + GrShaderVar(stitchData, kVec2f_GrSLType) + }; + + SkString noiseCode; + + noiseCode.appendf("\tvec4 %s;\n", floorVal); + noiseCode.appendf("\t%s.xy = floor(%s);\n", floorVal, noiseVec); + noiseCode.appendf("\t%s.zw = %s.xy + vec2(1.0);\n", floorVal, floorVal); + noiseCode.appendf("\tvec2 %s = fract(%s);\n", fractVal, noiseVec); + + // smooth curve : t * t * (3 - 2 * t) + noiseCode.appendf("\n\tvec2 %s = %s * %s * (vec2(3.0) - vec2(2.0) * %s);", + noiseSmooth, fractVal, fractVal, fractVal); + + // Adjust frequencies if we're stitching tiles + if (pne.stitchTiles()) { + noiseCode.appendf("\n\tif(%s.x >= %s.x) { %s.x -= %s.x; }", + floorVal, stitchData, floorVal, stitchData); + noiseCode.appendf("\n\tif(%s.y >= %s.y) { %s.y -= %s.y; }", + floorVal, stitchData, floorVal, stitchData); + noiseCode.appendf("\n\tif(%s.z >= %s.x) { %s.z -= %s.x; }", + floorVal, stitchData, floorVal, stitchData); + noiseCode.appendf("\n\tif(%s.w >= %s.y) { %s.w -= %s.y; }", + floorVal, stitchData, floorVal, stitchData); + } + + // Get texture coordinates and normalize + noiseCode.appendf("\n\t%s = fract(floor(mod(%s, 256.0)) / vec4(256.0));\n", + floorVal, floorVal); + + // Get permutation for x + { + SkString xCoords(""); + xCoords.appendf("vec2(%s.x, 0.5)", floorVal); + + noiseCode.appendf("\n\tvec2 %s;\n\t%s.x = ", latticeIdx, latticeIdx); + fragBuilder->appendTextureLookup(&noiseCode, args.fTexSamplers[0], xCoords.c_str(), + kVec2f_GrSLType); + noiseCode.append(".r;"); + } + + // Get permutation for x + 1 + { + SkString xCoords(""); + xCoords.appendf("vec2(%s.z, 0.5)", floorVal); + + noiseCode.appendf("\n\t%s.y = ", latticeIdx); + fragBuilder->appendTextureLookup(&noiseCode, args.fTexSamplers[0], xCoords.c_str(), + kVec2f_GrSLType); + noiseCode.append(".r;"); + } + +#if defined(SK_BUILD_FOR_ANDROID) + // Android rounding for Tegra devices, like, for example: Xoom (Tegra 2), Nexus 7 (Tegra 3). + // The issue is that colors aren't accurate enough on Tegra devices. For example, if an 8 bit + // value of 124 (or 0.486275 here) is entered, we can get a texture value of 123.513725 + // (or 0.484368 here). The following rounding operation prevents these precision issues from + // affecting the result of the noise by making sure that we only have multiples of 1/255. + // (Note that 1/255 is about 0.003921569, which is the value used here). + noiseCode.appendf("\n\t%s = floor(%s * vec2(255.0) + vec2(0.5)) * vec2(0.003921569);", + latticeIdx, latticeIdx); +#endif + + // Get (x,y) coordinates with the permutated x + noiseCode.appendf("\n\tvec4 %s = fract(%s.xyxy + %s.yyww);", bcoords, latticeIdx, floorVal); + + noiseCode.appendf("\n\n\tvec2 %s;", uv); + // Compute u, at offset (0,0) + { + SkString latticeCoords(""); + latticeCoords.appendf("vec2(%s.x, %s)", bcoords, chanCoord); + noiseCode.appendf("\n\tvec4 %s = ", lattice); + fragBuilder->appendTextureLookup(&noiseCode, args.fTexSamplers[1], latticeCoords.c_str(), + kVec2f_GrSLType); + noiseCode.appendf(".bgra;\n\t%s.x = ", uv); + noiseCode.appendf(dotLattice, lattice, lattice, inc8bit, fractVal); + } + + noiseCode.appendf("\n\t%s.x -= 1.0;", fractVal); + // Compute v, at offset (-1,0) + { + SkString latticeCoords(""); + latticeCoords.appendf("vec2(%s.y, %s)", bcoords, chanCoord); + noiseCode.append("\n\tlattice = "); + fragBuilder->appendTextureLookup(&noiseCode, args.fTexSamplers[1], latticeCoords.c_str(), + kVec2f_GrSLType); + noiseCode.appendf(".bgra;\n\t%s.y = ", uv); + noiseCode.appendf(dotLattice, lattice, lattice, inc8bit, fractVal); + } + + // Compute 'a' as a linear interpolation of 'u' and 'v' + noiseCode.appendf("\n\tvec2 %s;", ab); + noiseCode.appendf("\n\t%s.x = mix(%s.x, %s.y, %s.x);", ab, uv, uv, noiseSmooth); + + noiseCode.appendf("\n\t%s.y -= 1.0;", fractVal); + // Compute v, at offset (-1,-1) + { + SkString latticeCoords(""); + latticeCoords.appendf("vec2(%s.w, %s)", bcoords, chanCoord); + noiseCode.append("\n\tlattice = "); + fragBuilder->appendTextureLookup(&noiseCode, args.fTexSamplers[1], latticeCoords.c_str(), + kVec2f_GrSLType); + noiseCode.appendf(".bgra;\n\t%s.y = ", uv); + noiseCode.appendf(dotLattice, lattice, lattice, inc8bit, fractVal); + } + + noiseCode.appendf("\n\t%s.x += 1.0;", fractVal); + // Compute u, at offset (0,-1) + { + SkString latticeCoords(""); + latticeCoords.appendf("vec2(%s.z, %s)", bcoords, chanCoord); + noiseCode.append("\n\tlattice = "); + fragBuilder->appendTextureLookup(&noiseCode, args.fTexSamplers[1], latticeCoords.c_str(), + kVec2f_GrSLType); + noiseCode.appendf(".bgra;\n\t%s.x = ", uv); + noiseCode.appendf(dotLattice, lattice, lattice, inc8bit, fractVal); + } + + // Compute 'b' as a linear interpolation of 'u' and 'v' + noiseCode.appendf("\n\t%s.y = mix(%s.x, %s.y, %s.x);", ab, uv, uv, noiseSmooth); + // Compute the noise as a linear interpolation of 'a' and 'b' + noiseCode.appendf("\n\treturn mix(%s.x, %s.y, %s.y);\n", ab, ab, noiseSmooth); + + SkString noiseFuncName; + if (pne.stitchTiles()) { + fragBuilder->emitFunction(kFloat_GrSLType, + "perlinnoise", SK_ARRAY_COUNT(gPerlinNoiseStitchArgs), + gPerlinNoiseStitchArgs, noiseCode.c_str(), &noiseFuncName); + } else { + fragBuilder->emitFunction(kFloat_GrSLType, + "perlinnoise", SK_ARRAY_COUNT(gPerlinNoiseArgs), + gPerlinNoiseArgs, noiseCode.c_str(), &noiseFuncName); + } + + // There are rounding errors if the floor operation is not performed here + fragBuilder->codeAppendf("\n\t\tvec2 %s = floor(%s.xy) * %s;", + noiseVec, vCoords.c_str(), baseFrequencyUni); + + // Clear the color accumulator + fragBuilder->codeAppendf("\n\t\t%s = vec4(0.0);", args.fOutputColor); + + if (pne.stitchTiles()) { + // Set up TurbulenceInitial stitch values. + fragBuilder->codeAppendf("\n\t\tvec2 %s = %s;", stitchData, stitchDataUni); + } + + fragBuilder->codeAppendf("\n\t\tfloat %s = 1.0;", ratio); + + // Loop over all octaves + fragBuilder->codeAppendf("for (int octave = 0; octave < %d; ++octave) {", pne.numOctaves()); + + fragBuilder->codeAppendf("\n\t\t\t%s += ", args.fOutputColor); + if (pne.type() != SkPerlinNoiseShaderImpl::kFractalNoise_Type) { + fragBuilder->codeAppend("abs("); + } + if (pne.stitchTiles()) { + fragBuilder->codeAppendf( + "vec4(\n\t\t\t\t%s(%s, %s, %s),\n\t\t\t\t%s(%s, %s, %s)," + "\n\t\t\t\t%s(%s, %s, %s),\n\t\t\t\t%s(%s, %s, %s))", + noiseFuncName.c_str(), chanCoordR, noiseVec, stitchData, + noiseFuncName.c_str(), chanCoordG, noiseVec, stitchData, + noiseFuncName.c_str(), chanCoordB, noiseVec, stitchData, + noiseFuncName.c_str(), chanCoordA, noiseVec, stitchData); + } else { + fragBuilder->codeAppendf( + "vec4(\n\t\t\t\t%s(%s, %s),\n\t\t\t\t%s(%s, %s)," + "\n\t\t\t\t%s(%s, %s),\n\t\t\t\t%s(%s, %s))", + noiseFuncName.c_str(), chanCoordR, noiseVec, + noiseFuncName.c_str(), chanCoordG, noiseVec, + noiseFuncName.c_str(), chanCoordB, noiseVec, + noiseFuncName.c_str(), chanCoordA, noiseVec); + } + if (pne.type() != SkPerlinNoiseShaderImpl::kFractalNoise_Type) { + fragBuilder->codeAppendf(")"); // end of "abs(" + } + fragBuilder->codeAppendf(" * %s;", ratio); + + fragBuilder->codeAppendf("\n\t\t\t%s *= vec2(2.0);", noiseVec); + fragBuilder->codeAppendf("\n\t\t\t%s *= 0.5;", ratio); + + if (pne.stitchTiles()) { + fragBuilder->codeAppendf("\n\t\t\t%s *= vec2(2.0);", stitchData); + } + fragBuilder->codeAppend("\n\t\t}"); // end of the for loop on octaves + + if (pne.type() == SkPerlinNoiseShaderImpl::kFractalNoise_Type) { + // The value of turbulenceFunctionResult comes from ((turbulenceFunctionResult) + 1) / 2 + // by fractalNoise and (turbulenceFunctionResult) by turbulence. + fragBuilder->codeAppendf("\n\t\t%s = %s * vec4(0.5) + vec4(0.5);", + args.fOutputColor,args.fOutputColor); + } + + // Clamp values + fragBuilder->codeAppendf("\n\t\t%s = clamp(%s, 0.0, 1.0);", args.fOutputColor, args.fOutputColor); + + // Pre-multiply the result + fragBuilder->codeAppendf("\n\t\t%s = vec4(%s.rgb * %s.aaa, %s.a);\n", + args.fOutputColor, args.fOutputColor, + args.fOutputColor, args.fOutputColor); +} + +void GrGLPerlinNoise::GenKey(const GrProcessor& processor, const GrShaderCaps&, + GrProcessorKeyBuilder* b) { + const GrPerlinNoise2Effect& turbulence = processor.cast<GrPerlinNoise2Effect>(); + + uint32_t key = turbulence.numOctaves(); + + key = key << 3; // Make room for next 3 bits + + switch (turbulence.type()) { + case SkPerlinNoiseShaderImpl::kFractalNoise_Type: + key |= 0x1; + break; + case SkPerlinNoiseShaderImpl::kTurbulence_Type: + key |= 0x2; + break; + default: + // leave key at 0 + break; + } + + if (turbulence.stitchTiles()) { + key |= 0x4; // Flip the 3rd bit if tile stitching is on + } + + b->add32(key); +} + +void GrGLPerlinNoise::onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& processor) { + INHERITED::onSetData(pdman, processor); + + const GrPerlinNoise2Effect& turbulence = processor.cast<GrPerlinNoise2Effect>(); + + const SkVector& baseFrequency = turbulence.baseFrequency(); + pdman.set2f(fBaseFrequencyUni, baseFrequency.fX, baseFrequency.fY); + + if (turbulence.stitchTiles()) { + const SkPerlinNoiseShaderImpl::StitchData& stitchData = turbulence.stitchData(); + pdman.set2f(fStitchDataUni, SkIntToScalar(stitchData.fWidth), + SkIntToScalar(stitchData.fHeight)); + } +} + +///////////////////////////////////////////////////////////////////// + +class GrGLImprovedPerlinNoise : public GrGLSLFragmentProcessor { +public: + void emitCode(EmitArgs&) override; + + static inline void GenKey(const GrProcessor&, const GrShaderCaps&, GrProcessorKeyBuilder*); + +protected: + void onSetData(const GrGLSLProgramDataManager&, const GrFragmentProcessor&) override; + +private: + GrGLSLProgramDataManager::UniformHandle fZUni; + GrGLSLProgramDataManager::UniformHandle fOctavesUni; + GrGLSLProgramDataManager::UniformHandle fBaseFrequencyUni; + + typedef GrGLSLFragmentProcessor INHERITED; +}; + +///////////////////////////////////////////////////////////////////// + +class GrImprovedPerlinNoiseEffect : public GrFragmentProcessor { +public: + static sk_sp<GrFragmentProcessor> Make(GrResourceProvider* resourceProvider, + int octaves, SkScalar z, + SkPerlinNoiseShaderImpl::PaintingData* paintingData, + sk_sp<GrTextureProxy> permutationsProxy, + sk_sp<GrTextureProxy> gradientProxy, + const SkMatrix& matrix) { + return sk_sp<GrFragmentProcessor>( + new GrImprovedPerlinNoiseEffect(resourceProvider, octaves, z, paintingData, + std::move(permutationsProxy), + std::move(gradientProxy), matrix)); + } + + ~GrImprovedPerlinNoiseEffect() override { delete fPaintingData; } + + const char* name() const override { return "ImprovedPerlinNoise"; } + + const SkVector& baseFrequency() const { return fPaintingData->fBaseFrequency; } + SkScalar z() const { return fZ; } + int octaves() const { return fOctaves; } + const SkMatrix& matrix() const { return fCoordTransform.getMatrix(); } + +private: + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override { + return new GrGLImprovedPerlinNoise; + } + + void onGetGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override { + GrGLImprovedPerlinNoise::GenKey(*this, caps, b); + } + + bool onIsEqual(const GrFragmentProcessor& sBase) const override { + const GrImprovedPerlinNoiseEffect& s = sBase.cast<GrImprovedPerlinNoiseEffect>(); + return fZ == fZ && + fPaintingData->fBaseFrequency == s.fPaintingData->fBaseFrequency; + } + + GrImprovedPerlinNoiseEffect(GrResourceProvider* resourceProvider, + int octaves, SkScalar z, + SkPerlinNoiseShaderImpl::PaintingData* paintingData, + sk_sp<GrTextureProxy> permutationsProxy, + sk_sp<GrTextureProxy> gradientProxy, + const SkMatrix& matrix) + : INHERITED(kNone_OptimizationFlags) + , fOctaves(octaves) + , fZ(z) + , fPermutationsSampler(resourceProvider, std::move(permutationsProxy)) + , fGradientSampler(resourceProvider, std::move(gradientProxy)) + , fPaintingData(paintingData) { + this->initClassID<GrImprovedPerlinNoiseEffect>(); + this->addTextureSampler(&fPermutationsSampler); + this->addTextureSampler(&fGradientSampler); + fCoordTransform.reset(matrix); + this->addCoordTransform(&fCoordTransform); + } + + GR_DECLARE_FRAGMENT_PROCESSOR_TEST; + + GrCoordTransform fCoordTransform; + int fOctaves; + SkScalar fZ; + TextureSampler fPermutationsSampler; + TextureSampler fGradientSampler; + SkPerlinNoiseShaderImpl::PaintingData* fPaintingData; + + typedef GrFragmentProcessor INHERITED; +}; + +///////////////////////////////////////////////////////////////////// +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrImprovedPerlinNoiseEffect); + +#if GR_TEST_UTILS +sk_sp<GrFragmentProcessor> GrImprovedPerlinNoiseEffect::TestCreate(GrProcessorTestData* d) { + SkScalar baseFrequencyX = d->fRandom->nextRangeScalar(0.01f, + 0.99f); + SkScalar baseFrequencyY = d->fRandom->nextRangeScalar(0.01f, + 0.99f); + int numOctaves = d->fRandom->nextRangeU(2, 10); + SkScalar z = SkIntToScalar(d->fRandom->nextU()); + + sk_sp<SkShader> shader(SkPerlinNoiseShader::MakeImprovedNoise(baseFrequencyX, + baseFrequencyY, + numOctaves, + z)); + + GrTest::TestAsFPArgs asFPArgs(d); + return as_SB(shader)->asFragmentProcessor(asFPArgs.args()); +} +#endif + +void GrGLImprovedPerlinNoise::emitCode(EmitArgs& args) { + GrGLSLFragmentBuilder* fragBuilder = args.fFragBuilder; + GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; + SkString vCoords = fragBuilder->ensureCoords2D(args.fTransformedCoords[0]); + + fBaseFrequencyUni = uniformHandler->addUniform(kFragment_GrShaderFlag, + kVec2f_GrSLType, kDefault_GrSLPrecision, + "baseFrequency"); + const char* baseFrequencyUni = uniformHandler->getUniformCStr(fBaseFrequencyUni); + + fOctavesUni = uniformHandler->addUniform(kFragment_GrShaderFlag, + kFloat_GrSLType, kDefault_GrSLPrecision, + "octaves"); + const char* octavesUni = uniformHandler->getUniformCStr(fOctavesUni); + + fZUni = uniformHandler->addUniform(kFragment_GrShaderFlag, + kFloat_GrSLType, kDefault_GrSLPrecision, + "z"); + const char* zUni = uniformHandler->getUniformCStr(fZUni); + + // fade function + static const GrShaderVar fadeArgs[] = { + GrShaderVar("t", kVec3f_GrSLType) + }; + SkString fadeFuncName; + fragBuilder->emitFunction(kVec3f_GrSLType, "fade", SK_ARRAY_COUNT(fadeArgs), + fadeArgs, + "return t * t * t * (t * (t * 6.0 - 15.0) + 10.0);", + &fadeFuncName); + + // perm function + static const GrShaderVar permArgs[] = { + GrShaderVar("x", kFloat_GrSLType) + }; + SkString permFuncName; + SkString permCode("return "); + // FIXME even though I'm creating these textures with kRepeat_TileMode, they're clamped. Not + // sure why. Using fract() (here and the next texture lookup) as a workaround. + fragBuilder->appendTextureLookup(&permCode, args.fTexSamplers[0], "vec2(fract(x / 256.0), 0.0)", + kVec2f_GrSLType); + permCode.append(".r * 255.0;"); + fragBuilder->emitFunction(kFloat_GrSLType, "perm", SK_ARRAY_COUNT(permArgs), permArgs, + permCode.c_str(), &permFuncName); + + // grad function + static const GrShaderVar gradArgs[] = { + GrShaderVar("x", kFloat_GrSLType), + GrShaderVar("p", kVec3f_GrSLType) + }; + SkString gradFuncName; + SkString gradCode("return dot("); + fragBuilder->appendTextureLookup(&gradCode, args.fTexSamplers[1], "vec2(fract(x / 16.0), 0.0)", + kVec2f_GrSLType); + gradCode.append(".rgb * 255.0 - vec3(1.0), p);"); + fragBuilder->emitFunction(kFloat_GrSLType, "grad", SK_ARRAY_COUNT(gradArgs), gradArgs, + gradCode.c_str(), &gradFuncName); + + // lerp function + static const GrShaderVar lerpArgs[] = { + GrShaderVar("a", kFloat_GrSLType), + GrShaderVar("b", kFloat_GrSLType), + GrShaderVar("w", kFloat_GrSLType) + }; + SkString lerpFuncName; + fragBuilder->emitFunction(kFloat_GrSLType, "lerp", SK_ARRAY_COUNT(lerpArgs), lerpArgs, + "return a + w * (b - a);", &lerpFuncName); + + // noise function + static const GrShaderVar noiseArgs[] = { + GrShaderVar("p", kVec3f_GrSLType), + }; + SkString noiseFuncName; + SkString noiseCode; + noiseCode.append("vec3 P = mod(floor(p), 256.0);"); + noiseCode.append("p -= floor(p);"); + noiseCode.appendf("vec3 f = %s(p);", fadeFuncName.c_str()); + noiseCode.appendf("float A = %s(P.x) + P.y;", permFuncName.c_str()); + noiseCode.appendf("float AA = %s(A) + P.z;", permFuncName.c_str()); + noiseCode.appendf("float AB = %s(A + 1.0) + P.z;", permFuncName.c_str()); + noiseCode.appendf("float B = %s(P.x + 1.0) + P.y;", permFuncName.c_str()); + noiseCode.appendf("float BA = %s(B) + P.z;", permFuncName.c_str()); + noiseCode.appendf("float BB = %s(B + 1.0) + P.z;", permFuncName.c_str()); + noiseCode.appendf("float result = %s(", lerpFuncName.c_str()); + noiseCode.appendf("%s(%s(%s(%s(AA), p),", lerpFuncName.c_str(), lerpFuncName.c_str(), + gradFuncName.c_str(), permFuncName.c_str()); + noiseCode.appendf("%s(%s(BA), p + vec3(-1.0, 0.0, 0.0)), f.x),", gradFuncName.c_str(), + permFuncName.c_str()); + noiseCode.appendf("%s(%s(%s(AB), p + vec3(0.0, -1.0, 0.0)),", lerpFuncName.c_str(), + gradFuncName.c_str(), permFuncName.c_str()); + noiseCode.appendf("%s(%s(BB), p + vec3(-1.0, -1.0, 0.0)), f.x), f.y),", + gradFuncName.c_str(), permFuncName.c_str()); + noiseCode.appendf("%s(%s(%s(%s(AA + 1.0), p + vec3(0.0, 0.0, -1.0)),", + lerpFuncName.c_str(), lerpFuncName.c_str(), gradFuncName.c_str(), + permFuncName.c_str()); + noiseCode.appendf("%s(%s(BA + 1.0), p + vec3(-1.0, 0.0, -1.0)), f.x),", + gradFuncName.c_str(), permFuncName.c_str()); + noiseCode.appendf("%s(%s(%s(AB + 1.0), p + vec3(0.0, -1.0, -1.0)),", + lerpFuncName.c_str(), gradFuncName.c_str(), permFuncName.c_str()); + noiseCode.appendf("%s(%s(BB + 1.0), p + vec3(-1.0, -1.0, -1.0)), f.x), f.y), f.z);", + gradFuncName.c_str(), permFuncName.c_str()); + noiseCode.append("return result;"); + fragBuilder->emitFunction(kFloat_GrSLType, "noise", SK_ARRAY_COUNT(noiseArgs), noiseArgs, + noiseCode.c_str(), &noiseFuncName); + + // noiseOctaves function + static const GrShaderVar noiseOctavesArgs[] = { + GrShaderVar("p", kVec3f_GrSLType), + GrShaderVar("octaves", kFloat_GrSLType), + }; + SkString noiseOctavesFuncName; + SkString noiseOctavesCode; + noiseOctavesCode.append("float result = 0.0;"); + noiseOctavesCode.append("float ratio = 1.0;"); + noiseOctavesCode.append("for (float i = 0.0; i < octaves; i++) {"); + noiseOctavesCode.appendf("result += %s(p) / ratio;", noiseFuncName.c_str()); + noiseOctavesCode.append("p *= 2.0;"); + noiseOctavesCode.append("ratio *= 2.0;"); + noiseOctavesCode.append("}"); + noiseOctavesCode.append("return (result + 1.0) / 2.0;"); + fragBuilder->emitFunction(kFloat_GrSLType, "noiseOctaves", SK_ARRAY_COUNT(noiseOctavesArgs), + noiseOctavesArgs, noiseOctavesCode.c_str(), &noiseOctavesFuncName); + + fragBuilder->codeAppendf("vec2 coords = %s * %s;", vCoords.c_str(), baseFrequencyUni); + fragBuilder->codeAppendf("float r = %s(vec3(coords, %s), %s);", noiseOctavesFuncName.c_str(), + zUni, octavesUni); + fragBuilder->codeAppendf("float g = %s(vec3(coords, %s + 0000.0), %s);", + noiseOctavesFuncName.c_str(), zUni, octavesUni); + fragBuilder->codeAppendf("float b = %s(vec3(coords, %s + 0000.0), %s);", + noiseOctavesFuncName.c_str(), zUni, octavesUni); + fragBuilder->codeAppendf("float a = %s(vec3(coords, %s + 0000.0), %s);", + noiseOctavesFuncName.c_str(), zUni, octavesUni); + fragBuilder->codeAppendf("%s = vec4(r, g, b, a);", args.fOutputColor); + + // Clamp values + fragBuilder->codeAppendf("%s = clamp(%s, 0.0, 1.0);", args.fOutputColor, args.fOutputColor); + + // Pre-multiply the result + fragBuilder->codeAppendf("\n\t\t%s = vec4(%s.rgb * %s.aaa, %s.a);\n", + args.fOutputColor, args.fOutputColor, + args.fOutputColor, args.fOutputColor); +} + +void GrGLImprovedPerlinNoise::GenKey(const GrProcessor& processor, const GrShaderCaps&, + GrProcessorKeyBuilder* b) { +} + +void GrGLImprovedPerlinNoise::onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& processor) { + INHERITED::onSetData(pdman, processor); + + const GrImprovedPerlinNoiseEffect& noise = processor.cast<GrImprovedPerlinNoiseEffect>(); + + const SkVector& baseFrequency = noise.baseFrequency(); + pdman.set2f(fBaseFrequencyUni, baseFrequency.fX, baseFrequency.fY); + + pdman.set1f(fOctavesUni, SkIntToScalar(noise.octaves())); + + pdman.set1f(fZUni, noise.z()); +} + +///////////////////////////////////////////////////////////////////// +sk_sp<GrFragmentProcessor> SkPerlinNoiseShaderImpl::asFragmentProcessor(const AsFPArgs& args) const { + SkASSERT(args.fContext); + + SkMatrix localMatrix = this->getLocalMatrix(); + if (args.fLocalMatrix) { + localMatrix.preConcat(*args.fLocalMatrix); + } + + SkMatrix matrix = *args.fViewMatrix; + matrix.preConcat(localMatrix); + + // Either we don't stitch tiles, either we have a valid tile size + SkASSERT(!fStitchTiles || !fTileSize.isEmpty()); + + SkPerlinNoiseShaderImpl::PaintingData* paintingData = + new PaintingData(fTileSize, fSeed, fBaseFrequencyX, fBaseFrequencyY, matrix); + + SkMatrix m = *args.fViewMatrix; + m.setTranslateX(-localMatrix.getTranslateX() + SK_Scalar1); + m.setTranslateY(-localMatrix.getTranslateY() + SK_Scalar1); + + if (fType == kImprovedNoise_Type) { + GrSamplerParams textureParams(SkShader::TileMode::kRepeat_TileMode, + GrSamplerParams::FilterMode::kNone_FilterMode); + sk_sp<GrTextureProxy> permutationsTexture( + GrRefCachedBitmapTextureProxy(args.fContext, + paintingData->getImprovedPermutationsBitmap(), + textureParams, nullptr)); + sk_sp<GrTextureProxy> gradientTexture( + GrRefCachedBitmapTextureProxy(args.fContext, + paintingData->getGradientBitmap(), + textureParams, nullptr)); + return GrImprovedPerlinNoiseEffect::Make(args.fContext->resourceProvider(), + fNumOctaves, fSeed, paintingData, + std::move(permutationsTexture), + std::move(gradientTexture), m); + } + + if (0 == fNumOctaves) { + if (kFractalNoise_Type == fType) { + // Extract the incoming alpha and emit rgba = (a/4, a/4, a/4, a/2) + // TODO: Either treat the output of this shader as sRGB or allow client to specify a + // color space of the noise. Either way, this case (and the GLSL) need to convert to + // the destination. + sk_sp<GrFragmentProcessor> inner( + GrConstColorProcessor::Make(GrColor4f::FromGrColor(0x80404040), + GrConstColorProcessor::kModulateRGBA_InputMode)); + return GrFragmentProcessor::MulOutputByInputAlpha(std::move(inner)); + } + // Emit zero. + return GrConstColorProcessor::Make(GrColor4f::TransparentBlack(), + GrConstColorProcessor::kIgnore_InputMode); + } + + sk_sp<GrTextureProxy> permutationsProxy = GrMakeCachedBitmapProxy( + args.fContext->resourceProvider(), + paintingData->getPermutationsBitmap()); + sk_sp<GrTextureProxy> noiseProxy = GrMakeCachedBitmapProxy(args.fContext->resourceProvider(), + paintingData->getNoiseBitmap()); + + if (permutationsProxy && noiseProxy) { + sk_sp<GrFragmentProcessor> inner( + GrPerlinNoise2Effect::Make(args.fContext->resourceProvider(), + fType, + fNumOctaves, + fStitchTiles, + paintingData, + std::move(permutationsProxy), + std::move(noiseProxy), + m)); + return GrFragmentProcessor::MulOutputByInputAlpha(std::move(inner)); + } + delete paintingData; + return nullptr; +} + +#endif + +#ifndef SK_IGNORE_TO_STRING +void SkPerlinNoiseShaderImpl::toString(SkString* str) const { + str->append("SkPerlinNoiseShaderImpl: ("); + + str->append("type: "); + switch (fType) { + case kFractalNoise_Type: + str->append("\"fractal noise\""); + break; + case kTurbulence_Type: + str->append("\"turbulence\""); + break; + default: + str->append("\"unknown\""); + break; + } + str->append(" base frequency: ("); + str->appendScalar(fBaseFrequencyX); + str->append(", "); + str->appendScalar(fBaseFrequencyY); + str->append(") number of octaves: "); + str->appendS32(fNumOctaves); + str->append(" seed: "); + str->appendScalar(fSeed); + str->append(" stitch tiles: "); + str->append(fStitchTiles ? "true " : "false "); + + this->INHERITED::toString(str); + + str->append(")"); +} +#endif + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +sk_sp<SkShader> SkPerlinNoiseShader::MakeFractalNoise(SkScalar baseFrequencyX, + SkScalar baseFrequencyY, + int numOctaves, SkScalar seed, + const SkISize* tileSize) { + return sk_sp<SkShader>(new SkPerlinNoiseShaderImpl(SkPerlinNoiseShaderImpl::kFractalNoise_Type, + baseFrequencyX, baseFrequencyY, numOctaves, seed, + tileSize)); +} + +sk_sp<SkShader> SkPerlinNoiseShader::MakeTurbulence(SkScalar baseFrequencyX, + SkScalar baseFrequencyY, + int numOctaves, SkScalar seed, + const SkISize* tileSize) { + return sk_sp<SkShader>(new SkPerlinNoiseShaderImpl(SkPerlinNoiseShaderImpl::kTurbulence_Type, + baseFrequencyX, baseFrequencyY, numOctaves, seed, + tileSize)); +} + +sk_sp<SkShader> SkPerlinNoiseShader::MakeImprovedNoise(SkScalar baseFrequencyX, + SkScalar baseFrequencyY, + int numOctaves, SkScalar z) { + return sk_sp<SkShader>(new SkPerlinNoiseShaderImpl(SkPerlinNoiseShaderImpl::kImprovedNoise_Type, + baseFrequencyX, baseFrequencyY, numOctaves, z, + nullptr)); +} + +SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkPerlinNoiseShader) + SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkPerlinNoiseShaderImpl) +SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END diff --git a/src/shaders/SkPictureShader.cpp b/src/shaders/SkPictureShader.cpp new file mode 100644 index 0000000000..d6ee941251 --- /dev/null +++ b/src/shaders/SkPictureShader.cpp @@ -0,0 +1,364 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkPictureShader.h" + +#include "SkArenaAlloc.h" +#include "SkBitmap.h" +#include "SkBitmapProcShader.h" +#include "SkCanvas.h" +#include "SkColorSpaceXformCanvas.h" +#include "SkImage.h" +#include "SkImageShader.h" +#include "SkMatrixUtils.h" +#include "SkPicture.h" +#include "SkPictureImageGenerator.h" +#include "SkReadBuffer.h" +#include "SkResourceCache.h" + +#if SK_SUPPORT_GPU +#include "GrContext.h" +#include "GrCaps.h" +#include "GrFragmentProcessor.h" +#endif + +namespace { +static unsigned gBitmapSkaderKeyNamespaceLabel; + +struct BitmapShaderKey : public SkResourceCache::Key { +public: + BitmapShaderKey(sk_sp<SkColorSpace> colorSpace, + uint32_t pictureID, + const SkRect& tile, + SkShader::TileMode tmx, + SkShader::TileMode tmy, + const SkSize& scale, + const SkMatrix& localMatrix, + SkTransferFunctionBehavior blendBehavior) + : fColorSpace(std::move(colorSpace)) + , fPictureID(pictureID) + , fTile(tile) + , fTmx(tmx) + , fTmy(tmy) + , fScale(scale) + , fBlendBehavior(blendBehavior) { + + for (int i = 0; i < 9; ++i) { + fLocalMatrixStorage[i] = localMatrix[i]; + } + + static const size_t keySize = sizeof(fColorSpace) + + sizeof(fPictureID) + + sizeof(fTile) + + sizeof(fTmx) + sizeof(fTmy) + + sizeof(fScale) + + sizeof(fLocalMatrixStorage) + + sizeof(fBlendBehavior); + // This better be packed. + SkASSERT(sizeof(uint32_t) * (&fEndOfStruct - (uint32_t*)&fColorSpace) == keySize); + this->init(&gBitmapSkaderKeyNamespaceLabel, 0, keySize); + } + +private: + sk_sp<SkColorSpace> fColorSpace; + uint32_t fPictureID; + SkRect fTile; + SkShader::TileMode fTmx, fTmy; + SkSize fScale; + SkScalar fLocalMatrixStorage[9]; + SkTransferFunctionBehavior fBlendBehavior; + + SkDEBUGCODE(uint32_t fEndOfStruct;) +}; + +struct BitmapShaderRec : public SkResourceCache::Rec { + BitmapShaderRec(const BitmapShaderKey& key, SkShader* tileShader) + : fKey(key) + , fShader(SkRef(tileShader)) {} + + BitmapShaderKey fKey; + sk_sp<SkShader> fShader; + size_t fBitmapBytes; + + const Key& getKey() const override { return fKey; } + size_t bytesUsed() const override { + // Just the record overhead -- the actual pixels are accounted by SkImageCacherator. + return sizeof(fKey) + sizeof(SkImageShader); + } + const char* getCategory() const override { return "bitmap-shader"; } + SkDiscardableMemory* diagnostic_only_getDiscardable() const override { return nullptr; } + + static bool Visitor(const SkResourceCache::Rec& baseRec, void* contextShader) { + const BitmapShaderRec& rec = static_cast<const BitmapShaderRec&>(baseRec); + sk_sp<SkShader>* result = reinterpret_cast<sk_sp<SkShader>*>(contextShader); + + *result = rec.fShader; + + // The bitmap shader is backed by an image generator, thus it can always re-generate its + // pixels if discarded. + return true; + } +}; + +} // namespace + +SkPictureShader::SkPictureShader(sk_sp<SkPicture> picture, TileMode tmx, TileMode tmy, + const SkMatrix* localMatrix, const SkRect* tile, + sk_sp<SkColorSpace> colorSpace) + : INHERITED(localMatrix) + , fPicture(std::move(picture)) + , fTile(tile ? *tile : fPicture->cullRect()) + , fTmx(tmx) + , fTmy(tmy) + , fColorSpace(std::move(colorSpace)) +{} + +sk_sp<SkShader> SkPictureShader::Make(sk_sp<SkPicture> picture, TileMode tmx, TileMode tmy, + const SkMatrix* localMatrix, const SkRect* tile) { + if (!picture || picture->cullRect().isEmpty() || (tile && tile->isEmpty())) { + return SkShader::MakeEmptyShader(); + } + return sk_sp<SkShader>(new SkPictureShader(std::move(picture), tmx, tmy, localMatrix, tile, + nullptr)); +} + +sk_sp<SkFlattenable> SkPictureShader::CreateProc(SkReadBuffer& buffer) { + SkMatrix lm; + buffer.readMatrix(&lm); + TileMode mx = (TileMode)buffer.read32(); + TileMode my = (TileMode)buffer.read32(); + SkRect tile; + buffer.readRect(&tile); + + sk_sp<SkPicture> picture; + + if (buffer.isCrossProcess() && SkPicture::PictureIOSecurityPrecautionsEnabled()) { + if (buffer.isVersionLT(SkReadBuffer::kPictureShaderHasPictureBool_Version)) { + // Older code blindly serialized pictures. We don't trust them. + buffer.validate(false); + return nullptr; + } + // Newer code won't serialize pictures in disallow-cross-process-picture mode. + // Assert that they didn't serialize anything except a false here. + buffer.validate(!buffer.readBool()); + } else { + // Old code always serialized the picture. New code writes a 'true' first if it did. + if (buffer.isVersionLT(SkReadBuffer::kPictureShaderHasPictureBool_Version) || + buffer.readBool()) { + picture = SkPicture::MakeFromBuffer(buffer); + } + } + return SkPictureShader::Make(picture, mx, my, &lm, &tile); +} + +void SkPictureShader::flatten(SkWriteBuffer& buffer) const { + buffer.writeMatrix(this->getLocalMatrix()); + buffer.write32(fTmx); + buffer.write32(fTmy); + buffer.writeRect(fTile); + + // The deserialization code won't trust that our serialized picture is safe to deserialize. + // So write a 'false' telling it that we're not serializing a picture. + if (buffer.isCrossProcess() && SkPicture::PictureIOSecurityPrecautionsEnabled()) { + buffer.writeBool(false); + } else { + buffer.writeBool(true); + fPicture->flatten(buffer); + } +} + +sk_sp<SkShader> SkPictureShader::refBitmapShader(const SkMatrix& viewMatrix, const SkMatrix* localM, + SkColorSpace* dstColorSpace, + const int maxTextureSize) const { + SkASSERT(fPicture && !fPicture->cullRect().isEmpty()); + + SkMatrix m; + m.setConcat(viewMatrix, this->getLocalMatrix()); + if (localM) { + m.preConcat(*localM); + } + + // Use a rotation-invariant scale + SkPoint scale; + // + // TODO: replace this with decomposeScale() -- but beware LayoutTest rebaselines! + // + if (!SkDecomposeUpper2x2(m, nullptr, &scale, nullptr)) { + // Decomposition failed, use an approximation. + scale.set(SkScalarSqrt(m.getScaleX() * m.getScaleX() + m.getSkewX() * m.getSkewX()), + SkScalarSqrt(m.getScaleY() * m.getScaleY() + m.getSkewY() * m.getSkewY())); + } + SkSize scaledSize = SkSize::Make(SkScalarAbs(scale.x() * fTile.width()), + SkScalarAbs(scale.y() * fTile.height())); + + // Clamp the tile size to about 4M pixels + static const SkScalar kMaxTileArea = 2048 * 2048; + SkScalar tileArea = scaledSize.width() * scaledSize.height(); + if (tileArea > kMaxTileArea) { + SkScalar clampScale = SkScalarSqrt(kMaxTileArea / tileArea); + scaledSize.set(scaledSize.width() * clampScale, + scaledSize.height() * clampScale); + } +#if SK_SUPPORT_GPU + // Scale down the tile size if larger than maxTextureSize for GPU Path or it should fail on create texture + if (maxTextureSize) { + if (scaledSize.width() > maxTextureSize || scaledSize.height() > maxTextureSize) { + SkScalar downScale = maxTextureSize / SkMaxScalar(scaledSize.width(), scaledSize.height()); + scaledSize.set(SkScalarFloorToScalar(scaledSize.width() * downScale), + SkScalarFloorToScalar(scaledSize.height() * downScale)); + } + } +#endif + +#ifdef SK_SUPPORT_LEGACY_PICTURESHADER_ROUNDING + const SkISize tileSize = scaledSize.toRound(); +#else + const SkISize tileSize = scaledSize.toCeil(); +#endif + if (tileSize.isEmpty()) { + return SkShader::MakeEmptyShader(); + } + + // The actual scale, compensating for rounding & clamping. + const SkSize tileScale = SkSize::Make(SkIntToScalar(tileSize.width()) / fTile.width(), + SkIntToScalar(tileSize.height()) / fTile.height()); + + // |fColorSpace| will only be set when using an SkColorSpaceXformCanvas to do pre-draw xforms. + // This canvas is strictly for legacy mode. A non-null |dstColorSpace| indicates that we + // should perform color correct rendering and xform at draw time. + SkASSERT(!fColorSpace || !dstColorSpace); + sk_sp<SkColorSpace> keyCS = dstColorSpace ? sk_ref_sp(dstColorSpace) : fColorSpace; + SkTransferFunctionBehavior blendBehavior = dstColorSpace ? SkTransferFunctionBehavior::kRespect + : SkTransferFunctionBehavior::kIgnore; + + sk_sp<SkShader> tileShader; + BitmapShaderKey key(std::move(keyCS), + fPicture->uniqueID(), + fTile, + fTmx, + fTmy, + tileScale, + this->getLocalMatrix(), + blendBehavior); + + if (!SkResourceCache::Find(key, BitmapShaderRec::Visitor, &tileShader)) { + SkMatrix tileMatrix; + tileMatrix.setRectToRect(fTile, SkRect::MakeIWH(tileSize.width(), tileSize.height()), + SkMatrix::kFill_ScaleToFit); + + sk_sp<SkImage> tileImage = SkImage::MakeFromGenerator( + SkPictureImageGenerator::Make(tileSize, fPicture, &tileMatrix, nullptr, + SkImage::BitDepth::kU8, sk_ref_sp(dstColorSpace))); + if (!tileImage) { + return nullptr; + } + + if (fColorSpace) { + tileImage = tileImage->makeColorSpace(fColorSpace, SkTransferFunctionBehavior::kIgnore); + } + + SkMatrix shaderMatrix = this->getLocalMatrix(); + shaderMatrix.preScale(1 / tileScale.width(), 1 / tileScale.height()); + tileShader = tileImage->makeShader(fTmx, fTmy, &shaderMatrix); + + SkResourceCache::Add(new BitmapShaderRec(key, tileShader.get())); + } + + return tileShader; +} + +bool SkPictureShader::onAppendStages(SkRasterPipeline* p, SkColorSpace* cs, SkArenaAlloc* alloc, + const SkMatrix& ctm, const SkPaint& paint, + const SkMatrix* localMatrix) const { + // Keep bitmapShader alive by using alloc instead of stack memory + auto& bitmapShader = *alloc->make<sk_sp<SkShader>>(); + bitmapShader = this->refBitmapShader(ctm, localMatrix, cs); + return bitmapShader && as_SB(bitmapShader)->appendStages(p, cs, alloc, ctm, paint); +} + +///////////////////////////////////////////////////////////////////////////////////////// +SkShaderBase::Context* SkPictureShader::onMakeContext(const ContextRec& rec, SkArenaAlloc* alloc) +const { + sk_sp<SkShader> bitmapShader(this->refBitmapShader(*rec.fMatrix, rec.fLocalMatrix, + rec.fDstColorSpace)); + if (!bitmapShader) { + return nullptr; + } + + PictureShaderContext* ctx = + alloc->make<PictureShaderContext>(*this, rec, std::move(bitmapShader), alloc); + if (nullptr == ctx->fBitmapShaderContext) { + ctx = nullptr; + } + return ctx; +} + +sk_sp<SkShader> SkPictureShader::onMakeColorSpace(SkColorSpaceXformer* xformer) const { + return sk_sp<SkPictureShader>(new SkPictureShader(fPicture, fTmx, fTmy, &this->getLocalMatrix(), + &fTile, xformer->dst())); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +SkPictureShader::PictureShaderContext::PictureShaderContext( + const SkPictureShader& shader, const ContextRec& rec, sk_sp<SkShader> bitmapShader, + SkArenaAlloc* alloc) + : INHERITED(shader, rec) + , fBitmapShader(std::move(bitmapShader)) +{ + fBitmapShaderContext = as_SB(fBitmapShader)->makeContext(rec, alloc); + //if fBitmapShaderContext is null, we are invalid +} + +uint32_t SkPictureShader::PictureShaderContext::getFlags() const { + SkASSERT(fBitmapShaderContext); + return fBitmapShaderContext->getFlags(); +} + +SkShaderBase::Context::ShadeProc SkPictureShader::PictureShaderContext::asAShadeProc(void** ctx) { + SkASSERT(fBitmapShaderContext); + return fBitmapShaderContext->asAShadeProc(ctx); +} + +void SkPictureShader::PictureShaderContext::shadeSpan(int x, int y, SkPMColor dstC[], int count) { + SkASSERT(fBitmapShaderContext); + fBitmapShaderContext->shadeSpan(x, y, dstC, count); +} + +#ifndef SK_IGNORE_TO_STRING +void SkPictureShader::toString(SkString* str) const { + static const char* gTileModeName[SkShader::kTileModeCount] = { + "clamp", "repeat", "mirror" + }; + + str->appendf("PictureShader: [%f:%f:%f:%f] ", + fPicture->cullRect().fLeft, + fPicture->cullRect().fTop, + fPicture->cullRect().fRight, + fPicture->cullRect().fBottom); + + str->appendf("(%s, %s)", gTileModeName[fTmx], gTileModeName[fTmy]); + + this->INHERITED::toString(str); +} +#endif + +#if SK_SUPPORT_GPU +sk_sp<GrFragmentProcessor> SkPictureShader::asFragmentProcessor(const AsFPArgs& args) const { + int maxTextureSize = 0; + if (args.fContext) { + maxTextureSize = args.fContext->caps()->maxTextureSize(); + } + sk_sp<SkShader> bitmapShader(this->refBitmapShader(*args.fViewMatrix, args.fLocalMatrix, + args.fDstColorSpace, maxTextureSize)); + if (!bitmapShader) { + return nullptr; + } + return as_SB(bitmapShader)->asFragmentProcessor(SkShaderBase::AsFPArgs( + args.fContext, args.fViewMatrix, nullptr, args.fFilterQuality, args.fDstColorSpace)); +} +#endif diff --git a/src/shaders/SkPictureShader.h b/src/shaders/SkPictureShader.h new file mode 100644 index 0000000000..f7a509f181 --- /dev/null +++ b/src/shaders/SkPictureShader.h @@ -0,0 +1,79 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkPictureShader_DEFINED +#define SkPictureShader_DEFINED + +#include "SkShaderBase.h" + +class SkArenaAlloc; +class SkBitmap; +class SkPicture; + +/* + * An SkPictureShader can be used to draw SkPicture-based patterns. + * + * The SkPicture is first rendered into a tile, which is then used to shade the area according + * to specified tiling rules. + */ +class SkPictureShader : public SkShaderBase { +public: + static sk_sp<SkShader> Make(sk_sp<SkPicture>, TileMode, TileMode, const SkMatrix*, + const SkRect*); + + SK_TO_STRING_OVERRIDE() + SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkPictureShader) + +#if SK_SUPPORT_GPU + sk_sp<GrFragmentProcessor> asFragmentProcessor(const AsFPArgs&) const override; +#endif + +protected: + SkPictureShader(SkReadBuffer&); + void flatten(SkWriteBuffer&) const override; + bool onAppendStages(SkRasterPipeline*, SkColorSpace*, SkArenaAlloc*, + const SkMatrix&, const SkPaint&, const SkMatrix*) const override; + Context* onMakeContext(const ContextRec&, SkArenaAlloc*) const override; + sk_sp<SkShader> onMakeColorSpace(SkColorSpaceXformer* xformer) const override; + +private: + SkPictureShader(sk_sp<SkPicture>, TileMode, TileMode, const SkMatrix*, const SkRect*, + sk_sp<SkColorSpace>); + + sk_sp<SkShader> refBitmapShader(const SkMatrix&, const SkMatrix* localMatrix, + SkColorSpace* dstColorSpace, + const int maxTextureSize = 0) const; + + sk_sp<SkPicture> fPicture; + SkRect fTile; + TileMode fTmx, fTmy; + + class PictureShaderContext : public Context { + public: + PictureShaderContext( + const SkPictureShader&, const ContextRec&, sk_sp<SkShader> bitmapShader, SkArenaAlloc*); + + uint32_t getFlags() const override; + + ShadeProc asAShadeProc(void** ctx) override; + void shadeSpan(int x, int y, SkPMColor dstC[], int count) override; + + sk_sp<SkShader> fBitmapShader; + SkShaderBase::Context* fBitmapShaderContext; + void* fBitmapShaderContextStorage; + + typedef Context INHERITED; + }; + + // Should never be set by a public constructor. This is only used when onMakeColorSpace() + // forces a deferred color space xform. + sk_sp<SkColorSpace> fColorSpace; + + typedef SkShaderBase INHERITED; +}; + +#endif // SkPictureShader_DEFINED diff --git a/src/shaders/SkShader.cpp b/src/shaders/SkShader.cpp new file mode 100644 index 0000000000..d04fbfe4df --- /dev/null +++ b/src/shaders/SkShader.cpp @@ -0,0 +1,305 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkArenaAlloc.h" +#include "SkAtomics.h" +#include "SkBitmapProcShader.h" +#include "SkColorShader.h" +#include "SkEmptyShader.h" +#include "SkMallocPixelRef.h" +#include "SkPaint.h" +#include "SkPicture.h" +#include "SkPictureShader.h" +#include "SkPM4fPriv.h" +#include "SkRasterPipeline.h" +#include "SkReadBuffer.h" +#include "SkScalar.h" +#include "SkShaderBase.h" +#include "SkTLazy.h" +#include "SkWriteBuffer.h" +#include "../jumper/SkJumper.h" + +#if SK_SUPPORT_GPU +#include "GrFragmentProcessor.h" +#endif + +//#define SK_TRACK_SHADER_LIFETIME + +#ifdef SK_TRACK_SHADER_LIFETIME + static int32_t gShaderCounter; +#endif + +static inline void inc_shader_counter() { +#ifdef SK_TRACK_SHADER_LIFETIME + int32_t prev = sk_atomic_inc(&gShaderCounter); + SkDebugf("+++ shader counter %d\n", prev + 1); +#endif +} +static inline void dec_shader_counter() { +#ifdef SK_TRACK_SHADER_LIFETIME + int32_t prev = sk_atomic_dec(&gShaderCounter); + SkDebugf("--- shader counter %d\n", prev - 1); +#endif +} + +SkShaderBase::SkShaderBase(const SkMatrix* localMatrix) + : fLocalMatrix(localMatrix ? *localMatrix : SkMatrix::I()) { + inc_shader_counter(); + // Pre-cache so future calls to fLocalMatrix.getType() are threadsafe. + (void)fLocalMatrix.getType(); +} + +SkShaderBase::~SkShaderBase() { + dec_shader_counter(); +} + +void SkShaderBase::flatten(SkWriteBuffer& buffer) const { + this->INHERITED::flatten(buffer); + bool hasLocalM = !fLocalMatrix.isIdentity(); + buffer.writeBool(hasLocalM); + if (hasLocalM) { + buffer.writeMatrix(fLocalMatrix); + } +} + +bool SkShaderBase::computeTotalInverse(const SkMatrix& ctm, + const SkMatrix* outerLocalMatrix, + SkMatrix* totalInverse) const { + SkMatrix total = SkMatrix::Concat(ctm, fLocalMatrix); + if (outerLocalMatrix) { + total.preConcat(*outerLocalMatrix); + } + + return total.invert(totalInverse); +} + +bool SkShaderBase::asLuminanceColor(SkColor* colorPtr) const { + SkColor storage; + if (nullptr == colorPtr) { + colorPtr = &storage; + } + if (this->onAsLuminanceColor(colorPtr)) { + *colorPtr = SkColorSetA(*colorPtr, 0xFF); // we only return opaque + return true; + } + return false; +} + +SkShaderBase::Context* SkShaderBase::makeContext(const ContextRec& rec, SkArenaAlloc* alloc) const { + if (!this->computeTotalInverse(*rec.fMatrix, rec.fLocalMatrix, nullptr)) { + return nullptr; + } + return this->onMakeContext(rec, alloc); +} + +SkShaderBase::Context::Context(const SkShaderBase& shader, const ContextRec& rec) + : fShader(shader), fCTM(*rec.fMatrix) +{ + // We should never use a context for RP-only shaders. + SkASSERT(!shader.isRasterPipelineOnly()); + + // Because the context parameters must be valid at this point, we know that the matrix is + // invertible. + SkAssertResult(fShader.computeTotalInverse(*rec.fMatrix, rec.fLocalMatrix, &fTotalInverse)); + fTotalInverseClass = (uint8_t)ComputeMatrixClass(fTotalInverse); + + fPaintAlpha = rec.fPaint->getAlpha(); +} + +SkShaderBase::Context::~Context() {} + +SkShaderBase::Context::ShadeProc SkShaderBase::Context::asAShadeProc(void** ctx) { + return nullptr; +} + +void SkShaderBase::Context::shadeSpan4f(int x, int y, SkPM4f dst[], int count) { + const int N = 128; + SkPMColor tmp[N]; + while (count > 0) { + int n = SkTMin(count, N); + this->shadeSpan(x, y, tmp, n); + for (int i = 0; i < n; ++i) { + dst[i] = SkPM4f::FromPMColor(tmp[i]); + } + dst += n; + x += n; + count -= n; + } +} + +#include "SkColorPriv.h" + +#define kTempColorQuadCount 6 // balance between speed (larger) and saving stack-space +#define kTempColorCount (kTempColorQuadCount << 2) + +#ifdef SK_CPU_BENDIAN + #define SkU32BitShiftToByteOffset(shift) (3 - ((shift) >> 3)) +#else + #define SkU32BitShiftToByteOffset(shift) ((shift) >> 3) +#endif + +void SkShaderBase::Context::shadeSpanAlpha(int x, int y, uint8_t alpha[], int count) { + SkASSERT(count > 0); + + SkPMColor colors[kTempColorCount]; + + while ((count -= kTempColorCount) >= 0) { + this->shadeSpan(x, y, colors, kTempColorCount); + x += kTempColorCount; + + const uint8_t* srcA = (const uint8_t*)colors + SkU32BitShiftToByteOffset(SK_A32_SHIFT); + int quads = kTempColorQuadCount; + do { + U8CPU a0 = srcA[0]; + U8CPU a1 = srcA[4]; + U8CPU a2 = srcA[8]; + U8CPU a3 = srcA[12]; + srcA += 4*4; + *alpha++ = SkToU8(a0); + *alpha++ = SkToU8(a1); + *alpha++ = SkToU8(a2); + *alpha++ = SkToU8(a3); + } while (--quads != 0); + } + SkASSERT(count < 0); + SkASSERT(count + kTempColorCount >= 0); + if (count += kTempColorCount) { + this->shadeSpan(x, y, colors, count); + + const uint8_t* srcA = (const uint8_t*)colors + SkU32BitShiftToByteOffset(SK_A32_SHIFT); + do { + *alpha++ = *srcA; + srcA += 4; + } while (--count != 0); + } +#if 0 + do { + int n = count; + if (n > kTempColorCount) + n = kTempColorCount; + SkASSERT(n > 0); + + this->shadeSpan(x, y, colors, n); + x += n; + count -= n; + + const uint8_t* srcA = (const uint8_t*)colors + SkU32BitShiftToByteOffset(SK_A32_SHIFT); + do { + *alpha++ = *srcA; + srcA += 4; + } while (--n != 0); + } while (count > 0); +#endif +} + +SkShaderBase::Context::MatrixClass SkShaderBase::Context::ComputeMatrixClass(const SkMatrix& mat) { + MatrixClass mc = kLinear_MatrixClass; + + if (mat.hasPerspective()) { + if (mat.isFixedStepInX()) { + mc = kFixedStepInX_MatrixClass; + } else { + mc = kPerspective_MatrixClass; + } + } + return mc; +} + +////////////////////////////////////////////////////////////////////////////// + +const SkMatrix& SkShader::getLocalMatrix() const { + return as_SB(this)->getLocalMatrix(); +} + +#ifdef SK_SUPPORT_LEGACY_SHADER_ISABITMAP +bool SkShader::isABitmap(SkBitmap* outTexture, SkMatrix* outMatrix, TileMode xy[2]) const { + return as_SB(this)->onIsABitmap(outTexture, outMatrix, xy); +} +#endif + +SkImage* SkShader::isAImage(SkMatrix* localMatrix, TileMode xy[2]) const { + return as_SB(this)->onIsAImage(localMatrix, xy); +} + +SkShader::GradientType SkShader::asAGradient(GradientInfo* info) const { + return kNone_GradientType; +} + +#if SK_SUPPORT_GPU +sk_sp<GrFragmentProcessor> SkShaderBase::asFragmentProcessor(const AsFPArgs&) const { + return nullptr; +} +#endif + +sk_sp<SkShader> SkShader::makeAsALocalMatrixShader(SkMatrix*) const { + return nullptr; +} + +sk_sp<SkShader> SkShader::MakeEmptyShader() { return sk_make_sp<SkEmptyShader>(); } + +sk_sp<SkShader> SkShader::MakeColorShader(SkColor color) { return sk_make_sp<SkColorShader>(color); } + +sk_sp<SkShader> SkShader::MakeBitmapShader(const SkBitmap& src, TileMode tmx, TileMode tmy, + const SkMatrix* localMatrix) { + if (localMatrix && !localMatrix->invert(nullptr)) { + return nullptr; + } + return SkMakeBitmapShader(src, tmx, tmy, localMatrix, kIfMutable_SkCopyPixelsMode); +} + +sk_sp<SkShader> SkShader::MakePictureShader(sk_sp<SkPicture> src, TileMode tmx, TileMode tmy, + const SkMatrix* localMatrix, const SkRect* tile) { + if (localMatrix && !localMatrix->invert(nullptr)) { + return nullptr; + } + return SkPictureShader::Make(std::move(src), tmx, tmy, localMatrix, tile); +} + +#ifndef SK_IGNORE_TO_STRING +void SkShaderBase::toString(SkString* str) const { + if (!fLocalMatrix.isIdentity()) { + str->append(" "); + fLocalMatrix.toString(str); + } +} +#endif + +bool SkShaderBase::appendStages(SkRasterPipeline* p, + SkColorSpace* dstCS, + SkArenaAlloc* alloc, + const SkMatrix& ctm, + const SkPaint& paint, + const SkMatrix* localM) const { + return this->onAppendStages(p, dstCS, alloc, ctm, paint, localM); +} + +bool SkShaderBase::onAppendStages(SkRasterPipeline* p, + SkColorSpace* dstCS, + SkArenaAlloc* alloc, + const SkMatrix& ctm, + const SkPaint& paint, + const SkMatrix* localM) const { + return false; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +sk_sp<SkFlattenable> SkEmptyShader::CreateProc(SkReadBuffer&) { + return SkShader::MakeEmptyShader(); +} + +#ifndef SK_IGNORE_TO_STRING +#include "SkEmptyShader.h" + +void SkEmptyShader::toString(SkString* str) const { + str->append("SkEmptyShader: ("); + + this->INHERITED::toString(str); + + str->append(")"); +} +#endif diff --git a/src/shaders/SkShaderBase.h b/src/shaders/SkShaderBase.h new file mode 100644 index 0000000000..94542321a3 --- /dev/null +++ b/src/shaders/SkShaderBase.h @@ -0,0 +1,272 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkShaderBase_DEFINED +#define SkShaderBase_DEFINED + +#include "SkFilterQuality.h" +#include "SkMatrix.h" +#include "SkShader.h" + +class GrContext; +class GrFragmentProcessor; +class SkArenaAlloc; +class SkColorSpace; +class SkColorSpaceXformer; +class SkImage; +struct SkImageInfo; +class SkPaint; +class SkRasterPipeline; + +class SkShaderBase : public SkShader { +public: + ~SkShaderBase() override; + + /** + * Returns true if the shader is guaranteed to produce only a single color. + * Subclasses can override this to allow loop-hoisting optimization. + */ + virtual bool isConstant() const { return false; } + + const SkMatrix& getLocalMatrix() const { return fLocalMatrix; } + + enum Flags { + //!< set if all of the colors will be opaque + kOpaqueAlpha_Flag = 1 << 0, + + /** set if the spans only vary in X (const in Y). + e.g. an Nx1 bitmap that is being tiled in Y, or a linear-gradient + that varies from left-to-right. This flag specifies this for + shadeSpan(). + */ + kConstInY32_Flag = 1 << 1, + + /** hint for the blitter that 4f is the preferred shading mode. + */ + kPrefers4f_Flag = 1 << 2, + }; + + /** + * ContextRec acts as a parameter bundle for creating Contexts. + */ + struct ContextRec { + enum DstType { + kPMColor_DstType, // clients prefer shading into PMColor dest + kPM4f_DstType, // clients prefer shading into PM4f dest + }; + + ContextRec(const SkPaint& paint, const SkMatrix& matrix, const SkMatrix* localM, + DstType dstType, SkColorSpace* dstColorSpace) + : fPaint(&paint) + , fMatrix(&matrix) + , fLocalMatrix(localM) + , fPreferredDstType(dstType) + , fDstColorSpace(dstColorSpace) {} + + const SkPaint* fPaint; // the current paint associated with the draw + const SkMatrix* fMatrix; // the current matrix in the canvas + const SkMatrix* fLocalMatrix; // optional local matrix + const DstType fPreferredDstType; // the "natural" client dest type + SkColorSpace* fDstColorSpace; // the color space of the dest surface (if any) + }; + + class Context : public ::SkNoncopyable { + public: + Context(const SkShaderBase& shader, const ContextRec&); + + virtual ~Context(); + + /** + * Called sometimes before drawing with this shader. Return the type of + * alpha your shader will return. The default implementation returns 0. + * Your subclass should override if it can (even sometimes) report a + * non-zero value, since that will enable various blitters to perform + * faster. + */ + virtual uint32_t getFlags() const { return 0; } + + /** + * Called for each span of the object being drawn. Your subclass should + * set the appropriate colors (with premultiplied alpha) that correspond + * to the specified device coordinates. + */ + virtual void shadeSpan(int x, int y, SkPMColor[], int count) = 0; + + virtual void shadeSpan4f(int x, int y, SkPM4f[], int count); + + /** + * The const void* ctx is only const because all the implementations are const. + * This can be changed to non-const if a new shade proc needs to change the ctx. + */ + typedef void (*ShadeProc)(const void* ctx, int x, int y, SkPMColor[], int count); + virtual ShadeProc asAShadeProc(void** ctx); + + /** + * Similar to shadeSpan, but only returns the alpha-channel for a span. + * The default implementation calls shadeSpan() and then extracts the alpha + * values from the returned colors. + */ + virtual void shadeSpanAlpha(int x, int y, uint8_t alpha[], int count); + + // Notification from blitter::blitMask in case we need to see the non-alpha channels + virtual void set3DMask(const SkMask*) {} + + protected: + // Reference to shader, so we don't have to dupe information. + const SkShaderBase& fShader; + + enum MatrixClass { + kLinear_MatrixClass, // no perspective + kFixedStepInX_MatrixClass, // fast perspective, need to call fixedStepInX() each + // scanline + kPerspective_MatrixClass // slow perspective, need to mappoints each pixel + }; + static MatrixClass ComputeMatrixClass(const SkMatrix&); + + uint8_t getPaintAlpha() const { return fPaintAlpha; } + const SkMatrix& getTotalInverse() const { return fTotalInverse; } + MatrixClass getInverseClass() const { return (MatrixClass)fTotalInverseClass; } + const SkMatrix& getCTM() const { return fCTM; } + + private: + SkMatrix fCTM; + SkMatrix fTotalInverse; + uint8_t fPaintAlpha; + uint8_t fTotalInverseClass; + + typedef SkNoncopyable INHERITED; + }; + + /** + * Make a context using the memory provided by the arena. + * + * @return pointer to context or nullptr if can't be created + */ + Context* makeContext(const ContextRec&, SkArenaAlloc*) const; + +#if SK_SUPPORT_GPU + struct AsFPArgs { + AsFPArgs() {} + AsFPArgs(GrContext* context, + const SkMatrix* viewMatrix, + const SkMatrix* localMatrix, + SkFilterQuality filterQuality, + SkColorSpace* dstColorSpace) + : fContext(context) + , fViewMatrix(viewMatrix) + , fLocalMatrix(localMatrix) + , fFilterQuality(filterQuality) + , fDstColorSpace(dstColorSpace) {} + + GrContext* fContext; + const SkMatrix* fViewMatrix; + const SkMatrix* fLocalMatrix; + SkFilterQuality fFilterQuality; + SkColorSpace* fDstColorSpace; + }; + + /** + * Returns a GrFragmentProcessor that implements the shader for the GPU backend. NULL is + * returned if there is no GPU implementation. + * + * The GPU device does not call SkShader::createContext(), instead we pass the view matrix, + * local matrix, and filter quality directly. + * + * The GrContext may be used by the to create textures that are required by the returned + * processor. + * + * The returned GrFragmentProcessor should expect an unpremultiplied input color and + * produce a premultiplied output. + */ + virtual sk_sp<GrFragmentProcessor> asFragmentProcessor(const AsFPArgs&) const; +#endif + + /** + * If the shader can represent its "average" luminance in a single color, return true and + * if color is not NULL, return that color. If it cannot, return false and ignore the color + * parameter. + * + * Note: if this returns true, the returned color will always be opaque, as only the RGB + * components are used to compute luminance. + */ + bool asLuminanceColor(SkColor*) const; + + /** + * Returns a shader transformed into a new color space via the |xformer|. + */ + sk_sp<SkShader> makeColorSpace(SkColorSpaceXformer* xformer) const { + return this->onMakeColorSpace(xformer); + } + + virtual bool isRasterPipelineOnly() const { return false; } + + bool appendStages(SkRasterPipeline*, SkColorSpace* dstCS, SkArenaAlloc*, + const SkMatrix& ctm, const SkPaint&, const SkMatrix* localM=nullptr) const; + + bool computeTotalInverse(const SkMatrix& ctm, + const SkMatrix* outerLocalMatrix, + SkMatrix* totalInverse) const; + +#ifdef SK_SUPPORT_LEGACY_SHADER_ISABITMAP + virtual bool onIsABitmap(SkBitmap*, SkMatrix*, TileMode[2]) const { + return false; + } +#endif + + virtual SkImage* onIsAImage(SkMatrix*, TileMode[2]) const { + return nullptr; + } + + SK_TO_STRING_VIRT() + + SK_DEFINE_FLATTENABLE_TYPE(SkShaderBase) + SK_DECLARE_FLATTENABLE_REGISTRAR_GROUP() + +protected: + SkShaderBase(const SkMatrix* localMatrix = nullptr); + + void flatten(SkWriteBuffer&) const override; + + /** + * Specialize creating a SkShader context using the supplied allocator. + * @return pointer to context owned by the arena allocator. + */ + virtual Context* onMakeContext(const ContextRec&, SkArenaAlloc*) const { + return nullptr; + } + + virtual bool onAsLuminanceColor(SkColor*) const { + return false; + } + + virtual sk_sp<SkShader> onMakeColorSpace(SkColorSpaceXformer*) const { + return sk_ref_sp(const_cast<SkShaderBase*>(this)); + } + + virtual bool onAppendStages(SkRasterPipeline*, SkColorSpace* dstCS, SkArenaAlloc*, + const SkMatrix&, const SkPaint&, const SkMatrix* localM) const; + +private: + // This is essentially const, but not officially so it can be modified in constructors. + SkMatrix fLocalMatrix; + + typedef SkShader INHERITED; +}; + +inline SkShaderBase* as_SB(SkShader* shader) { + return static_cast<SkShaderBase*>(shader); +} + +inline const SkShaderBase* as_SB(const SkShader* shader) { + return static_cast<const SkShaderBase*>(shader); +} + +inline const SkShaderBase* as_SB(const sk_sp<SkShader>& shader) { + return static_cast<SkShaderBase*>(shader.get()); +} + +#endif // SkShaderBase_DEFINED diff --git a/src/shaders/gradients/Sk4fGradientBase.cpp b/src/shaders/gradients/Sk4fGradientBase.cpp new file mode 100644 index 0000000000..e20f5f4702 --- /dev/null +++ b/src/shaders/gradients/Sk4fGradientBase.cpp @@ -0,0 +1,451 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "Sk4fGradientBase.h" + +#include <functional> + +namespace { + +Sk4f pack_color(SkColor c, bool premul, const Sk4f& component_scale) { + const SkColor4f c4f = SkColor4f::FromColor(c); + const Sk4f pm4f = premul + ? c4f.premul().to4f() + : Sk4f{c4f.fR, c4f.fG, c4f.fB, c4f.fA}; + + return pm4f * component_scale; +} + +class IntervalIterator { +public: + IntervalIterator(const SkColor* colors, const SkScalar* pos, int count, bool reverse) + : fColors(colors) + , fPos(pos) + , fCount(count) + , fFirstPos(reverse ? SK_Scalar1 : 0) + , fBegin(reverse ? count - 1 : 0) + , fAdvance(reverse ? -1 : 1) { + SkASSERT(colors); + SkASSERT(count > 0); + } + + void iterate(std::function<void(SkColor, SkColor, SkScalar, SkScalar)> func) const { + if (!fPos) { + this->iterateImplicitPos(func); + return; + } + + const int end = fBegin + fAdvance * (fCount - 1); + const SkScalar lastPos = 1 - fFirstPos; + int prev = fBegin; + SkScalar prevPos = fFirstPos; + + do { + const int curr = prev + fAdvance; + SkASSERT(curr >= 0 && curr < fCount); + + // TODO: this sanitization should be done in SkGradientShaderBase + const SkScalar currPos = (fAdvance > 0) + ? SkTPin(fPos[curr], prevPos, lastPos) + : SkTPin(fPos[curr], lastPos, prevPos); + + if (currPos != prevPos) { + SkASSERT((currPos - prevPos > 0) == (fAdvance > 0)); + func(fColors[prev], fColors[curr], prevPos, currPos); + } + + prev = curr; + prevPos = currPos; + } while (prev != end); + } + +private: + void iterateImplicitPos(std::function<void(SkColor, SkColor, SkScalar, SkScalar)> func) const { + // When clients don't provide explicit color stop positions (fPos == nullptr), + // the color stops are distributed evenly across the unit interval + // (implicit positioning). + const SkScalar dt = fAdvance * SK_Scalar1 / (fCount - 1); + const int end = fBegin + fAdvance * (fCount - 2); + int prev = fBegin; + SkScalar prevPos = fFirstPos; + + while (prev != end) { + const int curr = prev + fAdvance; + SkASSERT(curr >= 0 && curr < fCount); + + const SkScalar currPos = prevPos + dt; + func(fColors[prev], fColors[curr], prevPos, currPos); + prev = curr; + prevPos = currPos; + } + + // emit the last interval with a pinned end position, to avoid precision issues + func(fColors[prev], fColors[prev + fAdvance], prevPos, 1 - fFirstPos); + } + + const SkColor* fColors; + const SkScalar* fPos; + const int fCount; + const SkScalar fFirstPos; + const int fBegin; + const int fAdvance; +}; + +void addMirrorIntervals(const SkColor colors[], + const SkScalar pos[], int count, + const Sk4f& componentScale, + bool premulColors, bool reverse, + Sk4fGradientIntervalBuffer::BufferType* buffer) { + const IntervalIterator iter(colors, pos, count, reverse); + iter.iterate([&] (SkColor c0, SkColor c1, SkScalar t0, SkScalar t1) { + SkASSERT(buffer->empty() || buffer->back().fT1 == 2 - t0); + + const auto mirror_t0 = 2 - t0; + const auto mirror_t1 = 2 - t1; + // mirror_p1 & mirror_p1 may collapse for very small values - recheck to avoid + // triggering Interval asserts. + if (mirror_t0 != mirror_t1) { + buffer->emplace_back(pack_color(c0, premulColors, componentScale), mirror_t0, + pack_color(c1, premulColors, componentScale), mirror_t1); + } + }); +} + +} // anonymous namespace + +Sk4fGradientInterval::Sk4fGradientInterval(const Sk4f& c0, SkScalar t0, + const Sk4f& c1, SkScalar t1) + : fT0(t0) + , fT1(t1) { + SkASSERT(t0 != t1); + // Either p0 or p1 can be (-)inf for synthetic clamp edge intervals. + SkASSERT(SkScalarIsFinite(t0) || SkScalarIsFinite(t1)); + + const auto dt = t1 - t0; + + // Clamp edge intervals are always zero-ramp. + SkASSERT(SkScalarIsFinite(dt) || (c0 == c1).allTrue()); + SkASSERT(SkScalarIsFinite(t0) || (c0 == c1).allTrue()); + const Sk4f dc = SkScalarIsFinite(dt) ? (c1 - c0) / dt : 0; + const Sk4f bias = c0 - (SkScalarIsFinite(t0) ? t0 * dc : 0); + + bias.store(&fCb.fVec); + dc.store(&fCg.fVec); +} + +void Sk4fGradientIntervalBuffer::init(const SkColor colors[], const SkScalar pos[], int count, + SkShader::TileMode tileMode, bool premulColors, + SkScalar alpha, bool reverse) { + // The main job here is to build a specialized interval list: a different + // representation of the color stops data, optimized for efficient scan line + // access during shading. + // + // [{P0,C0} , {P1,C1}) [{P1,C2} , {P2,c3}) ... [{Pn,C2n} , {Pn+1,C2n+1}) + // + // The list may be inverted when requested (such that e.g. points are sorted + // in increasing x order when dx < 0). + // + // Note: the current representation duplicates pos data; we could refactor to + // avoid this if interval storage size becomes a concern. + // + // Aside from reordering, we also perform two more pre-processing steps at + // this stage: + // + // 1) scale the color components depending on paint alpha and the requested + // interpolation space (note: the interval color storage is SkPM4f, but + // that doesn't necessarily mean the colors are premultiplied; that + // property is tracked in fColorsArePremul) + // + // 2) inject synthetic intervals to support tiling. + // + // * for kRepeat, no extra intervals are needed - the iterator just + // wraps around at the end: + // + // ->[P0,P1)->..[Pn-1,Pn)-> + // + // * for kClamp, we add two "infinite" intervals before/after: + // + // [-/+inf , P0)->[P0 , P1)->..[Pn-1 , Pn)->[Pn , +/-inf) + // + // (the iterator should never run off the end in this mode) + // + // * for kMirror, we extend the range to [0..2] and add a flipped + // interval series - then the iterator operates just as in the + // kRepeat case: + // + // ->[P0,P1)->..[Pn-1,Pn)->[2 - Pn,2 - Pn-1)->..[2 - P1,2 - P0)-> + // + // TODO: investigate collapsing intervals << 1px. + + SkASSERT(count > 0); + SkASSERT(colors); + + fIntervals.reset(); + + const Sk4f componentScale = premulColors + ? Sk4f(alpha) + : Sk4f(1.0f, 1.0f, 1.0f, alpha); + const int first_index = reverse ? count - 1 : 0; + const int last_index = count - 1 - first_index; + const SkScalar first_pos = reverse ? SK_Scalar1 : 0; + const SkScalar last_pos = SK_Scalar1 - first_pos; + + if (tileMode == SkShader::kClamp_TileMode) { + // synthetic edge interval: -/+inf .. P0 + const Sk4f clamp_color = pack_color(colors[first_index], + premulColors, componentScale); + const SkScalar clamp_pos = reverse ? SK_ScalarInfinity : SK_ScalarNegativeInfinity; + fIntervals.emplace_back(clamp_color, clamp_pos, + clamp_color, first_pos); + } else if (tileMode == SkShader::kMirror_TileMode && reverse) { + // synthetic mirror intervals injected before main intervals: (2 .. 1] + addMirrorIntervals(colors, pos, count, componentScale, premulColors, false, &fIntervals); + } + + const IntervalIterator iter(colors, pos, count, reverse); + iter.iterate([&] (SkColor c0, SkColor c1, SkScalar t0, SkScalar t1) { + SkASSERT(fIntervals.empty() || fIntervals.back().fT1 == t0); + + fIntervals.emplace_back(pack_color(c0, premulColors, componentScale), t0, + pack_color(c1, premulColors, componentScale), t1); + }); + + if (tileMode == SkShader::kClamp_TileMode) { + // synthetic edge interval: Pn .. +/-inf + const Sk4f clamp_color = pack_color(colors[last_index], premulColors, componentScale); + const SkScalar clamp_pos = reverse ? SK_ScalarNegativeInfinity : SK_ScalarInfinity; + fIntervals.emplace_back(clamp_color, last_pos, + clamp_color, clamp_pos); + } else if (tileMode == SkShader::kMirror_TileMode && !reverse) { + // synthetic mirror intervals injected after main intervals: [1 .. 2) + addMirrorIntervals(colors, pos, count, componentScale, premulColors, true, &fIntervals); + } +} + +const Sk4fGradientInterval* Sk4fGradientIntervalBuffer::find(SkScalar t) const { + // Binary search. + const auto* i0 = fIntervals.begin(); + const auto* i1 = fIntervals.end() - 1; + + while (i0 != i1) { + SkASSERT(i0 < i1); + SkASSERT(t >= i0->fT0 && t <= i1->fT1); + + const auto* i = i0 + ((i1 - i0) >> 1); + + if (t > i->fT1) { + i0 = i + 1; + } else { + i1 = i; + } + } + + SkASSERT(i0->contains(t)); + return i0; +} + +const Sk4fGradientInterval* Sk4fGradientIntervalBuffer::findNext( + SkScalar t, const Sk4fGradientInterval* prev, bool increasing) const { + + SkASSERT(!prev->contains(t)); + SkASSERT(prev >= fIntervals.begin() && prev < fIntervals.end()); + SkASSERT(t >= fIntervals.front().fT0 && t <= fIntervals.back().fT1); + + const auto* i = prev; + + // Use the |increasing| signal to figure which direction we should search for + // the next interval, then perform a linear search. + if (increasing) { + do { + i += 1; + if (i >= fIntervals.end()) { + i = fIntervals.begin(); + } + } while (!i->contains(t)); + } else { + do { + i -= 1; + if (i < fIntervals.begin()) { + i = fIntervals.end() - 1; + } + } while (!i->contains(t)); + } + + return i; +} + +SkGradientShaderBase:: +GradientShaderBase4fContext::GradientShaderBase4fContext(const SkGradientShaderBase& shader, + const ContextRec& rec) + : INHERITED(shader, rec) + , fFlags(this->INHERITED::getFlags()) +#ifdef SK_SUPPORT_LEGACY_GRADIENT_DITHERING + , fDither(true) +#else + , fDither(rec.fPaint->isDither()) +#endif +{ + const SkMatrix& inverse = this->getTotalInverse(); + fDstToPos.setConcat(shader.fPtsToUnit, inverse); + fDstToPosProc = fDstToPos.getMapXYProc(); + fDstToPosClass = static_cast<uint8_t>(INHERITED::ComputeMatrixClass(fDstToPos)); + + if (shader.fColorsAreOpaque && this->getPaintAlpha() == SK_AlphaOPAQUE) { + fFlags |= kOpaqueAlpha_Flag; + } + + fColorsArePremul = + (shader.fGradFlags & SkGradientShader::kInterpolateColorsInPremul_Flag) + || shader.fColorsAreOpaque; +} + +bool SkGradientShaderBase:: +GradientShaderBase4fContext::isValid() const { + return fDstToPos.isFinite(); +} + +void SkGradientShaderBase:: +GradientShaderBase4fContext::shadeSpan(int x, int y, SkPMColor dst[], int count) { + if (fColorsArePremul) { + this->shadePremulSpan<DstType::L32, ApplyPremul::False>(x, y, dst, count); + } else { + this->shadePremulSpan<DstType::L32, ApplyPremul::True>(x, y, dst, count); + } +} + +void SkGradientShaderBase:: +GradientShaderBase4fContext::shadeSpan4f(int x, int y, SkPM4f dst[], int count) { + if (fColorsArePremul) { + this->shadePremulSpan<DstType::F32, ApplyPremul::False>(x, y, dst, count); + } else { + this->shadePremulSpan<DstType::F32, ApplyPremul::True>(x, y, dst, count); + } +} + +template<DstType dstType, ApplyPremul premul> +void SkGradientShaderBase:: +GradientShaderBase4fContext::shadePremulSpan(int x, int y, + typename DstTraits<dstType, premul>::Type dst[], + int count) const { + const SkGradientShaderBase& shader = + static_cast<const SkGradientShaderBase&>(fShader); + + switch (shader.fTileMode) { + case kClamp_TileMode: + this->shadeSpanInternal<dstType, + premul, + kClamp_TileMode>(x, y, dst, count); + break; + case kRepeat_TileMode: + this->shadeSpanInternal<dstType, + premul, + kRepeat_TileMode>(x, y, dst, count); + break; + case kMirror_TileMode: + this->shadeSpanInternal<dstType, + premul, + kMirror_TileMode>(x, y, dst, count); + break; + } +} + +template<DstType dstType, ApplyPremul premul, SkShader::TileMode tileMode> +void SkGradientShaderBase:: +GradientShaderBase4fContext::shadeSpanInternal(int x, int y, + typename DstTraits<dstType, premul>::Type dst[], + int count) const { + static const int kBufSize = 128; + SkScalar ts[kBufSize]; + TSampler<dstType, premul, tileMode> sampler(*this); + + SkASSERT(count > 0); + do { + const int n = SkTMin(kBufSize, count); + this->mapTs(x, y, ts, n); + for (int i = 0; i < n; ++i) { + const Sk4f c = sampler.sample(ts[i]); + DstTraits<dstType, premul>::store(c, dst++); + } + x += n; + count -= n; + } while (count > 0); +} + +template<DstType dstType, ApplyPremul premul, SkShader::TileMode tileMode> +class SkGradientShaderBase::GradientShaderBase4fContext::TSampler { +public: + TSampler(const GradientShaderBase4fContext& ctx) + : fCtx(ctx) + , fInterval(nullptr) { + switch (tileMode) { + case kClamp_TileMode: + fLargestIntervalValue = SK_ScalarInfinity; + break; + case kRepeat_TileMode: + fLargestIntervalValue = nextafterf(1, 0); + break; + case kMirror_TileMode: + fLargestIntervalValue = nextafterf(2.0f, 0); + break; + } + } + + Sk4f sample(SkScalar t) { + const auto tiled_t = tileProc(t); + + if (!fInterval) { + // Very first sample => locate the initial interval. + // TODO: maybe do this in ctor to remove a branch? + fInterval = fCtx.fIntervals.find(tiled_t); + this->loadIntervalData(fInterval); + } else if (!fInterval->contains(tiled_t)) { + fInterval = fCtx.fIntervals.findNext(tiled_t, fInterval, t >= fPrevT); + this->loadIntervalData(fInterval); + } + + fPrevT = t; + return lerp(tiled_t); + } + +private: + SkScalar tileProc(SkScalar t) const { + switch (tileMode) { + case kClamp_TileMode: + // synthetic clamp-mode edge intervals allow for a free-floating t: + // [-inf..0)[0..1)[1..+inf) + return t; + case kRepeat_TileMode: + // t % 1 (intervals range: [0..1)) + // Due to the extra arithmetic, we must clamp to ensure the value remains less than 1. + return SkTMin(t - SkScalarFloorToScalar(t), fLargestIntervalValue); + case kMirror_TileMode: + // t % 2 (synthetic mirror intervals expand the range to [0..2) + // Due to the extra arithmetic, we must clamp to ensure the value remains less than 2. + return SkTMin(t - SkScalarFloorToScalar(t / 2) * 2, fLargestIntervalValue); + } + + SK_ABORT("Unhandled tile mode."); + return 0; + } + + Sk4f lerp(SkScalar t) { + SkASSERT(fInterval->contains(t)); + return fCb + fCg * t; + } + + void loadIntervalData(const Sk4fGradientInterval* i) { + fCb = DstTraits<dstType, premul>::load(i->fCb); + fCg = DstTraits<dstType, premul>::load(i->fCg); + } + + const GradientShaderBase4fContext& fCtx; + const Sk4fGradientInterval* fInterval; + SkScalar fPrevT; + SkScalar fLargestIntervalValue; + Sk4f fCb; + Sk4f fCg; +}; diff --git a/src/shaders/gradients/Sk4fGradientBase.h b/src/shaders/gradients/Sk4fGradientBase.h new file mode 100644 index 0000000000..a660d6bde5 --- /dev/null +++ b/src/shaders/gradients/Sk4fGradientBase.h @@ -0,0 +1,97 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef Sk4fGradientBase_DEFINED +#define Sk4fGradientBase_DEFINED + +#include "Sk4fGradientPriv.h" +#include "SkColor.h" +#include "SkGradientShaderPriv.h" +#include "SkMatrix.h" +#include "SkNx.h" +#include "SkPM4f.h" +#include "SkShaderBase.h" +#include "SkTArray.h" + +struct Sk4fGradientInterval { + Sk4fGradientInterval(const Sk4f& c0, SkScalar t0, + const Sk4f& c1, SkScalar t1); + + bool contains(SkScalar t) const { + // True if t is in [p0,p1]. Note: this helper assumes a + // natural/increasing interval - so it's not usable in Sk4fLinearGradient. + SkASSERT(fT0 < fT1); + return t >= fT0 && t <= fT1; + } + + // Color bias and color gradient, such that for a t in this interval + // + // C = fCb + t * fCg; + SkPM4f fCb, fCg; + SkScalar fT0, fT1; +}; + +class Sk4fGradientIntervalBuffer { +public: + void init(const SkColor colors[], const SkScalar pos[], int count, + SkShader::TileMode tileMode, bool premulColors, SkScalar alpha, bool reverse); + + const Sk4fGradientInterval* find(SkScalar t) const; + const Sk4fGradientInterval* findNext(SkScalar t, const Sk4fGradientInterval* prev, + bool increasing) const; + + using BufferType = SkSTArray<8, Sk4fGradientInterval, true>; + + const BufferType* operator->() const { return &fIntervals; } + +private: + BufferType fIntervals; +}; + +class SkGradientShaderBase:: +GradientShaderBase4fContext : public Context { +public: + GradientShaderBase4fContext(const SkGradientShaderBase&, + const ContextRec&); + + uint32_t getFlags() const override { return fFlags; } + + void shadeSpan(int x, int y, SkPMColor dst[], int count) override; + void shadeSpan4f(int x, int y, SkPM4f dst[], int count) override; + + bool isValid() const; + +protected: + virtual void mapTs(int x, int y, SkScalar ts[], int count) const = 0; + + Sk4fGradientIntervalBuffer fIntervals; + SkMatrix fDstToPos; + SkMatrix::MapXYProc fDstToPosProc; + uint8_t fDstToPosClass; + uint8_t fFlags; + bool fDither; + bool fColorsArePremul; + +private: + using INHERITED = Context; + + void addMirrorIntervals(const SkGradientShaderBase&, + const Sk4f& componentScale, bool reverse); + + template<DstType, ApplyPremul, SkShader::TileMode tileMode> + class TSampler; + + template <DstType dstType, ApplyPremul premul> + void shadePremulSpan(int x, int y, typename DstTraits<dstType, premul>::Type[], + int count) const; + + template <DstType dstType, ApplyPremul premul, SkShader::TileMode tileMode> + void shadeSpanInternal(int x, int y, typename DstTraits<dstType, premul>::Type[], + int count) const; +}; + +#endif // Sk4fGradientBase_DEFINED diff --git a/src/shaders/gradients/Sk4fGradientPriv.h b/src/shaders/gradients/Sk4fGradientPriv.h new file mode 100644 index 0000000000..f18d6ced7b --- /dev/null +++ b/src/shaders/gradients/Sk4fGradientPriv.h @@ -0,0 +1,135 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef Sk4fGradientPriv_DEFINED +#define Sk4fGradientPriv_DEFINED + +#include "SkColor.h" +#include "SkHalf.h" +#include "SkImageInfo.h" +#include "SkNx.h" +#include "SkPM4f.h" +#include "SkPM4fPriv.h" +#include "SkUtils.h" + +// Templates shared by various 4f gradient flavors. + +namespace { + +enum class ApplyPremul { True, False }; + +enum class DstType { + L32, // Linear 32bit. + F32, // Linear float. +}; + +template <ApplyPremul> +struct PremulTraits; + +template <> +struct PremulTraits<ApplyPremul::False> { + static Sk4f apply(const Sk4f& c) { return c; } +}; + +template <> +struct PremulTraits<ApplyPremul::True> { + static Sk4f apply(const Sk4f& c) { + const float alpha = c[SkPM4f::A]; + // FIXME: portable swizzle? + return c * Sk4f(alpha, alpha, alpha, 1); + } +}; + +// Struct encapsulating various dest-dependent ops: +// +// - load() Load a SkPM4f value into Sk4f. Normally called once per interval +// advance. Also applies a scale and swizzle suitable for DstType. +// +// - store() Store one Sk4f to dest. Optionally handles premul, color space +// conversion, etc. +// +// - store(count) Store the Sk4f value repeatedly to dest, count times. +// +// - store4x() Store 4 Sk4f values to dest (opportunistic optimization). +// +template <DstType, ApplyPremul premul> +struct DstTraits; + +template <ApplyPremul premul> +struct DstTraits<DstType::L32, premul> { + using PM = PremulTraits<premul>; + using Type = SkPMColor; + + // For L32, prescaling by 255 saves a per-pixel multiplication when premul is not needed. + static Sk4f load(const SkPM4f& c) { + return premul == ApplyPremul::False + ? c.to4f_pmorder() * Sk4f(255) + : c.to4f_pmorder(); + } + + static void store(const Sk4f& c, Type* dst) { + if (premul == ApplyPremul::False) { + // c is prescaled by 255, just store. + SkNx_cast<uint8_t>(c).store(dst); + } else { + *dst = Sk4f_toL32(PM::apply(c)); + } + } + + static void store(const Sk4f& c, Type* dst, int n) { + Type pmc; + store(c, &pmc); + sk_memset32(dst, pmc, n); + } + + static void store4x(const Sk4f& c0, const Sk4f& c1, + const Sk4f& c2, const Sk4f& c3, + Type* dst) { + if (premul == ApplyPremul::False) { + Sk4f_ToBytes((uint8_t*)dst, c0, c1, c2, c3); + } else { + store(c0, dst + 0); + store(c1, dst + 1); + store(c2, dst + 2); + store(c3, dst + 3); + } + } +}; + +template <ApplyPremul premul> +struct DstTraits<DstType::F32, premul> { + using PM = PremulTraits<premul>; + using Type = SkPM4f; + + static Sk4f load(const SkPM4f& c) { + return c.to4f(); + } + + static void store(const Sk4f& c, Type* dst) { + PM::apply(c).store(dst->fVec); + } + + static void store(const Sk4f& c, Type* dst, int n) { + const Sk4f pmc = PM::apply(c); + for (int i = 0; i < n; ++i) { + pmc.store(dst[i].fVec); + } + } + + static void store4x(const Sk4f& c0, const Sk4f& c1, + const Sk4f& c2, const Sk4f& c3, + Type* dst) { + store(c0, dst + 0); + store(c1, dst + 1); + store(c2, dst + 2); + store(c3, dst + 3); + } +}; + +} // anonymous namespace + +#endif // Sk4fGradientPriv_DEFINED diff --git a/src/shaders/gradients/Sk4fLinearGradient.cpp b/src/shaders/gradients/Sk4fLinearGradient.cpp new file mode 100644 index 0000000000..7b7498eaef --- /dev/null +++ b/src/shaders/gradients/Sk4fLinearGradient.cpp @@ -0,0 +1,430 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "Sk4fLinearGradient.h" +#include "Sk4x4f.h" + +#include <cmath> + +namespace { + +template<DstType dstType, ApplyPremul premul> +void ramp(const Sk4f& c, const Sk4f& dc, typename DstTraits<dstType, premul>::Type dst[], int n) { + SkASSERT(n > 0); + + const Sk4f dc2 = dc + dc; + const Sk4f dc4 = dc2 + dc2; + + Sk4f c0 = c ; + Sk4f c1 = c + dc; + Sk4f c2 = c0 + dc2; + Sk4f c3 = c1 + dc2; + + while (n >= 4) { + DstTraits<dstType, premul>::store4x(c0, c1, c2, c3, dst); + dst += 4; + + c0 = c0 + dc4; + c1 = c1 + dc4; + c2 = c2 + dc4; + c3 = c3 + dc4; + n -= 4; + } + if (n & 2) { + DstTraits<dstType, premul>::store(c0, dst++); + DstTraits<dstType, premul>::store(c1, dst++); + c0 = c0 + dc2; + } + if (n & 1) { + DstTraits<dstType, premul>::store(c0, dst); + } +} + +template<SkShader::TileMode> +SkScalar pinFx(SkScalar); + +template<> +SkScalar pinFx<SkShader::kClamp_TileMode>(SkScalar fx) { + return fx; +} + +template<> +SkScalar pinFx<SkShader::kRepeat_TileMode>(SkScalar fx) { + SkScalar f = SkScalarFraction(fx); + if (f < 0) { + f = SkTMin(f + 1, nextafterf(1, 0)); + } + SkASSERT(f >= 0); + SkASSERT(f < 1.0f); + return f; +} + +template<> +SkScalar pinFx<SkShader::kMirror_TileMode>(SkScalar fx) { + SkScalar f = SkScalarMod(fx, 2.0f); + if (f < 0) { + f = SkTMin(f + 2, nextafterf(2, 0)); + } + SkASSERT(f >= 0); + SkASSERT(f < 2.0f); + return f; +} + +// true when x is in [k1,k2], or [k2, k1] when the interval is reversed. +// TODO(fmalita): hoist the reversed interval check out of this helper. +bool in_range(SkScalar x, SkScalar k1, SkScalar k2) { + SkASSERT(k1 != k2); + return (k1 < k2) + ? (x >= k1 && x <= k2) + : (x >= k2 && x <= k1); +} + +} // anonymous namespace + +SkLinearGradient:: +LinearGradient4fContext::LinearGradient4fContext(const SkLinearGradient& shader, + const ContextRec& rec) + : INHERITED(shader, rec) { + + // Our fast path expects interval points to be monotonically increasing in x. + const bool reverseIntervals = this->isFast() && std::signbit(fDstToPos.getScaleX()); + fIntervals.init(shader.fOrigColors, shader.fOrigPos, shader.fColorCount, shader.fTileMode, + fColorsArePremul, rec.fPaint->getAlpha() * (1.0f / 255), reverseIntervals); + + SkASSERT(fIntervals->count() > 0); + fCachedInterval = fIntervals->begin(); +} + +const Sk4fGradientInterval* +SkLinearGradient::LinearGradient4fContext::findInterval(SkScalar fx) const { + SkASSERT(in_range(fx, fIntervals->front().fT0, fIntervals->back().fT1)); + + if (1) { + // Linear search, using the last scanline interval as a starting point. + SkASSERT(fCachedInterval >= fIntervals->begin()); + SkASSERT(fCachedInterval < fIntervals->end()); + const int search_dir = fDstToPos.getScaleX() >= 0 ? 1 : -1; + while (!in_range(fx, fCachedInterval->fT0, fCachedInterval->fT1)) { + fCachedInterval += search_dir; + if (fCachedInterval >= fIntervals->end()) { + fCachedInterval = fIntervals->begin(); + } else if (fCachedInterval < fIntervals->begin()) { + fCachedInterval = fIntervals->end() - 1; + } + } + return fCachedInterval; + } else { + // Binary search. Seems less effective than linear + caching. + const auto* i0 = fIntervals->begin(); + const auto* i1 = fIntervals->end() - 1; + + while (i0 != i1) { + SkASSERT(i0 < i1); + SkASSERT(in_range(fx, i0->fT0, i1->fT1)); + + const auto* i = i0 + ((i1 - i0) >> 1); + + if (in_range(fx, i0->fT0, i->fT1)) { + i1 = i; + } else { + SkASSERT(in_range(fx, i->fT1, i1->fT1)); + i0 = i + 1; + } + } + + SkASSERT(in_range(fx, i0->fT0, i0->fT1)); + return i0; + } +} + +void SkLinearGradient:: +LinearGradient4fContext::shadeSpan(int x, int y, SkPMColor dst[], int count) { + if (!this->isFast()) { + this->INHERITED::shadeSpan(x, y, dst, count); + return; + } + + // TODO: plumb dithering + SkASSERT(count > 0); + if (fColorsArePremul) { + this->shadePremulSpan<DstType::L32, + ApplyPremul::False>(x, y, dst, count); + } else { + this->shadePremulSpan<DstType::L32, + ApplyPremul::True>(x, y, dst, count); + } +} + +void SkLinearGradient:: +LinearGradient4fContext::shadeSpan4f(int x, int y, SkPM4f dst[], int count) { + if (!this->isFast()) { + this->INHERITED::shadeSpan4f(x, y, dst, count); + return; + } + + // TONOTDO: plumb dithering + SkASSERT(count > 0); + if (fColorsArePremul) { + this->shadePremulSpan<DstType::F32, + ApplyPremul::False>(x, y, dst, count); + } else { + this->shadePremulSpan<DstType::F32, + ApplyPremul::True>(x, y, dst, count); + } +} + +template<DstType dstType, ApplyPremul premul> +void SkLinearGradient:: +LinearGradient4fContext::shadePremulSpan(int x, int y, + typename DstTraits<dstType, premul>::Type dst[], + int count) const { + const SkLinearGradient& shader = + static_cast<const SkLinearGradient&>(fShader); + switch (shader.fTileMode) { + case kClamp_TileMode: + this->shadeSpanInternal<dstType, + premul, + kClamp_TileMode>(x, y, dst, count); + break; + case kRepeat_TileMode: + this->shadeSpanInternal<dstType, + premul, + kRepeat_TileMode>(x, y, dst, count); + break; + case kMirror_TileMode: + this->shadeSpanInternal<dstType, + premul, + kMirror_TileMode>(x, y, dst, count); + break; + } +} + +template<DstType dstType, ApplyPremul premul, SkShader::TileMode tileMode> +void SkLinearGradient:: +LinearGradient4fContext::shadeSpanInternal(int x, int y, + typename DstTraits<dstType, premul>::Type dst[], + int count) const { + SkPoint pt; + fDstToPosProc(fDstToPos, + x + SK_ScalarHalf, + y + SK_ScalarHalf, + &pt); + const SkScalar fx = pinFx<tileMode>(pt.x()); + const SkScalar dx = fDstToPos.getScaleX(); + LinearIntervalProcessor<dstType, premul, tileMode> proc(fIntervals->begin(), + fIntervals->end() - 1, + this->findInterval(fx), + fx, + dx, + SkScalarNearlyZero(dx * count)); + while (count > 0) { + // What we really want here is SkTPin(advance, 1, count) + // but that's a significant perf hit for >> stops; investigate. + const int n = SkScalarTruncToInt( + SkTMin<SkScalar>(proc.currentAdvance() + 1, SkIntToScalar(count))); + + // The current interval advance can be +inf (e.g. when reaching + // the clamp mode end intervals) - when that happens, we expect to + // a) consume all remaining count in one swoop + // b) return a zero color gradient + SkASSERT(SkScalarIsFinite(proc.currentAdvance()) + || (n == count && proc.currentRampIsZero())); + + if (proc.currentRampIsZero()) { + DstTraits<dstType, premul>::store(proc.currentColor(), + dst, n); + } else { + ramp<dstType, premul>(proc.currentColor(), + proc.currentColorGrad(), + dst, n); + } + + proc.advance(SkIntToScalar(n)); + count -= n; + dst += n; + } +} + +template<DstType dstType, ApplyPremul premul, SkShader::TileMode tileMode> +class SkLinearGradient:: +LinearGradient4fContext::LinearIntervalProcessor { +public: + LinearIntervalProcessor(const Sk4fGradientInterval* firstInterval, + const Sk4fGradientInterval* lastInterval, + const Sk4fGradientInterval* i, + SkScalar fx, + SkScalar dx, + bool is_vertical) + : fAdvX(is_vertical ? SK_ScalarInfinity : (i->fT1 - fx) / dx) + , fFirstInterval(firstInterval) + , fLastInterval(lastInterval) + , fInterval(i) + , fDx(dx) + , fIsVertical(is_vertical) + { + SkASSERT(fAdvX >= 0); + SkASSERT(firstInterval <= lastInterval); + + if (tileMode != kClamp_TileMode && !is_vertical) { + const auto spanX = (lastInterval->fT1 - firstInterval->fT0) / dx; + SkASSERT(spanX >= 0); + + // If we're in a repeating tile mode and the whole gradient is compressed into a + // fraction of a pixel, we just use the average color in zero-ramp mode. + // This also avoids cases where we make no progress due to interval advances being + // close to zero. + static constexpr SkScalar kMinSpanX = .25f; + if (spanX < kMinSpanX) { + this->init_average_props(); + return; + } + } + + this->compute_interval_props(fx); + } + + SkScalar currentAdvance() const { + SkASSERT(fAdvX >= 0); + SkASSERT(fAdvX <= (fInterval->fT1 - fInterval->fT0) / fDx || !std::isfinite(fAdvX)); + return fAdvX; + } + + bool currentRampIsZero() const { return fZeroRamp; } + const Sk4f& currentColor() const { return fCc; } + const Sk4f& currentColorGrad() const { return fDcDx; } + + void advance(SkScalar advX) { + SkASSERT(advX > 0); + SkASSERT(fAdvX >= 0); + + if (advX >= fAdvX) { + advX = this->advance_interval(advX); + } + SkASSERT(advX < fAdvX); + + fCc = fCc + fDcDx * Sk4f(advX); + fAdvX -= advX; + } + +private: + void compute_interval_props(SkScalar t) { + SkASSERT(in_range(t, fInterval->fT0, fInterval->fT1)); + + const Sk4f dc = DstTraits<dstType, premul>::load(fInterval->fCg); + fCc = DstTraits<dstType, premul>::load(fInterval->fCb) + dc * Sk4f(t); + fDcDx = dc * fDx; + fZeroRamp = fIsVertical || (dc == 0).allTrue(); + } + + void init_average_props() { + fAdvX = SK_ScalarInfinity; + fZeroRamp = true; + fDcDx = 0; + fCc = Sk4f(0); + + // TODO: precompute the average at interval setup time? + for (const auto* i = fFirstInterval; i <= fLastInterval; ++i) { + // Each interval contributes its average color to the total/weighted average: + // + // C = (c0 + c1) / 2 = (Cb + Cg * t0 + Cb + Cg * t1) / 2 = Cb + Cg *(t0 + t1) / 2 + // + // Avg += C * (t1 - t0) + // + const auto c = DstTraits<dstType, premul>::load(i->fCb) + + DstTraits<dstType, premul>::load(i->fCg) * (i->fT0 + i->fT1) * 0.5f; + fCc = fCc + c * (i->fT1 - i->fT0); + } + } + + const Sk4fGradientInterval* next_interval(const Sk4fGradientInterval* i) const { + SkASSERT(i >= fFirstInterval); + SkASSERT(i <= fLastInterval); + i++; + + if (tileMode == kClamp_TileMode) { + SkASSERT(i <= fLastInterval); + return i; + } + + return (i <= fLastInterval) ? i : fFirstInterval; + } + + SkScalar advance_interval(SkScalar advX) { + SkASSERT(advX >= fAdvX); + + do { + advX -= fAdvX; + fInterval = this->next_interval(fInterval); + fAdvX = (fInterval->fT1 - fInterval->fT0) / fDx; + SkASSERT(fAdvX > 0); + } while (advX >= fAdvX); + + compute_interval_props(fInterval->fT0); + + SkASSERT(advX >= 0); + return advX; + } + + // Current interval properties. + Sk4f fDcDx; // dst color gradient (dc/dx) + Sk4f fCc; // current color, interpolated in dst + SkScalar fAdvX; // remaining interval advance in dst + bool fZeroRamp; // current interval color grad is 0 + + const Sk4fGradientInterval* fFirstInterval; + const Sk4fGradientInterval* fLastInterval; + const Sk4fGradientInterval* fInterval; // current interval + const SkScalar fDx; // 'dx' for consistency with other impls; actually dt/dx + const bool fIsVertical; +}; + +void SkLinearGradient:: +LinearGradient4fContext::mapTs(int x, int y, SkScalar ts[], int count) const { + SkASSERT(count > 0); + SkASSERT(fDstToPosClass != kLinear_MatrixClass); + + SkScalar sx = x + SK_ScalarHalf; + const SkScalar sy = y + SK_ScalarHalf; + SkPoint pt; + + if (fDstToPosClass != kPerspective_MatrixClass) { + // kLinear_MatrixClass, kFixedStepInX_MatrixClass => fixed dt per scanline + const SkScalar dtdx = fDstToPos.fixedStepInX(sy).x(); + fDstToPosProc(fDstToPos, sx, sy, &pt); + + const Sk4f dtdx4 = Sk4f(4 * dtdx); + Sk4f t4 = Sk4f(pt.x() + 0 * dtdx, + pt.x() + 1 * dtdx, + pt.x() + 2 * dtdx, + pt.x() + 3 * dtdx); + + while (count >= 4) { + t4.store(ts); + t4 = t4 + dtdx4; + ts += 4; + count -= 4; + } + + if (count & 2) { + *ts++ = t4[0]; + *ts++ = t4[1]; + t4 = SkNx_shuffle<2, 0, 1, 3>(t4); + } + + if (count & 1) { + *ts++ = t4[0]; + } + } else { + for (int i = 0; i < count; ++i) { + fDstToPosProc(fDstToPos, sx, sy, &pt); + // Perspective may yield NaN values. + // Short of a better idea, drop to 0. + ts[i] = SkScalarIsNaN(pt.x()) ? 0 : pt.x(); + sx += SK_Scalar1; + } + } +} diff --git a/src/shaders/gradients/Sk4fLinearGradient.h b/src/shaders/gradients/Sk4fLinearGradient.h new file mode 100644 index 0000000000..f1c0bb5a30 --- /dev/null +++ b/src/shaders/gradients/Sk4fLinearGradient.h @@ -0,0 +1,46 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef Sk4fLinearGradient_DEFINED +#define Sk4fLinearGradient_DEFINED + +#include "Sk4fGradientBase.h" +#include "SkLinearGradient.h" + +class SkLinearGradient:: +LinearGradient4fContext final : public GradientShaderBase4fContext { +public: + LinearGradient4fContext(const SkLinearGradient&, const ContextRec&); + + void shadeSpan(int x, int y, SkPMColor dst[], int count) override; + void shadeSpan4f(int x, int y, SkPM4f dst[], int count) override; + +protected: + void mapTs(int x, int y, SkScalar ts[], int count) const override; + +private: + using INHERITED = GradientShaderBase4fContext; + + template<DstType, ApplyPremul, TileMode> + class LinearIntervalProcessor; + + template <DstType dstType, ApplyPremul premul> + void shadePremulSpan(int x, int y, typename DstTraits<dstType, premul>::Type[], + int count) const; + + template <DstType dstType, ApplyPremul premul, SkShader::TileMode tileMode> + void shadeSpanInternal(int x, int y, typename DstTraits<dstType, premul>::Type[], + int count) const; + + const Sk4fGradientInterval* findInterval(SkScalar fx) const; + + bool isFast() const { return fDstToPosClass == kLinear_MatrixClass; } + + mutable const Sk4fGradientInterval* fCachedInterval; +}; + +#endif // Sk4fLinearGradient_DEFINED diff --git a/src/shaders/gradients/SkClampRange.cpp b/src/shaders/gradients/SkClampRange.cpp new file mode 100644 index 0000000000..efc93959d1 --- /dev/null +++ b/src/shaders/gradients/SkClampRange.cpp @@ -0,0 +1,178 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkClampRange.h" +#include "SkMathPriv.h" + +static int SkCLZ64(uint64_t value) { + int count = 0; + if (value >> 32) { + value >>= 32; + } else { + count += 32; + } + return count + SkCLZ(SkToU32(value)); +} + +static bool sk_64_smul_check(int64_t count, int64_t dx, int64_t* result) { + // Do it the slow way until we have some assembly. + if (dx == std::numeric_limits<int64_t>::min()) { + return false; // SkTAbs overflow + } + + SkASSERT(count >= 0); + uint64_t ucount = static_cast<uint64_t>(count); + uint64_t udx = static_cast<uint64_t>(SkTAbs(dx)); + int zeros = SkCLZ64(ucount) + SkCLZ64(udx); + // this is a conservative check: it may return false when in fact it would not have overflowed. + // Hackers Delight uses 34 as its convervative check, but that is for 32x32 multiplies. + // Since we are looking at 64x64 muls, we add 32 to the check. + if (zeros < (32 + 34)) { + return false; + } + *result = count * dx; + return true; +} + +static bool sk_64_sadd_check(int64_t a, int64_t b, int64_t* result) { + if (a > 0) { + if (b > std::numeric_limits<int64_t>::max() - a) { + return false; + } + } else { + if (b < std::numeric_limits<int64_t>::min() - a) { + return false; + } + } + + *result = a + b; + return true; +} + + +/* + * returns [0..count] for the number of steps (<= count) for which x0 <= edge + * given each step is followed by x0 += dx + */ +static int chop(int64_t x0, SkGradFixed edge, int64_t x1, int64_t dx, int count) { + SkASSERT(dx > 0); + SkASSERT(count >= 0); + + if (x0 >= edge) { + return 0; + } + if (x1 <= edge) { + return count; + } + int64_t n = (edge - x0 + dx - 1) / dx; + SkASSERT(n >= 0); + SkASSERT(n <= count); + return (int)n; +} + +void SkClampRange::initFor1(SkGradFixed fx) { + fCount0 = fCount1 = fCount2 = 0; + if (fx <= 0) { + fCount0 = 1; + } else if (fx < kFracMax_SkGradFixed) { + fCount1 = 1; + fFx1 = fx; + } else { + fCount2 = 1; + } +} + +void SkClampRange::init(SkGradFixed fx0, SkGradFixed dx0, int count, int v0, int v1) { + SkASSERT(count > 0); + + fV0 = v0; + fV1 = v1; + + // special case 1 == count, as it is slightly common for skia + // and avoids us ever calling divide or 64bit multiply + if (1 == count) { + this->initFor1(fx0); + return; + } + + int64_t fx = fx0; + int64_t dx = dx0; + + // start with ex equal to the last computed value + int64_t count_times_dx, ex; + if (!sk_64_smul_check(count - 1, dx, &count_times_dx) || + !sk_64_sadd_check(fx, count_times_dx, &ex)) { + // we can't represent the computed end in 32.32, so just draw something (first color) + fCount1 = fCount2 = 0; + fCount0 = count; + return; + } + + if ((uint64_t)(fx | ex) <= kFracMax_SkGradFixed) { + fCount0 = fCount2 = 0; + fCount1 = count; + fFx1 = fx0; + return; + } + if (fx <= 0 && ex <= 0) { + fCount1 = fCount2 = 0; + fCount0 = count; + return; + } + if (fx >= kFracMax_SkGradFixed && ex >= kFracMax_SkGradFixed) { + fCount0 = fCount1 = 0; + fCount2 = count; + return; + } + + // now make ex be 1 past the last computed value + ex += dx; + + bool doSwap = dx < 0; + + if (doSwap) { + ex -= dx; + fx -= dx; + SkTSwap(fx, ex); + dx = -dx; + } + + + fCount0 = chop(fx, 0, ex, dx, count); + SkASSERT(fCount0 >= 0); + SkASSERT(fCount0 <= count); + count -= fCount0; + fx += fCount0 * dx; + SkASSERT(fx >= 0); + SkASSERT(fCount0 == 0 || (fx - dx) < 0); + fCount1 = chop(fx, kFracMax_SkGradFixed, ex, dx, count); + SkASSERT(fCount1 >= 0); + SkASSERT(fCount1 <= count); + count -= fCount1; + fCount2 = count; + +#ifdef SK_DEBUG + fx += fCount1 * dx; + SkASSERT(fx <= ex); + if (fCount2 > 0) { + SkASSERT(fx >= kFracMax_SkGradFixed); + if (fCount1 > 0) { + SkASSERT(fx - dx < kFracMax_SkGradFixed); + } + } +#endif + + if (doSwap) { + SkTSwap(fCount0, fCount2); + SkTSwap(fV0, fV1); + dx = -dx; + } + + if (fCount1 > 0) { + fFx1 = fx0 + fCount0 * dx; + } +} diff --git a/src/shaders/gradients/SkClampRange.h b/src/shaders/gradients/SkClampRange.h new file mode 100644 index 0000000000..8a22e72d38 --- /dev/null +++ b/src/shaders/gradients/SkClampRange.h @@ -0,0 +1,62 @@ +/* + * Copyright 2011 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkClampRange_DEFINED +#define SkClampRange_DEFINED + +#include "SkFixed.h" +#include "SkScalar.h" + +#define SkGradFixed SkFixed3232 + +// We want the largest 32.32 value representable as a float. (float)0x7FFFFFFF +// becomes too big, due to limited mantissa on the float and its rounding rules, so +// we have to manually compute the next smaller value (aka nextafter). + +// #define SkGradFixedMaxScalar nextafterf(SkFixed3232ToFloat(SkFixed3232Max), 0) +// #define SkGradFixedMinScalar nextafterf(SkFixed3232ToFloat(SkFixed3232Min), 0) +#define SkGradFixedMaxScalar ( 2147483520.0f) +#define SkGradFixedMinScalar (-2147483520.0f) +#define SkScalarPinToGradFixed(x) SkScalarToFixed3232(SkTPin(x, \ + SkGradFixedMinScalar,\ + SkGradFixedMaxScalar)) +#define SkFixedToGradFixed(x) SkFixedToFixed3232(x) +#define SkGradFixedToFixed(x) (SkFixed)((x) >> 16) +#define kFracMax_SkGradFixed 0xFFFFFFFFLL + +/** + * Iteration fixed fx by dx, clamping as you go to [0..kFracMax_SkGradFixed], this class + * computes the (up to) 3 spans there are: + * + * range0: use constant value V0 + * range1: iterate as usual fx += dx + * range2: use constant value V1 + */ +struct SkClampRange { + int fCount0; // count for fV0 + int fCount1; // count for interpolating (fV0...fV1) + int fCount2; // count for fV1 + SkGradFixed fFx1; // initial fx value for the fCount1 range. + // only valid if fCount1 > 0 + int fV0, fV1; + + void init(SkGradFixed fx, SkGradFixed dx, int count, int v0, int v1); + + void validate(int count) const { +#ifdef SK_DEBUG + SkASSERT(fCount0 >= 0); + SkASSERT(fCount1 >= 0); + SkASSERT(fCount2 >= 0); + SkASSERT(fCount0 + fCount1 + fCount2 == count); +#endif + } + +private: + void initFor1(SkGradFixed fx); +}; + +#endif diff --git a/src/shaders/gradients/SkGradientBitmapCache.cpp b/src/shaders/gradients/SkGradientBitmapCache.cpp new file mode 100644 index 0000000000..06b2d8c3fe --- /dev/null +++ b/src/shaders/gradients/SkGradientBitmapCache.cpp @@ -0,0 +1,154 @@ +/* + * Copyright 2010 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#include "SkGradientBitmapCache.h" + +#include "SkMalloc.h" + +struct SkGradientBitmapCache::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); + } +}; + +SkGradientBitmapCache::SkGradientBitmapCache(int max) : fMaxEntries(max) { + fEntryCount = 0; + fHead = fTail = nullptr; + + this->validate(); +} + +SkGradientBitmapCache::~SkGradientBitmapCache() { + this->validate(); + + Entry* entry = fHead; + while (entry) { + Entry* next = entry->fNext; + delete entry; + entry = next; + } +} + +SkGradientBitmapCache::Entry* SkGradientBitmapCache::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 SkGradientBitmapCache::attachToHead(Entry* entry) const { + entry->fPrev = nullptr; + entry->fNext = fHead; + if (fHead) { + fHead->fPrev = entry; + } else { + fTail = entry; + } + fHead = entry; +} + +bool SkGradientBitmapCache::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 SkGradientBitmapCache::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; +} + +/////////////////////////////////////////////////////////////////////////////// + +#ifdef SK_DEBUG + +void SkGradientBitmapCache::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/shaders/gradients/SkGradientBitmapCache.h b/src/shaders/gradients/SkGradientBitmapCache.h new file mode 100644 index 0000000000..0dcd32272e --- /dev/null +++ b/src/shaders/gradients/SkGradientBitmapCache.h @@ -0,0 +1,48 @@ +/* + * Copyright 2010 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#ifndef SkGradientBitmapCache_DEFINED +#define SkGradientBitmapCache_DEFINED + +#include "SkBitmap.h" + +class SkGradientBitmapCache : SkNoncopyable { +public: + SkGradientBitmapCache(int maxEntries); + ~SkGradientBitmapCache(); + + bool find(const void* buffer, size_t len, SkBitmap*) const; + void add(const void* buffer, size_t len, const SkBitmap&); + +private: + int fEntryCount; + const int fMaxEntries; + + struct Entry; + mutable Entry* fHead; + mutable Entry* fTail; + + inline Entry* release(Entry*) const; + inline void attachToHead(Entry*) const; + +#ifdef SK_DEBUG + void validate() const; +#else + void validate() const {} +#endif + + class AutoValidate : SkNoncopyable { + public: + AutoValidate(const SkGradientBitmapCache* bc) : fBC(bc) { bc->validate(); } + ~AutoValidate() { fBC->validate(); } + private: + const SkGradientBitmapCache* fBC; + }; +}; + +#endif diff --git a/src/shaders/gradients/SkGradientShader.cpp b/src/shaders/gradients/SkGradientShader.cpp new file mode 100644 index 0000000000..137da84d0c --- /dev/null +++ b/src/shaders/gradients/SkGradientShader.cpp @@ -0,0 +1,2004 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include <algorithm> +#include "Sk4fLinearGradient.h" +#include "SkColorSpace_XYZ.h" +#include "SkGradientShaderPriv.h" +#include "SkHalf.h" +#include "SkLinearGradient.h" +#include "SkMallocPixelRef.h" +#include "SkRadialGradient.h" +#include "SkSweepGradient.h" +#include "SkTwoPointConicalGradient.h" +#include "../../jumper/SkJumper.h" + + +enum GradientSerializationFlags { + // Bits 29:31 used for various boolean flags + kHasPosition_GSF = 0x80000000, + kHasLocalMatrix_GSF = 0x40000000, + kHasColorSpace_GSF = 0x20000000, + + // Bits 12:28 unused + + // Bits 8:11 for fTileMode + kTileModeShift_GSF = 8, + kTileModeMask_GSF = 0xF, + + // Bits 0:7 for fGradFlags (note that kForce4fContext_PrivateFlag is 0x80) + kGradFlagsShift_GSF = 0, + kGradFlagsMask_GSF = 0xFF, +}; + +void SkGradientShaderBase::Descriptor::flatten(SkWriteBuffer& buffer) const { + uint32_t flags = 0; + if (fPos) { + flags |= kHasPosition_GSF; + } + if (fLocalMatrix) { + flags |= kHasLocalMatrix_GSF; + } + sk_sp<SkData> colorSpaceData = fColorSpace ? fColorSpace->serialize() : nullptr; + if (colorSpaceData) { + flags |= kHasColorSpace_GSF; + } + SkASSERT(static_cast<uint32_t>(fTileMode) <= kTileModeMask_GSF); + flags |= (fTileMode << kTileModeShift_GSF); + SkASSERT(fGradFlags <= kGradFlagsMask_GSF); + flags |= (fGradFlags << kGradFlagsShift_GSF); + + buffer.writeUInt(flags); + + buffer.writeColor4fArray(fColors, fCount); + if (colorSpaceData) { + buffer.writeDataAsByteArray(colorSpaceData.get()); + } + if (fPos) { + buffer.writeScalarArray(fPos, fCount); + } + if (fLocalMatrix) { + buffer.writeMatrix(*fLocalMatrix); + } +} + +bool SkGradientShaderBase::DescriptorScope::unflatten(SkReadBuffer& buffer) { + if (buffer.isVersionLT(SkReadBuffer::kGradientShaderFloatColor_Version)) { + fCount = buffer.getArrayCount(); + if (fCount > kStorageCount) { + size_t allocSize = (sizeof(SkColor4f) + sizeof(SkScalar)) * fCount; + fDynamicStorage.reset(allocSize); + fColors = (SkColor4f*)fDynamicStorage.get(); + fPos = (SkScalar*)(fColors + fCount); + } else { + fColors = fColorStorage; + fPos = fPosStorage; + } + + // Old gradients serialized SkColor. Read that to a temporary location, then convert. + SkSTArray<2, SkColor, true> colors; + colors.resize_back(fCount); + if (!buffer.readColorArray(colors.begin(), fCount)) { + return false; + } + for (int i = 0; i < fCount; ++i) { + mutableColors()[i] = SkColor4f::FromColor(colors[i]); + } + + if (buffer.readBool()) { + if (!buffer.readScalarArray(const_cast<SkScalar*>(fPos), fCount)) { + return false; + } + } else { + fPos = nullptr; + } + + fColorSpace = nullptr; + fTileMode = (SkShader::TileMode)buffer.read32(); + fGradFlags = buffer.read32(); + + if (buffer.readBool()) { + fLocalMatrix = &fLocalMatrixStorage; + buffer.readMatrix(&fLocalMatrixStorage); + } else { + fLocalMatrix = nullptr; + } + } else { + // New gradient format. Includes floating point color, color space, densely packed flags + uint32_t flags = buffer.readUInt(); + + fTileMode = (SkShader::TileMode)((flags >> kTileModeShift_GSF) & kTileModeMask_GSF); + fGradFlags = (flags >> kGradFlagsShift_GSF) & kGradFlagsMask_GSF; + + fCount = buffer.getArrayCount(); + if (fCount > kStorageCount) { + size_t allocSize = (sizeof(SkColor4f) + sizeof(SkScalar)) * fCount; + fDynamicStorage.reset(allocSize); + fColors = (SkColor4f*)fDynamicStorage.get(); + fPos = (SkScalar*)(fColors + fCount); + } else { + fColors = fColorStorage; + fPos = fPosStorage; + } + if (!buffer.readColor4fArray(mutableColors(), fCount)) { + return false; + } + if (SkToBool(flags & kHasColorSpace_GSF)) { + sk_sp<SkData> data = buffer.readByteArrayAsData(); + fColorSpace = SkColorSpace::Deserialize(data->data(), data->size()); + } else { + fColorSpace = nullptr; + } + if (SkToBool(flags & kHasPosition_GSF)) { + if (!buffer.readScalarArray(mutablePos(), fCount)) { + return false; + } + } else { + fPos = nullptr; + } + if (SkToBool(flags & kHasLocalMatrix_GSF)) { + fLocalMatrix = &fLocalMatrixStorage; + buffer.readMatrix(&fLocalMatrixStorage); + } else { + fLocalMatrix = nullptr; + } + } + return buffer.isValid(); +} + +//////////////////////////////////////////////////////////////////////////////////////////// + +SkGradientShaderBase::SkGradientShaderBase(const Descriptor& desc, const SkMatrix& ptsToUnit) + : INHERITED(desc.fLocalMatrix) + , fPtsToUnit(ptsToUnit) +{ + fPtsToUnit.getType(); // Precache so reads are threadsafe. + SkASSERT(desc.fCount > 1); + + fGradFlags = static_cast<uint8_t>(desc.fGradFlags); + + SkASSERT((unsigned)desc.fTileMode < SkShader::kTileModeCount); + SkASSERT(SkShader::kTileModeCount == SK_ARRAY_COUNT(gTileProcs)); + fTileMode = desc.fTileMode; + fTileProc = gTileProcs[desc.fTileMode]; + + /* Note: we let the caller skip the first and/or last position. + i.e. pos[0] = 0.3, pos[1] = 0.7 + In these cases, we insert dummy entries to ensure that the final data + will be bracketed by [0, 1]. + i.e. our_pos[0] = 0, our_pos[1] = 0.3, our_pos[2] = 0.7, our_pos[3] = 1 + + Thus colorCount (the caller's value, and fColorCount (our value) may + differ by up to 2. In the above example: + colorCount = 2 + fColorCount = 4 + */ + fColorCount = desc.fCount; + // check if we need to add in dummy start and/or end position/colors + bool dummyFirst = false; + bool dummyLast = false; + if (desc.fPos) { + dummyFirst = desc.fPos[0] != 0; + dummyLast = desc.fPos[desc.fCount - 1] != SK_Scalar1; + fColorCount += dummyFirst + dummyLast; + } + + if (fColorCount > kColorStorageCount) { + size_t size = sizeof(SkColor) + sizeof(SkColor4f) + sizeof(Rec); + if (desc.fPos) { + size += sizeof(SkScalar); + } + fOrigColors = reinterpret_cast<SkColor*>(sk_malloc_throw(size * fColorCount)); + } + else { + fOrigColors = fStorage; + } + + fOrigColors4f = (SkColor4f*)(fOrigColors + fColorCount); + + // Now copy over the colors, adding the dummies as needed + SkColor4f* origColors = fOrigColors4f; + if (dummyFirst) { + *origColors++ = desc.fColors[0]; + } + memcpy(origColors, desc.fColors, desc.fCount * sizeof(SkColor4f)); + if (dummyLast) { + origColors += desc.fCount; + *origColors = desc.fColors[desc.fCount - 1]; + } + + // Convert our SkColor4f colors to SkColor as well. Note that this is incorrect if the + // source colors are not in sRGB gamut. We would need to do a gamut transformation, but + // SkColorSpaceXform can't do that (yet). GrColorSpaceXform can, but we may not have GPU + // support compiled in here. For the common case (sRGB colors), this does the right thing. + for (int i = 0; i < fColorCount; ++i) { + fOrigColors[i] = fOrigColors4f[i].toSkColor(); + } + + if (!desc.fColorSpace) { + // This happens if we were constructed from SkColors, so our colors are really sRGB + fColorSpace = SkColorSpace::MakeSRGBLinear(); + } else { + // The color space refers to the float colors, so it must be linear gamma + SkASSERT(desc.fColorSpace->gammaIsLinear()); + fColorSpace = desc.fColorSpace; + } + + if (desc.fPos && fColorCount) { + fOrigPos = (SkScalar*)(fOrigColors4f + fColorCount); + fRecs = (Rec*)(fOrigPos + fColorCount); + } else { + fOrigPos = nullptr; + fRecs = (Rec*)(fOrigColors4f + fColorCount); + } + + if (fColorCount > 2) { + Rec* recs = fRecs; + recs->fPos = 0; + // recs->fScale = 0; // unused; + recs += 1; + if (desc.fPos) { + SkScalar* origPosPtr = fOrigPos; + *origPosPtr++ = 0; + + /* We need to convert the user's array of relative positions into + fixed-point positions and scale factors. We need these results + to be strictly monotonic (no two values equal or out of order). + Hence this complex loop that just jams a zero for the scale + value if it sees a segment out of order, and it assures that + we start at 0 and end at 1.0 + */ + SkScalar prev = 0; + int startIndex = dummyFirst ? 0 : 1; + int count = desc.fCount + dummyLast; + for (int i = startIndex; i < count; i++) { + // force the last value to be 1.0 + SkScalar curr; + if (i == desc.fCount) { // we're really at the dummyLast + curr = 1; + } else { + curr = SkScalarPin(desc.fPos[i], 0, 1); + } + *origPosPtr++ = curr; + + recs->fPos = SkScalarToFixed(curr); + SkFixed diff = SkScalarToFixed(curr - prev); + if (diff > 0) { + recs->fScale = (1 << 24) / diff; + } else { + recs->fScale = 0; // ignore this segment + } + // get ready for the next value + prev = curr; + recs += 1; + } + } else { // assume even distribution + fOrigPos = nullptr; + + SkFixed dp = SK_Fixed1 / (desc.fCount - 1); + SkFixed p = dp; + SkFixed scale = (desc.fCount - 1) << 8; // (1 << 24) / dp + for (int i = 1; i < desc.fCount - 1; i++) { + recs->fPos = p; + recs->fScale = scale; + recs += 1; + p += dp; + } + recs->fPos = SK_Fixed1; + recs->fScale = scale; + } + } else if (desc.fPos) { + SkASSERT(2 == fColorCount); + fOrigPos[0] = SkScalarPin(desc.fPos[0], 0, 1); + fOrigPos[1] = SkScalarPin(desc.fPos[1], fOrigPos[0], 1); + if (0 == fOrigPos[0] && 1 == fOrigPos[1]) { + fOrigPos = nullptr; + } + } + this->initCommon(); +} + +SkGradientShaderBase::~SkGradientShaderBase() { + if (fOrigColors != fStorage) { + sk_free(fOrigColors); + } +} + +void SkGradientShaderBase::initCommon() { + unsigned colorAlpha = 0xFF; + for (int i = 0; i < fColorCount; i++) { + colorAlpha &= SkColorGetA(fOrigColors[i]); + } + fColorsAreOpaque = colorAlpha == 0xFF; +} + +void SkGradientShaderBase::flatten(SkWriteBuffer& buffer) const { + Descriptor desc; + desc.fColors = fOrigColors4f; + desc.fColorSpace = fColorSpace; + desc.fPos = fOrigPos; + desc.fCount = fColorCount; + desc.fTileMode = fTileMode; + desc.fGradFlags = fGradFlags; + + const SkMatrix& m = this->getLocalMatrix(); + desc.fLocalMatrix = m.isIdentity() ? nullptr : &m; + desc.flatten(buffer); +} + +void SkGradientShaderBase::FlipGradientColors(SkColor* colorDst, Rec* recDst, + SkColor* colorSrc, Rec* recSrc, + int count) { + SkAutoSTArray<8, SkColor> colorsTemp(count); + for (int i = 0; i < count; ++i) { + int offset = count - i - 1; + colorsTemp[i] = colorSrc[offset]; + } + if (count > 2) { + SkAutoSTArray<8, Rec> recsTemp(count); + for (int i = 0; i < count; ++i) { + int offset = count - i - 1; + recsTemp[i].fPos = SK_Fixed1 - recSrc[offset].fPos; + recsTemp[i].fScale = recSrc[offset].fScale; + } + memcpy(recDst, recsTemp.get(), count * sizeof(Rec)); + } + memcpy(colorDst, colorsTemp.get(), count * sizeof(SkColor)); +} + +static void add_stop_color(SkJumper_GradientCtx* ctx, size_t stop, SkPM4f Fs, SkPM4f Bs) { + (ctx->fs[0])[stop] = Fs.r(); + (ctx->fs[1])[stop] = Fs.g(); + (ctx->fs[2])[stop] = Fs.b(); + (ctx->fs[3])[stop] = Fs.a(); + (ctx->bs[0])[stop] = Bs.r(); + (ctx->bs[1])[stop] = Bs.g(); + (ctx->bs[2])[stop] = Bs.b(); + (ctx->bs[3])[stop] = Bs.a(); +} + +static void add_const_color(SkJumper_GradientCtx* ctx, size_t stop, SkPM4f color) { + add_stop_color(ctx, stop, SkPM4f::FromPremulRGBA(0,0,0,0), color); +} + +// Calculate a factor F and a bias B so that color = F*t + B when t is in range of +// the stop. Assume that the distance between stops is 1/gapCount. +static void init_stop_evenly( + SkJumper_GradientCtx* ctx, float gapCount, size_t stop, SkPM4f c_l, SkPM4f c_r) { + // Clankium's GCC 4.9 targeting ARMv7 is barfing when we use Sk4f math here, so go scalar... + SkPM4f Fs = {{ + (c_r.r() - c_l.r()) * gapCount, + (c_r.g() - c_l.g()) * gapCount, + (c_r.b() - c_l.b()) * gapCount, + (c_r.a() - c_l.a()) * gapCount, + }}; + SkPM4f Bs = {{ + c_l.r() - Fs.r()*(stop/gapCount), + c_l.g() - Fs.g()*(stop/gapCount), + c_l.b() - Fs.b()*(stop/gapCount), + c_l.a() - Fs.a()*(stop/gapCount), + }}; + add_stop_color(ctx, stop, Fs, Bs); +} + +// For each stop we calculate a bias B and a scale factor F, such that +// for any t between stops n and n+1, the color we want is B[n] + F[n]*t. +static void init_stop_pos( + SkJumper_GradientCtx* ctx, size_t stop, float t_l, float t_r, SkPM4f c_l, SkPM4f c_r) { + // See note about Clankium's old compiler in init_stop_evenly(). + SkPM4f Fs = {{ + (c_r.r() - c_l.r()) / (t_r - t_l), + (c_r.g() - c_l.g()) / (t_r - t_l), + (c_r.b() - c_l.b()) / (t_r - t_l), + (c_r.a() - c_l.a()) / (t_r - t_l), + }}; + SkPM4f Bs = {{ + c_l.r() - Fs.r()*t_l, + c_l.g() - Fs.g()*t_l, + c_l.b() - Fs.b()*t_l, + c_l.a() - Fs.a()*t_l, + }}; + ctx->ts[stop] = t_l; + add_stop_color(ctx, stop, Fs, Bs); +} + +bool SkGradientShaderBase::onAppendStages(SkRasterPipeline* p, + SkColorSpace* dstCS, + SkArenaAlloc* alloc, + const SkMatrix& ctm, + const SkPaint& paint, + const SkMatrix* localM) const { + SkMatrix matrix; + if (!this->computeTotalInverse(ctm, localM, &matrix)) { + return false; + } + + SkRasterPipeline_<256> subclass; + if (!this->adjustMatrixAndAppendStages(alloc, &matrix, &subclass)) { + return false; + } + + auto* m = alloc->makeArrayDefault<float>(9); + if (matrix.asAffine(m)) { + p->append(SkRasterPipeline::matrix_2x3, m); + } else { + matrix.get9(m); + p->append(SkRasterPipeline::matrix_perspective, m); + } + + p->extend(subclass); + + switch(fTileMode) { + case kMirror_TileMode: p->append(SkRasterPipeline::mirror_x_1); break; + case kRepeat_TileMode: p->append(SkRasterPipeline::repeat_x_1); break; + case kClamp_TileMode: + if (!fOrigPos) { + // We clamp only when the stops are evenly spaced. + // If not, there may be hard stops, and clamping ruins hard stops at 0 and/or 1. + // In that case, we must make sure we're using the general "gradient" stage, + // which is the only stage that will correctly handle unclamped t. + p->append(SkRasterPipeline::clamp_x_1); + } + } + + const bool premulGrad = fGradFlags & SkGradientShader::kInterpolateColorsInPremul_Flag; + auto prepareColor = [premulGrad, dstCS, this](int i) { + SkColor4f c = dstCS ? to_colorspace(fOrigColors4f[i], fColorSpace.get(), dstCS) + : SkColor4f_from_SkColor(fOrigColors[i], nullptr); + return premulGrad ? c.premul() + : SkPM4f::From4f(Sk4f::Load(&c)); + }; + + // The two-stop case with stops at 0 and 1. + if (fColorCount == 2 && fOrigPos == nullptr) { + const SkPM4f c_l = prepareColor(0), + c_r = prepareColor(1); + + // See F and B below. + auto* f_and_b = alloc->makeArrayDefault<SkPM4f>(2); + f_and_b[0] = SkPM4f::From4f(c_r.to4f() - c_l.to4f()); + f_and_b[1] = c_l; + + p->append(SkRasterPipeline::evenly_spaced_2_stop_gradient, f_and_b); + } else { + auto* ctx = alloc->make<SkJumper_GradientCtx>(); + + // Note: In order to handle clamps in search, the search assumes a stop conceptully placed + // at -inf. Therefore, the max number of stops is fColorCount+1. + for (int i = 0; i < 4; i++) { + // Allocate at least at for the AVX2 gather from a YMM register. + ctx->fs[i] = alloc->makeArray<float>(std::max(fColorCount+1, 8)); + ctx->bs[i] = alloc->makeArray<float>(std::max(fColorCount+1, 8)); + } + + if (fOrigPos == nullptr) { + // Handle evenly distributed stops. + + size_t stopCount = fColorCount; + float gapCount = stopCount - 1; + + SkPM4f c_l = prepareColor(0); + for (size_t i = 0; i < stopCount - 1; i++) { + SkPM4f c_r = prepareColor(i + 1); + init_stop_evenly(ctx, gapCount, i, c_l, c_r); + c_l = c_r; + } + add_const_color(ctx, stopCount - 1, c_l); + + ctx->stopCount = stopCount; + p->append(SkRasterPipeline::evenly_spaced_gradient, ctx); + } else { + // Handle arbitrary stops. + + ctx->ts = alloc->makeArray<float>(fColorCount+1); + + // Remove the dummy stops inserted by SkGradientShaderBase::SkGradientShaderBase + // because they are naturally handled by the search method. + int firstStop; + int lastStop; + if (fColorCount > 2) { + firstStop = fOrigColors4f[0] != fOrigColors4f[1] ? 0 : 1; + lastStop = fOrigColors4f[fColorCount - 2] != fOrigColors4f[fColorCount - 1] + ? fColorCount - 1 : fColorCount - 2; + } else { + firstStop = 0; + lastStop = 1; + } + + size_t stopCount = 0; + float t_l = fOrigPos[firstStop]; + SkPM4f c_l = prepareColor(firstStop); + add_const_color(ctx, stopCount++, c_l); + // N.B. lastStop is the index of the last stop, not one after. + for (int i = firstStop; i < lastStop; i++) { + float t_r = fOrigPos[i + 1]; + SkPM4f c_r = prepareColor(i + 1); + if (t_l < t_r) { + init_stop_pos(ctx, stopCount, t_l, t_r, c_l, c_r); + stopCount += 1; + } + t_l = t_r; + c_l = c_r; + } + + ctx->ts[stopCount] = t_l; + add_const_color(ctx, stopCount++, c_l); + + ctx->stopCount = stopCount; + p->append(SkRasterPipeline::gradient, ctx); + } + } + + if (!premulGrad && !this->colorsAreOpaque()) { + p->append(SkRasterPipeline::premul); + } + + return true; +} + + +bool SkGradientShaderBase::isOpaque() const { + return fColorsAreOpaque; +} + +static unsigned rounded_divide(unsigned numer, unsigned denom) { + return (numer + (denom >> 1)) / denom; +} + +bool SkGradientShaderBase::onAsLuminanceColor(SkColor* lum) const { + // we just compute an average color. + // possibly we could weight this based on the proportional width for each color + // assuming they are not evenly distributed in the fPos array. + int r = 0; + int g = 0; + int b = 0; + const int n = fColorCount; + for (int i = 0; i < n; ++i) { + SkColor c = fOrigColors[i]; + r += SkColorGetR(c); + g += SkColorGetG(c); + b += SkColorGetB(c); + } + *lum = SkColorSetRGB(rounded_divide(r, n), rounded_divide(g, n), rounded_divide(b, n)); + return true; +} + +SkGradientShaderBase::GradientShaderBaseContext::GradientShaderBaseContext( + const SkGradientShaderBase& shader, const ContextRec& rec) + : INHERITED(shader, rec) +#ifdef SK_SUPPORT_LEGACY_GRADIENT_DITHERING + , fDither(true) +#else + , fDither(rec.fPaint->isDither()) +#endif + , fCache(shader.refCache(getPaintAlpha(), fDither)) +{ + const SkMatrix& inverse = this->getTotalInverse(); + + fDstToIndex.setConcat(shader.fPtsToUnit, inverse); + + fDstToIndexProc = fDstToIndex.getMapXYProc(); + fDstToIndexClass = (uint8_t)SkShaderBase::Context::ComputeMatrixClass(fDstToIndex); + + // now convert our colors in to PMColors + unsigned paintAlpha = this->getPaintAlpha(); + + fFlags = this->INHERITED::getFlags(); + if (shader.fColorsAreOpaque && paintAlpha == 0xFF) { + fFlags |= kOpaqueAlpha_Flag; + } +} + +bool SkGradientShaderBase::GradientShaderBaseContext::isValid() const { + return fDstToIndex.isFinite(); +} + +SkGradientShaderBase::GradientShaderCache::GradientShaderCache( + U8CPU alpha, bool dither, const SkGradientShaderBase& shader) + : fCacheAlpha(alpha) + , fCacheDither(dither) + , fShader(shader) +{ + // Only initialize the cache in getCache32. + fCache32 = nullptr; +} + +SkGradientShaderBase::GradientShaderCache::~GradientShaderCache() {} + +/* + * r,g,b used to be SkFixed, but on gcc (4.2.1 mac and 4.6.3 goobuntu) in + * release builds, we saw a compiler error where the 0xFF parameter in + * SkPackARGB32() was being totally ignored whenever it was called with + * a non-zero add (e.g. 0x8000). + * + * We found two work-arounds: + * 1. change r,g,b to unsigned (or just one of them) + * 2. change SkPackARGB32 to + its (a << SK_A32_SHIFT) value instead + * of using | + * + * We chose #1 just because it was more localized. + * See http://code.google.com/p/skia/issues/detail?id=1113 + * + * The type SkUFixed encapsulate this need for unsigned, but logically Fixed. + */ +typedef uint32_t SkUFixed; + +void SkGradientShaderBase::GradientShaderCache::Build32bitCache( + SkPMColor cache[], SkColor c0, SkColor c1, + int count, U8CPU paintAlpha, uint32_t gradFlags, bool dither) { + SkASSERT(count > 1); + + // need to apply paintAlpha to our two endpoints + uint32_t a0 = SkMulDiv255Round(SkColorGetA(c0), paintAlpha); + uint32_t a1 = SkMulDiv255Round(SkColorGetA(c1), paintAlpha); + + + const bool interpInPremul = SkToBool(gradFlags & + SkGradientShader::kInterpolateColorsInPremul_Flag); + + uint32_t r0 = SkColorGetR(c0); + uint32_t g0 = SkColorGetG(c0); + uint32_t b0 = SkColorGetB(c0); + + uint32_t r1 = SkColorGetR(c1); + uint32_t g1 = SkColorGetG(c1); + uint32_t b1 = SkColorGetB(c1); + + if (interpInPremul) { + r0 = SkMulDiv255Round(r0, a0); + g0 = SkMulDiv255Round(g0, a0); + b0 = SkMulDiv255Round(b0, a0); + + r1 = SkMulDiv255Round(r1, a1); + g1 = SkMulDiv255Round(g1, a1); + b1 = SkMulDiv255Round(b1, a1); + } + + SkFixed da = SkIntToFixed(a1 - a0) / (count - 1); + SkFixed dr = SkIntToFixed(r1 - r0) / (count - 1); + SkFixed dg = SkIntToFixed(g1 - g0) / (count - 1); + SkFixed db = SkIntToFixed(b1 - b0) / (count - 1); + + /* We pre-add 1/8 to avoid having to add this to our [0] value each time + in the loop. Without this, the bias for each would be + 0x2000 0xA000 0xE000 0x6000 + With this trick, we can add 0 for the first (no-op) and just adjust the + others. + */ + const SkUFixed bias0 = dither ? 0x2000 : 0x8000; + const SkUFixed bias1 = dither ? 0x8000 : 0; + const SkUFixed bias2 = dither ? 0xC000 : 0; + const SkUFixed bias3 = dither ? 0x4000 : 0; + + SkUFixed a = SkIntToFixed(a0) + bias0; + SkUFixed r = SkIntToFixed(r0) + bias0; + SkUFixed g = SkIntToFixed(g0) + bias0; + SkUFixed b = SkIntToFixed(b0) + bias0; + + /* + * Our dither-cell (spatially) is + * 0 2 + * 3 1 + * Where + * [0] -> [-1/8 ... 1/8 ) values near 0 + * [1] -> [ 1/8 ... 3/8 ) values near 1/4 + * [2] -> [ 3/8 ... 5/8 ) values near 1/2 + * [3] -> [ 5/8 ... 7/8 ) values near 3/4 + */ + + if (0xFF == a0 && 0 == da) { + do { + cache[kCache32Count*0] = SkPackARGB32(0xFF, (r + 0 ) >> 16, + (g + 0 ) >> 16, + (b + 0 ) >> 16); + cache[kCache32Count*1] = SkPackARGB32(0xFF, (r + bias1) >> 16, + (g + bias1) >> 16, + (b + bias1) >> 16); + cache[kCache32Count*2] = SkPackARGB32(0xFF, (r + bias2) >> 16, + (g + bias2) >> 16, + (b + bias2) >> 16); + cache[kCache32Count*3] = SkPackARGB32(0xFF, (r + bias3) >> 16, + (g + bias3) >> 16, + (b + bias3) >> 16); + cache += 1; + r += dr; + g += dg; + b += db; + } while (--count != 0); + } else if (interpInPremul) { + do { + cache[kCache32Count*0] = SkPackARGB32((a + 0 ) >> 16, + (r + 0 ) >> 16, + (g + 0 ) >> 16, + (b + 0 ) >> 16); + cache[kCache32Count*1] = SkPackARGB32((a + bias1) >> 16, + (r + bias1) >> 16, + (g + bias1) >> 16, + (b + bias1) >> 16); + cache[kCache32Count*2] = SkPackARGB32((a + bias2) >> 16, + (r + bias2) >> 16, + (g + bias2) >> 16, + (b + bias2) >> 16); + cache[kCache32Count*3] = SkPackARGB32((a + bias3) >> 16, + (r + bias3) >> 16, + (g + bias3) >> 16, + (b + bias3) >> 16); + cache += 1; + a += da; + r += dr; + g += dg; + b += db; + } while (--count != 0); + } else { // interpolate in unpreml space + do { + cache[kCache32Count*0] = SkPremultiplyARGBInline((a + 0 ) >> 16, + (r + 0 ) >> 16, + (g + 0 ) >> 16, + (b + 0 ) >> 16); + cache[kCache32Count*1] = SkPremultiplyARGBInline((a + bias1) >> 16, + (r + bias1) >> 16, + (g + bias1) >> 16, + (b + bias1) >> 16); + cache[kCache32Count*2] = SkPremultiplyARGBInline((a + bias2) >> 16, + (r + bias2) >> 16, + (g + bias2) >> 16, + (b + bias2) >> 16); + cache[kCache32Count*3] = SkPremultiplyARGBInline((a + bias3) >> 16, + (r + bias3) >> 16, + (g + bias3) >> 16, + (b + bias3) >> 16); + cache += 1; + a += da; + r += dr; + g += dg; + b += db; + } while (--count != 0); + } +} + +static inline int SkFixedToFFFF(SkFixed x) { + SkASSERT((unsigned)x <= SK_Fixed1); + return x - (x >> 16); +} + +const SkPMColor* SkGradientShaderBase::GradientShaderCache::getCache32() { + fCache32InitOnce(SkGradientShaderBase::GradientShaderCache::initCache32, this); + SkASSERT(fCache32); + return fCache32; +} + +void SkGradientShaderBase::GradientShaderCache::initCache32(GradientShaderCache* cache) { + const int kNumberOfDitherRows = 4; + const SkImageInfo info = SkImageInfo::MakeN32Premul(kCache32Count, kNumberOfDitherRows); + + SkASSERT(nullptr == cache->fCache32PixelRef); + cache->fCache32PixelRef = SkMallocPixelRef::MakeAllocate(info, 0, nullptr); + cache->fCache32 = (SkPMColor*)cache->fCache32PixelRef->pixels(); + if (cache->fShader.fColorCount == 2) { + Build32bitCache(cache->fCache32, cache->fShader.fOrigColors[0], + cache->fShader.fOrigColors[1], kCache32Count, cache->fCacheAlpha, + cache->fShader.fGradFlags, cache->fCacheDither); + } else { + Rec* rec = cache->fShader.fRecs; + int prevIndex = 0; + for (int i = 1; i < cache->fShader.fColorCount; i++) { + int nextIndex = SkFixedToFFFF(rec[i].fPos) >> kCache32Shift; + SkASSERT(nextIndex < kCache32Count); + + if (nextIndex > prevIndex) + Build32bitCache(cache->fCache32 + prevIndex, cache->fShader.fOrigColors[i-1], + cache->fShader.fOrigColors[i], nextIndex - prevIndex + 1, + cache->fCacheAlpha, cache->fShader.fGradFlags, cache->fCacheDither); + prevIndex = nextIndex; + } + } +} + +void SkGradientShaderBase::initLinearBitmap(SkBitmap* bitmap) const { + const bool interpInPremul = SkToBool(fGradFlags & + SkGradientShader::kInterpolateColorsInPremul_Flag); + 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); +} + +/* + * The gradient holds a cache for the most recent value of alpha. Successive + * callers with the same alpha value will share the same cache. + */ +sk_sp<SkGradientShaderBase::GradientShaderCache> SkGradientShaderBase::refCache(U8CPU alpha, + bool dither) const { + SkAutoMutexAcquire ama(fCacheMutex); + if (!fCache || fCache->getAlpha() != alpha || fCache->getDither() != dither) { + fCache.reset(new GradientShaderCache(alpha, dither, *this)); + } + // Increment the ref counter inside the mutex to ensure the returned pointer is still valid. + // Otherwise, the pointer may have been overwritten on a different thread before the object's + // ref count was incremented. + return fCache; +} + +SK_DECLARE_STATIC_MUTEX(gGradientCacheMutex); +/* + * Because our caller might rebuild the same (logically the same) gradient + * over and over, we'd like to return exactly the same "bitmap" if possible, + * allowing the client to utilize a cache of our bitmap (e.g. with a GPU). + * To do that, we maintain a private cache of built-bitmaps, based on our + * 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, + GradientBitmapType bitmapType) const { + // our caller assumes no external alpha, so we ensure that our cache is built with 0xFF + sk_sp<GradientShaderCache> cache(this->refCache(0xFF, true)); + + // build our key: [numColors + colors[] + {positions[]} + flags + colorType ] + int count = 1 + fColorCount + 1 + 1; + if (fColorCount > 2) { + count += fColorCount - 1; // fRecs[].fPos + } + + SkAutoSTMalloc<16, int32_t> storage(count); + int32_t* buffer = storage.get(); + + *buffer++ = fColorCount; + memcpy(buffer, fOrigColors, fColorCount * sizeof(SkColor)); + buffer += fColorCount; + if (fColorCount > 2) { + for (int i = 1; i < fColorCount; i++) { + *buffer++ = fRecs[i].fPos; + } + } + *buffer++ = fGradFlags; + *buffer++ = static_cast<int32_t>(bitmapType); + SkASSERT(buffer - storage.get() == count); + + /////////////////////////////////// + + static SkGradientBitmapCache* gCache; + // 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); + + if (nullptr == gCache) { + gCache = new SkGradientBitmapCache(MAX_NUM_CACHED_GRADIENT_BITMAPS); + } + size_t size = count * sizeof(int32_t); + + if (!gCache->find(storage.get(), size, bitmap)) { + if (GradientBitmapType::kLegacy == bitmapType) { + // force our cache32pixelref to be built + (void)cache->getCache32(); + bitmap->setInfo(SkImageInfo::MakeN32Premul(kCache32Count, 1)); + bitmap->setPixelRef(sk_ref_sp(cache->getCache32PixelRef()), 0, 0); + } 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::MakeSRGB()); + break; + case GradientBitmapType::kHalfFloat: + info = SkImageInfo::Make( + kCache32Count, 1, kRGBA_F16_SkColorType, kPremul_SkAlphaType, + SkColorSpace::MakeSRGBLinear()); + break; + default: + SkFAIL("Unexpected bitmap type"); + return; + } + bitmap->allocPixels(info); + this->initLinearBitmap(bitmap); + } + gCache->add(storage.get(), size, *bitmap); + } +} + +void SkGradientShaderBase::commonAsAGradient(GradientInfo* info, bool flipGrad) const { + if (info) { + if (info->fColorCount >= fColorCount) { + SkColor* colorLoc; + Rec* recLoc; + SkAutoSTArray<8, SkColor> colorStorage; + SkAutoSTArray<8, Rec> recStorage; + if (flipGrad && (info->fColors || info->fColorOffsets)) { + colorStorage.reset(fColorCount); + recStorage.reset(fColorCount); + colorLoc = colorStorage.get(); + recLoc = recStorage.get(); + FlipGradientColors(colorLoc, recLoc, fOrigColors, fRecs, fColorCount); + } else { + colorLoc = fOrigColors; + recLoc = fRecs; + } + if (info->fColors) { + memcpy(info->fColors, colorLoc, fColorCount * sizeof(SkColor)); + } + if (info->fColorOffsets) { + if (fColorCount == 2) { + info->fColorOffsets[0] = 0; + info->fColorOffsets[1] = SK_Scalar1; + } else if (fColorCount > 2) { + for (int i = 0; i < fColorCount; ++i) { + info->fColorOffsets[i] = SkFixedToScalar(recLoc[i].fPos); + } + } + } + } + info->fColorCount = fColorCount; + info->fTileMode = fTileMode; + info->fGradientFlags = fGradFlags; + } +} + +#ifndef SK_IGNORE_TO_STRING +void SkGradientShaderBase::toString(SkString* str) const { + + str->appendf("%d colors: ", fColorCount); + + for (int i = 0; i < fColorCount; ++i) { + str->appendHex(fOrigColors[i], 8); + if (i < fColorCount-1) { + str->append(", "); + } + } + + if (fColorCount > 2) { + str->append(" points: ("); + for (int i = 0; i < fColorCount; ++i) { + str->appendScalar(SkFixedToScalar(fRecs[i].fPos)); + if (i < fColorCount-1) { + str->append(", "); + } + } + str->append(")"); + } + + static const char* gTileModeName[SkShader::kTileModeCount] = { + "clamp", "repeat", "mirror" + }; + + str->append(" "); + str->append(gTileModeName[fTileMode]); + + this->INHERITED::toString(str); +} +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +// Return true if these parameters are valid/legal/safe to construct a gradient +// +static bool valid_grad(const SkColor4f colors[], const SkScalar pos[], int count, + unsigned tileMode) { + return nullptr != colors && count >= 1 && tileMode < (unsigned)SkShader::kTileModeCount; +} + +static void desc_init(SkGradientShaderBase::Descriptor* desc, + const SkColor4f colors[], sk_sp<SkColorSpace> colorSpace, + const SkScalar pos[], int colorCount, + SkShader::TileMode mode, uint32_t flags, const SkMatrix* localMatrix) { + SkASSERT(colorCount > 1); + + desc->fColors = colors; + desc->fColorSpace = std::move(colorSpace); + desc->fPos = pos; + desc->fCount = colorCount; + desc->fTileMode = mode; + desc->fGradFlags = flags; + desc->fLocalMatrix = localMatrix; +} + +// assumes colors is SkColor4f* and pos is SkScalar* +#define EXPAND_1_COLOR(count) \ + SkColor4f tmp[2]; \ + do { \ + if (1 == count) { \ + tmp[0] = tmp[1] = colors[0]; \ + colors = tmp; \ + pos = nullptr; \ + count = 2; \ + } \ + } while (0) + +struct ColorStopOptimizer { + ColorStopOptimizer(const SkColor4f* colors, const SkScalar* pos, + int count, SkShader::TileMode mode) + : fColors(colors) + , fPos(pos) + , fCount(count) { + + if (!pos || count != 3) { + return; + } + + if (SkScalarNearlyEqual(pos[0], 0.0f) && + SkScalarNearlyEqual(pos[1], 0.0f) && + SkScalarNearlyEqual(pos[2], 1.0f)) { + + if (SkShader::kRepeat_TileMode == mode || + SkShader::kMirror_TileMode == mode || + colors[0] == colors[1]) { + + // Ignore the leftmost color/pos. + fColors += 1; + fPos += 1; + fCount = 2; + } + } else if (SkScalarNearlyEqual(pos[0], 0.0f) && + SkScalarNearlyEqual(pos[1], 1.0f) && + SkScalarNearlyEqual(pos[2], 1.0f)) { + + if (SkShader::kRepeat_TileMode == mode || + SkShader::kMirror_TileMode == mode || + colors[1] == colors[2]) { + + // Ignore the rightmost color/pos. + fCount = 2; + } + } + } + + const SkColor4f* fColors; + const SkScalar* fPos; + int fCount; +}; + +struct ColorConverter { + ColorConverter(const SkColor* colors, int count) { + for (int i = 0; i < count; ++i) { + fColors4f.push_back(SkColor4f::FromColor(colors[i])); + } + } + + SkSTArray<2, SkColor4f, true> fColors4f; +}; + +sk_sp<SkShader> SkGradientShader::MakeLinear(const SkPoint pts[2], + const SkColor colors[], + const SkScalar pos[], int colorCount, + SkShader::TileMode mode, + uint32_t flags, + const SkMatrix* localMatrix) { + ColorConverter converter(colors, colorCount); + return MakeLinear(pts, converter.fColors4f.begin(), nullptr, pos, colorCount, mode, flags, + localMatrix); +} + +sk_sp<SkShader> SkGradientShader::MakeLinear(const SkPoint pts[2], + const SkColor4f colors[], + sk_sp<SkColorSpace> colorSpace, + const SkScalar pos[], int colorCount, + SkShader::TileMode mode, + uint32_t flags, + const SkMatrix* localMatrix) { + if (!pts || !SkScalarIsFinite((pts[1] - pts[0]).length())) { + return nullptr; + } + if (!valid_grad(colors, pos, colorCount, mode)) { + return nullptr; + } + if (1 == colorCount) { + return SkShader::MakeColorShader(colors[0], std::move(colorSpace)); + } + if (localMatrix && !localMatrix->invert(nullptr)) { + return nullptr; + } + + ColorStopOptimizer opt(colors, pos, colorCount, mode); + + SkGradientShaderBase::Descriptor desc; + desc_init(&desc, opt.fColors, std::move(colorSpace), opt.fPos, opt.fCount, mode, flags, + localMatrix); + return sk_make_sp<SkLinearGradient>(pts, desc); +} + +sk_sp<SkShader> SkGradientShader::MakeRadial(const SkPoint& center, SkScalar radius, + const SkColor colors[], + const SkScalar pos[], int colorCount, + SkShader::TileMode mode, + uint32_t flags, + const SkMatrix* localMatrix) { + ColorConverter converter(colors, colorCount); + return MakeRadial(center, radius, converter.fColors4f.begin(), nullptr, pos, colorCount, mode, + flags, localMatrix); +} + +sk_sp<SkShader> SkGradientShader::MakeRadial(const SkPoint& center, SkScalar radius, + const SkColor4f colors[], + sk_sp<SkColorSpace> colorSpace, + const SkScalar pos[], int colorCount, + SkShader::TileMode mode, + uint32_t flags, + const SkMatrix* localMatrix) { + if (radius <= 0) { + return nullptr; + } + if (!valid_grad(colors, pos, colorCount, mode)) { + return nullptr; + } + if (1 == colorCount) { + return SkShader::MakeColorShader(colors[0], std::move(colorSpace)); + } + if (localMatrix && !localMatrix->invert(nullptr)) { + return nullptr; + } + + ColorStopOptimizer opt(colors, pos, colorCount, mode); + + SkGradientShaderBase::Descriptor desc; + desc_init(&desc, opt.fColors, std::move(colorSpace), opt.fPos, opt.fCount, mode, flags, + localMatrix); + return sk_make_sp<SkRadialGradient>(center, radius, desc); +} + +sk_sp<SkShader> SkGradientShader::MakeTwoPointConical(const SkPoint& start, + SkScalar startRadius, + const SkPoint& end, + SkScalar endRadius, + const SkColor colors[], + const SkScalar pos[], + int colorCount, + SkShader::TileMode mode, + uint32_t flags, + const SkMatrix* localMatrix) { + ColorConverter converter(colors, colorCount); + return MakeTwoPointConical(start, startRadius, end, endRadius, converter.fColors4f.begin(), + nullptr, pos, colorCount, mode, flags, localMatrix); +} + +sk_sp<SkShader> SkGradientShader::MakeTwoPointConical(const SkPoint& start, + SkScalar startRadius, + const SkPoint& end, + SkScalar endRadius, + const SkColor4f colors[], + sk_sp<SkColorSpace> colorSpace, + const SkScalar pos[], + int colorCount, + SkShader::TileMode mode, + uint32_t flags, + const SkMatrix* localMatrix) { + if (startRadius < 0 || endRadius < 0) { + return nullptr; + } + if (!valid_grad(colors, pos, colorCount, mode)) { + return nullptr; + } + if (startRadius == endRadius) { + if (start == end || startRadius == 0) { + return SkShader::MakeEmptyShader(); + } + } + if (localMatrix && !localMatrix->invert(nullptr)) { + return nullptr; + } + EXPAND_1_COLOR(colorCount); + + ColorStopOptimizer opt(colors, pos, colorCount, mode); + + bool flipGradient = startRadius > endRadius; + + SkGradientShaderBase::Descriptor desc; + + if (!flipGradient) { + desc_init(&desc, opt.fColors, std::move(colorSpace), opt.fPos, opt.fCount, mode, flags, + localMatrix); + return sk_make_sp<SkTwoPointConicalGradient>(start, startRadius, end, endRadius, + flipGradient, desc); + } else { + SkAutoSTArray<8, SkColor4f> colorsNew(opt.fCount); + SkAutoSTArray<8, SkScalar> posNew(opt.fCount); + for (int i = 0; i < opt.fCount; ++i) { + colorsNew[i] = opt.fColors[opt.fCount - i - 1]; + } + + if (pos) { + for (int i = 0; i < opt.fCount; ++i) { + posNew[i] = 1 - opt.fPos[opt.fCount - i - 1]; + } + desc_init(&desc, colorsNew.get(), std::move(colorSpace), posNew.get(), opt.fCount, mode, + flags, localMatrix); + } else { + desc_init(&desc, colorsNew.get(), std::move(colorSpace), nullptr, opt.fCount, mode, + flags, localMatrix); + } + + return sk_make_sp<SkTwoPointConicalGradient>(end, endRadius, start, startRadius, + flipGradient, desc); + } +} + +sk_sp<SkShader> SkGradientShader::MakeSweep(SkScalar cx, SkScalar cy, + const SkColor colors[], + const SkScalar pos[], + int colorCount, + uint32_t flags, + const SkMatrix* localMatrix) { + ColorConverter converter(colors, colorCount); + return MakeSweep(cx, cy, converter.fColors4f.begin(), nullptr, pos, colorCount, flags, + localMatrix); +} + +sk_sp<SkShader> SkGradientShader::MakeSweep(SkScalar cx, SkScalar cy, + const SkColor4f colors[], + sk_sp<SkColorSpace> colorSpace, + const SkScalar pos[], + int colorCount, + uint32_t flags, + const SkMatrix* localMatrix) { + if (!valid_grad(colors, pos, colorCount, SkShader::kClamp_TileMode)) { + return nullptr; + } + if (1 == colorCount) { + return SkShader::MakeColorShader(colors[0], std::move(colorSpace)); + } + if (localMatrix && !localMatrix->invert(nullptr)) { + return nullptr; + } + + auto mode = SkShader::kClamp_TileMode; + + ColorStopOptimizer opt(colors, pos, colorCount, mode); + + SkGradientShaderBase::Descriptor desc; + desc_init(&desc, opt.fColors, std::move(colorSpace), opt.fPos, opt.fCount, mode, flags, + localMatrix); + return sk_make_sp<SkSweepGradient>(cx, cy, desc); +} + +SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkGradientShader) + SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkLinearGradient) + SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkRadialGradient) + SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkSweepGradient) + SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkTwoPointConicalGradient) +SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END + +/////////////////////////////////////////////////////////////////////////////// + +#if SK_SUPPORT_GPU + +#include "GrContext.h" +#include "GrShaderCaps.h" +#include "GrTextureStripAtlas.h" +#include "gl/GrGLContext.h" +#include "glsl/GrGLSLColorSpaceXformHelper.h" +#include "glsl/GrGLSLFragmentShaderBuilder.h" +#include "glsl/GrGLSLProgramDataManager.h" +#include "glsl/GrGLSLUniformHandler.h" +#include "SkGr.h" + +static inline bool close_to_one_half(const SkFixed& val) { + return SkScalarNearlyEqual(SkFixedToScalar(val), SK_ScalarHalf); +} + +static inline int color_type_to_color_count(GrGradientEffect::ColorType colorType) { + switch (colorType) { +#if GR_GL_USE_ACCURATE_HARD_STOP_GRADIENTS + case GrGradientEffect::kSingleHardStop_ColorType: + return 4; + case GrGradientEffect::kHardStopLeftEdged_ColorType: + case GrGradientEffect::kHardStopRightEdged_ColorType: + return 3; +#endif + case GrGradientEffect::kTwo_ColorType: + return 2; + case GrGradientEffect::kThree_ColorType: + return 3; + case GrGradientEffect::kTexture_ColorType: + return 0; + } + + SkDEBUGFAIL("Unhandled ColorType in color_type_to_color_count()"); + return -1; +} + +GrGradientEffect::ColorType GrGradientEffect::determineColorType( + const SkGradientShaderBase& shader) { +#if GR_GL_USE_ACCURATE_HARD_STOP_GRADIENTS + if (shader.fOrigPos) { + if (4 == shader.fColorCount) { + if (SkScalarNearlyEqual(shader.fOrigPos[0], 0.0f) && + SkScalarNearlyEqual(shader.fOrigPos[1], shader.fOrigPos[2]) && + SkScalarNearlyEqual(shader.fOrigPos[3], 1.0f)) { + + return kSingleHardStop_ColorType; + } + } else if (3 == shader.fColorCount) { + if (SkScalarNearlyEqual(shader.fOrigPos[0], 0.0f) && + SkScalarNearlyEqual(shader.fOrigPos[1], 0.0f) && + SkScalarNearlyEqual(shader.fOrigPos[2], 1.0f)) { + + return kHardStopLeftEdged_ColorType; + } else if (SkScalarNearlyEqual(shader.fOrigPos[0], 0.0f) && + SkScalarNearlyEqual(shader.fOrigPos[1], 1.0f) && + SkScalarNearlyEqual(shader.fOrigPos[2], 1.0f)) { + + return kHardStopRightEdged_ColorType; + } + } + } +#endif + + if (SkShader::kClamp_TileMode == shader.getTileMode()) { + if (2 == shader.fColorCount) { + return kTwo_ColorType; + } else if (3 == shader.fColorCount && + close_to_one_half(shader.getRecs()[1].fPos)) { + return kThree_ColorType; + } + } + + return kTexture_ColorType; +} + +void GrGradientEffect::GLSLProcessor::emitUniforms(GrGLSLUniformHandler* uniformHandler, + const GrGradientEffect& ge) { + if (int colorCount = color_type_to_color_count(ge.getColorType())) { + fColorsUni = uniformHandler->addUniformArray(kFragment_GrShaderFlag, + kVec4f_GrSLType, + kDefault_GrSLPrecision, + "Colors", + colorCount); + if (ge.fColorType == kSingleHardStop_ColorType) { + fHardStopT = uniformHandler->addUniform(kFragment_GrShaderFlag, kFloat_GrSLType, + kDefault_GrSLPrecision, "HardStopT"); + } + } else { + fFSYUni = uniformHandler->addUniform(kFragment_GrShaderFlag, + kFloat_GrSLType, kDefault_GrSLPrecision, + "GradientYCoordFS"); + } +} + +static inline void set_after_interp_color_uni_array( + const GrGLSLProgramDataManager& pdman, + const GrGLSLProgramDataManager::UniformHandle uni, + const SkTDArray<SkColor4f>& colors, + const GrColorSpaceXform* colorSpaceXform) { + int count = colors.count(); + if (colorSpaceXform) { + constexpr int kSmallCount = 10; + SkAutoSTArray<4 * kSmallCount, float> vals(4 * count); + + for (int i = 0; i < count; i++) { + colorSpaceXform->srcToDst().mapScalars(colors[i].vec(), &vals[4 * i]); + } + + pdman.set4fv(uni, count, vals.get()); + } else { + pdman.set4fv(uni, count, (float*)&colors[0]); + } +} + +static inline void set_before_interp_color_uni_array( + const GrGLSLProgramDataManager& pdman, + const GrGLSLProgramDataManager::UniformHandle uni, + const SkTDArray<SkColor4f>& colors, + const GrColorSpaceXform* colorSpaceXform) { + int count = colors.count(); + constexpr int kSmallCount = 10; + SkAutoSTArray<4 * kSmallCount, float> vals(4 * count); + + for (int i = 0; i < count; i++) { + float a = colors[i].fA; + vals[4 * i + 0] = colors[i].fR * a; + vals[4 * i + 1] = colors[i].fG * a; + vals[4 * i + 2] = colors[i].fB * a; + vals[4 * i + 3] = a; + } + + if (colorSpaceXform) { + for (int i = 0; i < count; i++) { + colorSpaceXform->srcToDst().mapScalars(&vals[4 * i]); + } + } + + pdman.set4fv(uni, count, vals.get()); +} + +static inline void set_after_interp_color_uni_array(const GrGLSLProgramDataManager& pdman, + const GrGLSLProgramDataManager::UniformHandle uni, + const SkTDArray<SkColor>& colors) { + int count = colors.count(); + constexpr int kSmallCount = 10; + + SkAutoSTArray<4*kSmallCount, float> vals(4*count); + + for (int i = 0; i < colors.count(); i++) { + // RGBA + vals[4*i + 0] = SkColorGetR(colors[i]) / 255.f; + vals[4*i + 1] = SkColorGetG(colors[i]) / 255.f; + vals[4*i + 2] = SkColorGetB(colors[i]) / 255.f; + vals[4*i + 3] = SkColorGetA(colors[i]) / 255.f; + } + + pdman.set4fv(uni, colors.count(), vals.get()); +} + +static inline void set_before_interp_color_uni_array(const GrGLSLProgramDataManager& pdman, + const GrGLSLProgramDataManager::UniformHandle uni, + const SkTDArray<SkColor>& colors) { + int count = colors.count(); + constexpr int kSmallCount = 10; + + SkAutoSTArray<4*kSmallCount, float> vals(4*count); + + for (int i = 0; i < count; i++) { + float a = SkColorGetA(colors[i]) / 255.f; + float aDiv255 = a / 255.f; + + // RGBA + vals[4*i + 0] = SkColorGetR(colors[i]) * aDiv255; + vals[4*i + 1] = SkColorGetG(colors[i]) * aDiv255; + vals[4*i + 2] = SkColorGetB(colors[i]) * aDiv255; + vals[4*i + 3] = a; + } + + pdman.set4fv(uni, count, vals.get()); +} + +void GrGradientEffect::GLSLProcessor::onSetData(const GrGLSLProgramDataManager& pdman, + const GrFragmentProcessor& processor) { + const GrGradientEffect& e = processor.cast<GrGradientEffect>(); + + switch (e.getColorType()) { +#if GR_GL_USE_ACCURATE_HARD_STOP_GRADIENTS + case GrGradientEffect::kSingleHardStop_ColorType: + pdman.set1f(fHardStopT, e.fPositions[1]); + // fall through + case GrGradientEffect::kHardStopLeftEdged_ColorType: + case GrGradientEffect::kHardStopRightEdged_ColorType: +#endif + case GrGradientEffect::kTwo_ColorType: + case GrGradientEffect::kThree_ColorType: { + if (e.fColors4f.count() > 0) { + // Gamma-correct / color-space aware + if (GrGradientEffect::kBeforeInterp_PremulType == e.getPremulType()) { + set_before_interp_color_uni_array(pdman, fColorsUni, e.fColors4f, + e.fColorSpaceXform.get()); + } else { + set_after_interp_color_uni_array(pdman, fColorsUni, e.fColors4f, + e.fColorSpaceXform.get()); + } + } else { + // Legacy mode. Would be nice if we had converted the 8-bit colors to float earlier + if (GrGradientEffect::kBeforeInterp_PremulType == e.getPremulType()) { + set_before_interp_color_uni_array(pdman, fColorsUni, e.fColors); + } else { + set_after_interp_color_uni_array(pdman, fColorsUni, e.fColors); + } + } + + break; + } + + case GrGradientEffect::kTexture_ColorType: { + SkScalar yCoord = e.getYCoord(); + if (yCoord != fCachedYCoord) { + pdman.set1f(fFSYUni, yCoord); + fCachedYCoord = yCoord; + } + if (SkToBool(e.fColorSpaceXform)) { + fColorSpaceHelper.setData(pdman, e.fColorSpaceXform.get()); + } + break; + } + } +} + +uint32_t GrGradientEffect::GLSLProcessor::GenBaseGradientKey(const GrProcessor& processor) { + const GrGradientEffect& e = processor.cast<GrGradientEffect>(); + + uint32_t key = 0; + + if (GrGradientEffect::kBeforeInterp_PremulType == e.getPremulType()) { + key |= kPremulBeforeInterpKey; + } + + if (GrGradientEffect::kTwo_ColorType == e.getColorType()) { + key |= kTwoColorKey; + } else if (GrGradientEffect::kThree_ColorType == e.getColorType()) { + key |= kThreeColorKey; + } +#if GR_GL_USE_ACCURATE_HARD_STOP_GRADIENTS + else if (GrGradientEffect::kSingleHardStop_ColorType == e.getColorType()) { + key |= kHardStopCenteredKey; + } else if (GrGradientEffect::kHardStopLeftEdged_ColorType == e.getColorType()) { + key |= kHardStopZeroZeroOneKey; + } else if (GrGradientEffect::kHardStopRightEdged_ColorType == e.getColorType()) { + key |= kHardStopZeroOneOneKey; + } + + if (SkShader::TileMode::kClamp_TileMode == e.fTileMode) { + key |= kClampTileMode; + } else if (SkShader::TileMode::kRepeat_TileMode == e.fTileMode) { + key |= kRepeatTileMode; + } else { + key |= kMirrorTileMode; + } +#endif + + key |= GrColorSpaceXform::XformKey(e.fColorSpaceXform.get()) << kReservedBits; + + return key; +} + +void GrGradientEffect::GLSLProcessor::emitColor(GrGLSLFPFragmentBuilder* fragBuilder, + GrGLSLUniformHandler* uniformHandler, + const GrShaderCaps* shaderCaps, + const GrGradientEffect& ge, + const char* gradientTValue, + const char* outputColor, + const char* inputColor, + const TextureSamplers& texSamplers) { + switch (ge.getColorType()) { +#if GR_GL_USE_ACCURATE_HARD_STOP_GRADIENTS + case kSingleHardStop_ColorType: { + const char* t = gradientTValue; + const char* colors = uniformHandler->getUniformCStr(fColorsUni); + const char* stopT = uniformHandler->getUniformCStr(fHardStopT); + + fragBuilder->codeAppendf("float clamp_t = clamp(%s, 0.0, 1.0);", t); + + // Account for tile mode + if (SkShader::kRepeat_TileMode == ge.fTileMode) { + fragBuilder->codeAppendf("clamp_t = fract(%s);", t); + } else if (SkShader::kMirror_TileMode == ge.fTileMode) { + fragBuilder->codeAppendf("if (%s < 0.0 || %s > 1.0) {", t, t); + fragBuilder->codeAppendf(" if (mod(floor(%s), 2.0) == 0.0) {", t); + fragBuilder->codeAppendf(" clamp_t = fract(%s);", t); + fragBuilder->codeAppendf(" } else {"); + fragBuilder->codeAppendf(" clamp_t = 1.0 - fract(%s);", t); + fragBuilder->codeAppendf(" }"); + fragBuilder->codeAppendf("}"); + } + + // Calculate color + fragBuilder->codeAppend ("vec4 start, end;"); + fragBuilder->codeAppend ("float relative_t;"); + fragBuilder->codeAppendf("if (clamp_t < %s) {", stopT); + fragBuilder->codeAppendf(" start = %s[0];", colors); + fragBuilder->codeAppendf(" end = %s[1];", colors); + fragBuilder->codeAppendf(" relative_t = clamp_t / %s;", stopT); + fragBuilder->codeAppend ("} else {"); + fragBuilder->codeAppendf(" start = %s[2];", colors); + fragBuilder->codeAppendf(" end = %s[3];", colors); + fragBuilder->codeAppendf(" relative_t = (clamp_t - %s) / (1 - %s);", stopT, stopT); + fragBuilder->codeAppend ("}"); + fragBuilder->codeAppend ("vec4 colorTemp = mix(start, end, relative_t);"); + + if (GrGradientEffect::kAfterInterp_PremulType == ge.getPremulType()) { + fragBuilder->codeAppend("colorTemp.rgb *= colorTemp.a;"); + } + if (ge.fColorSpaceXform) { + fragBuilder->codeAppend("colorTemp.rgb = clamp(colorTemp.rgb, 0, colorTemp.a);"); + } + fragBuilder->codeAppendf("%s = %s * colorTemp;", outputColor, inputColor); + + break; + } + + case kHardStopLeftEdged_ColorType: { + const char* t = gradientTValue; + const char* colors = uniformHandler->getUniformCStr(fColorsUni); + + fragBuilder->codeAppendf("float clamp_t = clamp(%s, 0.0, 1.0);", t); + + // Account for tile mode + if (SkShader::kRepeat_TileMode == ge.fTileMode) { + fragBuilder->codeAppendf("clamp_t = fract(%s);", t); + } else if (SkShader::kMirror_TileMode == ge.fTileMode) { + fragBuilder->codeAppendf("if (%s < 0.0 || %s > 1.0) {", t, t); + fragBuilder->codeAppendf(" if (mod(floor(%s), 2.0) == 0.0) {", t); + fragBuilder->codeAppendf(" clamp_t = fract(%s);", t); + fragBuilder->codeAppendf(" } else {"); + fragBuilder->codeAppendf(" clamp_t = 1.0 - fract(%s);", t); + fragBuilder->codeAppendf(" }"); + fragBuilder->codeAppendf("}"); + } + + fragBuilder->codeAppendf("vec4 colorTemp = mix(%s[1], %s[2], clamp_t);", colors, + colors); + if (SkShader::kClamp_TileMode == ge.fTileMode) { + fragBuilder->codeAppendf("if (%s < 0.0) {", t); + fragBuilder->codeAppendf(" colorTemp = %s[0];", colors); + fragBuilder->codeAppendf("}"); + } + + if (GrGradientEffect::kAfterInterp_PremulType == ge.getPremulType()) { + fragBuilder->codeAppend("colorTemp.rgb *= colorTemp.a;"); + } + if (ge.fColorSpaceXform) { + fragBuilder->codeAppend("colorTemp.rgb = clamp(colorTemp.rgb, 0, colorTemp.a);"); + } + fragBuilder->codeAppendf("%s = %s * colorTemp;", outputColor, inputColor); + + break; + } + + case kHardStopRightEdged_ColorType: { + const char* t = gradientTValue; + const char* colors = uniformHandler->getUniformCStr(fColorsUni); + + fragBuilder->codeAppendf("float clamp_t = clamp(%s, 0.0, 1.0);", t); + + // Account for tile mode + if (SkShader::kRepeat_TileMode == ge.fTileMode) { + fragBuilder->codeAppendf("clamp_t = fract(%s);", t); + } else if (SkShader::kMirror_TileMode == ge.fTileMode) { + fragBuilder->codeAppendf("if (%s < 0.0 || %s > 1.0) {", t, t); + fragBuilder->codeAppendf(" if (mod(floor(%s), 2.0) == 0.0) {", t); + fragBuilder->codeAppendf(" clamp_t = fract(%s);", t); + fragBuilder->codeAppendf(" } else {"); + fragBuilder->codeAppendf(" clamp_t = 1.0 - fract(%s);", t); + fragBuilder->codeAppendf(" }"); + fragBuilder->codeAppendf("}"); + } + + fragBuilder->codeAppendf("vec4 colorTemp = mix(%s[0], %s[1], clamp_t);", colors, + colors); + if (SkShader::kClamp_TileMode == ge.fTileMode) { + fragBuilder->codeAppendf("if (%s > 1.0) {", t); + fragBuilder->codeAppendf(" colorTemp = %s[2];", colors); + fragBuilder->codeAppendf("}"); + } + + if (GrGradientEffect::kAfterInterp_PremulType == ge.getPremulType()) { + fragBuilder->codeAppend("colorTemp.rgb *= colorTemp.a;"); + } + if (ge.fColorSpaceXform) { + fragBuilder->codeAppend("colorTemp.rgb = clamp(colorTemp.rgb, 0, colorTemp.a);"); + } + fragBuilder->codeAppendf("%s = %s * colorTemp;", outputColor, inputColor); + + break; + } +#endif + + case kTwo_ColorType: { + const char* t = gradientTValue; + const char* colors = uniformHandler->getUniformCStr(fColorsUni); + + fragBuilder->codeAppendf("vec4 colorTemp = mix(%s[0], %s[1], clamp(%s, 0.0, 1.0));", + colors, colors, t); + + // We could skip this step if both colors are known to be opaque. Two + // considerations: + // The gradient SkShader reporting opaque is more restrictive than necessary in the two + // pt case. Make sure the key reflects this optimization (and note that it can use the + // same shader as thekBeforeIterp case). This same optimization applies to the 3 color + // case below. + if (GrGradientEffect::kAfterInterp_PremulType == ge.getPremulType()) { + fragBuilder->codeAppend("colorTemp.rgb *= colorTemp.a;"); + } + if (ge.fColorSpaceXform) { + fragBuilder->codeAppend("colorTemp.rgb = clamp(colorTemp.rgb, 0, colorTemp.a);"); + } + + fragBuilder->codeAppendf("%s = %s * colorTemp;", outputColor, inputColor); + + break; + } + + case kThree_ColorType: { + const char* t = gradientTValue; + const char* colors = uniformHandler->getUniformCStr(fColorsUni); + + fragBuilder->codeAppendf("float oneMinus2t = 1.0 - (2.0 * %s);", t); + fragBuilder->codeAppendf("vec4 colorTemp = clamp(oneMinus2t, 0.0, 1.0) * %s[0];", + colors); + if (!shaderCaps->canUseMinAndAbsTogether()) { + // The Tegra3 compiler will sometimes never return if we have + // min(abs(oneMinus2t), 1.0), or do the abs first in a separate expression. + fragBuilder->codeAppendf("float minAbs = abs(oneMinus2t);"); + fragBuilder->codeAppendf("minAbs = minAbs > 1.0 ? 1.0 : minAbs;"); + fragBuilder->codeAppendf("colorTemp += (1.0 - minAbs) * %s[1];", colors); + } else { + fragBuilder->codeAppendf("colorTemp += (1.0 - min(abs(oneMinus2t), 1.0)) * %s[1];", + colors); + } + fragBuilder->codeAppendf("colorTemp += clamp(-oneMinus2t, 0.0, 1.0) * %s[2];", colors); + + if (GrGradientEffect::kAfterInterp_PremulType == ge.getPremulType()) { + fragBuilder->codeAppend("colorTemp.rgb *= colorTemp.a;"); + } + if (ge.fColorSpaceXform) { + fragBuilder->codeAppend("colorTemp.rgb = clamp(colorTemp.rgb, 0, colorTemp.a);"); + } + + fragBuilder->codeAppendf("%s = %s * colorTemp;", outputColor, inputColor); + + break; + } + + case kTexture_ColorType: { + fColorSpaceHelper.emitCode(uniformHandler, ge.fColorSpaceXform.get()); + + 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", + kVec2f_GrSLType, &fColorSpaceHelper); + fragBuilder->codeAppend(";"); + + break; + } + } +} + +///////////////////////////////////////////////////////////////////// + +inline GrFragmentProcessor::OptimizationFlags GrGradientEffect::OptFlags(bool isOpaque) { + return isOpaque + ? kPreservesOpaqueInput_OptimizationFlag | + kCompatibleWithCoverageAsAlpha_OptimizationFlag + : kCompatibleWithCoverageAsAlpha_OptimizationFlag; +} + +GrGradientEffect::GrGradientEffect(const CreateArgs& args, bool isOpaque) + : INHERITED(OptFlags(isOpaque)) { + const SkGradientShaderBase& shader(*args.fShader); + + 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); + } else { + fColors = SkTDArray<SkColor>(shader.fOrigColors, shader.fColorCount); + } + +#if GR_GL_USE_ACCURATE_HARD_STOP_GRADIENTS + if (shader.fOrigPos) { + fPositions = SkTDArray<SkScalar>(shader.fOrigPos, shader.fColorCount); + } +#endif + } + +#if GR_GL_USE_ACCURATE_HARD_STOP_GRADIENTS + fTileMode = args.fTileMode; +#endif + + switch (fColorType) { + // The two and three color specializations do not currently support tiling. + case kTwo_ColorType: + case kThree_ColorType: +#if GR_GL_USE_ACCURATE_HARD_STOP_GRADIENTS + case kHardStopLeftEdged_ColorType: + case kHardStopRightEdged_ColorType: + case kSingleHardStop_ColorType: +#endif + fRow = -1; + + if (SkGradientShader::kInterpolateColorsInPremul_Flag & shader.getGradFlags()) { + fPremulType = kBeforeInterp_PremulType; + } else { + fPremulType = kAfterInterp_PremulType; + } + + fCoordTransform.reset(*args.fMatrix); + + break; + case kTexture_ColorType: + // doesn't matter how this is set, just be consistent because it is part of the + // 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 can happen, but only if someone explicitly creates an unsupported + // (eg sRGB) surface. Just fall back to legacy behavior. + } + } + + SkBitmap bitmap; + shader.getGradientTableBitmap(&bitmap, bitmapType); + SkASSERT(1 == bitmap.height() && SkIsPow2(bitmap.width())); + + + GrTextureStripAtlas::Desc desc; + desc.fWidth = bitmap.width(); + desc.fHeight = 32; + desc.fRowHeight = bitmap.height(); + desc.fContext = args.fContext; + desc.fConfig = SkImageInfo2GrPixelConfig(bitmap.info(), *args.fContext->caps()); + fAtlas = GrTextureStripAtlas::GetAtlas(desc); + SkASSERT(fAtlas); + + // We always filter the gradient table. Each table is one row of a texture, always + // y-clamp. + GrSamplerParams params; + params.setFilterMode(GrSamplerParams::kBilerp_FilterMode); + params.setTileModeX(args.fTileMode); + + fRow = fAtlas->lockRow(bitmap); + if (-1 != fRow) { + fYCoord = fAtlas->getYOffset(fRow)+SK_ScalarHalf*fAtlas->getNormalizedTexelHeight(); + // This is 1/2 places where auto-normalization is disabled + fCoordTransform.reset(args.fContext->resourceProvider(), *args.fMatrix, + fAtlas->asTextureProxyRef().get(), false); + fTextureSampler.reset(args.fContext->resourceProvider(), + fAtlas->asTextureProxyRef(), params); + } else { + // In this instance we know the params are: + // clampY, bilerp + // and the proxy is: + // exact fit, power of two in both dimensions + // Only the x-tileMode is unknown. However, given all the other knowns we know + // that GrMakeCachedBitmapProxy is sufficient (i.e., it won't need to be + // extracted to a subset or mipmapped). + sk_sp<GrTextureProxy> proxy = GrMakeCachedBitmapProxy( + args.fContext->resourceProvider(), + bitmap); + if (!proxy) { + return; + } + // This is 2/2 places where auto-normalization is disabled + fCoordTransform.reset(args.fContext->resourceProvider(), *args.fMatrix, + proxy.get(), false); + fTextureSampler.reset(args.fContext->resourceProvider(), + std::move(proxy), params); + fYCoord = SK_ScalarHalf; + } + + this->addTextureSampler(&fTextureSampler); + + break; + } + + this->addCoordTransform(&fCoordTransform); +} + +GrGradientEffect::~GrGradientEffect() { + if (this->useAtlas()) { + fAtlas->unlockRow(fRow); + } +} + +bool GrGradientEffect::onIsEqual(const GrFragmentProcessor& processor) const { + const GrGradientEffect& ge = processor.cast<GrGradientEffect>(); + + if (this->fColorType != ge.getColorType()) { + return false; + } + SkASSERT(this->useAtlas() == ge.useAtlas()); + if (kTexture_ColorType == fColorType) { + if (fYCoord != ge.getYCoord()) { + return false; + } + } else { + if (kSingleHardStop_ColorType == fColorType) { + if (!SkScalarNearlyEqual(ge.fPositions[1], fPositions[1])) { + return false; + } + } + if (this->getPremulType() != ge.getPremulType() || + this->fColors.count() != ge.fColors.count() || + this->fColors4f.count() != ge.fColors4f.count()) { + return false; + } + + for (int i = 0; i < this->fColors.count(); i++) { + if (*this->getColors(i) != *ge.getColors(i)) { + return false; + } + } + for (int i = 0; i < this->fColors4f.count(); i++) { + if (*this->getColors4f(i) != *ge.getColors4f(i)) { + return false; + } + } + } + return GrColorSpaceXform::Equals(this->fColorSpaceXform.get(), ge.fColorSpaceXform.get()); +} + +#if GR_TEST_UTILS +GrGradientEffect::RandomGradientParams::RandomGradientParams(SkRandom* random) { + // Set color count to min of 2 so that we don't trigger the const color optimization and make + // a non-gradient processor. + fColorCount = random->nextRangeU(2, kMaxRandomGradientColors); + fUseColors4f = random->nextBool(); + + // if one color, omit stops, otherwise randomly decide whether or not to + if (fColorCount == 1 || (fColorCount >= 2 && random->nextBool())) { + fStops = nullptr; + } else { + fStops = fStopStorage; + } + + // if using SkColor4f, attach a random (possibly null) color space (with linear gamma) + if (fUseColors4f) { + fColorSpace = GrTest::TestColorSpace(random); + if (fColorSpace) { + SkASSERT(SkColorSpace_Base::Type::kXYZ == as_CSB(fColorSpace)->type()); + fColorSpace = static_cast<SkColorSpace_XYZ*>(fColorSpace.get())->makeLinearGamma(); + } + } + + SkScalar stop = 0.f; + for (int i = 0; i < fColorCount; ++i) { + if (fUseColors4f) { + fColors4f[i].fR = random->nextUScalar1(); + fColors4f[i].fG = random->nextUScalar1(); + fColors4f[i].fB = random->nextUScalar1(); + fColors4f[i].fA = random->nextUScalar1(); + } else { + fColors[i] = random->nextU(); + } + if (fStops) { + fStops[i] = stop; + stop = i < fColorCount - 1 ? stop + random->nextUScalar1() * (1.f - stop) : 1.f; + } + } + fTileMode = static_cast<SkShader::TileMode>(random->nextULessThan(SkShader::kTileModeCount)); +} +#endif + +#endif diff --git a/src/shaders/gradients/SkGradientShaderPriv.h b/src/shaders/gradients/SkGradientShaderPriv.h new file mode 100644 index 0000000000..7a66edaffc --- /dev/null +++ b/src/shaders/gradients/SkGradientShaderPriv.h @@ -0,0 +1,540 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkGradientShaderPriv_DEFINED +#define SkGradientShaderPriv_DEFINED + +#include "SkGradientBitmapCache.h" +#include "SkGradientShader.h" + +#include "SkArenaAlloc.h" +#include "SkAutoMalloc.h" +#include "SkClampRange.h" +#include "SkColorPriv.h" +#include "SkColorSpace.h" +#include "SkOnce.h" +#include "SkPM4fPriv.h" +#include "SkRasterPipeline.h" +#include "SkReadBuffer.h" +#include "SkShaderBase.h" +#include "SkUtils.h" +#include "SkWriteBuffer.h" + +#if SK_SUPPORT_GPU + #define GR_GL_USE_ACCURATE_HARD_STOP_GRADIENTS 1 +#endif + +static inline void sk_memset32_dither(uint32_t dst[], uint32_t v0, uint32_t v1, + int count) { + if (count > 0) { + if (v0 == v1) { + sk_memset32(dst, v0, count); + } else { + int pairs = count >> 1; + for (int i = 0; i < pairs; i++) { + *dst++ = v0; + *dst++ = v1; + } + if (count & 1) { + *dst = v0; + } + } + } +} + +// Clamp + +static inline SkFixed clamp_tileproc(SkFixed x) { + return SkClampMax(x, 0xFFFF); +} + +// Repeat + +static inline SkFixed repeat_tileproc(SkFixed x) { + return x & 0xFFFF; +} + +// Mirror + +static inline SkFixed mirror_tileproc(SkFixed x) { + int s = SkLeftShift(x, 15) >> 31; + return (x ^ s) & 0xFFFF; +} + +/////////////////////////////////////////////////////////////////////////////// + +typedef SkFixed (*TileProc)(SkFixed); + +/////////////////////////////////////////////////////////////////////////////// + +static const TileProc gTileProcs[] = { + clamp_tileproc, + repeat_tileproc, + mirror_tileproc +}; + +/////////////////////////////////////////////////////////////////////////////// + +class SkGradientShaderBase : public SkShaderBase { +public: + struct Descriptor { + Descriptor() { + sk_bzero(this, sizeof(*this)); + fTileMode = SkShader::kClamp_TileMode; + } + + const SkMatrix* fLocalMatrix; + const SkColor4f* fColors; + sk_sp<SkColorSpace> fColorSpace; + const SkScalar* fPos; + int fCount; + SkShader::TileMode fTileMode; + uint32_t fGradFlags; + + void flatten(SkWriteBuffer&) const; + }; + + class DescriptorScope : public Descriptor { + public: + DescriptorScope() {} + + bool unflatten(SkReadBuffer&); + + // fColors and fPos always point into local memory, so they can be safely mutated + // + SkColor4f* mutableColors() { return const_cast<SkColor4f*>(fColors); } + SkScalar* mutablePos() { return const_cast<SkScalar*>(fPos); } + + private: + enum { + kStorageCount = 16 + }; + SkColor4f fColorStorage[kStorageCount]; + SkScalar fPosStorage[kStorageCount]; + SkMatrix fLocalMatrixStorage; + SkAutoMalloc fDynamicStorage; + }; + + SkGradientShaderBase(const Descriptor& desc, const SkMatrix& ptsToUnit); + ~SkGradientShaderBase() override; + + // The cache is initialized on-demand when getCache32 is called. + class GradientShaderCache : public SkRefCnt { + public: + GradientShaderCache(U8CPU alpha, bool dither, const SkGradientShaderBase& shader); + ~GradientShaderCache(); + + const SkPMColor* getCache32(); + + SkPixelRef* getCache32PixelRef() const { return fCache32PixelRef.get(); } + + unsigned getAlpha() const { return fCacheAlpha; } + bool getDither() const { return fCacheDither; } + + private: + // Working pointer. If it's nullptr, we need to recompute the cache values. + SkPMColor* fCache32; + + sk_sp<SkPixelRef> fCache32PixelRef; + const unsigned fCacheAlpha; // The alpha value we used when we computed the cache. + // Larger than 8bits so we can store uninitialized + // value. + const bool fCacheDither; // The dither flag used when we computed the cache. + + const SkGradientShaderBase& fShader; + + // Make sure we only initialize the cache once. + SkOnce fCache32InitOnce; + + static void initCache32(GradientShaderCache* cache); + + static void Build32bitCache(SkPMColor[], SkColor c0, SkColor c1, int count, + U8CPU alpha, uint32_t gradFlags, bool dither); + }; + + class GradientShaderBaseContext : public Context { + public: + GradientShaderBaseContext(const SkGradientShaderBase& shader, const ContextRec&); + + uint32_t getFlags() const override { return fFlags; } + + bool isValid() const; + + protected: + SkMatrix fDstToIndex; + SkMatrix::MapXYProc fDstToIndexProc; + uint8_t fDstToIndexClass; + uint8_t fFlags; + bool fDither; + + sk_sp<GradientShaderCache> fCache; + + private: + typedef Context INHERITED; + }; + + bool isOpaque() const override; + + 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 + /// it, use a larger cache. + kCache32Bits = 8, + kCache32Count = (1 << kCache32Bits), + kCache32Shift = 16 - kCache32Bits, + kSqrt32Shift = 8 - kCache32Bits, + + /// This value is used to *read* the dither cache; it may be 0 + /// if dithering is disabled. + kDitherStride32 = kCache32Count, + }; + + uint32_t getGradFlags() const { return fGradFlags; } + +protected: + struct Rec { + SkFixed fPos; // 0...1 + uint32_t fScale; // (1 << 24) / range + }; + + class GradientShaderBase4fContext; + + SkGradientShaderBase(SkReadBuffer& ); + void flatten(SkWriteBuffer&) const override; + SK_TO_STRING_OVERRIDE() + + void commonAsAGradient(GradientInfo*, bool flipGrad = false) const; + + 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 + * It will then flip all the color and rec information and return in their respective Dst + * pointers. It is assumed that space has already been allocated for the Dst pointers. + * The rec src and dst are only assumed to be valid if count > 2 + */ + static void FlipGradientColors(SkColor* colorDst, Rec* recDst, + SkColor* colorSrc, Rec* recSrc, + int count); + + bool onAppendStages(SkRasterPipeline* pipeline, SkColorSpace* dstCS, SkArenaAlloc* alloc, + const SkMatrix& ctm, const SkPaint& paint, + const SkMatrix* localM) const override; + + virtual bool adjustMatrixAndAppendStages(SkArenaAlloc* alloc, + SkMatrix* matrix, + SkRasterPipeline* p) const { return false; } + + template <typename T, typename... Args> + static Context* CheckedMakeContext(SkArenaAlloc* alloc, Args&&... args) { + auto* ctx = alloc->make<T>(std::forward<Args>(args)...); + if (!ctx->isValid()) { + return nullptr; + } + return ctx; + } + + const SkMatrix fPtsToUnit; + TileMode fTileMode; + TileProc fTileProc; + uint8_t fGradFlags; + Rec* fRecs; + +private: + enum { + kColorStorageCount = 4, // more than this many colors, and we'll use sk_malloc for the space + + kStorageSize = kColorStorageCount * + (sizeof(SkColor) + sizeof(SkScalar) + sizeof(Rec) + sizeof(SkColor4f)) + }; + SkColor fStorage[(kStorageSize + 3) >> 2]; +public: + SkColor* fOrigColors; // original colors, before modulation by paint in context. + SkColor4f* fOrigColors4f; // original colors, as linear floats + SkScalar* fOrigPos; // original positions + int fColorCount; + sk_sp<SkColorSpace> fColorSpace; // color space of gradient stops + + bool colorsAreOpaque() const { return fColorsAreOpaque; } + + TileMode getTileMode() const { return fTileMode; } + Rec* getRecs() const { return fRecs; } + +private: + bool fColorsAreOpaque; + + sk_sp<GradientShaderCache> refCache(U8CPU alpha, bool dither) const; + mutable SkMutex fCacheMutex; + mutable sk_sp<GradientShaderCache> fCache; + + void initCommon(); + + typedef SkShaderBase INHERITED; +}; + + +static inline int init_dither_toggle(int x, int y) { + x &= 1; + y = (y & 1) << 1; + return (x | y) * SkGradientShaderBase::kDitherStride32; +} + +static inline int next_dither_toggle(int toggle) { + return toggle ^ SkGradientShaderBase::kDitherStride32; +} + +/////////////////////////////////////////////////////////////////////////////// + +#if SK_SUPPORT_GPU + +#include "GrColorSpaceXform.h" +#include "GrCoordTransform.h" +#include "GrFragmentProcessor.h" +#include "glsl/GrGLSLColorSpaceXformHelper.h" +#include "glsl/GrGLSLFragmentProcessor.h" +#include "glsl/GrGLSLProgramDataManager.h" + +class GrInvariantOutput; + +/* + * The interpretation of the texture matrix depends on the sample mode. The + * texture matrix is applied both when the texture coordinates are explicit + * and when vertex positions are used as texture coordinates. In the latter + * case the texture matrix is applied to the pre-view-matrix position + * values. + * + * Normal SampleMode + * The post-matrix texture coordinates are in normalize space with (0,0) at + * the top-left and (1,1) at the bottom right. + * RadialGradient + * The matrix specifies the radial gradient parameters. + * (0,0) in the post-matrix space is center of the radial gradient. + * Radial2Gradient + * Matrix transforms to space where first circle is centered at the + * origin. The second circle will be centered (x, 0) where x may be + * 0 and is provided by setRadial2Params. The post-matrix space is + * normalized such that 1 is the second radius - first radius. + * SweepGradient + * The angle from the origin of texture coordinates in post-matrix space + * determines the gradient value. + */ + + class GrTextureStripAtlas; + +// Base class for Gr gradient effects +class GrGradientEffect : public GrFragmentProcessor { +public: + struct CreateArgs { + CreateArgs(GrContext* context, + const SkGradientShaderBase* shader, + const SkMatrix* matrix, + SkShader::TileMode tileMode, + sk_sp<GrColorSpaceXform> colorSpaceXform, + bool gammaCorrect) + : fContext(context) + , fShader(shader) + , fMatrix(matrix) + , fTileMode(tileMode) + , fColorSpaceXform(std::move(colorSpaceXform)) + , fGammaCorrect(gammaCorrect) {} + + GrContext* fContext; + const SkGradientShaderBase* fShader; + const SkMatrix* fMatrix; + SkShader::TileMode fTileMode; + sk_sp<GrColorSpaceXform> fColorSpaceXform; + bool fGammaCorrect; + }; + + class GLSLProcessor; + + ~GrGradientEffect() override; + + bool useAtlas() const { return SkToBool(-1 != fRow); } + SkScalar getYCoord() const { return fYCoord; } + + enum ColorType { + kTwo_ColorType, + kThree_ColorType, // Symmetric three color + kTexture_ColorType, + +#if GR_GL_USE_ACCURATE_HARD_STOP_GRADIENTS + kSingleHardStop_ColorType, // 0, t, t, 1 + kHardStopLeftEdged_ColorType, // 0, 0, 1 + kHardStopRightEdged_ColorType, // 0, 1, 1 +#endif + }; + + ColorType getColorType() const { return fColorType; } + + // Determines the type of gradient, one of: + // - Two-color + // - Symmetric three-color + // - Texture + // - Centered hard stop + // - Left-edged hard stop + // - Right-edged hard stop + ColorType determineColorType(const SkGradientShaderBase& shader); + + enum PremulType { + kBeforeInterp_PremulType, + kAfterInterp_PremulType, + }; + + PremulType getPremulType() const { return fPremulType; } + + const SkColor* getColors(int pos) const { + SkASSERT(fColorType != kTexture_ColorType); + SkASSERT(pos < fColors.count()); + return &fColors[pos]; + } + + const SkColor4f* getColors4f(int pos) const { + SkASSERT(fColorType != kTexture_ColorType); + SkASSERT(pos < fColors4f.count()); + return &fColors4f[pos]; + } + +protected: + GrGradientEffect(const CreateArgs&, bool isOpaque); + + #if GR_TEST_UTILS + /** Helper struct that stores (and populates) parameters to construct a random gradient. + If fUseColors4f is true, then the SkColor4f factory should be called, with fColors4f and + fColorSpace. Otherwise, the SkColor factory should be called, with fColors. fColorCount + will be the number of color stops in either case, and fColors and fStops can be passed to + the gradient factory. (The constructor may decide not to use stops, in which case fStops + will be nullptr). */ + struct RandomGradientParams { + static const int kMaxRandomGradientColors = 5; + + RandomGradientParams(SkRandom* r); + + bool fUseColors4f; + SkColor fColors[kMaxRandomGradientColors]; + SkColor4f fColors4f[kMaxRandomGradientColors]; + sk_sp<SkColorSpace> fColorSpace; + SkScalar fStopStorage[kMaxRandomGradientColors]; + SkShader::TileMode fTileMode; + int fColorCount; + SkScalar* fStops; + }; + #endif + + bool onIsEqual(const GrFragmentProcessor&) const override; + + const GrCoordTransform& getCoordTransform() const { return fCoordTransform; } + +private: + static OptimizationFlags OptFlags(bool isOpaque); + + // If we're in legacy mode, then fColors will be populated. If we're gamma-correct, then + // fColors4f and fColorSpaceXform will be populated. + SkTDArray<SkColor> fColors; + + SkTDArray<SkColor4f> fColors4f; + sk_sp<GrColorSpaceXform> fColorSpaceXform; + + SkTDArray<SkScalar> fPositions; + SkShader::TileMode fTileMode; + + GrCoordTransform fCoordTransform; + TextureSampler fTextureSampler; + SkScalar fYCoord; + GrTextureStripAtlas* fAtlas; + int fRow; + bool fIsOpaque; + ColorType fColorType; + PremulType fPremulType; // This is already baked into the table for texture gradients, and + // only changes behavior for gradients that don't use a texture. + typedef GrFragmentProcessor INHERITED; + +}; + +/////////////////////////////////////////////////////////////////////////////// + +// Base class for GL gradient effects +class GrGradientEffect::GLSLProcessor : public GrGLSLFragmentProcessor { +public: + GLSLProcessor() { + fCachedYCoord = SK_ScalarMax; + } + +protected: + void onSetData(const GrGLSLProgramDataManager&, const GrFragmentProcessor&) override; + +protected: + /** + * Subclasses must call this. It will return a key for the part of the shader code controlled + * by the base class. The subclasses must stick it in their key and then pass it to the below + * emit* functions from their emitCode function. + */ + static uint32_t GenBaseGradientKey(const GrProcessor&); + + // Emits the uniform used as the y-coord to texture samples in derived classes. Subclasses + // should call this method from their emitCode(). + void emitUniforms(GrGLSLUniformHandler*, const GrGradientEffect&); + + // Emit code that gets a fragment's color from an expression for t; has branches for + // several control flows inside -- 2-color gradients, 3-color symmetric gradients, 4+ + // color gradients that use the traditional texture lookup, as well as several varieties + // of hard stop gradients + void emitColor(GrGLSLFPFragmentBuilder* fragBuilder, + GrGLSLUniformHandler* uniformHandler, + const GrShaderCaps* shaderCaps, + const GrGradientEffect&, + const char* gradientTValue, + const char* outputColor, + const char* inputColor, + const TextureSamplers&); + +private: + enum { + // First bit for premul before/after interp + kPremulBeforeInterpKey = 1, + + // Next three bits for 2/3 color type or different special + // hard stop cases (neither means using texture atlas) + kTwoColorKey = 2, + kThreeColorKey = 4, +#if GR_GL_USE_ACCURATE_HARD_STOP_GRADIENTS + kHardStopCenteredKey = 6, + kHardStopZeroZeroOneKey = 8, + kHardStopZeroOneOneKey = 10, + + // Next two bits for tile mode + kClampTileMode = 16, + kRepeatTileMode = 32, + kMirrorTileMode = 48, + + // Lower six bits for premul, 2/3 color type, and tile mode + kReservedBits = 6, +#endif + }; + + SkScalar fCachedYCoord; + GrGLSLProgramDataManager::UniformHandle fColorsUni; + GrGLSLProgramDataManager::UniformHandle fHardStopT; + GrGLSLProgramDataManager::UniformHandle fFSYUni; + GrGLSLColorSpaceXformHelper fColorSpaceHelper; + + typedef GrGLSLFragmentProcessor INHERITED; +}; + +#endif + +#endif diff --git a/src/shaders/gradients/SkLinearGradient.cpp b/src/shaders/gradients/SkLinearGradient.cpp new file mode 100644 index 0000000000..17c4fd36a4 --- /dev/null +++ b/src/shaders/gradients/SkLinearGradient.cpp @@ -0,0 +1,804 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "Sk4fLinearGradient.h" +#include "SkColorSpaceXformer.h" +#include "SkLinearGradient.h" +#include "SkRefCnt.h" + +// define to test the 4f gradient path +// #define FORCE_4F_CONTEXT + +static const float kInv255Float = 1.0f / 255; + +static inline int repeat_8bits(int x) { + return x & 0xFF; +} + +static inline int mirror_8bits(int x) { + if (x & 256) { + x = ~x; + } + return x & 255; +} + +static SkMatrix pts_to_unit_matrix(const SkPoint pts[2]) { + SkVector vec = pts[1] - pts[0]; + SkScalar mag = vec.length(); + SkScalar inv = mag ? SkScalarInvert(mag) : 0; + + vec.scale(inv); + SkMatrix matrix; + matrix.setSinCos(-vec.fY, vec.fX, pts[0].fX, pts[0].fY); + matrix.postTranslate(-pts[0].fX, -pts[0].fY); + matrix.postScale(inv, inv); + return matrix; +} + +static bool use_4f_context(const SkShaderBase::ContextRec& rec, uint32_t flags) { +#ifdef FORCE_4F_CONTEXT + return true; +#else + return rec.fPreferredDstType == SkShaderBase::ContextRec::kPM4f_DstType + || SkToBool(flags & SkLinearGradient::kForce4fContext_PrivateFlag); +#endif +} + +/////////////////////////////////////////////////////////////////////////////// + +SkLinearGradient::SkLinearGradient(const SkPoint pts[2], const Descriptor& desc) + : SkGradientShaderBase(desc, pts_to_unit_matrix(pts)) + , fStart(pts[0]) + , fEnd(pts[1]) { +} + +sk_sp<SkFlattenable> SkLinearGradient::CreateProc(SkReadBuffer& buffer) { + DescriptorScope desc; + if (!desc.unflatten(buffer)) { + return nullptr; + } + SkPoint pts[2]; + pts[0] = buffer.readPoint(); + pts[1] = buffer.readPoint(); + return SkGradientShader::MakeLinear(pts, desc.fColors, std::move(desc.fColorSpace), desc.fPos, + desc.fCount, desc.fTileMode, desc.fGradFlags, + desc.fLocalMatrix); +} + +void SkLinearGradient::flatten(SkWriteBuffer& buffer) const { + this->INHERITED::flatten(buffer); + buffer.writePoint(fStart); + buffer.writePoint(fEnd); +} + +SkShaderBase::Context* SkLinearGradient::onMakeContext( + const ContextRec& rec, SkArenaAlloc* alloc) const +{ + return use_4f_context(rec, fGradFlags) + ? CheckedMakeContext<LinearGradient4fContext>(alloc, *this, rec) + : CheckedMakeContext< LinearGradientContext>(alloc, *this, rec); +} + +bool SkLinearGradient::adjustMatrixAndAppendStages(SkArenaAlloc* alloc, + SkMatrix* matrix, + SkRasterPipeline* p) const { + *matrix = SkMatrix::Concat(fPtsToUnit, *matrix); + // If the gradient is less than a quarter of a pixel, this falls into the + // subpixel gradient code handled on a different path. + SkVector dx = matrix->mapVector(1, 0); + if (dx.fX >= 4) { + return false; + } + return true; +} + +sk_sp<SkShader> SkLinearGradient::onMakeColorSpace(SkColorSpaceXformer* xformer) const { + SkPoint pts[2] = { fStart, fEnd }; + SkSTArray<8, SkColor> xformedColors(fColorCount); + xformer->apply(xformedColors.begin(), fOrigColors, fColorCount); + return SkGradientShader::MakeLinear(pts, xformedColors.begin(), fOrigPos, fColorCount, + fTileMode, fGradFlags, &this->getLocalMatrix()); +} + +// This swizzles SkColor into the same component order as SkPMColor, but does not actually +// "pre" multiply the color components. +// +// This allows us to map directly to Sk4f, and eventually scale down to bytes to output a +// SkPMColor from the floats, without having to swizzle each time. +// +static uint32_t SkSwizzle_Color_to_PMColor(SkColor c) { + return SkPackARGB32NoCheck(SkColorGetA(c), SkColorGetR(c), SkColorGetG(c), SkColorGetB(c)); +} + +SkLinearGradient::LinearGradientContext::LinearGradientContext( + const SkLinearGradient& shader, const ContextRec& ctx) + : INHERITED(shader, ctx) +{ + // setup for Sk4f + const int count = shader.fColorCount; + SkASSERT(count > 1); + + fRecs.setCount(count); + Rec* rec = fRecs.begin(); + if (shader.fOrigPos) { + rec[0].fPos = 0; + SkDEBUGCODE(rec[0].fPosScale = SK_FloatNaN;) // should never get used + for (int i = 1; i < count; ++i) { + rec[i].fPos = SkTPin(shader.fOrigPos[i], rec[i - 1].fPos, 1.0f); + float diff = rec[i].fPos - rec[i - 1].fPos; + if (diff > 0) { + rec[i].fPosScale = 1.0f / diff; + } else { + rec[i].fPosScale = 0; + } + } + } else { + // no pos specified, so we compute evenly spaced values + const float scale = float(count - 1); + const float invScale = 1.0f / scale; + for (int i = 0; i < count; ++i) { + rec[i].fPos = i * invScale; + rec[i].fPosScale = scale; + } + } + rec[count - 1].fPos = 1; // overwrite the last value just to be sure we end at 1.0 + + fApplyAlphaAfterInterp = true; + if ((shader.getGradFlags() & SkGradientShader::kInterpolateColorsInPremul_Flag) || + shader.colorsAreOpaque()) + { + fApplyAlphaAfterInterp = false; + } + + if (fApplyAlphaAfterInterp) { + // Our fColor values are in PMColor order, but are still unpremultiplied, allowing us to + // interpolate in unpremultiplied space first, and then scale by alpha right before we + // convert to SkPMColor bytes. + const float paintAlpha = ctx.fPaint->getAlpha() * kInv255Float; + const Sk4f scale(1, 1, 1, paintAlpha); + for (int i = 0; i < count; ++i) { + uint32_t c = SkSwizzle_Color_to_PMColor(shader.fOrigColors[i]); + rec[i].fColor = SkNx_cast<float>(Sk4b::Load(&c)) * scale; + if (i > 0) { + SkASSERT(rec[i - 1].fPos <= rec[i].fPos); + } + } + } else { + // Our fColor values are premultiplied, so converting to SkPMColor is just a matter + // of converting the floats down to bytes. + unsigned alphaScale = ctx.fPaint->getAlpha() + (ctx.fPaint->getAlpha() >> 7); + for (int i = 0; i < count; ++i) { + SkPMColor pmc = SkPreMultiplyColor(shader.fOrigColors[i]); + pmc = SkAlphaMulQ(pmc, alphaScale); + rec[i].fColor = SkNx_cast<float>(Sk4b::Load(&pmc)); + if (i > 0) { + SkASSERT(rec[i - 1].fPos <= rec[i].fPos); + } + } + } +} + +#define NO_CHECK_ITER \ + do { \ + unsigned fi = SkGradFixedToFixed(fx) >> SkGradientShaderBase::kCache32Shift; \ + SkASSERT(fi <= 0xFF); \ + fx += dx; \ + *dstC++ = cache[toggle + fi]; \ + toggle = next_dither_toggle(toggle); \ + } while (0) + +namespace { + +typedef void (*LinearShadeProc)(TileProc proc, SkGradFixed dx, SkGradFixed fx, + SkPMColor* dstC, const SkPMColor* cache, + int toggle, int count); + +// Linear interpolation (lerp) is unnecessary if there are no sharp +// discontinuities in the gradient - which must be true if there are +// only 2 colors - but it's cheap. +void shadeSpan_linear_vertical_lerp(TileProc proc, SkGradFixed dx, SkGradFixed fx, + SkPMColor* SK_RESTRICT dstC, + const SkPMColor* SK_RESTRICT cache, + int toggle, int count) { + // We're a vertical gradient, so no change in a span. + // If colors change sharply across the gradient, dithering is + // insufficient (it subsamples the color space) and we need to lerp. + unsigned fullIndex = proc(SkGradFixedToFixed(fx)); + unsigned fi = fullIndex >> SkGradientShaderBase::kCache32Shift; + unsigned remainder = fullIndex & ((1 << SkGradientShaderBase::kCache32Shift) - 1); + + int index0 = fi + toggle; + int index1 = index0; + if (fi < SkGradientShaderBase::kCache32Count - 1) { + index1 += 1; + } + SkPMColor lerp = SkFastFourByteInterp(cache[index1], cache[index0], remainder); + index0 ^= SkGradientShaderBase::kDitherStride32; + index1 ^= SkGradientShaderBase::kDitherStride32; + SkPMColor dlerp = SkFastFourByteInterp(cache[index1], cache[index0], remainder); + sk_memset32_dither(dstC, lerp, dlerp, count); +} + +void shadeSpan_linear_clamp(TileProc proc, SkGradFixed dx, SkGradFixed fx, + SkPMColor* SK_RESTRICT dstC, + const SkPMColor* SK_RESTRICT cache, + int toggle, int count) { + SkClampRange range; + range.init(fx, dx, count, 0, SkGradientShaderBase::kCache32Count - 1); + range.validate(count); + + if ((count = range.fCount0) > 0) { + sk_memset32_dither(dstC, + cache[toggle + range.fV0], + cache[next_dither_toggle(toggle) + range.fV0], + count); + dstC += count; + } + if ((count = range.fCount1) > 0) { + int unroll = count >> 3; + fx = range.fFx1; + for (int i = 0; i < unroll; i++) { + NO_CHECK_ITER; NO_CHECK_ITER; + NO_CHECK_ITER; NO_CHECK_ITER; + NO_CHECK_ITER; NO_CHECK_ITER; + NO_CHECK_ITER; NO_CHECK_ITER; + } + if ((count &= 7) > 0) { + do { + NO_CHECK_ITER; + } while (--count != 0); + } + } + if ((count = range.fCount2) > 0) { + sk_memset32_dither(dstC, + cache[toggle + range.fV1], + cache[next_dither_toggle(toggle) + range.fV1], + count); + } +} + +void shadeSpan_linear_mirror(TileProc proc, SkGradFixed dx, SkGradFixed fx, + SkPMColor* SK_RESTRICT dstC, + const SkPMColor* SK_RESTRICT cache, + int toggle, int count) { + do { + unsigned fi = mirror_8bits(SkGradFixedToFixed(fx) >> 8); + SkASSERT(fi <= 0xFF); + fx += dx; + *dstC++ = cache[toggle + fi]; + toggle = next_dither_toggle(toggle); + } while (--count != 0); +} + +void shadeSpan_linear_repeat(TileProc proc, SkGradFixed dx, SkGradFixed fx, + SkPMColor* SK_RESTRICT dstC, + const SkPMColor* SK_RESTRICT cache, + int toggle, int count) { + do { + unsigned fi = repeat_8bits(SkGradFixedToFixed(fx) >> 8); + SkASSERT(fi <= 0xFF); + fx += dx; + *dstC++ = cache[toggle + fi]; + toggle = next_dither_toggle(toggle); + } while (--count != 0); +} + +} + +void SkLinearGradient::LinearGradientContext::shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC, + int count) { + SkASSERT(count > 0); + const SkLinearGradient& linearGradient = static_cast<const SkLinearGradient&>(fShader); + + if (SkShader::kClamp_TileMode == linearGradient.fTileMode && + kLinear_MatrixClass == fDstToIndexClass) + { + this->shade4_clamp(x, y, dstC, count); + return; + } + + SkPoint srcPt; + SkMatrix::MapXYProc dstProc = fDstToIndexProc; + TileProc proc = linearGradient.fTileProc; + const SkPMColor* SK_RESTRICT cache = fCache->getCache32(); + int toggle = init_dither_toggle(x, y); + + if (fDstToIndexClass != kPerspective_MatrixClass) { + dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf, + SkIntToScalar(y) + SK_ScalarHalf, &srcPt); + SkGradFixed dx, fx = SkScalarPinToGradFixed(srcPt.fX); + + if (fDstToIndexClass == kFixedStepInX_MatrixClass) { + const auto step = fDstToIndex.fixedStepInX(SkIntToScalar(y)); + // todo: do we need a real/high-precision value for dx here? + dx = SkScalarPinToGradFixed(step.fX); + } else { + SkASSERT(fDstToIndexClass == kLinear_MatrixClass); + dx = SkScalarPinToGradFixed(fDstToIndex.getScaleX()); + } + + LinearShadeProc shadeProc = shadeSpan_linear_repeat; + if (0 == dx) { + shadeProc = shadeSpan_linear_vertical_lerp; + } else if (SkShader::kClamp_TileMode == linearGradient.fTileMode) { + shadeProc = shadeSpan_linear_clamp; + } else if (SkShader::kMirror_TileMode == linearGradient.fTileMode) { + shadeProc = shadeSpan_linear_mirror; + } else { + SkASSERT(SkShader::kRepeat_TileMode == linearGradient.fTileMode); + } + (*shadeProc)(proc, dx, fx, dstC, cache, toggle, count); + } else { + SkScalar dstX = SkIntToScalar(x); + SkScalar dstY = SkIntToScalar(y); + do { + dstProc(fDstToIndex, dstX, dstY, &srcPt); + unsigned fi = proc(SkScalarToFixed(srcPt.fX)); + SkASSERT(fi <= 0xFFFF); + *dstC++ = cache[toggle + (fi >> kCache32Shift)]; + toggle = next_dither_toggle(toggle); + dstX += SK_Scalar1; + } while (--count != 0); + } +} + +SkShader::GradientType SkLinearGradient::asAGradient(GradientInfo* info) const { + if (info) { + commonAsAGradient(info); + info->fPoint[0] = fStart; + info->fPoint[1] = fEnd; + } + return kLinear_GradientType; +} + +#if SK_SUPPORT_GPU + +#include "GrColorSpaceXform.h" +#include "GrShaderCaps.h" +#include "glsl/GrGLSLFragmentShaderBuilder.h" +#include "SkGr.h" + +///////////////////////////////////////////////////////////////////// + +class GrLinearGradient : public GrGradientEffect { +public: + class GLSLLinearProcessor; + + static sk_sp<GrFragmentProcessor> Make(const CreateArgs& args) { + return sk_sp<GrFragmentProcessor>(new GrLinearGradient(args)); + } + + ~GrLinearGradient() override {} + + const char* name() const override { return "Linear Gradient"; } + +private: + GrLinearGradient(const CreateArgs& args) : INHERITED(args, args.fShader->colorsAreOpaque()) { + this->initClassID<GrLinearGradient>(); + } + + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + + virtual void onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const override; + + GR_DECLARE_FRAGMENT_PROCESSOR_TEST; + + typedef GrGradientEffect INHERITED; +}; + +///////////////////////////////////////////////////////////////////// + +class GrLinearGradient::GLSLLinearProcessor : public GrGradientEffect::GLSLProcessor { +public: + GLSLLinearProcessor(const GrProcessor&) {} + + ~GLSLLinearProcessor() override {} + + virtual void emitCode(EmitArgs&) override; + + static void GenKey(const GrProcessor& processor, const GrShaderCaps&, GrProcessorKeyBuilder* b) { + b->add32(GenBaseGradientKey(processor)); + } + +private: + typedef GrGradientEffect::GLSLProcessor INHERITED; +}; + +///////////////////////////////////////////////////////////////////// + +GrGLSLFragmentProcessor* GrLinearGradient::onCreateGLSLInstance() const { + return new GrLinearGradient::GLSLLinearProcessor(*this); +} + +void GrLinearGradient::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const { + GrLinearGradient::GLSLLinearProcessor::GenKey(*this, caps, b); +} + +///////////////////////////////////////////////////////////////////// + +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrLinearGradient); + +#if GR_TEST_UTILS +sk_sp<GrFragmentProcessor> GrLinearGradient::TestCreate(GrProcessorTestData* d) { + SkPoint points[] = {{d->fRandom->nextUScalar1(), d->fRandom->nextUScalar1()}, + {d->fRandom->nextUScalar1(), d->fRandom->nextUScalar1()}}; + + RandomGradientParams params(d->fRandom); + auto shader = params.fUseColors4f ? + SkGradientShader::MakeLinear(points, params.fColors4f, params.fColorSpace, params.fStops, + params.fColorCount, params.fTileMode) : + SkGradientShader::MakeLinear(points, params.fColors, params.fStops, + params.fColorCount, params.fTileMode); + GrTest::TestAsFPArgs asFPArgs(d); + sk_sp<GrFragmentProcessor> fp = as_SB(shader)->asFragmentProcessor(asFPArgs.args()); + GrAlwaysAssert(fp); + return fp; +} +#endif + +///////////////////////////////////////////////////////////////////// + +void GrLinearGradient::GLSLLinearProcessor::emitCode(EmitArgs& args) { + const GrLinearGradient& ge = args.fFp.cast<GrLinearGradient>(); + this->emitUniforms(args.fUniformHandler, ge); + SkString t = args.fFragBuilder->ensureCoords2D(args.fTransformedCoords[0]); + t.append(".x"); + this->emitColor(args.fFragBuilder, + args.fUniformHandler, + args.fShaderCaps, + ge, + t.c_str(), + args.fOutputColor, + args.fInputColor, + args.fTexSamplers); +} + +///////////////////////////////////////////////////////////////////// + +sk_sp<GrFragmentProcessor> SkLinearGradient::asFragmentProcessor(const AsFPArgs& args) const { + SkASSERT(args.fContext); + + SkMatrix matrix; + if (!this->getLocalMatrix().invert(&matrix)) { + return nullptr; + } + if (args.fLocalMatrix) { + SkMatrix inv; + if (!args.fLocalMatrix->invert(&inv)) { + return nullptr; + } + matrix.postConcat(inv); + } + matrix.postConcat(fPtsToUnit); + + sk_sp<GrColorSpaceXform> colorSpaceXform = GrColorSpaceXform::Make(fColorSpace.get(), + args.fDstColorSpace); + sk_sp<GrFragmentProcessor> inner(GrLinearGradient::Make( + GrGradientEffect::CreateArgs(args.fContext, this, &matrix, fTileMode, + std::move(colorSpaceXform), SkToBool(args.fDstColorSpace)))); + return GrFragmentProcessor::MulOutputByInputAlpha(std::move(inner)); +} + + +#endif + +#ifndef SK_IGNORE_TO_STRING +void SkLinearGradient::toString(SkString* str) const { + str->append("SkLinearGradient ("); + + str->appendf("start: (%f, %f)", fStart.fX, fStart.fY); + str->appendf(" end: (%f, %f) ", fEnd.fX, fEnd.fY); + + this->INHERITED::toString(str); + + str->append(")"); +} +#endif + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#include "SkNx.h" + +static const SkLinearGradient::LinearGradientContext::Rec* +find_forward(const SkLinearGradient::LinearGradientContext::Rec rec[], float tiledX) { + SkASSERT(tiledX >= 0 && tiledX <= 1); + + SkASSERT(rec[0].fPos >= 0 && rec[0].fPos <= 1); + SkASSERT(rec[1].fPos >= 0 && rec[1].fPos <= 1); + SkASSERT(rec[0].fPos <= rec[1].fPos); + rec += 1; + while (rec->fPos < tiledX || rec->fPosScale == 0) { + SkASSERT(rec[0].fPos >= 0 && rec[0].fPos <= 1); + SkASSERT(rec[1].fPos >= 0 && rec[1].fPos <= 1); + SkASSERT(rec[0].fPos <= rec[1].fPos); + rec += 1; + } + return rec - 1; +} + +static const SkLinearGradient::LinearGradientContext::Rec* +find_backward(const SkLinearGradient::LinearGradientContext::Rec rec[], float tiledX) { + SkASSERT(tiledX >= 0 && tiledX <= 1); + + SkASSERT(rec[0].fPos >= 0 && rec[0].fPos <= 1); + SkASSERT(rec[1].fPos >= 0 && rec[1].fPos <= 1); + SkASSERT(rec[0].fPos <= rec[1].fPos); + while (tiledX < rec->fPos || rec[1].fPosScale == 0) { + rec -= 1; + SkASSERT(rec[0].fPos >= 0 && rec[0].fPos <= 1); + SkASSERT(rec[1].fPos >= 0 && rec[1].fPos <= 1); + SkASSERT(rec[0].fPos <= rec[1].fPos); + } + return rec; +} + +// As an optimization, we can apply the dither bias before interpolation -- but only when +// operating in premul space (apply_alpha == false). When apply_alpha == true, we must +// defer the bias application until after premul. +// +// The following two helpers encapsulate this logic: pre_bias is called before interpolation, +// and effects the bias when apply_alpha == false, while post_bias is called after premul and +// effects the bias for the apply_alpha == true case. + +template <bool apply_alpha> +Sk4f pre_bias(const Sk4f& x, const Sk4f& bias) { + return apply_alpha ? x : x + bias; +} + +template <bool apply_alpha> +Sk4f post_bias(const Sk4f& x, const Sk4f& bias) { + return apply_alpha ? x + bias : x; +} + +template <bool apply_alpha> SkPMColor trunc_from_255(const Sk4f& x, const Sk4f& bias) { + SkPMColor c; + Sk4f c4f255 = x; + if (apply_alpha) { + const float scale = x[SkPM4f::A] * (1 / 255.f); + c4f255 *= Sk4f(scale, scale, scale, 1); + } + SkNx_cast<uint8_t>(post_bias<apply_alpha>(c4f255, bias)).store(&c); + + return c; +} + +template <bool apply_alpha> void fill(SkPMColor dst[], int count, + const Sk4f& c4, const Sk4f& bias0, const Sk4f& bias1) { + const SkPMColor c0 = trunc_from_255<apply_alpha>(pre_bias<apply_alpha>(c4, bias0), bias0); + const SkPMColor c1 = trunc_from_255<apply_alpha>(pre_bias<apply_alpha>(c4, bias1), bias1); + sk_memset32_dither(dst, c0, c1, count); +} + +template <bool apply_alpha> void fill(SkPMColor dst[], int count, const Sk4f& c4) { + // Assumes that c4 does not need to be dithered. + sk_memset32(dst, trunc_from_255<apply_alpha>(c4, 0), count); +} + +/* + * TODOs + * + * - tilemodes + * - interp before or after premul + * - perspective + * - optimizations + * - use fixed (32bit or 16bit) instead of floats? + */ + +static Sk4f lerp_color(float fx, const SkLinearGradient::LinearGradientContext::Rec* rec) { + SkASSERT(fx >= rec[0].fPos); + SkASSERT(fx <= rec[1].fPos); + + const float p0 = rec[0].fPos; + const Sk4f c0 = rec[0].fColor; + const Sk4f c1 = rec[1].fColor; + const Sk4f diffc = c1 - c0; + const float scale = rec[1].fPosScale; + const float t = (fx - p0) * scale; + return c0 + Sk4f(t) * diffc; +} + +template <bool apply_alpha> void ramp(SkPMColor dstC[], int n, const Sk4f& c, const Sk4f& dc, + const Sk4f& dither0, const Sk4f& dither1) { + Sk4f dc2 = dc + dc; + Sk4f dc4 = dc2 + dc2; + Sk4f cd0 = pre_bias<apply_alpha>(c , dither0); + Sk4f cd1 = pre_bias<apply_alpha>(c + dc, dither1); + Sk4f cd2 = cd0 + dc2; + Sk4f cd3 = cd1 + dc2; + while (n >= 4) { + if (!apply_alpha) { + Sk4f_ToBytes((uint8_t*)dstC, cd0, cd1, cd2, cd3); + dstC += 4; + } else { + *dstC++ = trunc_from_255<apply_alpha>(cd0, dither0); + *dstC++ = trunc_from_255<apply_alpha>(cd1, dither1); + *dstC++ = trunc_from_255<apply_alpha>(cd2, dither0); + *dstC++ = trunc_from_255<apply_alpha>(cd3, dither1); + } + cd0 = cd0 + dc4; + cd1 = cd1 + dc4; + cd2 = cd2 + dc4; + cd3 = cd3 + dc4; + n -= 4; + } + if (n & 2) { + *dstC++ = trunc_from_255<apply_alpha>(cd0, dither0); + *dstC++ = trunc_from_255<apply_alpha>(cd1, dither1); + cd0 = cd0 + dc2; + } + if (n & 1) { + *dstC++ = trunc_from_255<apply_alpha>(cd0, dither0); + } +} + +template <bool apply_alpha, bool dx_is_pos> +void SkLinearGradient::LinearGradientContext::shade4_dx_clamp(SkPMColor dstC[], int count, + float fx, float dx, float invDx, + const float dither[2]) { + Sk4f dither0(dither[0]); + Sk4f dither1(dither[1]); + const Rec* rec = fRecs.begin(); + + const Sk4f dx4 = Sk4f(dx); + SkDEBUGCODE(SkPMColor* endDstC = dstC + count;) + + if (dx_is_pos) { + if (fx < 0) { + // count is guaranteed to be positive, but the first arg may overflow int32 after + // increment => casting to uint32 ensures correct clamping. + int n = SkTMin<uint32_t>(static_cast<uint32_t>(SkFloatToIntFloor(-fx * invDx)) + 1, + count); + SkASSERT(n > 0); + fill<apply_alpha>(dstC, n, rec[0].fColor); + count -= n; + dstC += n; + fx += n * dx; + SkASSERT(0 == count || fx >= 0); + if (n & 1) { + SkTSwap(dither0, dither1); + } + } + } else { // dx < 0 + if (fx > 1) { + // count is guaranteed to be positive, but the first arg may overflow int32 after + // increment => casting to uint32 ensures correct clamping. + int n = SkTMin<uint32_t>(static_cast<uint32_t>(SkFloatToIntFloor((1 - fx) * invDx)) + 1, + count); + SkASSERT(n > 0); + fill<apply_alpha>(dstC, n, rec[fRecs.count() - 1].fColor); + count -= n; + dstC += n; + fx += n * dx; + SkASSERT(0 == count || fx <= 1); + if (n & 1) { + SkTSwap(dither0, dither1); + } + } + } + SkASSERT(count >= 0); + + const Rec* r; + if (dx_is_pos) { + r = fRecs.begin(); // start at the beginning + } else { + r = fRecs.begin() + fRecs.count() - 2; // start at the end + } + + while (count > 0) { + if (dx_is_pos) { + if (fx >= 1) { + fill<apply_alpha>(dstC, count, rec[fRecs.count() - 1].fColor); + return; + } + } else { // dx < 0 + if (fx <= 0) { + fill<apply_alpha>(dstC, count, rec[0].fColor); + return; + } + } + + if (dx_is_pos) { + r = find_forward(r, fx); + } else { + r = find_backward(r, fx); + } + SkASSERT(r >= fRecs.begin() && r < fRecs.begin() + fRecs.count() - 1); + + const float p0 = r[0].fPos; + const Sk4f c0 = r[0].fColor; + const float p1 = r[1].fPos; + const Sk4f diffc = Sk4f(r[1].fColor) - c0; + const float scale = r[1].fPosScale; + const float t = (fx - p0) * scale; + const Sk4f c = c0 + Sk4f(t) * diffc; + const Sk4f dc = diffc * dx4 * Sk4f(scale); + + int n; + if (dx_is_pos) { + n = SkTMin((int)((p1 - fx) * invDx) + 1, count); + } else { + n = SkTMin((int)((p0 - fx) * invDx) + 1, count); + } + + fx += n * dx; + // fx should now outside of the p0..p1 interval. However, due to float precision loss, + // its possible that fx is slightly too small/large, so we clamp it. + if (dx_is_pos) { + fx = SkTMax(fx, p1); + } else { + fx = SkTMin(fx, p0); + } + + ramp<apply_alpha>(dstC, n, c, dc, dither0, dither1); + dstC += n; + SkASSERT(dstC <= endDstC); + + if (n & 1) { + SkTSwap(dither0, dither1); + } + + count -= n; + SkASSERT(count >= 0); + } +} + +void SkLinearGradient::LinearGradientContext::shade4_clamp(int x, int y, SkPMColor dstC[], + int count) { + SkASSERT(count > 0); + SkASSERT(kLinear_MatrixClass == fDstToIndexClass); + + SkPoint srcPt; + fDstToIndexProc(fDstToIndex, x + SK_ScalarHalf, y + SK_ScalarHalf, &srcPt); + float fx = srcPt.x(); + const float dx = fDstToIndex.getScaleX(); + + // Default our dither bias values to 1/2, (rounding), which is no dithering + float dither0 = 0.5f; + float dither1 = 0.5f; + if (fDither) { + const float ditherCell[] = { + 1/8.0f, 5/8.0f, + 7/8.0f, 3/8.0f, + }; + const int rowIndex = (y & 1) << 1; + dither0 = ditherCell[rowIndex]; + dither1 = ditherCell[rowIndex + 1]; + if (x & 1) { + SkTSwap(dither0, dither1); + } + } + const float dither[2] = { dither0, dither1 }; + + if (SkScalarNearlyZero(dx * count)) { // gradient is vertical + const float pinFx = SkTPin(fx, 0.0f, 1.0f); + Sk4f c = lerp_color(pinFx, find_forward(fRecs.begin(), pinFx)); + if (fApplyAlphaAfterInterp) { + fill<true>(dstC, count, c, dither0, dither1); + } else { + fill<false>(dstC, count, c, dither0, dither1); + } + return; + } + + SkASSERT(0.f != dx); + const float invDx = 1 / dx; + if (dx > 0) { + if (fApplyAlphaAfterInterp) { + this->shade4_dx_clamp<true, true>(dstC, count, fx, dx, invDx, dither); + } else { + this->shade4_dx_clamp<false, true>(dstC, count, fx, dx, invDx, dither); + } + } else { + if (fApplyAlphaAfterInterp) { + this->shade4_dx_clamp<true, false>(dstC, count, fx, dx, invDx, dither); + } else { + this->shade4_dx_clamp<false, false>(dstC, count, fx, dx, invDx, dither); + } + } +} diff --git a/src/shaders/gradients/SkLinearGradient.h b/src/shaders/gradients/SkLinearGradient.h new file mode 100644 index 0000000000..19a965c7bb --- /dev/null +++ b/src/shaders/gradients/SkLinearGradient.h @@ -0,0 +1,87 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkLinearGradient_DEFINED +#define SkLinearGradient_DEFINED + +#include "SkGradientShaderPriv.h" +#include "SkNx.h" + +struct Sk4fStorage { + float fArray[4]; + + operator Sk4f() const { + return Sk4f::Load(fArray); + } + + Sk4fStorage& operator=(const Sk4f& src) { + src.store(fArray); + return *this; + } +}; + +class SkLinearGradient : public SkGradientShaderBase { +public: + enum { + // Temp flag for testing the 4f impl. + kForce4fContext_PrivateFlag = 1 << 7, + }; + + SkLinearGradient(const SkPoint pts[2], const Descriptor&); + + class LinearGradientContext : public SkGradientShaderBase::GradientShaderBaseContext { + public: + LinearGradientContext(const SkLinearGradient&, const ContextRec&); + + void shadeSpan(int x, int y, SkPMColor dstC[], int count) override; + + struct Rec { + Sk4fStorage fColor; + float fPos; + float fPosScale; + }; + private: + SkTDArray<Rec> fRecs; + bool fApplyAlphaAfterInterp; + + void shade4_clamp(int x, int y, SkPMColor dstC[], int count); + template <bool, bool> void shade4_dx_clamp(SkPMColor dstC[], int count, float fx, float dx, + float invDx, const float dither[2]); + + typedef SkGradientShaderBase::GradientShaderBaseContext INHERITED; + }; + + GradientType asAGradient(GradientInfo* info) const override; +#if SK_SUPPORT_GPU + sk_sp<GrFragmentProcessor> asFragmentProcessor(const AsFPArgs&) const override; +#endif + + SK_TO_STRING_OVERRIDE() + SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkLinearGradient) + +protected: + SkLinearGradient(SkReadBuffer& buffer); + void flatten(SkWriteBuffer& buffer) const override; + Context* onMakeContext(const ContextRec&, SkArenaAlloc*) const override; + + bool adjustMatrixAndAppendStages(SkArenaAlloc* alloc, + SkMatrix* matrix, + SkRasterPipeline* p) const final; + + + sk_sp<SkShader> onMakeColorSpace(SkColorSpaceXformer* xformer) const override; + +private: + class LinearGradient4fContext; + + friend class SkGradientShader; + typedef SkGradientShaderBase INHERITED; + const SkPoint fStart; + const SkPoint fEnd; +}; + +#endif diff --git a/src/shaders/gradients/SkRadialGradient.cpp b/src/shaders/gradients/SkRadialGradient.cpp new file mode 100644 index 0000000000..d49b3dd8e1 --- /dev/null +++ b/src/shaders/gradients/SkRadialGradient.cpp @@ -0,0 +1,405 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkColorSpaceXformer.h" +#include "SkRadialGradient.h" +#include "SkNx.h" + +namespace { + +// GCC doesn't like using static functions as template arguments. So force these to be non-static. +inline SkFixed mirror_tileproc_nonstatic(SkFixed x) { + return mirror_tileproc(x); +} + +inline SkFixed repeat_tileproc_nonstatic(SkFixed x) { + return repeat_tileproc(x); +} + +SkMatrix rad_to_unit_matrix(const SkPoint& center, SkScalar radius) { + SkScalar inv = SkScalarInvert(radius); + + SkMatrix matrix; + matrix.setTranslate(-center.fX, -center.fY); + matrix.postScale(inv, inv); + return matrix; +} + + +} // namespace + +///////////////////////////////////////////////////////////////////// + +SkRadialGradient::SkRadialGradient(const SkPoint& center, SkScalar radius, const Descriptor& desc) + : SkGradientShaderBase(desc, rad_to_unit_matrix(center, radius)) + , fCenter(center) + , fRadius(radius) { +} + +SkShaderBase::Context* SkRadialGradient::onMakeContext( + const ContextRec& rec, SkArenaAlloc* alloc) const +{ + return CheckedMakeContext<RadialGradientContext>(alloc, *this, rec); +} + +SkRadialGradient::RadialGradientContext::RadialGradientContext( + const SkRadialGradient& shader, const ContextRec& rec) + : INHERITED(shader, rec) {} + +SkShader::GradientType SkRadialGradient::asAGradient(GradientInfo* info) const { + if (info) { + commonAsAGradient(info); + info->fPoint[0] = fCenter; + info->fRadius[0] = fRadius; + } + return kRadial_GradientType; +} + +sk_sp<SkFlattenable> SkRadialGradient::CreateProc(SkReadBuffer& buffer) { + DescriptorScope desc; + if (!desc.unflatten(buffer)) { + return nullptr; + } + const SkPoint center = buffer.readPoint(); + const SkScalar radius = buffer.readScalar(); + return SkGradientShader::MakeRadial(center, radius, desc.fColors, std::move(desc.fColorSpace), + desc.fPos, desc.fCount, desc.fTileMode, desc.fGradFlags, + desc.fLocalMatrix); +} + +void SkRadialGradient::flatten(SkWriteBuffer& buffer) const { + this->INHERITED::flatten(buffer); + buffer.writePoint(fCenter); + buffer.writeScalar(fRadius); +} + +namespace { + +inline bool radial_completely_pinned(SkScalar fx, SkScalar dx, SkScalar fy, SkScalar dy) { + // fast, overly-conservative test: checks unit square instead of unit circle + bool xClamped = (fx >= 1 && dx >= 0) || (fx <= -1 && dx <= 0); + bool yClamped = (fy >= 1 && dy >= 0) || (fy <= -1 && dy <= 0); + return xClamped || yClamped; +} + +typedef void (* RadialShadeProc)(SkScalar sfx, SkScalar sdx, + SkScalar sfy, SkScalar sdy, + SkPMColor* dstC, const SkPMColor* cache, + int count, int toggle); + +static inline Sk4f fast_sqrt(const Sk4f& R) { + return R * R.rsqrt(); +} + +static inline Sk4f sum_squares(const Sk4f& a, const Sk4f& b) { + return a * a + b * b; +} + +void shadeSpan_radial_clamp2(SkScalar sfx, SkScalar sdx, SkScalar sfy, SkScalar sdy, + SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache, + int count, int toggle) { + if (radial_completely_pinned(sfx, sdx, sfy, sdy)) { + unsigned fi = SkGradientShaderBase::kCache32Count - 1; + sk_memset32_dither(dstC, + cache[toggle + fi], + cache[next_dither_toggle(toggle) + fi], + count); + } else { + const Sk4f min(SK_ScalarNearlyZero); + const Sk4f max(255); + const float scale = 255; + sfx *= scale; + sfy *= scale; + sdx *= scale; + sdy *= scale; + const Sk4f fx4(sfx, sfx + sdx, sfx + 2*sdx, sfx + 3*sdx); + const Sk4f fy4(sfy, sfy + sdy, sfy + 2*sdy, sfy + 3*sdy); + const Sk4f dx4(sdx * 4); + const Sk4f dy4(sdy * 4); + + Sk4f tmpxy = fx4 * dx4 + fy4 * dy4; + Sk4f tmpdxdy = sum_squares(dx4, dy4); + Sk4f R = Sk4f::Max(sum_squares(fx4, fy4), min); + Sk4f dR = tmpxy + tmpxy + tmpdxdy; + const Sk4f ddR = tmpdxdy + tmpdxdy; + + for (int i = 0; i < (count >> 2); ++i) { + Sk4f dist = Sk4f::Min(fast_sqrt(R), max); + R = Sk4f::Max(R + dR, min); + dR = dR + ddR; + + uint8_t fi[4]; + SkNx_cast<uint8_t>(dist).store(fi); + + for (int i = 0; i < 4; i++) { + *dstC++ = cache[toggle + fi[i]]; + toggle = next_dither_toggle(toggle); + } + } + count &= 3; + if (count) { + Sk4f dist = Sk4f::Min(fast_sqrt(R), max); + + uint8_t fi[4]; + SkNx_cast<uint8_t>(dist).store(fi); + for (int i = 0; i < count; i++) { + *dstC++ = cache[toggle + fi[i]]; + toggle = next_dither_toggle(toggle); + } + } + } +} + +// Unrolling this loop doesn't seem to help (when float); we're stalling to +// get the results of the sqrt (?), and don't have enough extra registers to +// have many in flight. +template <SkFixed (*TileProc)(SkFixed)> +void shadeSpan_radial(SkScalar fx, SkScalar dx, SkScalar fy, SkScalar dy, + SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache, + int count, int toggle) { + do { + const SkFixed dist = SkFloatToFixed(sk_float_sqrt(fx*fx + fy*fy)); + const unsigned fi = TileProc(dist); + SkASSERT(fi <= 0xFFFF); + *dstC++ = cache[toggle + (fi >> SkGradientShaderBase::kCache32Shift)]; + toggle = next_dither_toggle(toggle); + fx += dx; + fy += dy; + } while (--count != 0); +} + +void shadeSpan_radial_mirror(SkScalar fx, SkScalar dx, SkScalar fy, SkScalar dy, + SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache, + int count, int toggle) { + shadeSpan_radial<mirror_tileproc_nonstatic>(fx, dx, fy, dy, dstC, cache, count, toggle); +} + +void shadeSpan_radial_repeat(SkScalar fx, SkScalar dx, SkScalar fy, SkScalar dy, + SkPMColor* SK_RESTRICT dstC, const SkPMColor* SK_RESTRICT cache, + int count, int toggle) { + shadeSpan_radial<repeat_tileproc_nonstatic>(fx, dx, fy, dy, dstC, cache, count, toggle); +} + +} // namespace + +void SkRadialGradient::RadialGradientContext::shadeSpan(int x, int y, + SkPMColor* SK_RESTRICT dstC, int count) { + SkASSERT(count > 0); + + const SkRadialGradient& radialGradient = static_cast<const SkRadialGradient&>(fShader); + + SkPoint srcPt; + SkMatrix::MapXYProc dstProc = fDstToIndexProc; + TileProc proc = radialGradient.fTileProc; + const SkPMColor* SK_RESTRICT cache = fCache->getCache32(); + int toggle = init_dither_toggle(x, y); + + if (fDstToIndexClass != kPerspective_MatrixClass) { + dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf, + SkIntToScalar(y) + SK_ScalarHalf, &srcPt); + SkScalar sdx = fDstToIndex.getScaleX(); + SkScalar sdy = fDstToIndex.getSkewY(); + + if (fDstToIndexClass == kFixedStepInX_MatrixClass) { + const auto step = fDstToIndex.fixedStepInX(SkIntToScalar(y)); + sdx = step.fX; + sdy = step.fY; + } else { + SkASSERT(fDstToIndexClass == kLinear_MatrixClass); + } + + RadialShadeProc shadeProc = shadeSpan_radial_repeat; + if (SkShader::kClamp_TileMode == radialGradient.fTileMode) { + shadeProc = shadeSpan_radial_clamp2; + } else if (SkShader::kMirror_TileMode == radialGradient.fTileMode) { + shadeProc = shadeSpan_radial_mirror; + } else { + SkASSERT(SkShader::kRepeat_TileMode == radialGradient.fTileMode); + } + (*shadeProc)(srcPt.fX, sdx, srcPt.fY, sdy, dstC, cache, count, toggle); + } else { // perspective case + SkScalar dstX = SkIntToScalar(x); + SkScalar dstY = SkIntToScalar(y); + do { + dstProc(fDstToIndex, dstX, dstY, &srcPt); + unsigned fi = proc(SkScalarToFixed(srcPt.length())); + SkASSERT(fi <= 0xFFFF); + *dstC++ = cache[fi >> SkGradientShaderBase::kCache32Shift]; + dstX += SK_Scalar1; + } while (--count != 0); + } +} + +///////////////////////////////////////////////////////////////////// + +#if SK_SUPPORT_GPU + +#include "SkGr.h" +#include "GrShaderCaps.h" +#include "glsl/GrGLSLFragmentShaderBuilder.h" + +class GrRadialGradient : public GrGradientEffect { +public: + class GLSLRadialProcessor; + + static sk_sp<GrFragmentProcessor> Make(const CreateArgs& args) { + return sk_sp<GrFragmentProcessor>(new GrRadialGradient(args)); + } + + ~GrRadialGradient() override {} + + const char* name() const override { return "Radial Gradient"; } + +private: + GrRadialGradient(const CreateArgs& args) : INHERITED(args, args.fShader->colorsAreOpaque()) { + this->initClassID<GrRadialGradient>(); + } + + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + + virtual void onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const override; + + GR_DECLARE_FRAGMENT_PROCESSOR_TEST; + + typedef GrGradientEffect INHERITED; +}; + +///////////////////////////////////////////////////////////////////// + +class GrRadialGradient::GLSLRadialProcessor : public GrGradientEffect::GLSLProcessor { +public: + GLSLRadialProcessor(const GrProcessor&) {} + ~GLSLRadialProcessor() override {} + + virtual void emitCode(EmitArgs&) override; + + static void GenKey(const GrProcessor& processor, const GrShaderCaps&, GrProcessorKeyBuilder* b) { + b->add32(GenBaseGradientKey(processor)); + } + +private: + typedef GrGradientEffect::GLSLProcessor INHERITED; + +}; + +///////////////////////////////////////////////////////////////////// + +GrGLSLFragmentProcessor* GrRadialGradient::onCreateGLSLInstance() const { + return new GrRadialGradient::GLSLRadialProcessor(*this); +} + +void GrRadialGradient::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const { + GrRadialGradient::GLSLRadialProcessor::GenKey(*this, caps, b); +} + +///////////////////////////////////////////////////////////////////// + +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrRadialGradient); + +#if GR_TEST_UTILS +sk_sp<GrFragmentProcessor> GrRadialGradient::TestCreate(GrProcessorTestData* d) { + sk_sp<SkShader> shader; + do { + RandomGradientParams params(d->fRandom); + SkPoint center = {d->fRandom->nextUScalar1(), d->fRandom->nextUScalar1()}; + SkScalar radius = d->fRandom->nextUScalar1(); + shader = params.fUseColors4f + ? SkGradientShader::MakeRadial(center, radius, params.fColors4f, + params.fColorSpace, params.fStops, + params.fColorCount, params.fTileMode) + : SkGradientShader::MakeRadial(center, radius, params.fColors, + params.fStops, params.fColorCount, + params.fTileMode); + } while (!shader); + GrTest::TestAsFPArgs asFPArgs(d); + sk_sp<GrFragmentProcessor> fp = as_SB(shader)->asFragmentProcessor(asFPArgs.args()); + GrAlwaysAssert(fp); + return fp; +} +#endif + +///////////////////////////////////////////////////////////////////// + +void GrRadialGradient::GLSLRadialProcessor::emitCode(EmitArgs& args) { + const GrRadialGradient& ge = args.fFp.cast<GrRadialGradient>(); + this->emitUniforms(args.fUniformHandler, ge); + SkString t("length("); + t.append(args.fFragBuilder->ensureCoords2D(args.fTransformedCoords[0])); + t.append(")"); + this->emitColor(args.fFragBuilder, + args.fUniformHandler, + args.fShaderCaps, + ge, t.c_str(), + args.fOutputColor, + args.fInputColor, + args.fTexSamplers); +} + +///////////////////////////////////////////////////////////////////// + +sk_sp<GrFragmentProcessor> SkRadialGradient::asFragmentProcessor(const AsFPArgs& args) const { + SkASSERT(args.fContext); + + SkMatrix matrix; + if (!this->getLocalMatrix().invert(&matrix)) { + return nullptr; + } + if (args.fLocalMatrix) { + SkMatrix inv; + if (!args.fLocalMatrix->invert(&inv)) { + return nullptr; + } + matrix.postConcat(inv); + } + matrix.postConcat(fPtsToUnit); + sk_sp<GrColorSpaceXform> colorSpaceXform = GrColorSpaceXform::Make(fColorSpace.get(), + args.fDstColorSpace); + sk_sp<GrFragmentProcessor> inner(GrRadialGradient::Make( + GrGradientEffect::CreateArgs(args.fContext, this, &matrix, fTileMode, + std::move(colorSpaceXform), SkToBool(args.fDstColorSpace)))); + return GrFragmentProcessor::MulOutputByInputAlpha(std::move(inner)); +} + +#endif + +sk_sp<SkShader> SkRadialGradient::onMakeColorSpace(SkColorSpaceXformer* xformer) const { + SkSTArray<8, SkColor> xformedColors(fColorCount); + xformer->apply(xformedColors.begin(), fOrigColors, fColorCount); + return SkGradientShader::MakeRadial(fCenter, fRadius, xformedColors.begin(), fOrigPos, + fColorCount, fTileMode, fGradFlags, + &this->getLocalMatrix()); +} + +bool SkRadialGradient::adjustMatrixAndAppendStages(SkArenaAlloc* alloc, + SkMatrix* matrix, + SkRasterPipeline* p) const { + matrix->postTranslate(-fCenter.fX, -fCenter.fY); + matrix->postScale(1/fRadius, 1/fRadius); + + p->append(SkRasterPipeline::xy_to_radius); + return true; +} + +#ifndef SK_IGNORE_TO_STRING +void SkRadialGradient::toString(SkString* str) const { + str->append("SkRadialGradient: ("); + + str->append("center: ("); + str->appendScalar(fCenter.fX); + str->append(", "); + str->appendScalar(fCenter.fY); + str->append(") radius: "); + str->appendScalar(fRadius); + str->append(" "); + + this->INHERITED::toString(str); + + str->append(")"); +} +#endif diff --git a/src/shaders/gradients/SkRadialGradient.h b/src/shaders/gradients/SkRadialGradient.h new file mode 100644 index 0000000000..69ec4b1285 --- /dev/null +++ b/src/shaders/gradients/SkRadialGradient.h @@ -0,0 +1,53 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkRadialGradient_DEFINED +#define SkRadialGradient_DEFINED + +#include "SkGradientShaderPriv.h" + +class SkRadialGradient : public SkGradientShaderBase { +public: + SkRadialGradient(const SkPoint& center, SkScalar radius, const Descriptor&); + + class RadialGradientContext : public SkGradientShaderBase::GradientShaderBaseContext { + public: + RadialGradientContext(const SkRadialGradient&, const ContextRec&); + + void shadeSpan(int x, int y, SkPMColor dstC[], int count) override; + + private: + typedef SkGradientShaderBase::GradientShaderBaseContext INHERITED; + }; + + GradientType asAGradient(GradientInfo* info) const override; +#if SK_SUPPORT_GPU + sk_sp<GrFragmentProcessor> asFragmentProcessor(const AsFPArgs&) const override; +#endif + + SK_TO_STRING_OVERRIDE() + SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkRadialGradient) + +protected: + SkRadialGradient(SkReadBuffer& buffer); + void flatten(SkWriteBuffer& buffer) const override; + Context* onMakeContext(const ContextRec&, SkArenaAlloc*) const override; + sk_sp<SkShader> onMakeColorSpace(SkColorSpaceXformer* xformer) const override; + + bool adjustMatrixAndAppendStages(SkArenaAlloc* alloc, + SkMatrix* matrix, + SkRasterPipeline* p) const final; + +private: + const SkPoint fCenter; + const SkScalar fRadius; + + friend class SkGradientShader; + typedef SkGradientShaderBase INHERITED; +}; + +#endif diff --git a/src/shaders/gradients/SkSweepGradient.cpp b/src/shaders/gradients/SkSweepGradient.cpp new file mode 100644 index 0000000000..1e583c2ee0 --- /dev/null +++ b/src/shaders/gradients/SkSweepGradient.cpp @@ -0,0 +1,306 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkColorSpaceXformer.h" +#include "SkSweepGradient.h" + +#include <algorithm> +#include <cmath> + +#include "SkPM4fPriv.h" +#include "SkRasterPipeline.h" + +static SkMatrix translate(SkScalar dx, SkScalar dy) { + SkMatrix matrix; + matrix.setTranslate(dx, dy); + return matrix; +} + +SkSweepGradient::SkSweepGradient(SkScalar cx, SkScalar cy, const Descriptor& desc) + : SkGradientShaderBase(desc, translate(-cx, -cy)) + , fCenter(SkPoint::Make(cx, cy)) +{ + // overwrite the tilemode to a canonical value (since sweep ignores it) + fTileMode = SkShader::kClamp_TileMode; +} + +SkShader::GradientType SkSweepGradient::asAGradient(GradientInfo* info) const { + if (info) { + commonAsAGradient(info); + info->fPoint[0] = fCenter; + } + return kSweep_GradientType; +} + +sk_sp<SkFlattenable> SkSweepGradient::CreateProc(SkReadBuffer& buffer) { + DescriptorScope desc; + if (!desc.unflatten(buffer)) { + return nullptr; + } + const SkPoint center = buffer.readPoint(); + return SkGradientShader::MakeSweep(center.x(), center.y(), desc.fColors, + std::move(desc.fColorSpace), desc.fPos, desc.fCount, + desc.fGradFlags, desc.fLocalMatrix); +} + +void SkSweepGradient::flatten(SkWriteBuffer& buffer) const { + this->INHERITED::flatten(buffer); + buffer.writePoint(fCenter); +} + +SkShaderBase::Context* SkSweepGradient::onMakeContext( + const ContextRec& rec, SkArenaAlloc* alloc) const +{ + return CheckedMakeContext<SweepGradientContext>(alloc, *this, rec); +} + +SkSweepGradient::SweepGradientContext::SweepGradientContext( + const SkSweepGradient& shader, const ContextRec& rec) + : INHERITED(shader, rec) {} + +bool SkSweepGradient::isRasterPipelineOnly() const { +#ifdef SK_LEGACY_SWEEP_GRADIENT + return false; +#else + return true; +#endif +} + +// returns angle in a circle [0..2PI) -> [0..255] +static unsigned SkATan2_255(float y, float x) { + // static const float g255Over2PI = 255 / (2 * SK_ScalarPI); + static const float g255Over2PI = 40.584510488433314f; + + float result = sk_float_atan2(y, x); + if (!SkScalarIsFinite(result)) { + return 0; + } + if (result < 0) { + result += 2 * SK_ScalarPI; + } + SkASSERT(result >= 0); + // since our value is always >= 0, we can cast to int, which is faster than + // calling floorf() + int ir = (int)(result * g255Over2PI); + SkASSERT(ir >= 0 && ir <= 255); + return ir; +} + +void SkSweepGradient::SweepGradientContext::shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC, + int count) { + SkMatrix::MapXYProc proc = fDstToIndexProc; + const SkMatrix& matrix = fDstToIndex; + const SkPMColor* SK_RESTRICT cache = fCache->getCache32(); + int toggle = init_dither_toggle(x, y); + SkPoint srcPt; + + if (fDstToIndexClass != kPerspective_MatrixClass) { + proc(matrix, SkIntToScalar(x) + SK_ScalarHalf, + SkIntToScalar(y) + SK_ScalarHalf, &srcPt); + SkScalar dx, fx = srcPt.fX; + SkScalar dy, fy = srcPt.fY; + + if (fDstToIndexClass == kFixedStepInX_MatrixClass) { + const auto step = matrix.fixedStepInX(SkIntToScalar(y) + SK_ScalarHalf); + dx = step.fX; + dy = step.fY; + } else { + SkASSERT(fDstToIndexClass == kLinear_MatrixClass); + dx = matrix.getScaleX(); + dy = matrix.getSkewY(); + } + + for (; count > 0; --count) { + *dstC++ = cache[toggle + SkATan2_255(fy, fx)]; + fx += dx; + fy += dy; + toggle = next_dither_toggle(toggle); + } + } else { // perspective case + for (int stop = x + count; x < stop; x++) { + proc(matrix, SkIntToScalar(x) + SK_ScalarHalf, + SkIntToScalar(y) + SK_ScalarHalf, &srcPt); + *dstC++ = cache[toggle + SkATan2_255(srcPt.fY, srcPt.fX)]; + toggle = next_dither_toggle(toggle); + } + } +} + +///////////////////////////////////////////////////////////////////// + +#if SK_SUPPORT_GPU + +#include "SkGr.h" +#include "GrShaderCaps.h" +#include "gl/GrGLContext.h" +#include "glsl/GrGLSLFragmentShaderBuilder.h" + +class GrSweepGradient : public GrGradientEffect { +public: + class GLSLSweepProcessor; + + static sk_sp<GrFragmentProcessor> Make(const CreateArgs& args) { + return sk_sp<GrFragmentProcessor>(new GrSweepGradient(args)); + } + ~GrSweepGradient() override {} + + const char* name() const override { return "Sweep Gradient"; } + +private: + GrSweepGradient(const CreateArgs& args) : INHERITED(args, args.fShader->colorsAreOpaque()) { + this->initClassID<GrSweepGradient>(); + } + + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + + virtual void onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const override; + + GR_DECLARE_FRAGMENT_PROCESSOR_TEST; + + typedef GrGradientEffect INHERITED; +}; + +///////////////////////////////////////////////////////////////////// + +class GrSweepGradient::GLSLSweepProcessor : public GrGradientEffect::GLSLProcessor { +public: + GLSLSweepProcessor(const GrProcessor&) {} + ~GLSLSweepProcessor() override {} + + virtual void emitCode(EmitArgs&) override; + + static void GenKey(const GrProcessor& processor, const GrShaderCaps&, GrProcessorKeyBuilder* b) { + b->add32(GenBaseGradientKey(processor)); + } + +private: + typedef GrGradientEffect::GLSLProcessor INHERITED; + +}; + +///////////////////////////////////////////////////////////////////// + +GrGLSLFragmentProcessor* GrSweepGradient::onCreateGLSLInstance() const { + return new GrSweepGradient::GLSLSweepProcessor(*this); +} + +void GrSweepGradient::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const { + GrSweepGradient::GLSLSweepProcessor::GenKey(*this, caps, b); +} + + +///////////////////////////////////////////////////////////////////// + +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrSweepGradient); + +#if GR_TEST_UTILS +sk_sp<GrFragmentProcessor> GrSweepGradient::TestCreate(GrProcessorTestData* d) { + SkPoint center = {d->fRandom->nextUScalar1(), d->fRandom->nextUScalar1()}; + + RandomGradientParams params(d->fRandom); + auto shader = params.fUseColors4f ? + SkGradientShader::MakeSweep(center.fX, center.fY, params.fColors4f, params.fColorSpace, + params.fStops, params.fColorCount) : + SkGradientShader::MakeSweep(center.fX, center.fY, params.fColors, + params.fStops, params.fColorCount); + GrTest::TestAsFPArgs asFPArgs(d); + sk_sp<GrFragmentProcessor> fp = as_SB(shader)->asFragmentProcessor(asFPArgs.args()); + GrAlwaysAssert(fp); + return fp; +} +#endif + +///////////////////////////////////////////////////////////////////// + +void GrSweepGradient::GLSLSweepProcessor::emitCode(EmitArgs& args) { + const GrSweepGradient& ge = args.fFp.cast<GrSweepGradient>(); + this->emitUniforms(args.fUniformHandler, ge); + SkString coords2D = args.fFragBuilder->ensureCoords2D(args.fTransformedCoords[0]); + SkString t; + // 0.1591549430918 is 1/(2*pi), used since atan returns values [-pi, pi] + if (args.fShaderCaps->atan2ImplementedAsAtanYOverX()) { + // On some devices they incorrectly implement atan2(y,x) as atan(y/x). In actuality it is + // atan2(y,x) = 2 * atan(y / (sqrt(x^2 + y^2) + x)). So to work around this we pass in + // (sqrt(x^2 + y^2) + x) as the second parameter to atan2 in these cases. We let the device + // handle the undefined behavior of the second paramenter being 0 instead of doing the + // divide ourselves and using atan instead. + t.printf("(2.0 * atan(- %s.y, length(%s) - %s.x) * 0.1591549430918 + 0.5)", + coords2D.c_str(), coords2D.c_str(), coords2D.c_str()); + } else { + t.printf("(atan(- %s.y, - %s.x) * 0.1591549430918 + 0.5)", + coords2D.c_str(), coords2D.c_str()); + } + this->emitColor(args.fFragBuilder, + args.fUniformHandler, + args.fShaderCaps, + ge, t.c_str(), + args.fOutputColor, + args.fInputColor, + args.fTexSamplers); +} + +///////////////////////////////////////////////////////////////////// + +sk_sp<GrFragmentProcessor> SkSweepGradient::asFragmentProcessor(const AsFPArgs& args) const { + + SkMatrix matrix; + if (!this->getLocalMatrix().invert(&matrix)) { + return nullptr; + } + if (args.fLocalMatrix) { + SkMatrix inv; + if (!args.fLocalMatrix->invert(&inv)) { + return nullptr; + } + matrix.postConcat(inv); + } + matrix.postConcat(fPtsToUnit); + + sk_sp<GrColorSpaceXform> colorSpaceXform = GrColorSpaceXform::Make(fColorSpace.get(), + args.fDstColorSpace); + sk_sp<GrFragmentProcessor> inner(GrSweepGradient::Make( + GrGradientEffect::CreateArgs(args.fContext, this, &matrix, SkShader::kClamp_TileMode, + std::move(colorSpaceXform), SkToBool(args.fDstColorSpace)))); + return GrFragmentProcessor::MulOutputByInputAlpha(std::move(inner)); +} + +#endif + +sk_sp<SkShader> SkSweepGradient::onMakeColorSpace(SkColorSpaceXformer* xformer) const { + SkSTArray<8, SkColor> xformedColors(fColorCount); + xformer->apply(xformedColors.begin(), fOrigColors, fColorCount); + return SkGradientShader::MakeSweep(fCenter.fX, fCenter.fY, xformedColors.begin(), fOrigPos, + fColorCount, fGradFlags, &this->getLocalMatrix()); +} + +#ifndef SK_IGNORE_TO_STRING +void SkSweepGradient::toString(SkString* str) const { + str->append("SkSweepGradient: ("); + + str->append("center: ("); + str->appendScalar(fCenter.fX); + str->append(", "); + str->appendScalar(fCenter.fY); + str->append(") "); + + this->INHERITED::toString(str); + + str->append(")"); +} + +bool SkSweepGradient::adjustMatrixAndAppendStages(SkArenaAlloc* alloc, + SkMatrix* matrix, + SkRasterPipeline* p) const { + matrix->postTranslate(-fCenter.fX, -fCenter.fY); + p->append(SkRasterPipeline::xy_to_unit_angle); + + return true; +} + +#endif diff --git a/src/shaders/gradients/SkSweepGradient.h b/src/shaders/gradients/SkSweepGradient.h new file mode 100644 index 0000000000..b7ed7e5bb9 --- /dev/null +++ b/src/shaders/gradients/SkSweepGradient.h @@ -0,0 +1,54 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkSweepGradient_DEFINED +#define SkSweepGradient_DEFINED + +#include "SkGradientShaderPriv.h" + +class SkSweepGradient : public SkGradientShaderBase { +public: + SkSweepGradient(SkScalar cx, SkScalar cy, const Descriptor&); + + class SweepGradientContext : public SkGradientShaderBase::GradientShaderBaseContext { + public: + SweepGradientContext(const SkSweepGradient& shader, const ContextRec&); + + void shadeSpan(int x, int y, SkPMColor dstC[], int count) override; + + private: + typedef SkGradientShaderBase::GradientShaderBaseContext INHERITED; + }; + + GradientType asAGradient(GradientInfo* info) const override; + +#if SK_SUPPORT_GPU + sk_sp<GrFragmentProcessor> asFragmentProcessor(const AsFPArgs&) const override; +#endif + + SK_TO_STRING_OVERRIDE() + SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkSweepGradient) + +protected: + void flatten(SkWriteBuffer& buffer) const override; + Context* onMakeContext(const ContextRec&, SkArenaAlloc*) const override; + sk_sp<SkShader> onMakeColorSpace(SkColorSpaceXformer* xformer) const override; + + bool adjustMatrixAndAppendStages(SkArenaAlloc* alloc, + SkMatrix* matrix, + SkRasterPipeline* p) const final; + + bool isRasterPipelineOnly() const final; + +private: + const SkPoint fCenter; + + friend class SkGradientShader; + typedef SkGradientShaderBase INHERITED; +}; + +#endif diff --git a/src/shaders/gradients/SkTwoPointConicalGradient.cpp b/src/shaders/gradients/SkTwoPointConicalGradient.cpp new file mode 100644 index 0000000000..4549527d51 --- /dev/null +++ b/src/shaders/gradients/SkTwoPointConicalGradient.cpp @@ -0,0 +1,421 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkTwoPointConicalGradient.h" + +struct TwoPtRadialContext { + const TwoPtRadial& fRec; + float fRelX, fRelY; + const float fIncX, fIncY; + float fB; + const float fDB; + + TwoPtRadialContext(const TwoPtRadial& rec, SkScalar fx, SkScalar fy, + SkScalar dfx, SkScalar dfy); + SkFixed nextT(); +}; + +static int valid_divide(float numer, float denom, float* ratio) { + SkASSERT(ratio); + if (0 == denom) { + return 0; + } + *ratio = numer / denom; + return 1; +} + +// Return the number of distinct real roots, and write them into roots[] in +// ascending order +static int find_quad_roots(float A, float B, float C, float roots[2], bool descendingOrder = false) { + SkASSERT(roots); + + if (A == 0) { + return valid_divide(-C, B, roots); + } + + float R = B*B - 4*A*C; + if (R < 0) { + return 0; + } + R = sk_float_sqrt(R); + +#if 1 + float Q = B; + if (Q < 0) { + Q -= R; + } else { + Q += R; + } +#else + // on 10.6 this was much slower than the above branch :( + float Q = B + copysignf(R, B); +#endif + Q *= -0.5f; + if (0 == Q) { + roots[0] = 0; + return 1; + } + + float r0 = Q / A; + float r1 = C / Q; + roots[0] = r0 < r1 ? r0 : r1; + roots[1] = r0 > r1 ? r0 : r1; + if (descendingOrder) { + SkTSwap(roots[0], roots[1]); + } + return 2; +} + +static float lerp(float x, float dx, float t) { + return x + t * dx; +} + +static float sqr(float x) { return x * x; } + +void TwoPtRadial::init(const SkPoint& center0, SkScalar rad0, + const SkPoint& center1, SkScalar rad1, + bool flipped) { + fCenterX = SkScalarToFloat(center0.fX); + fCenterY = SkScalarToFloat(center0.fY); + fDCenterX = SkScalarToFloat(center1.fX) - fCenterX; + fDCenterY = SkScalarToFloat(center1.fY) - fCenterY; + fRadius = SkScalarToFloat(rad0); + fDRadius = SkScalarToFloat(rad1) - fRadius; + + fA = sqr(fDCenterX) + sqr(fDCenterY) - sqr(fDRadius); + fRadius2 = sqr(fRadius); + fRDR = fRadius * fDRadius; + + fFlipped = flipped; +} + +TwoPtRadialContext::TwoPtRadialContext(const TwoPtRadial& rec, SkScalar fx, SkScalar fy, + SkScalar dfx, SkScalar dfy) + : fRec(rec) + , fRelX(SkScalarToFloat(fx) - rec.fCenterX) + , fRelY(SkScalarToFloat(fy) - rec.fCenterY) + , fIncX(SkScalarToFloat(dfx)) + , fIncY(SkScalarToFloat(dfy)) + , fB(-2 * (rec.fDCenterX * fRelX + rec.fDCenterY * fRelY + rec.fRDR)) + , fDB(-2 * (rec.fDCenterX * fIncX + rec.fDCenterY * fIncY)) {} + +SkFixed TwoPtRadialContext::nextT() { + float roots[2]; + + float C = sqr(fRelX) + sqr(fRelY) - fRec.fRadius2; + int countRoots = find_quad_roots(fRec.fA, fB, C, roots, fRec.fFlipped); + + fRelX += fIncX; + fRelY += fIncY; + fB += fDB; + + if (0 == countRoots) { + return TwoPtRadial::kDontDrawT; + } + + // Prefer the bigger t value if both give a radius(t) > 0 + // find_quad_roots returns the values sorted, so we start with the last + float t = roots[countRoots - 1]; + float r = lerp(fRec.fRadius, fRec.fDRadius, t); + if (r < 0) { + t = roots[0]; // might be the same as roots[countRoots-1] + r = lerp(fRec.fRadius, fRec.fDRadius, t); + if (r < 0) { + return TwoPtRadial::kDontDrawT; + } + } + return SkFloatToFixed(t); +} + +typedef void (*TwoPointConicalProc)(TwoPtRadialContext* rec, SkPMColor* dstC, + const SkPMColor* cache, int toggle, int count); + +static void twopoint_clamp(TwoPtRadialContext* rec, SkPMColor* SK_RESTRICT dstC, + const SkPMColor* SK_RESTRICT cache, int toggle, + int count) { + for (; count > 0; --count) { + SkFixed t = rec->nextT(); + if (TwoPtRadial::DontDrawT(t)) { + *dstC++ = 0; + } else { + SkFixed index = SkClampMax(t, 0xFFFF); + SkASSERT(index <= 0xFFFF); + *dstC++ = cache[toggle + + (index >> SkGradientShaderBase::kCache32Shift)]; + } + toggle = next_dither_toggle(toggle); + } +} + +static void twopoint_repeat(TwoPtRadialContext* rec, SkPMColor* SK_RESTRICT dstC, + const SkPMColor* SK_RESTRICT cache, int toggle, + int count) { + for (; count > 0; --count) { + SkFixed t = rec->nextT(); + if (TwoPtRadial::DontDrawT(t)) { + *dstC++ = 0; + } else { + SkFixed index = repeat_tileproc(t); + SkASSERT(index <= 0xFFFF); + *dstC++ = cache[toggle + + (index >> SkGradientShaderBase::kCache32Shift)]; + } + toggle = next_dither_toggle(toggle); + } +} + +static void twopoint_mirror(TwoPtRadialContext* rec, SkPMColor* SK_RESTRICT dstC, + const SkPMColor* SK_RESTRICT cache, int toggle, + int count) { + for (; count > 0; --count) { + SkFixed t = rec->nextT(); + if (TwoPtRadial::DontDrawT(t)) { + *dstC++ = 0; + } else { + SkFixed index = mirror_tileproc(t); + SkASSERT(index <= 0xFFFF); + *dstC++ = cache[toggle + + (index >> SkGradientShaderBase::kCache32Shift)]; + } + toggle = next_dither_toggle(toggle); + } +} + +///////////////////////////////////////////////////////////////////// + +SkTwoPointConicalGradient::SkTwoPointConicalGradient( + const SkPoint& start, SkScalar startRadius, + const SkPoint& end, SkScalar endRadius, + bool flippedGrad, const Descriptor& desc) + : SkGradientShaderBase(desc, SkMatrix::I()) + , fCenter1(start) + , fCenter2(end) + , fRadius1(startRadius) + , fRadius2(endRadius) + , fFlippedGrad(flippedGrad) +{ + // this is degenerate, and should be caught by our caller + SkASSERT(fCenter1 != fCenter2 || fRadius1 != fRadius2); + fRec.init(fCenter1, fRadius1, fCenter2, fRadius2, fFlippedGrad); +} + +bool SkTwoPointConicalGradient::isOpaque() const { + // Because areas outside the cone are left untouched, we cannot treat the + // shader as opaque even if the gradient itself is opaque. + // TODO(junov): Compute whether the cone fills the plane crbug.com/222380 + return false; +} + +SkShaderBase::Context* SkTwoPointConicalGradient::onMakeContext( + const ContextRec& rec, SkArenaAlloc* alloc) const { + return CheckedMakeContext<TwoPointConicalGradientContext>(alloc, *this, rec); +} + +SkTwoPointConicalGradient::TwoPointConicalGradientContext::TwoPointConicalGradientContext( + const SkTwoPointConicalGradient& shader, const ContextRec& rec) + : INHERITED(shader, rec) +{ + // in general, we might discard based on computed-radius, so clear + // this flag (todo: sometimes we can detect that we never discard...) + fFlags &= ~kOpaqueAlpha_Flag; +} + +void SkTwoPointConicalGradient::TwoPointConicalGradientContext::shadeSpan( + int x, int y, SkPMColor* dstCParam, int count) { + const SkTwoPointConicalGradient& twoPointConicalGradient = + static_cast<const SkTwoPointConicalGradient&>(fShader); + + int toggle = init_dither_toggle(x, y); + + SkASSERT(count > 0); + + SkPMColor* SK_RESTRICT dstC = dstCParam; + + SkMatrix::MapXYProc dstProc = fDstToIndexProc; + + const SkPMColor* SK_RESTRICT cache = fCache->getCache32(); + + TwoPointConicalProc shadeProc = twopoint_repeat; + if (SkShader::kClamp_TileMode == twoPointConicalGradient.fTileMode) { + shadeProc = twopoint_clamp; + } else if (SkShader::kMirror_TileMode == twoPointConicalGradient.fTileMode) { + shadeProc = twopoint_mirror; + } else { + SkASSERT(SkShader::kRepeat_TileMode == twoPointConicalGradient.fTileMode); + } + + if (fDstToIndexClass != kPerspective_MatrixClass) { + SkPoint srcPt; + dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf, + SkIntToScalar(y) + SK_ScalarHalf, &srcPt); + SkScalar dx, fx = srcPt.fX; + SkScalar dy, fy = srcPt.fY; + + if (fDstToIndexClass == kFixedStepInX_MatrixClass) { + const auto step = fDstToIndex.fixedStepInX(SkIntToScalar(y)); + dx = step.fX; + dy = step.fY; + } else { + SkASSERT(fDstToIndexClass == kLinear_MatrixClass); + dx = fDstToIndex.getScaleX(); + dy = fDstToIndex.getSkewY(); + } + + TwoPtRadialContext rec(twoPointConicalGradient.fRec, fx, fy, dx, dy); + (*shadeProc)(&rec, dstC, cache, toggle, count); + } else { // perspective case + SkScalar dstX = SkIntToScalar(x) + SK_ScalarHalf; + SkScalar dstY = SkIntToScalar(y) + SK_ScalarHalf; + for (; count > 0; --count) { + SkPoint srcPt; + dstProc(fDstToIndex, dstX, dstY, &srcPt); + TwoPtRadialContext rec(twoPointConicalGradient.fRec, srcPt.fX, srcPt.fY, 0, 0); + (*shadeProc)(&rec, dstC, cache, toggle, 1); + + dstX += SK_Scalar1; + toggle = next_dither_toggle(toggle); + dstC += 1; + } + } +} + +// Returns the original non-sorted version of the gradient +SkShader::GradientType SkTwoPointConicalGradient::asAGradient( + GradientInfo* info) const { + if (info) { + commonAsAGradient(info, fFlippedGrad); + info->fPoint[0] = fCenter1; + info->fPoint[1] = fCenter2; + info->fRadius[0] = fRadius1; + info->fRadius[1] = fRadius2; + if (fFlippedGrad) { + SkTSwap(info->fPoint[0], info->fPoint[1]); + SkTSwap(info->fRadius[0], info->fRadius[1]); + } + } + return kConical_GradientType; +} + +sk_sp<SkFlattenable> SkTwoPointConicalGradient::CreateProc(SkReadBuffer& buffer) { + DescriptorScope desc; + if (!desc.unflatten(buffer)) { + return nullptr; + } + SkPoint c1 = buffer.readPoint(); + SkPoint c2 = buffer.readPoint(); + SkScalar r1 = buffer.readScalar(); + SkScalar r2 = buffer.readScalar(); + + if (buffer.readBool()) { // flipped + SkTSwap(c1, c2); + SkTSwap(r1, r2); + + SkColor4f* colors = desc.mutableColors(); + SkScalar* pos = desc.mutablePos(); + const int last = desc.fCount - 1; + const int half = desc.fCount >> 1; + for (int i = 0; i < half; ++i) { + SkTSwap(colors[i], colors[last - i]); + if (pos) { + SkScalar tmp = pos[i]; + pos[i] = SK_Scalar1 - pos[last - i]; + pos[last - i] = SK_Scalar1 - tmp; + } + } + if (pos) { + if (desc.fCount & 1) { + pos[half] = SK_Scalar1 - pos[half]; + } + } + } + + return SkGradientShader::MakeTwoPointConical(c1, r1, c2, r2, desc.fColors, + std::move(desc.fColorSpace), desc.fPos, + desc.fCount, desc.fTileMode, desc.fGradFlags, + desc.fLocalMatrix); +} + +void SkTwoPointConicalGradient::flatten(SkWriteBuffer& buffer) const { + this->INHERITED::flatten(buffer); + buffer.writePoint(fCenter1); + buffer.writePoint(fCenter2); + buffer.writeScalar(fRadius1); + buffer.writeScalar(fRadius2); + buffer.writeBool(fFlippedGrad); +} + +#if SK_SUPPORT_GPU + +#include "SkGr.h" +#include "SkTwoPointConicalGradient_gpu.h" + +sk_sp<GrFragmentProcessor> SkTwoPointConicalGradient::asFragmentProcessor( + const AsFPArgs& args) const { + SkASSERT(args.fContext); + SkASSERT(fPtsToUnit.isIdentity()); + sk_sp<GrColorSpaceXform> colorSpaceXform = GrColorSpaceXform::Make(fColorSpace.get(), + args.fDstColorSpace); + sk_sp<GrFragmentProcessor> inner(Gr2PtConicalGradientEffect::Make( + GrGradientEffect::CreateArgs(args.fContext, this, args.fLocalMatrix, fTileMode, + std::move(colorSpaceXform), SkToBool(args.fDstColorSpace)))); + return GrFragmentProcessor::MulOutputByInputAlpha(std::move(inner)); +} + +#endif + +sk_sp<SkShader> SkTwoPointConicalGradient::onMakeColorSpace(SkColorSpaceXformer* xformer) const { + SkSTArray<8, SkColor> origColorsStorage(fColorCount); + SkSTArray<8, SkScalar> origPosStorage(fColorCount); + SkSTArray<8, SkColor> xformedColorsStorage(fColorCount); + SkColor* origColors = origColorsStorage.begin(); + SkScalar* origPos = fOrigPos ? origPosStorage.begin() : nullptr; + SkColor* xformedColors = xformedColorsStorage.begin(); + + // Flip if necessary + SkPoint center1 = fFlippedGrad ? fCenter2 : fCenter1; + SkPoint center2 = fFlippedGrad ? fCenter1 : fCenter2; + SkScalar radius1 = fFlippedGrad ? fRadius2 : fRadius1; + SkScalar radius2 = fFlippedGrad ? fRadius1 : fRadius2; + for (int i = 0; i < fColorCount; i++) { + origColors[i] = fFlippedGrad ? fOrigColors[fColorCount - i - 1] : fOrigColors[i]; + if (origPos) { + origPos[i] = fFlippedGrad ? 1.0f - fOrigPos[fColorCount - i - 1] : fOrigPos[i]; + } + } + + xformer->apply(xformedColors, origColors, fColorCount); + return SkGradientShader::MakeTwoPointConical(center1, radius1, center2, radius2, xformedColors, + origPos, fColorCount, fTileMode, fGradFlags, + &this->getLocalMatrix()); +} + + +#ifndef SK_IGNORE_TO_STRING +void SkTwoPointConicalGradient::toString(SkString* str) const { + str->append("SkTwoPointConicalGradient: ("); + + str->append("center1: ("); + str->appendScalar(fCenter1.fX); + str->append(", "); + str->appendScalar(fCenter1.fY); + str->append(") radius1: "); + str->appendScalar(fRadius1); + str->append(" "); + + str->append("center2: ("); + str->appendScalar(fCenter2.fX); + str->append(", "); + str->appendScalar(fCenter2.fY); + str->append(") radius2: "); + str->appendScalar(fRadius2); + str->append(" "); + + this->INHERITED::toString(str); + + str->append(")"); +} +#endif diff --git a/src/shaders/gradients/SkTwoPointConicalGradient.h b/src/shaders/gradients/SkTwoPointConicalGradient.h new file mode 100644 index 0000000000..b32f52c1e0 --- /dev/null +++ b/src/shaders/gradients/SkTwoPointConicalGradient.h @@ -0,0 +1,93 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkTwoPointConicalGradient_DEFINED +#define SkTwoPointConicalGradient_DEFINED + +#include "SkColorSpaceXformer.h" +#include "SkGradientShaderPriv.h" + +// TODO(dominikg): Worth making it truly immutable (i.e. set values in constructor)? +// Should only be initialized once via init(). Immutable afterwards. +struct TwoPtRadial { + enum { + // This value is outside the range SK_FixedMin to SK_FixedMax. + kDontDrawT = 0x80000000 + }; + + float fCenterX, fCenterY; + float fDCenterX, fDCenterY; + float fRadius; + float fDRadius; + float fA; + float fRadius2; + float fRDR; + bool fFlipped; + + void init(const SkPoint& center0, SkScalar rad0, + const SkPoint& center1, SkScalar rad1, + bool flipped); + + static bool DontDrawT(SkFixed t) { + return kDontDrawT == (uint32_t)t; + } +}; + + +class SkTwoPointConicalGradient : public SkGradientShaderBase { + TwoPtRadial fRec; +public: + SkTwoPointConicalGradient(const SkPoint& start, SkScalar startRadius, + const SkPoint& end, SkScalar endRadius, + bool flippedGrad, const Descriptor&); + + class TwoPointConicalGradientContext : public SkGradientShaderBase::GradientShaderBaseContext { + public: + TwoPointConicalGradientContext(const SkTwoPointConicalGradient&, const ContextRec&); + ~TwoPointConicalGradientContext() override {} + + void shadeSpan(int x, int y, SkPMColor dstC[], int count) override; + + private: + typedef SkGradientShaderBase::GradientShaderBaseContext INHERITED; + }; + + SkShader::GradientType asAGradient(GradientInfo* info) const override; +#if SK_SUPPORT_GPU + sk_sp<GrFragmentProcessor> asFragmentProcessor(const AsFPArgs&) const override; +#endif + bool isOpaque() const override; + + SkScalar getCenterX1() const { return SkPoint::Distance(fCenter1, fCenter2); } + SkScalar getStartRadius() const { return fRadius1; } + SkScalar getDiffRadius() const { return fRadius2 - fRadius1; } + const SkPoint& getStartCenter() const { return fCenter1; } + const SkPoint& getEndCenter() const { return fCenter2; } + SkScalar getEndRadius() const { return fRadius2; } + bool isFlippedGrad() const { return fFlippedGrad; } + + SK_TO_STRING_OVERRIDE() + SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkTwoPointConicalGradient) + +protected: + SkTwoPointConicalGradient(SkReadBuffer& buffer); + void flatten(SkWriteBuffer& buffer) const override; + Context* onMakeContext(const ContextRec&, SkArenaAlloc*) const override; + sk_sp<SkShader> onMakeColorSpace(SkColorSpaceXformer* xformer) const override; + +private: + SkPoint fCenter1; + SkPoint fCenter2; + SkScalar fRadius1; + SkScalar fRadius2; + bool fFlippedGrad; + + friend class SkGradientShader; + typedef SkGradientShaderBase INHERITED; +}; + +#endif diff --git a/src/shaders/gradients/SkTwoPointConicalGradient_gpu.cpp b/src/shaders/gradients/SkTwoPointConicalGradient_gpu.cpp new file mode 100644 index 0000000000..8402199362 --- /dev/null +++ b/src/shaders/gradients/SkTwoPointConicalGradient_gpu.cpp @@ -0,0 +1,1341 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#include "SkTwoPointConicalGradient.h" + +#if SK_SUPPORT_GPU +#include "GrCoordTransform.h" +#include "GrPaint.h" +#include "glsl/GrGLSLFragmentShaderBuilder.h" +#include "glsl/GrGLSLProgramDataManager.h" +#include "glsl/GrGLSLUniformHandler.h" +#include "SkTwoPointConicalGradient_gpu.h" + +// For brevity +typedef GrGLSLProgramDataManager::UniformHandle UniformHandle; + +static const SkScalar kErrorTol = 0.00001f; +static const SkScalar kEdgeErrorTol = 5.f * kErrorTol; + +/** + * We have three general cases for 2pt conical gradients. First we always assume that + * the start radius <= end radius. Our first case (kInside_) is when the start circle + * is completely enclosed by the end circle. The second case (kOutside_) is the case + * when the start circle is either completely outside the end circle or the circles + * overlap. The final case (kEdge_) is when the start circle is inside the end one, + * but the two are just barely touching at 1 point along their edges. + */ +enum ConicalType { + kInside_ConicalType, + kOutside_ConicalType, + kEdge_ConicalType, +}; + +////////////////////////////////////////////////////////////////////////////// + +static void set_matrix_edge_conical(const SkTwoPointConicalGradient& shader, + SkMatrix* invLMatrix) { + // Inverse of the current local matrix is passed in then, + // translate to center1, rotate so center2 is on x axis. + const SkPoint& center1 = shader.getStartCenter(); + const SkPoint& center2 = shader.getEndCenter(); + + invLMatrix->postTranslate(-center1.fX, -center1.fY); + + SkPoint diff = center2 - center1; + SkScalar diffLen = diff.length(); + if (0 != diffLen) { + SkScalar invDiffLen = SkScalarInvert(diffLen); + SkMatrix rot; + rot.setSinCos(-invDiffLen * diff.fY, invDiffLen * diff.fX); + invLMatrix->postConcat(rot); + } +} + +class Edge2PtConicalEffect : public GrGradientEffect { +public: + class GLSLEdge2PtConicalProcessor; + + static sk_sp<GrFragmentProcessor> Make(const CreateArgs& args) { + return sk_sp<GrFragmentProcessor>(new Edge2PtConicalEffect(args)); + } + + ~Edge2PtConicalEffect() override {} + + const char* name() const override { + return "Two-Point Conical Gradient Edge Touching"; + } + + // The radial gradient parameters can collapse to a linear (instead of quadratic) equation. + SkScalar center() const { return fCenterX1; } + SkScalar diffRadius() const { return fDiffRadius; } + SkScalar radius() const { return fRadius0; } + +private: + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; + + bool onIsEqual(const GrFragmentProcessor& sBase) const override { + const Edge2PtConicalEffect& s = sBase.cast<Edge2PtConicalEffect>(); + return (INHERITED::onIsEqual(sBase) && + this->fCenterX1 == s.fCenterX1 && + this->fRadius0 == s.fRadius0 && + this->fDiffRadius == s.fDiffRadius); + } + + Edge2PtConicalEffect(const CreateArgs& args) + : INHERITED(args, false /* opaque: draws transparent black outside of the cone. */) { + const SkTwoPointConicalGradient& shader = + *static_cast<const SkTwoPointConicalGradient*>(args.fShader); + fCenterX1 = shader.getCenterX1(); + fRadius0 = shader.getStartRadius(); + fDiffRadius = shader.getDiffRadius(); + this->initClassID<Edge2PtConicalEffect>(); + // We should only be calling this shader if we are degenerate case with touching circles + // When deciding if we are in edge case, we scaled by the end radius for cases when the + // start radius was close to zero, otherwise we scaled by the start radius. In addition + // Our test for the edge case in set_matrix_circle_conical has a higher tolerance so we + // need the sqrt value below + SkASSERT(SkScalarAbs(SkScalarAbs(fDiffRadius) - fCenterX1) < + (fRadius0 < kErrorTol ? shader.getEndRadius() * kEdgeErrorTol : + fRadius0 * sqrt(kEdgeErrorTol))); + + // We pass the linear part of the quadratic as a varying. + // float b = -2.0 * (fCenterX1 * x + fRadius0 * fDiffRadius * z) + fBTransform = this->getCoordTransform(); + SkMatrix& bMatrix = *fBTransform.accessMatrix(); + SkScalar r0dr = fRadius0 * fDiffRadius; + bMatrix[SkMatrix::kMScaleX] = -2 * (fCenterX1 * bMatrix[SkMatrix::kMScaleX] + + r0dr * bMatrix[SkMatrix::kMPersp0]); + bMatrix[SkMatrix::kMSkewX] = -2 * (fCenterX1 * bMatrix[SkMatrix::kMSkewX] + + r0dr * bMatrix[SkMatrix::kMPersp1]); + bMatrix[SkMatrix::kMTransX] = -2 * (fCenterX1 * bMatrix[SkMatrix::kMTransX] + + r0dr * bMatrix[SkMatrix::kMPersp2]); + this->addCoordTransform(&fBTransform); + } + + GR_DECLARE_FRAGMENT_PROCESSOR_TEST; + + // @{ + // Cache of values - these can change arbitrarily, EXCEPT + // we shouldn't change between degenerate and non-degenerate?! + + GrCoordTransform fBTransform; + SkScalar fCenterX1; + SkScalar fRadius0; + SkScalar fDiffRadius; + + // @} + + typedef GrGradientEffect INHERITED; +}; + +class Edge2PtConicalEffect::GLSLEdge2PtConicalProcessor : public GrGradientEffect::GLSLProcessor { +public: + GLSLEdge2PtConicalProcessor(const GrProcessor&); + ~GLSLEdge2PtConicalProcessor() override {} + + virtual void emitCode(EmitArgs&) override; + + static void GenKey(const GrProcessor&, const GrShaderCaps& caps, GrProcessorKeyBuilder* b); + +protected: + void onSetData(const GrGLSLProgramDataManager&, const GrFragmentProcessor&) override; + + UniformHandle fParamUni; + + const char* fVSVaryingName; + const char* fFSVaryingName; + + // @{ + /// Values last uploaded as uniforms + + SkScalar fCachedRadius; + SkScalar fCachedDiffRadius; + + // @} + +private: + typedef GrGradientEffect::GLSLProcessor INHERITED; + +}; + +void Edge2PtConicalEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const { + Edge2PtConicalEffect::GLSLEdge2PtConicalProcessor::GenKey(*this, caps, b); +} + +GrGLSLFragmentProcessor* Edge2PtConicalEffect::onCreateGLSLInstance() const { + return new Edge2PtConicalEffect::GLSLEdge2PtConicalProcessor(*this); +} + +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(Edge2PtConicalEffect); + +/* + * All Two point conical gradient test create functions may occasionally create edge case shaders + */ +#if GR_TEST_UTILS +sk_sp<GrFragmentProcessor> Edge2PtConicalEffect::TestCreate(GrProcessorTestData* d) { + SkPoint center1 = {d->fRandom->nextUScalar1(), d->fRandom->nextUScalar1()}; + SkScalar radius1 = d->fRandom->nextUScalar1(); + SkPoint center2; + SkScalar radius2; + do { + center2.set(d->fRandom->nextUScalar1(), d->fRandom->nextUScalar1()); + // If the circles are identical the factory will give us an empty shader. + // This will happen if we pick identical centers + } while (center1 == center2); + + // Below makes sure that circle one is contained within circle two + // and both circles are touching on an edge + SkPoint diff = center2 - center1; + SkScalar diffLen = diff.length(); + radius2 = radius1 + diffLen; + + RandomGradientParams params(d->fRandom); + auto shader = params.fUseColors4f ? + SkGradientShader::MakeTwoPointConical(center1, radius1, center2, radius2, + params.fColors4f, params.fColorSpace, params.fStops, + params.fColorCount, params.fTileMode) : + SkGradientShader::MakeTwoPointConical(center1, radius1, center2, radius2, + params.fColors, params.fStops, + params.fColorCount, params.fTileMode); + GrTest::TestAsFPArgs asFPArgs(d); + sk_sp<GrFragmentProcessor> fp = as_SB(shader)->asFragmentProcessor(asFPArgs.args()); + GrAlwaysAssert(fp); + return fp; +} +#endif + +Edge2PtConicalEffect::GLSLEdge2PtConicalProcessor::GLSLEdge2PtConicalProcessor(const GrProcessor&) + : fVSVaryingName(nullptr) + , fFSVaryingName(nullptr) + , fCachedRadius(-SK_ScalarMax) + , fCachedDiffRadius(-SK_ScalarMax) {} + +void Edge2PtConicalEffect::GLSLEdge2PtConicalProcessor::emitCode(EmitArgs& args) { + const Edge2PtConicalEffect& ge = args.fFp.cast<Edge2PtConicalEffect>(); + GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; + this->emitUniforms(uniformHandler, ge); + fParamUni = uniformHandler->addUniform(kFragment_GrShaderFlag, + kVec3f_GrSLType, kDefault_GrSLPrecision, + "Conical2FSParams"); + + SkString cName("c"); + SkString tName("t"); + SkString p0; // start radius + SkString p1; // start radius squared + SkString p2; // difference in radii (r1 - r0) + + + p0.appendf("%s.x", uniformHandler->getUniformVariable(fParamUni).getName().c_str()); + p1.appendf("%s.y", uniformHandler->getUniformVariable(fParamUni).getName().c_str()); + p2.appendf("%s.z", uniformHandler->getUniformVariable(fParamUni).getName().c_str()); + + // We interpolate the linear component in coords[1]. + SkASSERT(args.fTransformedCoords[0].getType() == args.fTransformedCoords[1].getType()); + const char* coords2D; + SkString bVar; + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + if (kVec3f_GrSLType == args.fTransformedCoords[0].getType()) { + fragBuilder->codeAppendf("\tvec3 interpolants = vec3(%s.xy / %s.z, %s.x / %s.z);\n", + args.fTransformedCoords[0].c_str(), + args.fTransformedCoords[0].c_str(), + args.fTransformedCoords[1].c_str(), + args.fTransformedCoords[1].c_str()); + coords2D = "interpolants.xy"; + bVar = "interpolants.z"; + } else { + coords2D = args.fTransformedCoords[0].c_str(); + bVar.printf("%s.x", args.fTransformedCoords[1].c_str()); + } + + // output will default to transparent black (we simply won't write anything + // else to it if invalid, instead of discarding or returning prematurely) + fragBuilder->codeAppendf("\t%s = vec4(0.0,0.0,0.0,0.0);\n", args.fOutputColor); + + // c = (x^2)+(y^2) - params[1] + fragBuilder->codeAppendf("\tfloat %s = dot(%s, %s) - %s;\n", + cName.c_str(), coords2D, coords2D, p1.c_str()); + + // linear case: t = -c/b + fragBuilder->codeAppendf("\tfloat %s = -(%s / %s);\n", tName.c_str(), + cName.c_str(), bVar.c_str()); + + // if r(t) > 0, then t will be the x coordinate + fragBuilder->codeAppendf("\tif (%s * %s + %s > 0.0) {\n", tName.c_str(), + p2.c_str(), p0.c_str()); + fragBuilder->codeAppend("\t"); + this->emitColor(fragBuilder, + uniformHandler, + args.fShaderCaps, + ge, + tName.c_str(), + args.fOutputColor, + args.fInputColor, + args.fTexSamplers); + fragBuilder->codeAppend("\t}\n"); +} + +void Edge2PtConicalEffect::GLSLEdge2PtConicalProcessor::onSetData( + const GrGLSLProgramDataManager& pdman, const GrFragmentProcessor& processor) { + INHERITED::onSetData(pdman, processor); + const Edge2PtConicalEffect& data = processor.cast<Edge2PtConicalEffect>(); + SkScalar radius0 = data.radius(); + SkScalar diffRadius = data.diffRadius(); + + if (fCachedRadius != radius0 || + fCachedDiffRadius != diffRadius) { + + pdman.set3f(fParamUni, radius0, radius0 * radius0, diffRadius); + fCachedRadius = radius0; + fCachedDiffRadius = diffRadius; + } +} + +void Edge2PtConicalEffect::GLSLEdge2PtConicalProcessor::GenKey(const GrProcessor& processor, + const GrShaderCaps&, GrProcessorKeyBuilder* b) { + b->add32(GenBaseGradientKey(processor)); +} + +////////////////////////////////////////////////////////////////////////////// +// Focal Conical Gradients +////////////////////////////////////////////////////////////////////////////// + +static ConicalType set_matrix_focal_conical(const SkTwoPointConicalGradient& shader, + SkMatrix* invLMatrix, SkScalar* focalX) { + // Inverse of the current local matrix is passed in then, + // translate, scale, and rotate such that endCircle is unit circle on x-axis, + // and focal point is at the origin. + ConicalType conicalType; + const SkPoint& focal = shader.getStartCenter(); + const SkPoint& centerEnd = shader.getEndCenter(); + SkScalar radius = shader.getEndRadius(); + SkScalar invRadius = 1.f / radius; + + SkMatrix matrix; + + matrix.setTranslate(-centerEnd.fX, -centerEnd.fY); + matrix.postScale(invRadius, invRadius); + + SkPoint focalTrans; + matrix.mapPoints(&focalTrans, &focal, 1); + *focalX = focalTrans.length(); + + if (0.f != *focalX) { + SkScalar invFocalX = SkScalarInvert(*focalX); + SkMatrix rot; + rot.setSinCos(-invFocalX * focalTrans.fY, invFocalX * focalTrans.fX); + matrix.postConcat(rot); + } + + matrix.postTranslate(-(*focalX), 0.f); + + // If the focal point is touching the edge of the circle it will + // cause a degenerate case that must be handled separately + // kEdgeErrorTol = 5 * kErrorTol was picked after manual testing the + // stability trade off versus the linear approx used in the Edge Shader + if (SkScalarAbs(1.f - (*focalX)) < kEdgeErrorTol) { + return kEdge_ConicalType; + } + + // Scale factor 1 / (1 - focalX * focalX) + SkScalar oneMinusF2 = 1.f - *focalX * *focalX; + SkScalar s = SkScalarInvert(oneMinusF2); + + + if (s >= 0.f) { + conicalType = kInside_ConicalType; + matrix.postScale(s, s * SkScalarSqrt(oneMinusF2)); + } else { + conicalType = kOutside_ConicalType; + matrix.postScale(s, s); + } + + invLMatrix->postConcat(matrix); + + return conicalType; +} + +////////////////////////////////////////////////////////////////////////////// + +class FocalOutside2PtConicalEffect : public GrGradientEffect { +public: + class GLSLFocalOutside2PtConicalProcessor; + + static sk_sp<GrFragmentProcessor> Make(const CreateArgs& args, SkScalar focalX) { + return sk_sp<GrFragmentProcessor>( + new FocalOutside2PtConicalEffect(args, focalX)); + } + + ~FocalOutside2PtConicalEffect() override {} + + const char* name() const override { + return "Two-Point Conical Gradient Focal Outside"; + } + + bool isFlipped() const { return fIsFlipped; } + SkScalar focal() const { return fFocalX; } + +private: + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; + + bool onIsEqual(const GrFragmentProcessor& sBase) const override { + const FocalOutside2PtConicalEffect& s = sBase.cast<FocalOutside2PtConicalEffect>(); + return (INHERITED::onIsEqual(sBase) && + this->fFocalX == s.fFocalX && + this->fIsFlipped == s.fIsFlipped); + } + + static bool IsFlipped(const CreateArgs& args) { + // eww. + return static_cast<const SkTwoPointConicalGradient*>(args.fShader)->isFlippedGrad(); + } + + FocalOutside2PtConicalEffect(const CreateArgs& args, SkScalar focalX) + : INHERITED(args, false /* opaque: draws transparent black outside of the cone. */) + , fFocalX(focalX) + , fIsFlipped(IsFlipped(args)) { + this->initClassID<FocalOutside2PtConicalEffect>(); + } + + GR_DECLARE_FRAGMENT_PROCESSOR_TEST; + + SkScalar fFocalX; + bool fIsFlipped; + + typedef GrGradientEffect INHERITED; +}; + +class FocalOutside2PtConicalEffect::GLSLFocalOutside2PtConicalProcessor + : public GrGradientEffect::GLSLProcessor { +public: + GLSLFocalOutside2PtConicalProcessor(const GrProcessor&); + ~GLSLFocalOutside2PtConicalProcessor() override {} + + virtual void emitCode(EmitArgs&) override; + + static void GenKey(const GrProcessor&, const GrShaderCaps& caps, GrProcessorKeyBuilder* b); + +protected: + void onSetData(const GrGLSLProgramDataManager&, const GrFragmentProcessor&) override; + + UniformHandle fParamUni; + + const char* fVSVaryingName; + const char* fFSVaryingName; + + bool fIsFlipped; + + // @{ + /// Values last uploaded as uniforms + + SkScalar fCachedFocal; + + // @} + +private: + typedef GrGradientEffect::GLSLProcessor INHERITED; + +}; + +void FocalOutside2PtConicalEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const { + FocalOutside2PtConicalEffect::GLSLFocalOutside2PtConicalProcessor::GenKey(*this, caps, b); +} + +GrGLSLFragmentProcessor* FocalOutside2PtConicalEffect::onCreateGLSLInstance() const { + return new FocalOutside2PtConicalEffect::GLSLFocalOutside2PtConicalProcessor(*this); +} + +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(FocalOutside2PtConicalEffect); + +/* + * All Two point conical gradient test create functions may occasionally create edge case shaders + */ +#if GR_TEST_UTILS +sk_sp<GrFragmentProcessor> FocalOutside2PtConicalEffect::TestCreate(GrProcessorTestData* d) { + SkPoint center1 = {d->fRandom->nextUScalar1(), d->fRandom->nextUScalar1()}; + SkScalar radius1 = 0.f; + SkPoint center2; + SkScalar radius2; + do { + center2.set(d->fRandom->nextUScalar1(), d->fRandom->nextUScalar1()); + // Need to make sure the centers are not the same or else focal point will be inside + } while (center1 == center2); + + SkPoint diff = center2 - center1; + SkScalar diffLen = diff.length(); + // Below makes sure that the focal point is not contained within circle two + radius2 = d->fRandom->nextRangeF(0.f, diffLen); + + RandomGradientParams params(d->fRandom); + auto shader = params.fUseColors4f ? + SkGradientShader::MakeTwoPointConical(center1, radius1, center2, radius2, + params.fColors4f, params.fColorSpace, params.fStops, + params.fColorCount, params.fTileMode) : + SkGradientShader::MakeTwoPointConical(center1, radius1, center2, radius2, + params.fColors, params.fStops, + params.fColorCount, params.fTileMode); + GrTest::TestAsFPArgs asFPArgs(d); + sk_sp<GrFragmentProcessor> fp = as_SB(shader)->asFragmentProcessor(asFPArgs.args()); + GrAlwaysAssert(fp); + return fp; +} +#endif + +FocalOutside2PtConicalEffect::GLSLFocalOutside2PtConicalProcessor + ::GLSLFocalOutside2PtConicalProcessor(const GrProcessor& processor) + : fVSVaryingName(nullptr) + , fFSVaryingName(nullptr) + , fCachedFocal(SK_ScalarMax) { + const FocalOutside2PtConicalEffect& data = processor.cast<FocalOutside2PtConicalEffect>(); + fIsFlipped = data.isFlipped(); +} + +void FocalOutside2PtConicalEffect::GLSLFocalOutside2PtConicalProcessor::emitCode(EmitArgs& args) { + const FocalOutside2PtConicalEffect& ge = args.fFp.cast<FocalOutside2PtConicalEffect>(); + GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; + this->emitUniforms(uniformHandler, ge); + fParamUni = uniformHandler->addUniform(kFragment_GrShaderFlag, + kVec2f_GrSLType, kDefault_GrSLPrecision, + "Conical2FSParams"); + SkString tName("t"); + SkString p0; // focalX + SkString p1; // 1 - focalX * focalX + + p0.appendf("%s.x", uniformHandler->getUniformVariable(fParamUni).getName().c_str()); + p1.appendf("%s.y", uniformHandler->getUniformVariable(fParamUni).getName().c_str()); + + // if we have a vec3 from being in perspective, convert it to a vec2 first + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + SkString coords2DString = fragBuilder->ensureCoords2D(args.fTransformedCoords[0]); + const char* coords2D = coords2DString.c_str(); + + // t = p.x * focal.x +/- sqrt(p.x^2 + (1 - focal.x^2) * p.y^2) + + // output will default to transparent black (we simply won't write anything + // else to it if invalid, instead of discarding or returning prematurely) + fragBuilder->codeAppendf("\t%s = vec4(0.0,0.0,0.0,0.0);\n", args.fOutputColor); + + fragBuilder->codeAppendf("\tfloat xs = %s.x * %s.x;\n", coords2D, coords2D); + fragBuilder->codeAppendf("\tfloat ys = %s.y * %s.y;\n", coords2D, coords2D); + fragBuilder->codeAppendf("\tfloat d = xs + %s * ys;\n", p1.c_str()); + + // Must check to see if we flipped the circle order (to make sure start radius < end radius) + // If so we must also flip sign on sqrt + if (!fIsFlipped) { + fragBuilder->codeAppendf("\tfloat %s = %s.x * %s + sqrt(d);\n", tName.c_str(), + coords2D, p0.c_str()); + } else { + fragBuilder->codeAppendf("\tfloat %s = %s.x * %s - sqrt(d);\n", tName.c_str(), + coords2D, p0.c_str()); + } + + fragBuilder->codeAppendf("\tif (%s >= 0.0 && d >= 0.0) {\n", tName.c_str()); + fragBuilder->codeAppend("\t\t"); + this->emitColor(fragBuilder, + uniformHandler, + args.fShaderCaps, + ge, + tName.c_str(), + args.fOutputColor, + args.fInputColor, + args.fTexSamplers); + fragBuilder->codeAppend("\t}\n"); +} + +void FocalOutside2PtConicalEffect::GLSLFocalOutside2PtConicalProcessor::onSetData( + const GrGLSLProgramDataManager& pdman, const GrFragmentProcessor& processor) { + INHERITED::onSetData(pdman, processor); + const FocalOutside2PtConicalEffect& data = processor.cast<FocalOutside2PtConicalEffect>(); + SkASSERT(data.isFlipped() == fIsFlipped); + SkScalar focal = data.focal(); + + if (fCachedFocal != focal) { + SkScalar oneMinus2F = 1.f - focal * focal; + + pdman.set2f(fParamUni, SkScalarToFloat(focal), SkScalarToFloat(oneMinus2F)); + fCachedFocal = focal; + } +} + +void FocalOutside2PtConicalEffect::GLSLFocalOutside2PtConicalProcessor::GenKey( + const GrProcessor& processor, + const GrShaderCaps&, GrProcessorKeyBuilder* b) { + uint32_t* key = b->add32n(2); + key[0] = GenBaseGradientKey(processor); + key[1] = processor.cast<FocalOutside2PtConicalEffect>().isFlipped(); +} + +////////////////////////////////////////////////////////////////////////////// + +class FocalInside2PtConicalEffect : public GrGradientEffect { +public: + class GLSLFocalInside2PtConicalProcessor; + + static sk_sp<GrFragmentProcessor> Make(const CreateArgs& args, SkScalar focalX) { + return sk_sp<GrFragmentProcessor>( + new FocalInside2PtConicalEffect(args, focalX)); + } + + ~FocalInside2PtConicalEffect() override {} + + const char* name() const override { + return "Two-Point Conical Gradient Focal Inside"; + } + + SkScalar focal() const { return fFocalX; } + + typedef FocalInside2PtConicalEffect::GLSLFocalInside2PtConicalProcessor GLSLProcessor; + +private: + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; + + bool onIsEqual(const GrFragmentProcessor& sBase) const override { + const FocalInside2PtConicalEffect& s = sBase.cast<FocalInside2PtConicalEffect>(); + return (INHERITED::onIsEqual(sBase) && + this->fFocalX == s.fFocalX); + } + + FocalInside2PtConicalEffect(const CreateArgs& args, SkScalar focalX) + : INHERITED(args, args.fShader->colorsAreOpaque()), fFocalX(focalX) { + this->initClassID<FocalInside2PtConicalEffect>(); + } + + GR_DECLARE_FRAGMENT_PROCESSOR_TEST; + + SkScalar fFocalX; + + typedef GrGradientEffect INHERITED; +}; + +class FocalInside2PtConicalEffect::GLSLFocalInside2PtConicalProcessor + : public GrGradientEffect::GLSLProcessor { +public: + GLSLFocalInside2PtConicalProcessor(const GrProcessor&); + ~GLSLFocalInside2PtConicalProcessor() override {} + + virtual void emitCode(EmitArgs&) override; + + static void GenKey(const GrProcessor&, const GrShaderCaps& caps, GrProcessorKeyBuilder* b); + +protected: + void onSetData(const GrGLSLProgramDataManager&, const GrFragmentProcessor&) override; + + UniformHandle fFocalUni; + + const char* fVSVaryingName; + const char* fFSVaryingName; + + // @{ + /// Values last uploaded as uniforms + + SkScalar fCachedFocal; + + // @} + +private: + typedef GrGradientEffect::GLSLProcessor INHERITED; + +}; + +void FocalInside2PtConicalEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const { + FocalInside2PtConicalEffect::GLSLFocalInside2PtConicalProcessor::GenKey(*this, caps, b); +} + +GrGLSLFragmentProcessor* FocalInside2PtConicalEffect::onCreateGLSLInstance() const { + return new FocalInside2PtConicalEffect::GLSLFocalInside2PtConicalProcessor(*this); +} + +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(FocalInside2PtConicalEffect); + +/* + * All Two point conical gradient test create functions may occasionally create edge case shaders + */ +#if GR_TEST_UTILS +sk_sp<GrFragmentProcessor> FocalInside2PtConicalEffect::TestCreate(GrProcessorTestData* d) { + SkPoint center1 = {d->fRandom->nextUScalar1(), d->fRandom->nextUScalar1()}; + SkScalar radius1 = 0.f; + SkPoint center2; + SkScalar radius2; + do { + center2.set(d->fRandom->nextUScalar1(), d->fRandom->nextUScalar1()); + // Below makes sure radius2 is larger enouch such that the focal point + // is inside the end circle + SkScalar increase = d->fRandom->nextUScalar1(); + SkPoint diff = center2 - center1; + SkScalar diffLen = diff.length(); + radius2 = diffLen + increase; + // If the circles are identical the factory will give us an empty shader. + } while (radius1 == radius2 && center1 == center2); + + RandomGradientParams params(d->fRandom); + auto shader = params.fUseColors4f ? + SkGradientShader::MakeTwoPointConical(center1, radius1, center2, radius2, + params.fColors4f, params.fColorSpace, params.fStops, + params.fColorCount, params.fTileMode) : + SkGradientShader::MakeTwoPointConical(center1, radius1, center2, radius2, + params.fColors, params.fStops, + params.fColorCount, params.fTileMode); + GrTest::TestAsFPArgs asFPArgs(d); + sk_sp<GrFragmentProcessor> fp = as_SB(shader)->asFragmentProcessor(asFPArgs.args()); + GrAlwaysAssert(fp); + return fp; +} +#endif + +FocalInside2PtConicalEffect::GLSLFocalInside2PtConicalProcessor + ::GLSLFocalInside2PtConicalProcessor(const GrProcessor&) + : fVSVaryingName(nullptr) + , fFSVaryingName(nullptr) + , fCachedFocal(SK_ScalarMax) {} + +void FocalInside2PtConicalEffect::GLSLFocalInside2PtConicalProcessor::emitCode(EmitArgs& args) { + const FocalInside2PtConicalEffect& ge = args.fFp.cast<FocalInside2PtConicalEffect>(); + GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; + this->emitUniforms(uniformHandler, ge); + fFocalUni = uniformHandler->addUniform(kFragment_GrShaderFlag, + kFloat_GrSLType, kDefault_GrSLPrecision, + "Conical2FSParams"); + SkString tName("t"); + + // this is the distance along x-axis from the end center to focal point in + // transformed coordinates + GrShaderVar focal = uniformHandler->getUniformVariable(fFocalUni); + + // if we have a vec3 from being in perspective, convert it to a vec2 first + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + SkString coords2DString = fragBuilder->ensureCoords2D(args.fTransformedCoords[0]); + const char* coords2D = coords2DString.c_str(); + + // t = p.x * focalX + length(p) + fragBuilder->codeAppendf("\tfloat %s = %s.x * %s + length(%s);\n", tName.c_str(), + coords2D, focal.c_str(), coords2D); + + this->emitColor(fragBuilder, + uniformHandler, + args.fShaderCaps, + ge, + tName.c_str(), + args.fOutputColor, + args.fInputColor, + args.fTexSamplers); +} + +void FocalInside2PtConicalEffect::GLSLFocalInside2PtConicalProcessor::onSetData( + const GrGLSLProgramDataManager& pdman, const GrFragmentProcessor& processor) { + INHERITED::onSetData(pdman, processor); + const FocalInside2PtConicalEffect& data = processor.cast<FocalInside2PtConicalEffect>(); + SkScalar focal = data.focal(); + + if (fCachedFocal != focal) { + pdman.set1f(fFocalUni, SkScalarToFloat(focal)); + fCachedFocal = focal; + } +} + +void FocalInside2PtConicalEffect::GLSLFocalInside2PtConicalProcessor::GenKey( + const GrProcessor& processor, + const GrShaderCaps&, GrProcessorKeyBuilder* b) { + b->add32(GenBaseGradientKey(processor)); +} + +////////////////////////////////////////////////////////////////////////////// +// Circle Conical Gradients +////////////////////////////////////////////////////////////////////////////// + +struct CircleConicalInfo { + SkPoint fCenterEnd; + SkScalar fA; + SkScalar fB; + SkScalar fC; +}; + +// Returns focal distance along x-axis in transformed coords +static ConicalType set_matrix_circle_conical(const SkTwoPointConicalGradient& shader, + SkMatrix* invLMatrix, CircleConicalInfo* info) { + // Inverse of the current local matrix is passed in then, + // translate and scale such that start circle is on the origin and has radius 1 + const SkPoint& centerStart = shader.getStartCenter(); + const SkPoint& centerEnd = shader.getEndCenter(); + SkScalar radiusStart = shader.getStartRadius(); + SkScalar radiusEnd = shader.getEndRadius(); + + SkMatrix matrix; + + matrix.setTranslate(-centerStart.fX, -centerStart.fY); + + SkScalar invStartRad = 1.f / radiusStart; + matrix.postScale(invStartRad, invStartRad); + + radiusEnd /= radiusStart; + + SkPoint centerEndTrans; + matrix.mapPoints(¢erEndTrans, ¢erEnd, 1); + + SkScalar A = centerEndTrans.fX * centerEndTrans.fX + centerEndTrans.fY * centerEndTrans.fY + - radiusEnd * radiusEnd + 2 * radiusEnd - 1; + + // Check to see if start circle is inside end circle with edges touching. + // If touching we return that it is of kEdge_ConicalType, and leave the matrix setting + // to the edge shader. kEdgeErrorTol = 5 * kErrorTol was picked after manual testing + // so that C = 1 / A is stable, and the linear approximation used in the Edge shader is + // still accurate. + if (SkScalarAbs(A) < kEdgeErrorTol) { + return kEdge_ConicalType; + } + + SkScalar C = 1.f / A; + SkScalar B = (radiusEnd - 1.f) * C; + + matrix.postScale(C, C); + + invLMatrix->postConcat(matrix); + + info->fCenterEnd = centerEndTrans; + info->fA = A; + info->fB = B; + info->fC = C; + + // if A ends up being negative, the start circle is contained completely inside the end cirlce + if (A < 0.f) { + return kInside_ConicalType; + } + return kOutside_ConicalType; +} + +class CircleInside2PtConicalEffect : public GrGradientEffect { +public: + class GLSLCircleInside2PtConicalProcessor; + + static sk_sp<GrFragmentProcessor> Make(const CreateArgs& args, const CircleConicalInfo& info) { + return sk_sp<GrFragmentProcessor>( + new CircleInside2PtConicalEffect(args, info)); + } + + ~CircleInside2PtConicalEffect() override {} + + const char* name() const override { return "Two-Point Conical Gradient Inside"; } + + SkScalar centerX() const { return fInfo.fCenterEnd.fX; } + SkScalar centerY() const { return fInfo.fCenterEnd.fY; } + SkScalar A() const { return fInfo.fA; } + SkScalar B() const { return fInfo.fB; } + SkScalar C() const { return fInfo.fC; } + +private: + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + + virtual void onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const override; + + bool onIsEqual(const GrFragmentProcessor& sBase) const override { + const CircleInside2PtConicalEffect& s = sBase.cast<CircleInside2PtConicalEffect>(); + return (INHERITED::onIsEqual(sBase) && + this->fInfo.fCenterEnd == s.fInfo.fCenterEnd && + this->fInfo.fA == s.fInfo.fA && + this->fInfo.fB == s.fInfo.fB && + this->fInfo.fC == s.fInfo.fC); + } + + CircleInside2PtConicalEffect(const CreateArgs& args, const CircleConicalInfo& info) + : INHERITED(args, args.fShader->colorsAreOpaque()), fInfo(info) { + this->initClassID<CircleInside2PtConicalEffect>(); + } + + GR_DECLARE_FRAGMENT_PROCESSOR_TEST; + + const CircleConicalInfo fInfo; + + typedef GrGradientEffect INHERITED; +}; + +class CircleInside2PtConicalEffect::GLSLCircleInside2PtConicalProcessor + : public GrGradientEffect::GLSLProcessor { +public: + GLSLCircleInside2PtConicalProcessor(const GrProcessor&); + ~GLSLCircleInside2PtConicalProcessor() override {} + + virtual void emitCode(EmitArgs&) override; + + static void GenKey(const GrProcessor&, const GrShaderCaps& caps, GrProcessorKeyBuilder* b); + +protected: + void onSetData(const GrGLSLProgramDataManager&, const GrFragmentProcessor&) override; + + UniformHandle fCenterUni; + UniformHandle fParamUni; + + const char* fVSVaryingName; + const char* fFSVaryingName; + + // @{ + /// Values last uploaded as uniforms + + SkScalar fCachedCenterX; + SkScalar fCachedCenterY; + SkScalar fCachedA; + SkScalar fCachedB; + SkScalar fCachedC; + + // @} + +private: + typedef GrGradientEffect::GLSLProcessor INHERITED; + +}; + +void CircleInside2PtConicalEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const { + CircleInside2PtConicalEffect::GLSLCircleInside2PtConicalProcessor::GenKey(*this, caps, b); +} + +GrGLSLFragmentProcessor* CircleInside2PtConicalEffect::onCreateGLSLInstance() const { + return new CircleInside2PtConicalEffect::GLSLCircleInside2PtConicalProcessor(*this); +} + +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(CircleInside2PtConicalEffect); + +/* + * All Two point conical gradient test create functions may occasionally create edge case shaders + */ +#if GR_TEST_UTILS +sk_sp<GrFragmentProcessor> CircleInside2PtConicalEffect::TestCreate(GrProcessorTestData* d) { + SkPoint center1 = {d->fRandom->nextUScalar1(), d->fRandom->nextUScalar1()}; + SkScalar radius1 = d->fRandom->nextUScalar1() + 0.0001f; // make sure radius1 != 0 + SkPoint center2; + SkScalar radius2; + do { + center2.set(d->fRandom->nextUScalar1(), d->fRandom->nextUScalar1()); + // Below makes sure that circle one is contained within circle two + SkScalar increase = d->fRandom->nextUScalar1(); + SkPoint diff = center2 - center1; + SkScalar diffLen = diff.length(); + radius2 = radius1 + diffLen + increase; + // If the circles are identical the factory will give us an empty shader. + } while (radius1 == radius2 && center1 == center2); + + RandomGradientParams params(d->fRandom); + auto shader = params.fUseColors4f ? + SkGradientShader::MakeTwoPointConical(center1, radius1, center2, radius2, + params.fColors4f, params.fColorSpace, params.fStops, + params.fColorCount, params.fTileMode) : + SkGradientShader::MakeTwoPointConical(center1, radius1, center2, radius2, + params.fColors, params.fStops, + params.fColorCount, params.fTileMode); + GrTest::TestAsFPArgs asFPArgs(d); + sk_sp<GrFragmentProcessor> fp = as_SB(shader)->asFragmentProcessor(asFPArgs.args()); + GrAlwaysAssert(fp); + return fp; +} +#endif + +CircleInside2PtConicalEffect::GLSLCircleInside2PtConicalProcessor + ::GLSLCircleInside2PtConicalProcessor(const GrProcessor& processor) + : fVSVaryingName(nullptr) + , fFSVaryingName(nullptr) + , fCachedCenterX(SK_ScalarMax) + , fCachedCenterY(SK_ScalarMax) + , fCachedA(SK_ScalarMax) + , fCachedB(SK_ScalarMax) + , fCachedC(SK_ScalarMax) {} + +void CircleInside2PtConicalEffect::GLSLCircleInside2PtConicalProcessor::emitCode(EmitArgs& args) { + const CircleInside2PtConicalEffect& ge = args.fFp.cast<CircleInside2PtConicalEffect>(); + GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; + this->emitUniforms(uniformHandler, ge); + fCenterUni = uniformHandler->addUniform(kFragment_GrShaderFlag, + kVec2f_GrSLType, kDefault_GrSLPrecision, + "Conical2FSCenter"); + fParamUni = uniformHandler->addUniform(kFragment_GrShaderFlag, + kVec3f_GrSLType, kDefault_GrSLPrecision, + "Conical2FSParams"); + SkString tName("t"); + + GrShaderVar center = uniformHandler->getUniformVariable(fCenterUni); + // params.x = A + // params.y = B + // params.z = C + GrShaderVar params = uniformHandler->getUniformVariable(fParamUni); + + // if we have a vec3 from being in perspective, convert it to a vec2 first + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + SkString coords2DString = fragBuilder->ensureCoords2D(args.fTransformedCoords[0]); + const char* coords2D = coords2DString.c_str(); + + // p = coords2D + // e = center end + // r = radius end + // A = dot(e, e) - r^2 + 2 * r - 1 + // B = (r -1) / A + // C = 1 / A + // d = dot(e, p) + B + // t = d +/- sqrt(d^2 - A * dot(p, p) + C) + fragBuilder->codeAppendf("\tfloat pDotp = dot(%s, %s);\n", coords2D, coords2D); + fragBuilder->codeAppendf("\tfloat d = dot(%s, %s) + %s.y;\n", coords2D, center.c_str(), + params.c_str()); + fragBuilder->codeAppendf("\tfloat %s = d + sqrt(d * d - %s.x * pDotp + %s.z);\n", + tName.c_str(), params.c_str(), params.c_str()); + + this->emitColor(fragBuilder, + uniformHandler, + args.fShaderCaps, + ge, + tName.c_str(), + args.fOutputColor, + args.fInputColor, + args.fTexSamplers); +} + +void CircleInside2PtConicalEffect::GLSLCircleInside2PtConicalProcessor::onSetData( + const GrGLSLProgramDataManager& pdman, const GrFragmentProcessor& processor) { + INHERITED::onSetData(pdman, processor); + const CircleInside2PtConicalEffect& data = processor.cast<CircleInside2PtConicalEffect>(); + SkScalar centerX = data.centerX(); + SkScalar centerY = data.centerY(); + SkScalar A = data.A(); + SkScalar B = data.B(); + SkScalar C = data.C(); + + if (fCachedCenterX != centerX || fCachedCenterY != centerY || + fCachedA != A || fCachedB != B || fCachedC != C) { + + pdman.set2f(fCenterUni, SkScalarToFloat(centerX), SkScalarToFloat(centerY)); + pdman.set3f(fParamUni, SkScalarToFloat(A), SkScalarToFloat(B), SkScalarToFloat(C)); + + fCachedCenterX = centerX; + fCachedCenterY = centerY; + fCachedA = A; + fCachedB = B; + fCachedC = C; + } +} + +void CircleInside2PtConicalEffect::GLSLCircleInside2PtConicalProcessor::GenKey( + const GrProcessor& processor, + const GrShaderCaps&, GrProcessorKeyBuilder* b) { + b->add32(GenBaseGradientKey(processor)); +} + +////////////////////////////////////////////////////////////////////////////// + +class CircleOutside2PtConicalEffect : public GrGradientEffect { +public: + class GLSLCircleOutside2PtConicalProcessor; + + static sk_sp<GrFragmentProcessor> Make(const CreateArgs& args, const CircleConicalInfo& info) { + return sk_sp<GrFragmentProcessor>( + new CircleOutside2PtConicalEffect(args, info)); + } + + ~CircleOutside2PtConicalEffect() override {} + + const char* name() const override { return "Two-Point Conical Gradient Outside"; } + + SkScalar centerX() const { return fInfo.fCenterEnd.fX; } + SkScalar centerY() const { return fInfo.fCenterEnd.fY; } + SkScalar A() const { return fInfo.fA; } + SkScalar B() const { return fInfo.fB; } + SkScalar C() const { return fInfo.fC; } + SkScalar tLimit() const { return fTLimit; } + bool isFlipped() const { return fIsFlipped; } + +private: + GrGLSLFragmentProcessor* onCreateGLSLInstance() const override; + + void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; + + bool onIsEqual(const GrFragmentProcessor& sBase) const override { + const CircleOutside2PtConicalEffect& s = sBase.cast<CircleOutside2PtConicalEffect>(); + return (INHERITED::onIsEqual(sBase) && + this->fInfo.fCenterEnd == s.fInfo.fCenterEnd && + this->fInfo.fA == s.fInfo.fA && + this->fInfo.fB == s.fInfo.fB && + this->fInfo.fC == s.fInfo.fC && + this->fTLimit == s.fTLimit && + this->fIsFlipped == s.fIsFlipped); + } + + CircleOutside2PtConicalEffect(const CreateArgs& args, const CircleConicalInfo& info) + : INHERITED(args, false /* opaque: draws transparent black outside of the cone. */) + , fInfo(info) { + this->initClassID<CircleOutside2PtConicalEffect>(); + const SkTwoPointConicalGradient& shader = + *static_cast<const SkTwoPointConicalGradient*>(args.fShader); + if (shader.getStartRadius() != shader.getEndRadius()) { + fTLimit = shader.getStartRadius() / (shader.getStartRadius() - shader.getEndRadius()); + } else { + fTLimit = SK_ScalarMin; + } + + fIsFlipped = shader.isFlippedGrad(); + } + + GR_DECLARE_FRAGMENT_PROCESSOR_TEST; + + const CircleConicalInfo fInfo; + SkScalar fTLimit; + bool fIsFlipped; + + typedef GrGradientEffect INHERITED; +}; + +class CircleOutside2PtConicalEffect::GLSLCircleOutside2PtConicalProcessor + : public GrGradientEffect::GLSLProcessor { +public: + GLSLCircleOutside2PtConicalProcessor(const GrProcessor&); + ~GLSLCircleOutside2PtConicalProcessor() override {} + + virtual void emitCode(EmitArgs&) override; + + static void GenKey(const GrProcessor&, const GrShaderCaps& caps, GrProcessorKeyBuilder* b); + +protected: + void onSetData(const GrGLSLProgramDataManager&, const GrFragmentProcessor&) override; + + UniformHandle fCenterUni; + UniformHandle fParamUni; + + const char* fVSVaryingName; + const char* fFSVaryingName; + + bool fIsFlipped; + + // @{ + /// Values last uploaded as uniforms + + SkScalar fCachedCenterX; + SkScalar fCachedCenterY; + SkScalar fCachedA; + SkScalar fCachedB; + SkScalar fCachedC; + SkScalar fCachedTLimit; + + // @} + +private: + typedef GrGradientEffect::GLSLProcessor INHERITED; + +}; + +void CircleOutside2PtConicalEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps, + GrProcessorKeyBuilder* b) const { + CircleOutside2PtConicalEffect::GLSLCircleOutside2PtConicalProcessor::GenKey(*this, caps, b); +} + +GrGLSLFragmentProcessor* CircleOutside2PtConicalEffect::onCreateGLSLInstance() const { + return new CircleOutside2PtConicalEffect::GLSLCircleOutside2PtConicalProcessor(*this); +} + +GR_DEFINE_FRAGMENT_PROCESSOR_TEST(CircleOutside2PtConicalEffect); + +/* + * All Two point conical gradient test create functions may occasionally create edge case shaders + */ +#if GR_TEST_UTILS +sk_sp<GrFragmentProcessor> CircleOutside2PtConicalEffect::TestCreate(GrProcessorTestData* d) { + SkPoint center1 = {d->fRandom->nextUScalar1(), d->fRandom->nextUScalar1()}; + SkScalar radius1 = d->fRandom->nextUScalar1() + 0.0001f; // make sure radius1 != 0 + SkPoint center2; + SkScalar radius2; + SkScalar diffLen; + do { + center2.set(d->fRandom->nextUScalar1(), d->fRandom->nextUScalar1()); + // If the circles share a center than we can't be in the outside case + } while (center1 == center2); + SkPoint diff = center2 - center1; + diffLen = diff.length(); + // Below makes sure that circle one is not contained within circle two + // and have radius2 >= radius to match sorting on cpu side + radius2 = radius1 + d->fRandom->nextRangeF(0.f, diffLen); + + RandomGradientParams params(d->fRandom); + auto shader = params.fUseColors4f ? + SkGradientShader::MakeTwoPointConical(center1, radius1, center2, radius2, + params.fColors4f, params.fColorSpace, params.fStops, + params.fColorCount, params.fTileMode) : + SkGradientShader::MakeTwoPointConical(center1, radius1, center2, radius2, + params.fColors, params.fStops, + params.fColorCount, params.fTileMode); + GrTest::TestAsFPArgs asFPArgs(d); + sk_sp<GrFragmentProcessor> fp = as_SB(shader)->asFragmentProcessor(asFPArgs.args()); + GrAlwaysAssert(fp); + return fp; +} +#endif + +CircleOutside2PtConicalEffect::GLSLCircleOutside2PtConicalProcessor + ::GLSLCircleOutside2PtConicalProcessor(const GrProcessor& processor) + : fVSVaryingName(nullptr) + , fFSVaryingName(nullptr) + , fCachedCenterX(SK_ScalarMax) + , fCachedCenterY(SK_ScalarMax) + , fCachedA(SK_ScalarMax) + , fCachedB(SK_ScalarMax) + , fCachedC(SK_ScalarMax) + , fCachedTLimit(SK_ScalarMax) { + const CircleOutside2PtConicalEffect& data = processor.cast<CircleOutside2PtConicalEffect>(); + fIsFlipped = data.isFlipped(); + } + +void CircleOutside2PtConicalEffect::GLSLCircleOutside2PtConicalProcessor::emitCode(EmitArgs& args) { + const CircleOutside2PtConicalEffect& ge = args.fFp.cast<CircleOutside2PtConicalEffect>(); + GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; + this->emitUniforms(uniformHandler, ge); + fCenterUni = uniformHandler->addUniform(kFragment_GrShaderFlag, + kVec2f_GrSLType, kDefault_GrSLPrecision, + "Conical2FSCenter"); + fParamUni = uniformHandler->addUniform(kFragment_GrShaderFlag, + kVec4f_GrSLType, kDefault_GrSLPrecision, + "Conical2FSParams"); + SkString tName("t"); + + GrShaderVar center = uniformHandler->getUniformVariable(fCenterUni); + // params.x = A + // params.y = B + // params.z = C + GrShaderVar params = uniformHandler->getUniformVariable(fParamUni); + + // if we have a vec3 from being in perspective, convert it to a vec2 first + GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; + SkString coords2DString = fragBuilder->ensureCoords2D(args.fTransformedCoords[0]); + const char* coords2D = coords2DString.c_str(); + + // output will default to transparent black (we simply won't write anything + // else to it if invalid, instead of discarding or returning prematurely) + fragBuilder->codeAppendf("\t%s = vec4(0.0,0.0,0.0,0.0);\n", args.fOutputColor); + + // p = coords2D + // e = center end + // r = radius end + // A = dot(e, e) - r^2 + 2 * r - 1 + // B = (r -1) / A + // C = 1 / A + // d = dot(e, p) + B + // t = d +/- sqrt(d^2 - A * dot(p, p) + C) + + fragBuilder->codeAppendf("\tfloat pDotp = dot(%s, %s);\n", coords2D, coords2D); + fragBuilder->codeAppendf("\tfloat d = dot(%s, %s) + %s.y;\n", coords2D, center.c_str(), + params.c_str()); + fragBuilder->codeAppendf("\tfloat deter = d * d - %s.x * pDotp + %s.z;\n", params.c_str(), + params.c_str()); + + // Must check to see if we flipped the circle order (to make sure start radius < end radius) + // If so we must also flip sign on sqrt + if (!fIsFlipped) { + fragBuilder->codeAppendf("\tfloat %s = d + sqrt(deter);\n", tName.c_str()); + } else { + fragBuilder->codeAppendf("\tfloat %s = d - sqrt(deter);\n", tName.c_str()); + } + + fragBuilder->codeAppendf("\tif (%s >= %s.w && deter >= 0.0) {\n", + tName.c_str(), params.c_str()); + fragBuilder->codeAppend("\t\t"); + this->emitColor(fragBuilder, + uniformHandler, + args.fShaderCaps, + ge, + tName.c_str(), + args.fOutputColor, + args.fInputColor, + args.fTexSamplers); + fragBuilder->codeAppend("\t}\n"); +} + +void CircleOutside2PtConicalEffect::GLSLCircleOutside2PtConicalProcessor::onSetData( + const GrGLSLProgramDataManager& pdman, const GrFragmentProcessor& processor) { + INHERITED::onSetData(pdman, processor); + const CircleOutside2PtConicalEffect& data = processor.cast<CircleOutside2PtConicalEffect>(); + SkASSERT(data.isFlipped() == fIsFlipped); + SkScalar centerX = data.centerX(); + SkScalar centerY = data.centerY(); + SkScalar A = data.A(); + SkScalar B = data.B(); + SkScalar C = data.C(); + SkScalar tLimit = data.tLimit(); + + if (fCachedCenterX != centerX || fCachedCenterY != centerY || + fCachedA != A || fCachedB != B || fCachedC != C || fCachedTLimit != tLimit) { + + pdman.set2f(fCenterUni, SkScalarToFloat(centerX), SkScalarToFloat(centerY)); + pdman.set4f(fParamUni, SkScalarToFloat(A), SkScalarToFloat(B), SkScalarToFloat(C), + SkScalarToFloat(tLimit)); + + fCachedCenterX = centerX; + fCachedCenterY = centerY; + fCachedA = A; + fCachedB = B; + fCachedC = C; + fCachedTLimit = tLimit; + } +} + +void CircleOutside2PtConicalEffect::GLSLCircleOutside2PtConicalProcessor::GenKey( + const GrProcessor& processor, + const GrShaderCaps&, GrProcessorKeyBuilder* b) { + uint32_t* key = b->add32n(2); + key[0] = GenBaseGradientKey(processor); + key[1] = processor.cast<CircleOutside2PtConicalEffect>().isFlipped(); +} + +////////////////////////////////////////////////////////////////////////////// + +sk_sp<GrFragmentProcessor> Gr2PtConicalGradientEffect::Make( + const GrGradientEffect::CreateArgs& args) { + const SkTwoPointConicalGradient& shader = + *static_cast<const SkTwoPointConicalGradient*>(args.fShader); + + SkMatrix matrix; + if (!shader.getLocalMatrix().invert(&matrix)) { + return nullptr; + } + if (args.fMatrix) { + SkMatrix inv; + if (!args.fMatrix->invert(&inv)) { + return nullptr; + } + matrix.postConcat(inv); + } + + GrGradientEffect::CreateArgs newArgs(args.fContext, args.fShader, &matrix, args.fTileMode, + std::move(args.fColorSpaceXform), args.fGammaCorrect); + + if (shader.getStartRadius() < kErrorTol) { + SkScalar focalX; + ConicalType type = set_matrix_focal_conical(shader, &matrix, &focalX); + if (type == kInside_ConicalType) { + return FocalInside2PtConicalEffect::Make(newArgs, focalX); + } else if(type == kEdge_ConicalType) { + set_matrix_edge_conical(shader, &matrix); + return Edge2PtConicalEffect::Make(newArgs); + } else { + return FocalOutside2PtConicalEffect::Make(newArgs, focalX); + } + } + + CircleConicalInfo info; + ConicalType type = set_matrix_circle_conical(shader, &matrix, &info); + + if (type == kInside_ConicalType) { + return CircleInside2PtConicalEffect::Make(newArgs, info); + } else if (type == kEdge_ConicalType) { + set_matrix_edge_conical(shader, &matrix); + return Edge2PtConicalEffect::Make(newArgs); + } else { + return CircleOutside2PtConicalEffect::Make(newArgs, info); + } +} + +#endif diff --git a/src/shaders/gradients/SkTwoPointConicalGradient_gpu.h b/src/shaders/gradients/SkTwoPointConicalGradient_gpu.h new file mode 100644 index 0000000000..46edb1f7d1 --- /dev/null +++ b/src/shaders/gradients/SkTwoPointConicalGradient_gpu.h @@ -0,0 +1,24 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkTwoPointConicalGradient_gpu_DEFINED +#define SkTwoPointConicalGradient_gpu_DEFINED + +#include "SkGradientShaderPriv.h" + +class GrProcessor; +class SkTwoPointConicalGradient; + +namespace Gr2PtConicalGradientEffect { + /** + * Creates an effect that produces a two point conical gradient based on the + * shader passed in. + */ + sk_sp<GrFragmentProcessor> Make(const GrGradientEffect::CreateArgs& args); +}; + +#endif |