From 5992f9eb43f0ef4b15bb1e1b890668ebda6284f9 Mon Sep 17 00:00:00 2001 From: Herb Derby Date: Sat, 17 Jul 2021 18:19:03 -0400 Subject: [PATCH] draw vertices: fast triangles using a shader MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Reviewed-by: Mike Reed --- gn/core.gni | 2 + src/core/SkDraw_vertices.cpp | 43 +++++++++++++-------- src/core/SkVMBlitter.cpp | 3 +- src/core/SkVMBlitter.h | 5 +++ src/shaders/SkImageShader.cpp | 42 ++++++++++++++++++++- src/shaders/SkImageShader.h | 8 ++++ src/shaders/SkShader.cpp | 13 +++++++ src/shaders/SkShaderBase.h | 10 +++++ src/shaders/SkTransformShader.cpp | 63 +++++++++++++++++++++++++++++++ src/shaders/SkTransformShader.h | 47 +++++++++++++++++++++++ 10 files changed, 217 insertions(+), 19 deletions(-) create mode 100644 src/shaders/SkTransformShader.cpp create mode 100644 src/shaders/SkTransformShader.h diff --git a/gn/core.gni b/gn/core.gni index 11a593b198..ea319ccd84 100644 --- a/gn/core.gni +++ b/gn/core.gni @@ -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", diff --git a/src/core/SkDraw_vertices.cpp b/src/core/SkDraw_vertices.cpp index 46618817f7..436d75abd7 100644 --- a/src/core/SkDraw_vertices.cpp +++ b/src/core/SkDraw_vertices.cpp @@ -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; diff --git a/src/core/SkVMBlitter.cpp b/src/core/SkVMBlitter.cpp index c388dbe541..2c707cb886 100644 --- a/src/core/SkVMBlitter.cpp +++ b/src/core/SkVMBlitter.cpp @@ -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)) {} diff --git a/src/core/SkVMBlitter.h b/src/core/SkVMBlitter.h index 940ae77903..41825ee80d 100644 --- a/src/core/SkVMBlitter.h +++ b/src/core/SkVMBlitter.h @@ -36,6 +36,11 @@ public: ~SkVMBlitter() override; + template + bool updateUniforms(Updater updater) { + return updater(&fUniforms); + } + private: enum class Coverage { Full, UniformF, MaskA8, MaskLCD16, Mask3D }; struct Key { diff --git a/src/shaders/SkImageShader.cpp b/src/shaders/SkImageShader.cpp index 864e819cd5..3f948d7c67 100755 --- a/src/shaders/SkImageShader.cpp +++ b/src/shaders/SkImageShader.cpp @@ -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(*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()) diff --git a/src/shaders/SkImageShader.h b/src/shaders/SkImageShader.h index acbc342636..dc64aa0d19 100644 --- a/src/shaders/SkImageShader.h +++ b/src/shaders/SkImageShader.h @@ -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 fImage; diff --git a/src/shaders/SkShader.cpp b/src/shaders/SkShader.cpp index 5fbc328690..f838b2e179 100644 --- a/src/shaders/SkShader.cpp +++ b/src/shaders/SkShader.cpp @@ -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 SkShaderBase::makeAsALocalMatrixShader(SkMatrix*) const { return nullptr; } +SkUpdatableShader* SkShaderBase::updatableShader(SkArenaAlloc* alloc) const { + if (auto updatable = this->onUpdatableShader(alloc)) { + return updatable; + } + + return alloc->make(*as_SB(this)); +} + +SkUpdatableShader* SkShaderBase::onUpdatableShader(SkArenaAlloc* alloc) const { + return nullptr; +} + sk_sp SkShaders::Empty() { return sk_make_sp(); } sk_sp SkShaders::Color(SkColor color) { return sk_make_sp(color); } diff --git a/src/shaders/SkShaderBase.h b/src/shaders/SkShaderBase.h index e4b71503f6..dc4e5b9c64 100644 --- a/src/shaders/SkShaderBase.h +++ b/src/shaders/SkShaderBase.h @@ -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 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(shader); } diff --git a/src/shaders/SkTransformShader.cpp b/src/shaders/SkTransformShader.cpp new file mode 100644 index 0000000000..56cb8f97f3 --- /dev/null +++ b/src/shaders/SkTransformShader.cpp @@ -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; +} diff --git a/src/shaders/SkTransformShader.h b/src/shaders/SkTransformShader.h new file mode 100644 index 0000000000..a15b6c5115 --- /dev/null +++ b/src/shaders/SkTransformShader.h @@ -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