blob: 080560a7828d635e296dccafd7d472dba3d94c77 [file] [log] [blame]
/*
* Copyright 2021 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "src/core/SkMatrixProvider.h"
#include "src/core/SkRasterPipeline.h"
#include "src/shaders/SkTransformShader.h"
SkTransformShader::SkTransformShader(const SkShaderBase& shader, bool allowPerspective)
: fShader{shader}, fAllowPerspective{allowPerspective} {
SkMatrix::I().get9(fMatrixStorage);
}
skvm::Color SkTransformShader::program(skvm::Builder* b,
skvm::Coord device,
skvm::Coord local,
skvm::Color color,
const MatrixRec& mRec,
const SkColorInfo& dst,
skvm::Uniforms* uniforms,
SkArenaAlloc* alloc) const {
// We have to seed and apply any constant matrices before appending our matrix that may
// mutate. We could try to apply one matrix stage and then incorporate the parent matrix
// with the variable matrix in each call to update(). However, in practice our callers
// fold the CTM into the update() matrix and don't wrap the transform shader in local matrix
// shaders so the call to apply below should be no-op. If this assert fires it just indicates an
// optimization opportunity, not a correctness bug.
SkASSERT(!mRec.hasPendingMatrix());
std::optional<MatrixRec> childMRec = mRec.apply(b, &local, uniforms);
if (!childMRec.has_value()) {
return {};
}
// The matrix we're about to insert gets updated between uses of the VM so our children can't
// know the total transform when they add their stages. We don't incorporate this shader's
// matrix into the MatrixRec at all.
childMRec->markTotalMatrixInvalid();
auto matrix = uniforms->pushPtr(&fMatrixStorage);
skvm::F32 x = local.x,
y = local.y;
auto dot = [&, x, y](int row) {
return b->mad(x,
b->arrayF(matrix, 3 * row + 0),
b->mad(y, b->arrayF(matrix, 3 * row + 1), b->arrayF(matrix, 3 * row + 2)));
};
x = dot(0);
y = dot(1);
if (fAllowPerspective) {
x = x * (1.0f / dot(2));
y = y * (1.0f / dot(2));
}
skvm::Coord newLocal = {x, y};
return fShader.program(b, device, newLocal, color, *childMRec, dst, uniforms, alloc);
}
bool SkTransformShader::update(const SkMatrix& matrix) {
if (SkMatrix inv; matrix.invert(&inv)) {
if (!fAllowPerspective && inv.hasPerspective()) {
return false;
}
inv.get9(fMatrixStorage);
return true;
}
return false;
}
bool SkTransformShader::appendStages(const SkStageRec& rec, const MatrixRec& mRec) const {
// We have to seed and apply any constant matrices before appending our matrix that may
// mutate. We could try to add one matrix stage and then incorporate the parent matrix
// with the variable matrix in each call to update(). However, in practice our callers
// fold the CTM into the update() matrix and don't wrap the transform shader in local matrix
// shaders so the call to apply below should just seed the coordinates. If this assert fires
// it just indicates an optimization opportunity, not a correctness bug.
SkASSERT(!mRec.hasPendingMatrix());
std::optional<MatrixRec> childMRec = mRec.apply(rec);
if (!childMRec.has_value()) {
return false;
}
// The matrix we're about to insert gets updated between uses of the pipeline so our children
// can't know the total transform when they add their stages. We don't even incorporate this
// matrix into the MatrixRec at all.
childMRec->markTotalMatrixInvalid();
auto type = fAllowPerspective ? SkRasterPipelineOp::matrix_perspective
: SkRasterPipelineOp::matrix_2x3;
rec.fPipeline->append(type, fMatrixStorage);
fShader.appendStages(rec, *childMRec);
return true;
}