draw vertices: fast triangles using a shader

Introduce a new call to SkShaderBase to return an
SkUpdatableShader. An SkUpdatableShader is both a SkShaderBase
and has an update() method.

If the onUpdatableShader call returns !nullptr, then the
shader's specialized updatable shader is used. Otherwise,
the shader is wrapped in a TexCoordShader.

This scheme allows Shaders to have specialized updaters,
but also adds updaters for shaders that don't need
specialization.

orig  this  bench
771µs 779µs verts_textures_colors
217µs 335µs verts_textures
275µs 380µs verts_textures_persp

Bug=skia:11822

Change-Id: I11ba344eba96188c5631228cd5e91c5d834ea3d6
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/429417
Commit-Queue: Herb Derby <herb@google.com>
Reviewed-by: Mike Reed <reed@google.com>
This commit is contained in:
Herb Derby 2021-07-17 18:19:03 -04:00
parent 17eaf62160
commit 5992f9eb43
10 changed files with 217 additions and 19 deletions

View File

@ -458,6 +458,8 @@ skia_core_sources = [
"$_src/shaders/SkLocalMatrixShader.h",
"$_src/shaders/SkShader.cpp",
"$_src/shaders/SkShaderBase.h",
"$_src/shaders/SkTransformShader.cpp",
"$_src/shaders/SkTransformShader.h",
# private
"$_include/private/SkChecksum.h",

View File

@ -255,6 +255,7 @@ static void fill_triangle_3(const VertState& state, SkBlitter* blitter, const Sk
}
}
}
static void fill_triangle(const VertState& state, SkBlitter* blitter, const SkRasterClip& rc,
const SkPoint dev2[], const SkPoint3 dev3[]) {
if (dev3) {
@ -308,27 +309,39 @@ void SkDraw::drawFixedVertices(const SkVertices* vertices, SkBlendMode blendMode
VertState state(vertexCount, indices, indexCount);
VertState::Proc vertProc = state.chooseProc(info.mode());
SkMatrix ctm = fMatrixProvider->localToDevice();
// No colors are changing and no texture coordinates are changing, so no updates between
// triangles are needed. Use SkVM to blit the triangles.
if (gUseSkVMBlitter && !colors && (!texCoords || texCoords == positions)) {
if (auto blitter = SkVMBlitter::Make(
fDst, paint, *fMatrixProvider, outerAlloc, this->fRC->clipShader())) {
while (vertProc(&state)) {
fill_triangle(state, blitter, *fRC, dev2, dev3);
if (gUseSkVMBlitter && !colors) {
if (!texCoords || texCoords == positions) {
if (auto blitter = SkVMBlitter::Make(
fDst, paint, *fMatrixProvider, outerAlloc, this->fRC->clipShader())) {
while (vertProc(&state)) {
fill_triangle(state, blitter, *fRC, dev2, dev3);
}
return;
}
} else {
auto texCoordShader = as_SB(shader)->updatableShader(outerAlloc);
SkPaint shaderPaint{paint};
shaderPaint.setShader(sk_ref_sp(texCoordShader));
if (auto blitter = SkVMBlitter::Make(
fDst, shaderPaint, *fMatrixProvider, outerAlloc, this->fRC->clipShader())) {
auto updater = [&](skvm::Uniforms* uniforms) {
SkMatrix localM;
return texture_to_matrix(state, positions, texCoords, &localM) &&
texCoordShader->update(SkMatrix::Concat(ctm, localM), uniforms);
};
while (vertProc(&state)) {
if (blitter->updateUniforms(updater)) {
fill_triangle(state, blitter, *fRC, dev2, dev3);
}
}
return;
}
return;
}
}
/* We need to know if we have perspective or not, so we can know what stage(s) we will need,
and how to prep our "uniforms" before each triangle in the tricolorshader.
We could just check the matrix on each triangle to decide, but we have to be sure to always
make the same decision, since we create 1 or 2 stages only once for the entire patch.
To be safe, we just make that determination here, and pass it into the tricolorshader.
*/
SkMatrix ctm = fMatrixProvider->localToDevice();
const bool usePerspective = ctm.hasPerspective();
SkTriColorShader* triShader = nullptr;

View File

@ -556,8 +556,7 @@ SkVMBlitter::SkVMBlitter(const SkPixmap& device,
bool* ok)
: fDevice(device), fSprite(sprite ? *sprite : SkPixmap{})
, fSpriteOffset(spriteOffset)
, fUniforms(skvm::Ptr{0}
, kBlitterUniformsCount)
, fUniforms(skvm::Ptr{0}, kBlitterUniformsCount)
, fParams(EffectiveParams(device, sprite, paint, matrices, std::move(clip)))
, fKey(CacheKey(fParams, &fUniforms, &fAlloc, ok)) {}

View File

@ -36,6 +36,11 @@ public:
~SkVMBlitter() override;
template<typename Updater>
bool updateUniforms(Updater updater) {
return updater(&fUniforms);
}
private:
enum class Coverage { Full, UniformF, MaskA8, MaskLCD16, Mask3D };
struct Key {

View File

@ -23,6 +23,7 @@
#include "src/image/SkImage_Base.h"
#include "src/shaders/SkBitmapProcShader.h"
#include "src/shaders/SkEmptyShader.h"
#include "src/shaders/SkTransformShader.h"
SkM44 SkImageShader::CubicResamplerMatrix(float B, float C) {
#if 0
@ -691,11 +692,43 @@ SkStageUpdater* SkImageShader::onAppendUpdatableStages(const SkStageRec& rec) co
return this->doStages(rec, updater) ? updater : nullptr;
}
skvm::Color SkImageShader::onProgram(skvm::Builder* p,
class SkImageShader::TransformShader : public SkTransformShader {
public:
explicit TransformShader(const SkImageShader& shader)
: SkTransformShader{shader}
, fImageShader{shader} {}
skvm::Color onProgram(skvm::Builder* b,
skvm::Coord device, skvm::Coord local, skvm::Color color,
const SkMatrixProvider& matrices, const SkMatrix* localM,
const SkColorInfo& dst,
skvm::Uniforms* uniforms, SkArenaAlloc* alloc) const override {
return fImageShader.makeProgram(
b, device, local, color, matrices, localM, dst, uniforms, this, alloc);
}
private:
const SkImageShader& fImageShader;
};
SkUpdatableShader* SkImageShader::onUpdatableShader(SkArenaAlloc* alloc) const {
return alloc->make<TransformShader>(*this);
}
skvm::Color SkImageShader::onProgram(skvm::Builder* b,
skvm::Coord device, skvm::Coord origLocal, skvm::Color paint,
const SkMatrixProvider& matrices, const SkMatrix* localM,
const SkColorInfo& dst,
skvm::Uniforms* uniforms, SkArenaAlloc* alloc) const {
return this->makeProgram(
b, device, origLocal, paint, matrices, localM, dst, uniforms, nullptr, alloc);
}
skvm::Color SkImageShader::makeProgram(
skvm::Builder* p, skvm::Coord device, skvm::Coord origLocal, skvm::Color paint,
const SkMatrixProvider& matrices, const SkMatrix* localM, const SkColorInfo& dst,
skvm::Uniforms* uniforms, const TransformShader* coordShader, SkArenaAlloc* alloc) const {
SkMatrix baseInv;
if (!this->computeTotalInverse(matrices.localToDevice(), localM, &baseInv)) {
return {};
@ -725,7 +758,12 @@ skvm::Color SkImageShader::onProgram(skvm::Builder* p,
lower = &lowerPixmap;
}
skvm::Coord upperLocal = SkShaderBase::ApplyMatrix(p, upperInv, origLocal, uniforms);
skvm::Coord upperLocal;
if (coordShader != nullptr) {
upperLocal = coordShader->applyMatrix(p, upperInv, origLocal, uniforms);
} else {
upperLocal = SkShaderBase::ApplyMatrix(p, upperInv, origLocal, uniforms);
}
// We can exploit image opacity to skip work unpacking alpha channels.
const bool input_is_opaque = SkAlphaTypeIsOpaque(upper.alphaType())

View File

@ -51,10 +51,18 @@ private:
bool onAppendStages(const SkStageRec&) const override;
SkStageUpdater* onAppendUpdatableStages(const SkStageRec&) const override;
SkUpdatableShader* onUpdatableShader(SkArenaAlloc* alloc) const override;
skvm::Color onProgram(skvm::Builder*, skvm::Coord device, skvm::Coord local, skvm::Color paint,
const SkMatrixProvider&, const SkMatrix* localM, const SkColorInfo& dst,
skvm::Uniforms* uniforms, SkArenaAlloc*) const override;
class TransformShader;
skvm::Color makeProgram(
skvm::Builder*, skvm::Coord device, skvm::Coord local, skvm::Color paint,
const SkMatrixProvider&, const SkMatrix* localM, const SkColorInfo& dst,
skvm::Uniforms* uniforms, const TransformShader* coordShader, SkArenaAlloc*) const;
bool doStages(const SkStageRec&, SkImageStageUpdater* = nullptr) const;
sk_sp<SkImage> fImage;

View File

@ -24,6 +24,7 @@
#include "src/shaders/SkImageShader.h"
#include "src/shaders/SkPictureShader.h"
#include "src/shaders/SkShaderBase.h"
#include "src/shaders/SkTransformShader.h"
#if SK_SUPPORT_GPU
#include "src/gpu/GrFragmentProcessor.h"
@ -135,6 +136,18 @@ sk_sp<SkShader> SkShaderBase::makeAsALocalMatrixShader(SkMatrix*) const {
return nullptr;
}
SkUpdatableShader* SkShaderBase::updatableShader(SkArenaAlloc* alloc) const {
if (auto updatable = this->onUpdatableShader(alloc)) {
return updatable;
}
return alloc->make<SkTransformShader>(*as_SB(this));
}
SkUpdatableShader* SkShaderBase::onUpdatableShader(SkArenaAlloc* alloc) const {
return nullptr;
}
sk_sp<SkShader> SkShaders::Empty() { return sk_make_sp<SkEmptyShader>(); }
sk_sp<SkShader> SkShaders::Color(SkColor color) { return sk_make_sp<SkColorShader>(color); }

View File

@ -48,6 +48,8 @@ public:
virtual bool SK_WARN_UNUSED_RESULT update(const SkMatrix& ctm) = 0;
};
class SkUpdatableShader;
class SkShaderBase : public SkShader {
public:
~SkShaderBase() override;
@ -211,6 +213,9 @@ public:
*/
virtual sk_sp<SkShader> makeAsALocalMatrixShader(SkMatrix* localMatrix) const;
SkUpdatableShader* updatableShader(SkArenaAlloc* alloc) const;
virtual SkUpdatableShader* onUpdatableShader(SkArenaAlloc* alloc) const;
SkStageUpdater* appendUpdatableStages(const SkStageRec& rec) const {
return this->onAppendUpdatableStages(rec);
}
@ -259,6 +264,11 @@ private:
using INHERITED = SkShader;
};
class SkUpdatableShader : public SkShaderBase {
public:
virtual bool update(const SkMatrix& ctm, skvm::Uniforms* u) const = 0;
};
inline SkShaderBase* as_SB(SkShader* shader) {
return static_cast<SkShaderBase*>(shader);
}

View File

@ -0,0 +1,63 @@
/*
* 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/shaders/SkTransformShader.h"
SkTransformShader::SkTransformShader(const SkShaderBase& shader) : fShader{shader} {}
skvm::Color SkTransformShader::onProgram(skvm::Builder* b,
skvm::Coord device, skvm::Coord local, skvm::Color color,
const SkMatrixProvider& matrices, const SkMatrix* localM,
const SkColorInfo& dst,
skvm::Uniforms* uniforms, SkArenaAlloc* alloc) const {
skvm::Coord newLocal = this->applyMatrix(b, matrices.localToDevice(), local, uniforms);
SkSimpleMatrixProvider matrixProvider{SkMatrix::I()};
return fShader.program(
b, device, newLocal, color, matrixProvider, localM, dst, uniforms, alloc);
}
skvm::Coord SkTransformShader::applyMatrix(
skvm::Builder* b, const SkMatrix& matrix, skvm::Coord local,
skvm::Uniforms* uniforms) const {
fMatrix = uniforms->pushPtr(&fMatrixStorage);
skvm::F32 x = local.x,
y = local.y;
auto dot = [&,x,y](int row) {
return b->mad(x, b->arrayF(fMatrix, 3*row+0),
b->mad(y, b->arrayF(fMatrix, 3*row+1),
b->arrayF(fMatrix, 3*row+2)));
};
x = dot(0);
y = dot(1);
if (matrix.hasPerspective() || fShader.getLocalMatrix().hasPerspective()) {
x = x * (1.0f / dot(2));
y = y * (1.0f / dot(2));
}
return {x, y};
}
bool SkTransformShader::update(const SkMatrix& ctm, skvm::Uniforms* u) const {
SkMatrix matrix;
if (this->computeTotalInverse(ctm, nullptr, &matrix)) {
for (int i = 0; i < 9; ++i) {
fMatrixStorage[i] = matrix[i];
}
return true;
}
return false;
}
bool SkTransformShader::onAppendStages(const SkStageRec& rec) const {
// TODO
return false;
}

View File

@ -0,0 +1,47 @@
/*
* Copyright 2021 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef SkTextCoordShader_DEFINED
#define SkTextCoordShader_DEFINED
#include "src/core/SkVM.h"
#include "src/shaders/SkShaderBase.h"
// SkTransformShader allows the transform used by the shader to change without regenerating the
// jitted code. This supports the drawVertices call to change the mapping as the texture
// coordinates associated with each vertex change with each new triangle.
class SkTransformShader : public SkUpdatableShader {
public:
explicit SkTransformShader(const SkShaderBase& shader);
// Adds instructions to use the mapping stored in the uniforms represented by fMatrix. After
// generating a new skvm::Coord, it passes the mapped coordinates to fShader's onProgram
// along with the identity matrix.
skvm::Color onProgram(skvm::Builder* b,
skvm::Coord device, skvm::Coord local, skvm::Color color,
const SkMatrixProvider& matrices, const SkMatrix* localM,
const SkColorInfo& dst,
skvm::Uniforms* uniforms, SkArenaAlloc* alloc) const override;
// Add code to calculate a new coordinate given local using the mapping in fMatrix.
skvm::Coord applyMatrix(
skvm::Builder* b, const SkMatrix& matrix, skvm::Coord local,
skvm::Uniforms* uniforms) const;
// Change the values represented by the uniforms in fMatrix.
bool update(const SkMatrix& ctm, skvm::Uniforms* u) const override;
bool onAppendStages(const SkStageRec& rec) const override;
private:
// For serialization. This will never be called.
Factory getFactory() const override { return nullptr; }
const char* getTypeName() const override { return nullptr; }
const SkShaderBase& fShader;
mutable SkScalar fMatrixStorage[9];
mutable skvm::Uniform fMatrix;
};
#endif //SkTextCoordShader_DEFINED