summaryrefslogtreecommitdiffstats
path: root/gfx/skia/skia/src/shaders/SkShader.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/skia/skia/src/shaders/SkShader.cpp')
-rw-r--r--gfx/skia/skia/src/shaders/SkShader.cpp334
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};
+}