diff options
Diffstat (limited to 'gfx/skia/skia/src/shaders/SkShader.cpp')
-rw-r--r-- | gfx/skia/skia/src/shaders/SkShader.cpp | 334 |
1 files changed, 334 insertions, 0 deletions
diff --git a/gfx/skia/skia/src/shaders/SkShader.cpp b/gfx/skia/skia/src/shaders/SkShader.cpp new file mode 100644 index 0000000000..83eee34278 --- /dev/null +++ b/gfx/skia/skia/src/shaders/SkShader.cpp @@ -0,0 +1,334 @@ +/* + * 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 "include/core/SkMallocPixelRef.h" +#include "include/core/SkPaint.h" +#include "include/core/SkScalar.h" +#include "src/base/SkArenaAlloc.h" +#include "src/base/SkTLazy.h" +#include "src/core/SkColorSpacePriv.h" +#include "src/core/SkColorSpaceXformSteps.h" +#include "src/core/SkMatrixProvider.h" +#include "src/core/SkRasterPipeline.h" +#include "src/core/SkReadBuffer.h" +#include "src/core/SkWriteBuffer.h" +#include "src/shaders/SkBitmapProcShader.h" +#include "src/shaders/SkImageShader.h" +#include "src/shaders/SkShaderBase.h" +#include "src/shaders/SkTransformShader.h" + +#if defined(SK_GANESH) +#include "src/gpu/ganesh/GrFragmentProcessor.h" +#include "src/gpu/ganesh/effects/GrMatrixEffect.h" +#endif + +#if defined(SK_GRAPHITE) +#include "src/gpu/graphite/KeyHelpers.h" +#include "src/gpu/graphite/PaintParamsKey.h" +#endif + +SkShaderBase::SkShaderBase() = default; + +SkShaderBase::~SkShaderBase() = default; + +SkShaderBase::MatrixRec::MatrixRec(const SkMatrix& ctm) : fCTM(ctm) {} + +std::optional<SkShaderBase::MatrixRec> +SkShaderBase::MatrixRec::apply(const SkStageRec& rec, const SkMatrix& postInv) const { + SkMatrix total = fPendingLocalMatrix; + if (!fCTMApplied) { + total = SkMatrix::Concat(fCTM, total); + } + if (!total.invert(&total)) { + return {}; + } + total = SkMatrix::Concat(postInv, total); + if (!fCTMApplied) { + rec.fPipeline->append(SkRasterPipelineOp::seed_shader); + } + // append_matrix is a no-op if total worked out to identity. + rec.fPipeline->append_matrix(rec.fAlloc, total); + return MatrixRec{fCTM, + fTotalLocalMatrix, + /*pendingLocalMatrix=*/SkMatrix::I(), + fTotalMatrixIsValid, + /*ctmApplied=*/true}; +} + +std::optional<SkShaderBase::MatrixRec> +SkShaderBase::MatrixRec::apply(skvm::Builder* p, + skvm::Coord* local, + skvm::Uniforms* uniforms, + const SkMatrix& postInv) const { + SkMatrix total = fPendingLocalMatrix; + if (!fCTMApplied) { + total = SkMatrix::Concat(fCTM, total); + } + if (!total.invert(&total)) { + return {}; + } + total = SkMatrix::Concat(postInv, total); + // ApplyMatrix is a no-op if total worked out to identity. + *local = SkShaderBase::ApplyMatrix(p, total, *local, uniforms); + return MatrixRec{fCTM, + fTotalLocalMatrix, + /*pendingLocalMatrix=*/SkMatrix::I(), + fTotalMatrixIsValid, + /*ctmApplied=*/true}; +} + +#if defined(SK_GANESH) +GrFPResult SkShaderBase::MatrixRec::apply(std::unique_ptr<GrFragmentProcessor> fp, + const SkMatrix& postInv) const { + // FP matrices work differently than SkRasterPipeline and SkVM. The starting coordinates + // provided to the root SkShader's FP are already in local space. So we never apply the inverse + // CTM. + SkASSERT(!fCTMApplied); + SkMatrix total; + if (!fPendingLocalMatrix.invert(&total)) { + return {false, std::move(fp)}; + } + total = SkMatrix::Concat(postInv, total); + // GrMatrixEffect returns 'fp' if total worked out to identity. + return {true, GrMatrixEffect::Make(total, std::move(fp))}; +} + +SkShaderBase::MatrixRec SkShaderBase::MatrixRec::applied() const { + // We mark the CTM as "not applied" because we *never* apply the CTM for FPs. Their starting + // coords are local, not device, coords. + return MatrixRec{fCTM, + fTotalLocalMatrix, + /*pendingLocalMatrix=*/SkMatrix::I(), + fTotalMatrixIsValid, + /*ctmApplied=*/false}; +} +#endif + +SkShaderBase::MatrixRec SkShaderBase::MatrixRec::concat(const SkMatrix& m) const { + return {fCTM, + SkShaderBase::ConcatLocalMatrices(fTotalLocalMatrix, m), + SkShaderBase::ConcatLocalMatrices(fPendingLocalMatrix, m), + fTotalMatrixIsValid, + fCTMApplied}; +} + +void SkShaderBase::flatten(SkWriteBuffer& buffer) const { this->INHERITED::flatten(buffer); } + +bool SkShaderBase::computeTotalInverse(const SkMatrix& ctm, + const SkMatrix* localMatrix, + SkMatrix* totalInverse) const { + return (localMatrix ? SkMatrix::Concat(ctm, *localMatrix) : ctm).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 { +#ifdef SK_ENABLE_LEGACY_SHADERCONTEXT + // We always fall back to raster pipeline when perspective is present. + if (rec.fMatrix->hasPerspective() || (rec.fLocalMatrix && rec.fLocalMatrix->hasPerspective()) || + !this->computeTotalInverse(*rec.fMatrix, rec.fLocalMatrix, nullptr)) { + return nullptr; + } + + return this->onMakeContext(rec, alloc); +#else + return nullptr; +#endif +} + +SkShaderBase::Context::Context(const SkShaderBase& shader, const ContextRec& rec) + : fShader(shader), fCTM(*rec.fMatrix) +{ + // We should never use a context with perspective. + SkASSERT(!rec.fMatrix->hasPerspective()); + SkASSERT(!rec.fLocalMatrix || !rec.fLocalMatrix->hasPerspective()); + + // 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)); + + fPaintAlpha = rec.fPaintAlpha; +} + +SkShaderBase::Context::~Context() {} + +bool SkShaderBase::ContextRec::isLegacyCompatible(SkColorSpace* shaderColorSpace) const { + // In legacy pipelines, shaders always produce premul (or opaque) and the destination is also + // always premul (or opaque). (And those "or opaque" caveats won't make any difference here.) + SkAlphaType shaderAT = kPremul_SkAlphaType, + dstAT = kPremul_SkAlphaType; + return 0 == SkColorSpaceXformSteps{shaderColorSpace, shaderAT, + fDstColorSpace, dstAT}.flags.mask(); +} + +SkImage* SkShader::isAImage(SkMatrix* localMatrix, SkTileMode xy[2]) const { + return as_SB(this)->onIsAImage(localMatrix, xy); +} + +#if defined(SK_GANESH) +std::unique_ptr<GrFragmentProcessor> +SkShaderBase::asRootFragmentProcessor(const GrFPArgs& args, const SkMatrix& ctm) const { + return this->asFragmentProcessor(args, MatrixRec(ctm)); +} + +std::unique_ptr<GrFragmentProcessor> SkShaderBase::asFragmentProcessor(const GrFPArgs&, + const MatrixRec&) const { + return nullptr; +} +#endif + +sk_sp<SkShader> SkShaderBase::makeAsALocalMatrixShader(SkMatrix*) const { + return nullptr; +} + +#if defined(SK_GRAPHITE) +// TODO: add implementations for derived classes +void SkShaderBase::addToKey(const skgpu::graphite::KeyContext& keyContext, + skgpu::graphite::PaintParamsKeyBuilder* builder, + skgpu::graphite::PipelineDataGatherer* gatherer) const { + using namespace skgpu::graphite; + + SolidColorShaderBlock::BeginBlock(keyContext, builder, gatherer, {1, 0, 0, 1}); + builder->endBlock(); +} +#endif + +bool SkShaderBase::appendRootStages(const SkStageRec& rec, const SkMatrix& ctm) const { + return this->appendStages(rec, MatrixRec(ctm)); +} + +bool SkShaderBase::appendStages(const SkStageRec& rec, const MatrixRec& mRec) const { + // SkShader::Context::shadeSpan() handles the paint opacity internally, + // but SkRasterPipelineBlitter applies it as a separate stage. + // We skip the internal shadeSpan() step by forcing the paint opaque. + SkColor4f opaquePaintColor = rec.fPaintColor.makeOpaque(); + + // We don't have a separate ctm and local matrix at this point. Just pass the combined matrix + // as the CTM. TODO: thread the MatrixRec through the legacy context system. + auto tm = mRec.totalMatrix(); + ContextRec cr(opaquePaintColor, + tm, + nullptr, + rec.fDstColorType, + sk_srgb_singleton(), + rec.fSurfaceProps); + + struct CallbackCtx : SkRasterPipeline_CallbackCtx { + sk_sp<const SkShader> shader; + Context* ctx; + }; + auto cb = rec.fAlloc->make<CallbackCtx>(); + cb->shader = sk_ref_sp(this); + cb->ctx = as_SB(this)->makeContext(cr, rec.fAlloc); + cb->fn = [](SkRasterPipeline_CallbackCtx* self, int active_pixels) { + auto c = (CallbackCtx*)self; + int x = (int)c->rgba[0], + y = (int)c->rgba[1]; + SkPMColor tmp[SkRasterPipeline_kMaxStride_highp]; + c->ctx->shadeSpan(x,y, tmp, active_pixels); + + for (int i = 0; i < active_pixels; i++) { + auto rgba_4f = SkPMColor4f::FromPMColor(tmp[i]); + memcpy(c->rgba + 4*i, rgba_4f.vec(), 4*sizeof(float)); + } + }; + + if (cb->ctx) { + rec.fPipeline->append(SkRasterPipelineOp::seed_shader); + rec.fPipeline->append(SkRasterPipelineOp::callback, cb); + rec.fAlloc->make<SkColorSpaceXformSteps>(sk_srgb_singleton(), kPremul_SkAlphaType, + rec.fDstCS, kPremul_SkAlphaType) + ->apply(rec.fPipeline); + return true; + } + return false; +} + +skvm::Color SkShaderBase::rootProgram(skvm::Builder* p, + skvm::Coord device, + skvm::Color paint, + const SkMatrix& ctm, + const SkColorInfo& dst, + skvm::Uniforms* uniforms, + SkArenaAlloc* alloc) const { + // Shader subclasses should always act as if the destination were premul or opaque. + // SkVMBlitter handles all the coordination of unpremul itself, via premul. + SkColorInfo tweaked = dst.alphaType() == kUnpremul_SkAlphaType + ? dst.makeAlphaType(kPremul_SkAlphaType) + : dst; + + // Force opaque alpha for all opaque shaders. + // + // This is primarily nice in that we usually have a 1.0f constant splat + // somewhere in the program anyway, and this will let us drop the work the + // shader notionally does to produce alpha, p->extract(...), etc. in favor + // of that simple hoistable splat. + // + // More subtly, it makes isOpaque() a parameter to all shader program + // generation, guaranteeing that is-opaque bit is mixed into the overall + // shader program hash and blitter Key. This makes it safe for us to use + // that bit to make decisions when constructing an SkVMBlitter, like doing + // SrcOver -> Src strength reduction. + if (auto color = this->program(p, + device, + /*local=*/device, + paint, + MatrixRec(ctm), + tweaked, + uniforms, + alloc)) { + if (this->isOpaque()) { + color.a = p->splat(1.0f); + } + return color; + } + return {}; +} + +// need a cheap way to invert the alpha channel of a shader (i.e. 1 - a) +sk_sp<SkShader> SkShaderBase::makeInvertAlpha() const { + return this->makeWithColorFilter(SkColorFilters::Blend(0xFFFFFFFF, SkBlendMode::kSrcOut)); +} + + +skvm::Coord SkShaderBase::ApplyMatrix(skvm::Builder* p, const SkMatrix& m, + skvm::Coord coord, skvm::Uniforms* uniforms) { + skvm::F32 x = coord.x, + y = coord.y; + if (m.isIdentity()) { + // That was easy. + } else if (m.isTranslate()) { + x = p->add(x, p->uniformF(uniforms->pushF(m[2]))); + y = p->add(y, p->uniformF(uniforms->pushF(m[5]))); + } else if (m.isScaleTranslate()) { + x = p->mad(x, p->uniformF(uniforms->pushF(m[0])), p->uniformF(uniforms->pushF(m[2]))); + y = p->mad(y, p->uniformF(uniforms->pushF(m[4])), p->uniformF(uniforms->pushF(m[5]))); + } else { // Affine or perspective. + auto dot = [&,x,y](int row) { + return p->mad(x, p->uniformF(uniforms->pushF(m[3*row+0])), + p->mad(y, p->uniformF(uniforms->pushF(m[3*row+1])), + p->uniformF(uniforms->pushF(m[3*row+2])))); + }; + x = dot(0); + y = dot(1); + if (m.hasPerspective()) { + x = x * (1.0f / dot(2)); + y = y * (1.0f / dot(2)); + } + } + return {x,y}; +} |