/*
|
* Copyright 2017 Google Inc.
|
*
|
* Use of this source code is governed by a BSD-style license that can be
|
* found in the LICENSE file.
|
*/
|
|
#include "SkArenaAlloc.h"
|
#include "SkAutoBlitterChoose.h"
|
#include "SkComposeShader.h"
|
#include "SkConvertPixels.h"
|
#include "SkDraw.h"
|
#include "SkNx.h"
|
#include "SkRasterClip.h"
|
#include "SkScan.h"
|
#include "SkShaderBase.h"
|
#include "SkString.h"
|
#include "SkVertState.h"
|
|
#include "SkArenaAlloc.h"
|
#include "SkCoreBlitters.h"
|
|
struct Matrix43 {
|
float fMat[12]; // column major
|
|
Sk4f map(float x, float y) const {
|
return Sk4f::Load(&fMat[0]) * x + Sk4f::Load(&fMat[4]) * y + Sk4f::Load(&fMat[8]);
|
}
|
|
void setConcat(const Matrix43& a, const SkMatrix& b) {
|
fMat[ 0] = a.dot(0, b.getScaleX(), b.getSkewY());
|
fMat[ 1] = a.dot(1, b.getScaleX(), b.getSkewY());
|
fMat[ 2] = a.dot(2, b.getScaleX(), b.getSkewY());
|
fMat[ 3] = a.dot(3, b.getScaleX(), b.getSkewY());
|
|
fMat[ 4] = a.dot(0, b.getSkewX(), b.getScaleY());
|
fMat[ 5] = a.dot(1, b.getSkewX(), b.getScaleY());
|
fMat[ 6] = a.dot(2, b.getSkewX(), b.getScaleY());
|
fMat[ 7] = a.dot(3, b.getSkewX(), b.getScaleY());
|
|
fMat[ 8] = a.dot(0, b.getTranslateX(), b.getTranslateY()) + a.fMat[ 8];
|
fMat[ 9] = a.dot(1, b.getTranslateX(), b.getTranslateY()) + a.fMat[ 9];
|
fMat[10] = a.dot(2, b.getTranslateX(), b.getTranslateY()) + a.fMat[10];
|
fMat[11] = a.dot(3, b.getTranslateX(), b.getTranslateY()) + a.fMat[11];
|
}
|
|
private:
|
float dot(int index, float x, float y) const {
|
return fMat[index + 0] * x + fMat[index + 4] * y;
|
}
|
};
|
|
static SkScan::HairRCProc ChooseHairProc(bool doAntiAlias) {
|
return doAntiAlias ? SkScan::AntiHairLine : SkScan::HairLine;
|
}
|
|
static bool SK_WARN_UNUSED_RESULT
|
texture_to_matrix(const VertState& state, const SkPoint verts[], const SkPoint texs[],
|
SkMatrix* matrix) {
|
SkPoint src[3], dst[3];
|
|
src[0] = texs[state.f0];
|
src[1] = texs[state.f1];
|
src[2] = texs[state.f2];
|
dst[0] = verts[state.f0];
|
dst[1] = verts[state.f1];
|
dst[2] = verts[state.f2];
|
return matrix->setPolyToPoly(src, dst, 3);
|
}
|
|
class SkTriColorShader : public SkShaderBase {
|
public:
|
SkTriColorShader(bool isOpaque) : fIsOpaque(isOpaque) {}
|
|
Matrix43* getMatrix43() { return &fM43; }
|
|
bool isOpaque() const override { return fIsOpaque; }
|
|
|
protected:
|
#ifdef SK_ENABLE_LEGACY_SHADERCONTEXT
|
Context* onMakeContext(const ContextRec& rec, SkArenaAlloc* alloc) const override {
|
return nullptr;
|
}
|
#endif
|
bool onAppendStages(const StageRec& rec) const override {
|
rec.fPipeline->append(SkRasterPipeline::seed_shader);
|
rec.fPipeline->append(SkRasterPipeline::matrix_4x3, &fM43);
|
return true;
|
}
|
|
private:
|
// For serialization. This will never be called.
|
Factory getFactory() const override { return nullptr; }
|
const char* getTypeName() const override { return nullptr; }
|
|
Matrix43 fM43;
|
const bool fIsOpaque;
|
|
typedef SkShaderBase INHERITED;
|
};
|
|
static bool SK_WARN_UNUSED_RESULT
|
update_tricolor_matrix(const SkMatrix& ctmInv, const SkPoint pts[], const SkPMColor4f colors[],
|
int index0, int index1, int index2, Matrix43* result) {
|
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;
|
}
|
|
SkMatrix dstToUnit;
|
dstToUnit.setConcat(im, ctmInv);
|
|
Sk4f c0 = Sk4f::Load(colors[index0].vec()),
|
c1 = Sk4f::Load(colors[index1].vec()),
|
c2 = Sk4f::Load(colors[index2].vec());
|
|
Matrix43 colorm;
|
(c1 - c0).store(&colorm.fMat[0]);
|
(c2 - c0).store(&colorm.fMat[4]);
|
c0.store(&colorm.fMat[8]);
|
result->setConcat(colorm, dstToUnit);
|
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 SkPMColor4f* convert_colors(const SkColor src[], int count, SkColorSpace* deviceCS,
|
SkArenaAlloc* alloc) {
|
SkPMColor4f* dst = alloc->makeArray<SkPMColor4f>(count);
|
SkImageInfo srcInfo = SkImageInfo::Make(count, 1, kBGRA_8888_SkColorType,
|
kUnpremul_SkAlphaType, SkColorSpace::MakeSRGB());
|
SkImageInfo dstInfo = SkImageInfo::Make(count, 1, kRGBA_F32_SkColorType,
|
kPremul_SkAlphaType, sk_ref_sp(deviceCS));
|
SkConvertPixels(dstInfo, dst, 0, srcInfo, src, 0);
|
return dst;
|
}
|
|
static bool compute_is_opaque(const SkColor colors[], int count) {
|
uint32_t c = ~0;
|
for (int i = 0; i < count; ++i) {
|
c &= colors[i];
|
}
|
return SkColorGetA(c) == 0xFF;
|
}
|
|
void SkDraw::drawVertices(SkVertices::VertexMode vmode, int vertexCount,
|
const SkPoint vertices[], const SkPoint textures[],
|
const SkColor colors[], const SkVertices::BoneIndices boneIndices[],
|
const SkVertices::BoneWeights boneWeights[], SkBlendMode bmode,
|
const uint16_t indices[], int indexCount,
|
const SkPaint& paint, const SkVertices::Bone bones[],
|
int boneCount) const {
|
SkASSERT(0 == vertexCount || vertices);
|
|
// abort early if there is nothing to draw
|
if (vertexCount < 3 || (indices && indexCount < 3) || fRC->isEmpty()) {
|
return;
|
}
|
SkMatrix ctmInv;
|
if (!fMatrix->invert(&ctmInv)) {
|
return;
|
}
|
|
// make textures and shader mutually consistent
|
SkShader* shader = paint.getShader();
|
if (!(shader && textures)) {
|
shader = nullptr;
|
textures = nullptr;
|
}
|
|
// We can simplify things for certain blendmodes. This is for speed, and SkComposeShader
|
// itself insists we don't pass kSrc or kDst to it.
|
//
|
if (colors && textures) {
|
switch (bmode) {
|
case SkBlendMode::kSrc:
|
colors = nullptr;
|
break;
|
case SkBlendMode::kDst:
|
textures = nullptr;
|
break;
|
default: break;
|
}
|
}
|
|
// we don't use the shader if there are no textures
|
if (!textures) {
|
shader = nullptr;
|
}
|
|
constexpr size_t kDefVertexCount = 16;
|
constexpr size_t kOuterSize = sizeof(SkTriColorShader) +
|
sizeof(SkComposeShader) +
|
(2 * sizeof(SkPoint) + sizeof(SkColor4f)) * kDefVertexCount;
|
SkSTArenaAlloc<kOuterSize> outerAlloc;
|
|
// deform vertices using the skeleton if it is passed in
|
if (bones && boneCount) {
|
// allocate space for the deformed vertices
|
SkPoint* deformed = outerAlloc.makeArray<SkPoint>(vertexCount);
|
|
// deform the vertices
|
if (boneIndices && boneWeights) {
|
for (int i = 0; i < vertexCount; i ++) {
|
const SkVertices::BoneIndices& indices = boneIndices[i];
|
const SkVertices::BoneWeights& weights = boneWeights[i];
|
|
// apply the world transform
|
SkPoint worldPoint = bones[0].mapPoint(vertices[i]);
|
|
// apply bone deformations
|
deformed[i] = SkPoint::Make(0.0f, 0.0f);
|
for (uint32_t j = 0; j < 4; j ++) {
|
// get the attachment data
|
uint32_t index = indices[j];
|
float weight = weights[j];
|
|
// skip the bone if there is no weight
|
if (weight == 0.0f) {
|
continue;
|
}
|
SkASSERT(index != 0);
|
|
// deformed += M * v * w
|
deformed[i] += bones[index].mapPoint(worldPoint) * weight;
|
}
|
}
|
} else {
|
// no bones, so only apply world transform
|
SkMatrix worldTransform = SkMatrix::I();
|
worldTransform.setAffine(bones[0].values);
|
worldTransform.mapPoints(deformed, vertices, vertexCount);
|
}
|
|
// change the vertices to point to deformed
|
vertices = deformed;
|
}
|
|
SkPoint* devVerts = outerAlloc.makeArray<SkPoint>(vertexCount);
|
fMatrix->mapPoints(devVerts, vertices, vertexCount);
|
|
{
|
SkRect bounds;
|
// this also sets bounds to empty if we see a non-finite value
|
bounds.set(devVerts, vertexCount);
|
if (bounds.isEmpty()) {
|
return;
|
}
|
}
|
|
VertState state(vertexCount, indices, indexCount);
|
VertState::Proc vertProc = state.chooseProc(vmode);
|
|
if (colors || textures) {
|
SkPMColor4f* dstColors = nullptr;
|
Matrix43* matrix43 = nullptr;
|
|
if (colors) {
|
dstColors = convert_colors(colors, vertexCount, fDst.colorSpace(), &outerAlloc);
|
|
SkTriColorShader* triShader = outerAlloc.make<SkTriColorShader>(
|
compute_is_opaque(colors,
|
vertexCount));
|
matrix43 = triShader->getMatrix43();
|
if (shader) {
|
shader = outerAlloc.make<SkComposeShader>(sk_ref_sp(triShader), sk_ref_sp(shader),
|
bmode, 1);
|
} else {
|
shader = triShader;
|
}
|
}
|
|
SkPaint p(paint);
|
p.setShader(sk_ref_sp(shader));
|
|
if (!textures) { // only tricolor shader
|
SkASSERT(matrix43);
|
auto blitter = SkCreateRasterPipelineBlitter(fDst, p, *fMatrix, &outerAlloc);
|
while (vertProc(&state)) {
|
if (!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, blitter);
|
}
|
} else {
|
while (vertProc(&state)) {
|
SkSTArenaAlloc<2048> innerAlloc;
|
|
const SkMatrix* ctm = fMatrix;
|
SkMatrix tmpCtm;
|
if (textures) {
|
SkMatrix localM;
|
if (!texture_to_matrix(state, vertices, textures, &localM)) {
|
continue;
|
}
|
tmpCtm = SkMatrix::Concat(*fMatrix, localM);
|
ctm = &tmpCtm;
|
}
|
|
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]
|
};
|
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(*this, nullptr, 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)) {
|
SkPoint array[] = {
|
devVerts[state.f0], devVerts[state.f1], devVerts[state.f2], devVerts[state.f0]
|
};
|
hairProc(array, 4, clip, blitter.get());
|
}
|
}
|
}
|