From 176f19cce532f8f5e2290515531660e3f59bbfde Mon Sep 17 00:00:00 2001 From: Mike Reed Date: Wed, 24 May 2017 13:53:36 -0400 Subject: [PATCH] Use rasterpipeline for drawVertices Bug: skia: Change-Id: If6da119ee98f26981cef9373162ddb526db77be5 Reviewed-on: https://skia-review.googlesource.com/17422 Commit-Queue: Mike Reed Reviewed-by: Florin Malita Reviewed-by: Mike Klein --- src/core/SkDraw_vertices.cpp | 480 ++++++++----------------------------------- tests/BlitRowTest.cpp | 31 ++- 2 files changed, 111 insertions(+), 400 deletions(-) diff --git a/src/core/SkDraw_vertices.cpp b/src/core/SkDraw_vertices.cpp index 398d02c..442ba08 100644 --- a/src/core/SkDraw_vertices.cpp +++ b/src/core/SkDraw_vertices.cpp @@ -7,7 +7,7 @@ #include "SkArenaAlloc.h" #include "SkAutoBlitterChoose.h" -#include "SkColorShader.h" +#include "SkComposeShader.h" #include "SkDraw.h" #include "SkNx.h" #include "SkPM4fPriv.h" @@ -17,7 +17,6 @@ #include "SkString.h" #include "SkVertState.h" -#include "SkRasterPipeline.h" #include "SkArenaAlloc.h" #include "SkCoreBlitters.h" #include "SkColorSpaceXform.h" @@ -72,188 +71,40 @@ static bool texture_to_matrix(const VertState& state, const SkPoint verts[], class SkTriColorShader : public SkShader { public: - SkTriColorShader(); - - class TriColorShaderContext : public SkShader::Context { - public: - TriColorShaderContext(const SkTriColorShader& shader, const ContextRec&); - ~TriColorShaderContext() override; - void shadeSpan(int x, int y, SkPMColor dstC[], int count) override; - void shadeSpan4f(int x, int y, SkPM4f dstC[], int count) override; - - private: - bool setup(const SkPoint pts[], const SkColor colors[], int, int, int); - - SkMatrix fDstToUnit; - SkPMColor fColors[3]; - bool fSetup; - - Matrix43 fM43; + SkTriColorShader(bool isOpaque) : fIsOpaque(isOpaque) {} - typedef SkShader::Context INHERITED; - }; + Matrix43* getMatrix43() { return &fM43; } - struct TriColorShaderData { - const SkPoint* pts; - const SkColor* colors; - const VertState *state; - }; + bool isOpaque() const override { return fIsOpaque; } SK_TO_STRING_OVERRIDE() // For serialization. This will never be called. Factory getFactory() const override { sk_throw(); return nullptr; } - // Supply setup data to context from drawing setup - void bindSetupData(TriColorShaderData* setupData) { fSetupData = setupData; } - - // Take the setup data from context when needed. - TriColorShaderData* takeSetupData() { - TriColorShaderData *data = fSetupData; - fSetupData = NULL; - return data; - } - protected: Context* onMakeContext(const ContextRec& rec, SkArenaAlloc* alloc) const override { - return alloc->make(*this, rec); + return nullptr; + } + bool onAppendStages(SkRasterPipeline* pipeine, SkColorSpace* dstCS, SkArenaAlloc* alloc, + const SkMatrix&, const SkPaint&, const SkMatrix*) const override { + pipeine->append(SkRasterPipeline::matrix_4x3, &fM43); + // In theory we should never need to clamp. However, either due to imprecision in our + // matrix43, or the scan converter passing us pixel centers that in fact are not within + // the triangle, we do see occasional (slightly) out-of-range values, so we add these + // clamp stages. It would be nice to find a way to detect when these are not needed. + pipeine->append(SkRasterPipeline::clamp_0); + pipeine->append(SkRasterPipeline::clamp_a); + return true; } private: - TriColorShaderData *fSetupData; + Matrix43 fM43; + const bool fIsOpaque; typedef SkShader INHERITED; }; -bool SkTriColorShader::TriColorShaderContext::setup(const SkPoint pts[], const SkColor colors[], - int index0, int index1, int index2) { - - fColors[0] = SkPreMultiplyColor(colors[index0]); - fColors[1] = SkPreMultiplyColor(colors[index1]); - fColors[2] = SkPreMultiplyColor(colors[index2]); - - SkMatrix m, im; - m.reset(); - m.set(0, pts[index1].fX - pts[index0].fX); - m.set(1, pts[index2].fX - pts[index0].fX); - m.set(2, pts[index0].fX); - m.set(3, pts[index1].fY - pts[index0].fY); - m.set(4, pts[index2].fY - pts[index0].fY); - m.set(5, pts[index0].fY); - if (!m.invert(&im)) { - return false; - } - // We can't call getTotalInverse(), because we explicitly don't want to look at the localmatrix - // as our interators are intrinsically tied to the vertices, and nothing else. - SkMatrix ctmInv; - if (!this->getCTM().invert(&ctmInv)) { - return false; - } - // TODO replace INV(m) * INV(ctm) with INV(ctm * m) - fDstToUnit.setConcat(im, ctmInv); - - Sk4f alpha(this->getPaintAlpha() * (1.0f / 255)), - c0 = SkPM4f::FromPMColor(fColors[0]).to4f() * alpha, - c1 = SkPM4f::FromPMColor(fColors[1]).to4f() * alpha, - c2 = SkPM4f::FromPMColor(fColors[2]).to4f() * alpha; - - Matrix43 colorm; - (c1 - c0).store(&colorm.fMat[0]); - (c2 - c0).store(&colorm.fMat[4]); - c0.store(&colorm.fMat[8]); - fM43.setConcat(colorm, fDstToUnit); - - return true; -} - -#include "SkColorPriv.h" -#include "SkComposeShader.h" - -static int ScalarTo256(SkScalar v) { - return static_cast(SkScalarPin(v, 0, 1) * 256 + 0.5); -} - -SkTriColorShader::SkTriColorShader() -: INHERITED(NULL) -, fSetupData(NULL) {} - -SkTriColorShader::TriColorShaderContext::TriColorShaderContext(const SkTriColorShader& shader, - const ContextRec& rec) -: INHERITED(shader, rec) -, fSetup(false) {} - -SkTriColorShader::TriColorShaderContext::~TriColorShaderContext() {} - -void SkTriColorShader::TriColorShaderContext::shadeSpan(int x, int y, SkPMColor dstC[], int count) { - SkTriColorShader* parent = static_cast(const_cast(&fShader)); - TriColorShaderData* set = parent->takeSetupData(); - if (set) { - fSetup = setup(set->pts, set->colors, set->state->f0, set->state->f1, set->state->f2); - } - - if (!fSetup) { - // Invalid matrices. Not checked before so no need to assert. - return; - } - - const int alphaScale = Sk255To256(this->getPaintAlpha()); - - SkPoint src; - - fDstToUnit.mapXY(SkIntToScalar(x) + 0.5, SkIntToScalar(y) + 0.5, &src); - for (int i = 0; i < count; i++) { - int scale1 = ScalarTo256(src.fX); - int scale2 = ScalarTo256(src.fY); - int scale0 = 256 - scale1 - scale2; - if (scale0 < 0) { - if (scale1 > scale2) { - scale2 = 256 - scale1; - } else { - scale1 = 256 - scale2; - } - scale0 = 0; - } - - if (256 != alphaScale) { - scale0 = SkAlphaMul(scale0, alphaScale); - scale1 = SkAlphaMul(scale1, alphaScale); - scale2 = SkAlphaMul(scale2, alphaScale); - } - - dstC[i] = SkAlphaMulQ(fColors[0], scale0) + - SkAlphaMulQ(fColors[1], scale1) + - SkAlphaMulQ(fColors[2], scale2); - - src.fX += fDstToUnit.getScaleX(); - src.fY += fDstToUnit.getSkewY(); - } -} - -void SkTriColorShader::TriColorShaderContext::shadeSpan4f(int x, int y, SkPM4f dstC[], int count) { - SkTriColorShader* parent = static_cast(const_cast(&fShader)); - TriColorShaderData* set = parent->takeSetupData(); - if (set) { - fSetup = setup(set->pts, set->colors, set->state->f0, set->state->f1, set->state->f2); - } - - if (!fSetup) { - // Invalid matrices. Not checked before so no need to assert. - return; - } - - Sk4f c = fM43.map(SkIntToScalar(x) + 0.5, SkIntToScalar(y) + 0.5), - dc = Sk4f::Load(&fM43.fMat[0]), - zero(0.0f), - one(1.0f); - - for (int i = 0; i < count; i++) { - // We don't expect to be wildly out of 0...1, but we pin just because of minor - // numerical imprecision. - Sk4f::Min(Sk4f::Max(c, zero), Sk4f::Min(c[3], one)).store(dstC + i); - c += dc; - } -} - #ifndef SK_IGNORE_TO_STRING void SkTriColorShader::toString(SkString* str) const { str->append("SkTriColorShader: ("); @@ -264,106 +115,6 @@ void SkTriColorShader::toString(SkString* str) const { } #endif - -namespace { - - // Similar to SkLocalMatrixShader, but composes the local matrix with the CTM (instead - // of composing with the inherited local matrix): - // - // rec' = {rec.ctm x localMatrix, rec.localMatrix} - // - // (as opposed to rec' = {rec.ctm, rec.localMatrix x localMatrix}) - // - class SkLocalInnerMatrixShader final : public SkShader { - public: - SkLocalInnerMatrixShader(sk_sp proxy, const SkMatrix& localMatrix) - : INHERITED(&localMatrix) - , fProxyShader(std::move(proxy)) {} - - Factory getFactory() const override { - SkASSERT(false); - return nullptr; - } - - protected: - void flatten(SkWriteBuffer&) const override { - SkASSERT(false); - } - - Context* onMakeContext(const ContextRec& rec, SkArenaAlloc* alloc) const override { - SkMatrix adjustedCTM = SkMatrix::Concat(*rec.fMatrix, this->getLocalMatrix()); - ContextRec newRec(rec); - newRec.fMatrix = &adjustedCTM; - return fProxyShader->makeContext(newRec, alloc); - } - - bool onAppendStages(SkRasterPipeline* p, SkColorSpace* cs, SkArenaAlloc* alloc, - const SkMatrix& ctm, const SkPaint& paint, - const SkMatrix* localM) const override { - // We control the shader graph ancestors, so we know there's no local matrix being - // injected before this. - SkASSERT(!localM); - - SkMatrix adjustedCTM = SkMatrix::Concat(ctm, this->getLocalMatrix()); - return fProxyShader->appendStages(p, cs, alloc, adjustedCTM, paint); - } - - private: - sk_sp fProxyShader; - - typedef SkShader INHERITED; - }; - - sk_sp MakeTextureShader(const VertState& state, const SkPoint verts[], - const SkPoint texs[], const SkPaint& paint, - SkColorSpace* dstColorSpace, - SkArenaAlloc* alloc) { - SkASSERT(paint.getShader()); - - const auto& p0 = texs[state.f0], - p1 = texs[state.f1], - p2 = texs[state.f2]; - - if (p0 != p1 || p0 != p2) { - // Common case (non-collapsed texture coordinates). - // Map the texture to vertices using a local transform. - - // We cannot use a plain SkLocalMatrix shader, because we need the texture matrix - // to compose next to the CTM. - SkMatrix localMatrix; - return texture_to_matrix(state, verts, texs, &localMatrix) - ? alloc->makeSkSp(paint.refShader(), localMatrix) - : nullptr; - } - - // Collapsed texture coordinates special case. - // The texture is a solid color, sampled at the given point. - SkMatrix shaderInvLocalMatrix; - SkAssertResult(paint.getShader()->getLocalMatrix().invert(&shaderInvLocalMatrix)); - - const auto sample = SkPoint::Make(0.5f, 0.5f); - const auto mappedSample = shaderInvLocalMatrix.mapXY(sample.x(), sample.y()), - mappedPoint = shaderInvLocalMatrix.mapXY(p0.x(), p0.y()); - const auto localMatrix = SkMatrix::MakeTrans(mappedSample.x() - mappedPoint.x(), - mappedSample.y() - mappedPoint.y()); - - SkShader::ContextRec rec(paint, SkMatrix::I(), &localMatrix, - SkShader::ContextRec::kPMColor_DstType, dstColorSpace); - auto* ctx = paint.getShader()->makeContext(rec, alloc); - if (!ctx) { - return nullptr; - } - - SkPMColor pmColor; - ctx->shadeSpan(SkScalarFloorToInt(sample.x()), SkScalarFloorToInt(sample.y()), &pmColor, 1); - - // no need to keep this temp context around. - alloc->reset(); - - return alloc->makeSkSp(SkUnPreMultiply::PMColorToColor(pmColor)); - } -} // anonymous ns - static bool update_tricolor_matrix(const SkMatrix& ctmInv, const SkPoint pts[], const SkPM4f colors[], int index0, int index1, int index2, Matrix43* result) { @@ -394,6 +145,15 @@ static bool update_tricolor_matrix(const SkMatrix& ctmInv, return true; } +// Convert the SkColors into float colors. The conversion depends on some conditions: +// - If the pixmap has a dst colorspace, we have to be "color-correct". +// Do we map into dst-colorspace before or after we interpolate? +// - We have to decide when to apply per-color alpha (before or after we interpolate) +// +// For now, we will take a simple approach, but recognize this is just a start: +// - convert colors into dst colorspace before interpolation (matches gradients) +// - apply per-color alpha before interpolation (matches old version of vertices) +// static SkPM4f* convert_colors(const SkColor src[], int count, SkColorSpace* deviceCS, SkArenaAlloc* alloc) { SkPM4f* dst = alloc->makeArray(count); @@ -435,159 +195,81 @@ void SkDraw::drawVertices(SkVertices::VertexMode vmode, int count, return; } - // transform out vertices into device coordinates - SkAutoSTMalloc<16, SkPoint> storage(count); - SkPoint* devVerts = storage.get(); - fMatrix->mapPoints(devVerts, vertices, count); - - /* - We can draw the vertices in 1 of 4 ways: - - - solid color (no shader/texture[], no colors[]) - - just colors (no shader/texture[], has colors[]) - - just texture (has shader/texture[], no colors[]) - - colors * texture (has shader/texture[], has colors[]) - - Thus for texture drawing, we need both texture[] and a shader. - */ - - if (colors && !textures) { - char arenaStorage[4096]; - SkArenaAlloc alloc(arenaStorage, sizeof(storage)); - Matrix43 matrix43; - SkRasterPipeline shaderPipeline(&alloc); - - // Convert the SkColors into float colors. The conversion depends on some conditions: - // - If the pixmap has a dst colorspace, we have to be "color-correct". - // Do we map into dst-colorspace before or after we interpolate? - // - We have to decide when to apply per-color alpha (before or after we interpolate) - // - // For now, we will take a simple approach, but recognize this is just a start: - // - convert colors into dst colorspace before interpolation (matches gradients) - // - apply per-color alpha before interpolation (matches old version of vertices) - // - SkPM4f* dstColors = convert_colors(colors, count, fDst.colorSpace(), &alloc); - - bool is_opaque; - if (paint.getAlpha() == 0xff) { - is_opaque = compute_is_opaque(colors, count); - } else { - is_opaque = false; - Sk4f alpha = paint.getAlpha() * (1/255.0f); - for (int i = 0; i < count; i++) { - (dstColors[i].to4f() * alpha).store(dstColors + i); - } - } - - shaderPipeline.append(SkRasterPipeline::matrix_4x3, &matrix43); - // In theory we should never need to clamp. However, either due to imprecision in our - // matrix43, or the scan converter passing us pixel centers that in fact are not within - // the triangle, we do see occasional (slightly) out-of-range values, so we add these - // clamp stages. It would be nice to find a way to detect when these are not needed. - shaderPipeline.append(SkRasterPipeline::clamp_0); - shaderPipeline.append(SkRasterPipeline::clamp_a); - - bool wants_dither = paint.isDither(); - auto blitter = SkCreateRasterPipelineBlitter(fDst, paint, shaderPipeline, - is_opaque, wants_dither, &alloc); - SkASSERT(!blitter->isNullBlitter()); - - // setup our state and function pointer for iterating triangles - VertState state(count, indices, indexCount); - VertState::Proc vertProc = state.chooseProc(vmode); - - while (vertProc(&state)) { - SkPoint tmp[] = { - devVerts[state.f0], devVerts[state.f1], devVerts[state.f2] - }; - if (update_tricolor_matrix(ctmInv, vertices, dstColors, state.f0, state.f1, state.f2, - &matrix43)) { - SkScan::FillTriangle(tmp, *fRC, blitter); - } - } - return; - } - - auto triShader = sk_make_sp(); - SkPaint p(paint); - - SkShader* shader = p.getShader(); - if (nullptr == shader) { - // if we have no shader, we ignore the texture coordinates - textures = nullptr; - } else if (nullptr == textures) { - // if we don't have texture coordinates, ignore the shader - p.setShader(nullptr); + // make textures and shader mutually consistent + SkShader* shader = paint.getShader(); + if (!(shader && textures)) { shader = nullptr; + textures = nullptr; } - // setup the custom shader (if needed) - if (colors) { - if (nullptr == textures) { - // just colors (no texture) - p.setShader(triShader); - } else { - // colors * texture - SkASSERT(shader); - p.setShader(SkShader::MakeComposeShader(triShader, sk_ref_sp(shader), bmode)); - } - } + constexpr size_t defCount = 16; + constexpr size_t outerSize = sizeof(SkTriColorShader) + + sizeof(SkComposeShader) + + (sizeof(SkPoint) + sizeof(SkPM4f)) * defCount; + char outerStorage[outerSize]; + SkArenaAlloc outerAlloc(outerStorage, sizeof(outerStorage)); - SkAutoBlitterChoose blitter(fDst, *fMatrix, p); - // Abort early if we failed to create a shader context. - if (blitter->isNullBlitter()) { - return; - } + SkPoint* devVerts = outerAlloc.makeArray(count); + fMatrix->mapPoints(devVerts, vertices, count); - // setup our state and function pointer for iterating triangles VertState state(count, indices, indexCount); VertState::Proc vertProc = state.chooseProc(vmode); - if (textures || colors) { - SkTriColorShader::TriColorShaderData verticesSetup = { vertices, colors, &state }; + if (colors || textures) { + SkPM4f* dstColors = nullptr; + Matrix43* matrix43 = nullptr; + + if (colors) { + dstColors = convert_colors(colors, count, fDst.colorSpace(), &outerAlloc); + + SkTriColorShader* triShader = outerAlloc.make( + compute_is_opaque(colors, count)); + matrix43 = triShader->getMatrix43(); + if (shader) { + shader = outerAlloc.make(sk_ref_sp(triShader), sk_ref_sp(shader), + bmode); + } else { + shader = triShader; + } + } + + SkPaint p(paint); + p.setShader(sk_ref_sp(shader)); while (vertProc(&state)) { - auto* blitterPtr = blitter.get(); - - // We're going to allocate at most - // - // * one SkLocalMatrixShader OR one SkColorShader - // * one SkComposeShader - // * one SkAutoBlitterChoose - // - static constexpr size_t kAllocSize = - sizeof(SkAutoBlitterChoose) + sizeof(SkComposeShader) + - SkTMax(sizeof(SkLocalInnerMatrixShader), sizeof(SkColorShader)); - char allocBuffer[kAllocSize]; - SkArenaAlloc alloc(allocBuffer); + char innerStorage[2048]; + SkArenaAlloc innerAlloc(innerStorage, sizeof(innerStorage)); + const SkMatrix* ctm = fMatrix; + SkMatrix tmpCtm; if (textures) { - sk_sp texShader = MakeTextureShader(state, vertices, textures, paint, - fDst.colorSpace(), &alloc); - if (texShader) { - SkPaint localPaint(p); - localPaint.setShader(colors - ? alloc.makeSkSp(triShader, std::move(texShader), bmode) - : std::move(texShader)); - - blitterPtr = alloc.make(fDst, *fMatrix, localPaint)->get(); - if (blitterPtr->isNullBlitter()) { - continue; - } - } + SkMatrix localM; + texture_to_matrix(state, vertices, textures, &localM); + tmpCtm = SkMatrix::Concat(*fMatrix, localM); + ctm = &tmpCtm; } - if (colors) { - triShader->bindSetupData(&verticesSetup); + + if (matrix43 && !update_tricolor_matrix(ctmInv, vertices, dstColors, + state.f0, state.f1, state.f2, + matrix43)) { + continue; } SkPoint tmp[] = { devVerts[state.f0], devVerts[state.f1], devVerts[state.f2] }; - SkScan::FillTriangle(tmp, *fRC, blitterPtr); - triShader->bindSetupData(nullptr); + auto blitter = SkCreateRasterPipelineBlitter(fDst, p, *ctm, &innerAlloc); + SkScan::FillTriangle(tmp, *fRC, blitter); } } else { // no colors[] and no texture, stroke hairlines with paint's color. + SkPaint p; + p.setStyle(SkPaint::kStroke_Style); + SkAutoBlitterChoose blitter(fDst, *fMatrix, p); + // Abort early if we failed to create a shader context. + if (blitter->isNullBlitter()) { + return; + } SkScan::HairRCProc hairProc = ChooseHairProc(paint.isAntiAlias()); const SkRasterClip& clip = *fRC; while (vertProc(&state)) { diff --git a/tests/BlitRowTest.cpp b/tests/BlitRowTest.cpp index d2f333d..45937b2 100644 --- a/tests/BlitRowTest.cpp +++ b/tests/BlitRowTest.cpp @@ -185,6 +185,35 @@ static void save_bm(const SkBitmap& bm, const char name[]) { sk_tool_utils::EncodeImageToFile(name, bm, SkEncodedImageFormat::kPNG, 100); } +static int max_diff(uint32_t u, uint32_t v) { + int d0 = SkAbs32(int((u >> 24) & 0xFF) - int((v >> 24) & 0xFF)); + int d1 = SkAbs32(int((u >> 16) & 0xFF) - int((v >> 16) & 0xFF)); + int d2 = SkAbs32(int((u >> 8) & 0xFF) - int((v >> 8) & 0xFF)); + int d3 = SkAbs32(int((u >> 0) & 0xFF) - int((v >> 0) & 0xFF)); + return SkMax32(d0, SkMax32(d1, SkMax32(d2, d3))); +} + +static bool nearly_eq(const SkBitmap& a, const SkBitmap& b) { + switch (a.colorType()) { + case kN32_SkColorType: { + for (int y = 0; y < a.width(); ++y) { + const SkPMColor* ap = a.getAddr32(0, y); + const SkPMColor* bp = b.getAddr32(0, y); + for (int x = 0; x < a.width(); ++x) { + int diff = max_diff(ap[x], bp[x]); + if (diff > 1) { + return false; + } + } + } + return true; + } break; + default: + break; + } + return !memcmp(a.getPixels(), b.getPixels(), a.getSize()); +} + static bool gOnce; // Make sure our blits are invariant with the width of the blit (i.e. that @@ -243,7 +272,7 @@ static void test_diagonal(skiatest::Reporter* reporter) { gOnce = true; } - if (memcmp(dstBM0.getPixels(), dstBM1.getPixels(), dstBM0.getSize())) { + if (!nearly_eq(dstBM0, dstBM1)) { ERRORF(reporter, "Diagonal colortype=%s bg=0x%x dither=%d" " alpha=0x%x src=0x%x", gColorTypeName[gDstColorType[i]], bgColor, dither, -- 2.7.4