Detect when perspective is really affine, and update the matrix as we handoff

the ctm to the device.

This catches cases where the matrix bottom-row might look like
   [ 0, 0, not_one ]

That would get categorized as perspective, but in reality that matrix
behaves like affine. If we can detect that pattern, and scale the entire
matrix by 1/not_one, we don't change its behavior, but it will now be
categorized as affine (seen as simpler/faster).

bug: skia:9698
Change-Id: Ib77b647c1d32f73538b1c0d8e9e49ec533610b3a
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/260776
Commit-Queue: Mike Reed <reed@google.com>
Reviewed-by: Mike Klein <mtklein@google.com>
Reviewed-by: Jim Van Verth <jvanverth@google.com>
This commit is contained in:
Mike Reed 2019-12-18 13:34:15 -05:00 committed by Skia Commit-Bot
parent 8fec4140f6
commit c880346ba5
5 changed files with 63 additions and 37 deletions

View File

@ -500,20 +500,8 @@ public:
@param persp2 perspective scale factor to store
*/
SkMatrix& setAll(SkScalar scaleX, SkScalar skewX, SkScalar transX,
SkScalar skewY, SkScalar scaleY, SkScalar transY,
SkScalar persp0, SkScalar persp1, SkScalar persp2) {
fMat[kMScaleX] = scaleX;
fMat[kMSkewX] = skewX;
fMat[kMTransX] = transX;
fMat[kMSkewY] = skewY;
fMat[kMScaleY] = scaleY;
fMat[kMTransY] = transY;
fMat[kMPersp0] = persp0;
fMat[kMPersp1] = persp1;
fMat[kMPersp2] = persp2;
this->setTypeMask(kUnknown_Mask);
return *this;
}
SkScalar skewY, SkScalar scaleY, SkScalar transY,
SkScalar persp0, SkScalar persp1, SkScalar persp2);
/** Copies nine scalar values contained by SkMatrix into buffer, in member value
ascending order: kMScaleX, kMSkewX, kMTransX, kMSkewY, kMScaleY, kMTransY,
@ -1235,6 +1223,19 @@ public:
*/
SkMatrix& setAffine(const SkScalar affine[6]);
/**
* A matrix is categorized as 'perspective' if the bottom row is not [0, 0, 1].
* However, for most uses (e.g. mapPoints) a bottom row of [0, 0, X] behaves like a
* non-perspective matrix, though it will be categorized as perspective. Calling
* normalizePerspective() will change the matrix such that, if its bottom row was [0, 0, X],
* it will be changed to [0, 0, 1] by scaling the rest of the matrix by 1/X.
*
* | A B C | | A/X B/X C/X |
* | D E F | -> | D/X E/X F/X | for X != 0
* | 0 0 X | | 0 0 1 |
*/
void normalizePerspective();
/** Maps src SkPoint array of length count to dst SkPoint array of equal or greater
length. SkPoint are mapped by multiplying each SkPoint by SkMatrix. Given:

View File

@ -670,7 +670,7 @@ class HalfPlaneCoons : public SampleCameraView {
bool onChar(SkUnichar uni) override {
switch (uni) {
case 'h': fShowHandles = !fShowHandles; return true;
case 's': fShowSkeleton = !fShowSkeleton; return true;
case 'k': fShowSkeleton = !fShowSkeleton; return true;
case 't': fShowTex = !fShowTex; return true;
default: break;
}

View File

@ -42,11 +42,13 @@ SkBaseDevice::SkBaseDevice(const SkImageInfo& info, const SkSurfaceProps& surfac
void SkBaseDevice::setOrigin(const SkMatrix& globalCTM, int x, int y) {
fOrigin.set(x, y);
fLocalToDevice = globalCTM;
fLocalToDevice.normalizePerspective();
fLocalToDevice.postTranslate(SkIntToScalar(-x), SkIntToScalar(-y));
}
void SkBaseDevice::setGlobalCTM(const SkMatrix& ctm) {
fLocalToDevice = ctm;
fLocalToDevice.normalizePerspective();
if (fOrigin.fX | fOrigin.fY) {
fLocalToDevice.postTranslate(-SkIntToScalar(fOrigin.fX), -SkIntToScalar(fOrigin.fY));
}

View File

@ -128,7 +128,7 @@ texture_to_matrix(const VertState& state, const SkPoint verts[], const SkPoint t
class SkTriColorShader : public SkShaderBase {
public:
SkTriColorShader(bool isOpaque) : fIsOpaque(isOpaque) {}
SkTriColorShader(bool isOpaque, bool usePersp) : fIsOpaque(isOpaque), fUsePersp(usePersp) {}
// This gets called for each triangle, without re-calling onAppendStages.
bool update(const SkMatrix& ctmInv, const SkPoint pts[], const SkPMColor4f colors[],
@ -142,7 +142,7 @@ protected:
#endif
bool onAppendStages(const SkStageRec& rec) const override {
rec.fPipeline->append(SkRasterPipeline::seed_shader);
if (rec.fCTM.hasPerspective()) {
if (fUsePersp) {
rec.fPipeline->append(SkRasterPipeline::matrix_perspective, &fM33);
}
rec.fPipeline->append(SkRasterPipeline::matrix_4x3, &fM43);
@ -155,12 +155,13 @@ private:
Factory getFactory() const override { return nullptr; }
const char* getTypeName() const override { return nullptr; }
// If ctm has perspective, we need both of these matrices,
// If fUsePersp, we need both of these matrices,
// otherwise we can combine them, and only use fM43
Matrix43 fM43;
SkMatrix fM33;
const bool fIsOpaque;
const bool fUsePersp; // controls our stages, and what we do in update()
typedef SkShaderBase INHERITED;
};
@ -189,7 +190,7 @@ bool SkTriColorShader::update(const SkMatrix& ctmInv, const SkPoint pts[],
(c2 - c0).store(&fM43.fMat[4]);
c0.store(&fM43.fMat[8]);
if (!fM33.hasPerspective()) {
if (!fUsePersp) {
fM43.setConcat(fM43, fM33);
}
return true;
@ -316,10 +317,20 @@ void SkDraw::drawVertices(SkVertices::VertexMode vmode, int vertexCount,
vertices = deformed;
}
/* 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.
*/
const bool usePerspective = fMatrix->hasPerspective();
SkPoint* devVerts = nullptr;
SkPoint3* dev3 = nullptr;
if (fMatrix->hasPerspective()) {
if (usePerspective) {
dev3 = outerAlloc.makeArray<SkPoint3>(vertexCount);
fMatrix->mapHomogeneousPoints(dev3, vertices, vertexCount);
} else {
@ -379,7 +390,8 @@ void SkDraw::drawVertices(SkVertices::VertexMode vmode, int vertexCount,
if (colors) {
dstColors = convert_colors(colors, vertexCount, fDst.colorSpace(), &outerAlloc);
triShader = outerAlloc.make<SkTriColorShader>(compute_is_opaque(colors, vertexCount));
triShader = outerAlloc.make<SkTriColorShader>(compute_is_opaque(colors, vertexCount),
usePerspective);
if (shader) {
shader = outerAlloc.make<SkShader_Blend>(bmode,
sk_ref_sp(triShader), sk_ref_sp(shader),

View File

@ -21,27 +21,24 @@
#include <cstddef>
#include <utility>
static void normalize_perspective(SkScalar mat[9]) {
// If it was interesting to never store the last element, we could divide all 8 other
// elements here by the 9th, making it 1.0...
void SkMatrix::normalizePerspective() {
// If the bottom row of the matrix is [0, 0, not_one], we will treat the matrix as if it
// is in perspective, even though it stills behaves like its affine. If we divide everything
// by the not_one value, then it will behave the same, but will be treated as affine,
// and therefore faster (e.g. clients can forward-difference calculations).
//
// When SkScalar was SkFixed, we would sometimes rescale the entire matrix to keep its
// component values from getting too large. This is not a concern when using floats/doubles,
// so we do nothing now.
// Disable this for now, but it could be enabled.
#if 0
if (0 == mat[SkMatrix::kMPersp0] && 0 == mat[SkMatrix::kMPersp1]) {
SkScalar p2 = mat[SkMatrix::kMPersp2];
if (0 == fMat[SkMatrix::kMPersp0] && 0 == fMat[SkMatrix::kMPersp1]) {
SkScalar p2 = fMat[SkMatrix::kMPersp2];
if (p2 != 0 && p2 != 1) {
double inv = 1.0 / p2;
for (int i = 0; i < 6; ++i) {
mat[i] = SkDoubleToScalar(mat[i] * inv);
fMat[i] = SkDoubleToScalar(fMat[i] * inv);
}
mat[SkMatrix::kMPersp2] = 1;
fMat[SkMatrix::kMPersp2] = 1;
}
this->setTypeMask(kUnknown_Mask);
(void)this->getType(); // trigger computing the new mask
}
#endif
}
// In a few places, we performed the following
@ -68,7 +65,22 @@ SkMatrix& SkMatrix::reset() { *this = SkMatrix(); return *this; }
SkMatrix& SkMatrix::set9(const SkScalar buffer[]) {
memcpy(fMat, buffer, 9 * sizeof(SkScalar));
normalize_perspective(fMat);
this->setTypeMask(kUnknown_Mask);
return *this;
}
SkMatrix& SkMatrix::setAll(SkScalar scaleX, SkScalar skewX, SkScalar transX,
SkScalar skewY, SkScalar scaleY, SkScalar transY,
SkScalar persp0, SkScalar persp1, SkScalar persp2) {
fMat[kMScaleX] = scaleX;
fMat[kMSkewX] = skewX;
fMat[kMTransX] = transX;
fMat[kMSkewY] = skewY;
fMat[kMScaleY] = scaleY;
fMat[kMTransY] = transY;
fMat[kMPersp0] = persp0;
fMat[kMPersp1] = persp1;
fMat[kMPersp2] = persp2;
this->setTypeMask(kUnknown_Mask);
return *this;
}
@ -641,7 +653,6 @@ SkMatrix& SkMatrix::setConcat(const SkMatrix& a, const SkMatrix& b) {
tmp.fMat[kMPersp1] = rowcol3(&a.fMat[6], &b.fMat[1]);
tmp.fMat[kMPersp2] = rowcol3(&a.fMat[6], &b.fMat[2]);
normalize_perspective(tmp.fMat);
tmp.setTypeMask(kUnknown_Mask);
} else {
tmp.fMat[kMScaleX] = muladdmul(a.fMat[kMScaleX],