[gpu] Remove getMaxStretch for perspective, use mapRadius for perspective path subdiv tol, add test

Review URL: http://codereview.appspot.com/4975063/



git-svn-id: http://skia.googlecode.com/svn/trunk@2246 2bbb7eff-a529-9590-31e7-b0007b416f81
This commit is contained in:
bsalomon@google.com 2011-09-09 19:32:04 +00:00
parent 99edd43813
commit 383963280d
7 changed files with 141 additions and 73 deletions

View File

@ -375,7 +375,7 @@ void GrDefaultPathRenderer::onDrawPath(GrDrawTarget::StageBitfield stages,
GrMatrix viewM = fTarget->getViewMatrix();
GrScalar tol = GR_Scalar1;
tol = GrPathUtils::scaleToleranceToSrc(tol, viewM);
tol = GrPathUtils::scaleToleranceToSrc(tol, viewM, fPath->getBounds());
// FIXME: It's really dumb that we recreate the verts for a new vertex
// layout. We only do that because the GrDrawTarget API doesn't allow

View File

@ -12,16 +12,23 @@
#include "GrPoint.h"
GrScalar GrPathUtils::scaleToleranceToSrc(GrScalar devTol,
const GrMatrix& viewM) {
const GrMatrix& viewM,
const GrRect& pathBounds) {
// In order to tesselate the path we get a bound on how much the matrix can
// stretch when mapping to screen coordinates.
GrScalar stretch = viewM.getMaxStretch();
GrScalar srcTol = devTol;
if (stretch < 0) {
// TODO: deal with perspective in some better way.
srcTol /= 5;
stretch = -stretch;
// take worst case mapRadius amoung four corners.
// (less than perfect)
for (int i = 0; i < 4; ++i) {
GrMatrix mat;
mat.setTranslate((i % 2) ? pathBounds.fLeft : pathBounds.fRight,
(i < 2) ? pathBounds.fTop : pathBounds.fBottom);
mat.postConcat(viewM);
stretch = SkMaxScalar(stretch, mat.mapRadius(SK_Scalar1));
}
}
srcTol = GrScalarDiv(srcTol, stretch);
return srcTol;

View File

@ -20,7 +20,8 @@ class GrPoint;
*/
namespace GrPathUtils {
GrScalar scaleToleranceToSrc(GrScalar devTol,
const GrMatrix& viewM);
const GrMatrix& viewM,
const GrRect& pathBounds);
/// Since we divide by tol if we're computing exact worst-case bounds,
/// very small tolerances will be increased to gMinCurveTol.

View File

@ -353,7 +353,7 @@ void GrTesselatedPathRenderer::drawPath(GrDrawTarget::StageBitfield stages) {
GrMatrix viewM = fTarget->getViewMatrix();
GrScalar tol = GR_Scalar1;
tol = GrPathUtils::scaleToleranceToSrc(tol, viewM);
tol = GrPathUtils::scaleToleranceToSrc(tol, viewM, fPath->getBounds());
GrScalar tolSqd = GrMul(tol, tol);
int subpathCnt;

View File

@ -502,11 +502,9 @@ public:
/**
* Calculates the maximum stretching factor of the matrix. If the matrix has
* perspective the max stretch at the origin (in the pre-matrix space) is
* computed and returned as a negative.
* perspective -1 is returned.
*
* @return maximum strecthing factor or negative max stretching factor at
* the origin if matrix has perspective.
* @return maximum strecthing factor
*/
SkScalar getMaxStretch() const;

View File

@ -1674,71 +1674,42 @@ bool SkMatrix::setPolyToPoly(const SkPoint src[], const SkPoint dst[],
SkScalar SkMatrix::getMaxStretch() const {
TypeMask mask = this->getType();
SkScalar stretch;
if (this->hasPerspective()) {
return -SK_Scalar1;
}
if (this->isIdentity()) {
stretch = SK_Scalar1;
} else if (!(mask & kAffine_Mask)) {
stretch = SkMaxScalar(SkScalarAbs(fMat[kMScaleX]), SkScalarAbs(fMat[kMScaleY]));
#if 0 // don't have this bit
} else if (mask & kZeroScale_TypeBit) {
stretch = SkMaxScalar(SkScalarAbs(fM[kSkewX]), SkScalarAbs(fM[kSkewY]));
#endif
return SK_Scalar1;
}
if (!(mask & kAffine_Mask)) {
return SkMaxScalar(SkScalarAbs(fMat[kMScaleX]),
SkScalarAbs(fMat[kMScaleY]));
}
// ignore the translation part of the matrix, just look at 2x2 portion.
// compute singular values, take largest abs value.
// [a b; b c] = A^T*A
SkScalar a = SkScalarMul(fMat[kMScaleX], fMat[kMScaleX]) +
SkScalarMul(fMat[kMSkewY], fMat[kMSkewY]);
SkScalar b = SkScalarMul(fMat[kMScaleX], fMat[kMSkewX]) +
SkScalarMul(fMat[kMScaleY], fMat[kMSkewY]);
SkScalar c = SkScalarMul(fMat[kMSkewX], fMat[kMSkewX]) +
SkScalarMul(fMat[kMScaleY], fMat[kMScaleY]);
// eigenvalues of A^T*A are the squared singular values of A.
// characteristic equation is det((A^T*A) - l*I) = 0
// l^2 - (a + c)l + (ac-b^2)
// solve using quadratic equation (divisor is non-zero since l^2 has 1 coeff
// and roots are guaraunteed to be pos and real).
SkScalar largerRoot;
SkScalar bSqd = SkScalarMul(b,b);
// if upper left 2x2 is orthogonal save some math
if (bSqd <= SK_ScalarNearlyZero) {
largerRoot = SkMaxScalar(a, c);
} else {
// ignore the translation part of the matrix, just look at 2x2 portion.
// compute singular values, take largest abs value.
// [a b; b c] = A^T*A
SkScalar a = SkScalarMul(fMat[kMScaleX], fMat[kMScaleX]) + SkScalarMul(fMat[kMSkewY], fMat[kMSkewY]);
SkScalar b = SkScalarMul(fMat[kMScaleX], fMat[kMSkewX]) + SkScalarMul(fMat[kMScaleY], fMat[kMSkewY]);
SkScalar c = SkScalarMul(fMat[kMSkewX], fMat[kMSkewX]) + SkScalarMul(fMat[kMScaleY], fMat[kMScaleY]);
// eigenvalues of A^T*A are the squared singular values of A.
// characteristic equation is det((A^T*A) - l*I) = 0
// l^2 - (a + c)l + (ac-b^2)
// solve using quadratic equation (divisor is non-zero since l^2 has 1 coeff
// and roots are guaraunteed to be pos and real).
SkScalar largerRoot;
SkScalar bSqd = SkScalarMul(b,b);
// if upper left 2x2 is orthogonal save some math
if (bSqd <= SK_ScalarNearlyZero) {
largerRoot = SkMaxScalar(a, c);
} else {
SkScalar aminusc = a - c;
SkScalar apluscdiv2 = (a + c) / 2;
SkScalar x = SkScalarSqrt(SkScalarMul(aminusc, aminusc) + 4 * bSqd) / 2;
largerRoot = apluscdiv2 + x;
}
stretch = SkScalarSqrt(largerRoot);
if (mask & kPerspective_Mask) {
stretch = -stretch;
if (fMat[kMPersp2] != kMatrix22Elem) {
#if defined(SK_SCALAR_IS_FLOAT)
stretch /= fMat[kMPersp2];
#else
stretch = SkFractDiv(stretch, fMat[kMPersp2]);
#endif
}
}
SkScalar aminusc = a - c;
SkScalar apluscdiv2 = SkScalarHalf(a + c);
SkScalar x = SkScalarHalf(SkScalarSqrt(SkScalarMul(aminusc, aminusc) + 4 * bSqd));
largerRoot = apluscdiv2 + x;
}
#if defined(SK_DEBUG) && 0
// test a bunch of vectors. None should be scaled by more than stretch
// (modulo some error) and we should find a vector that is scaled by almost
// stretch.
SkPoint pt;
SkScalar max = 0;
for (int i = 0; i < 1000; ++i) {
SkScalar x = (float)rand() / RAND_MAX;
SkScalar y = sqrtf(1 - (x*x));
pt.fX = fMat[kMScaleX]*x + fMat[kMSkewX]*y;
pt.fY = fMat[kMSkewY]*x + fMat[kMScaleY]*y;
SkScalar d = pt.distanceToOrigin();
SkASSERT(d <= (1.0001 * stretch));
if (max < pt.distanceToOrigin()) {
max = pt.distanceToOrigin();
}
}
SkASSERT((stretch - max) < .05*stretch);
#endif
return stretch;
return SkScalarSqrt(largerRoot);
}
const SkMatrix& SkMatrix::I() {

View File

@ -7,6 +7,7 @@
*/
#include "Test.h"
#include "SkMatrix.h"
#include "SkRandom.h"
static bool nearly_equal_scalar(SkScalar a, SkScalar b) {
// Note that we get more compounded error for multiple operations when
@ -55,6 +56,94 @@ static void test_flatten(skiatest::Reporter* reporter, const SkMatrix& m) {
REPORTER_ASSERT(reporter, memcmp(buffer, buffer2, size1) == 0);
}
void test_matrix_max_stretch(skiatest::Reporter* reporter) {
SkMatrix identity;
identity.reset();
REPORTER_ASSERT(reporter, SK_Scalar1 == identity.getMaxStretch());
SkMatrix scale;
scale.setScale(SK_Scalar1 * 2, SK_Scalar1 * 4);
REPORTER_ASSERT(reporter, SK_Scalar1 * 4 == scale.getMaxStretch());
SkMatrix rot90Scale;
rot90Scale.setRotate(90 * SK_Scalar1);
rot90Scale.postScale(SK_Scalar1 / 4, SK_Scalar1 / 2);
REPORTER_ASSERT(reporter, SK_Scalar1 / 2 == rot90Scale.getMaxStretch());
SkMatrix rotate;
rotate.setRotate(128 * SK_Scalar1);
REPORTER_ASSERT(reporter, SkScalarAbs(SK_Scalar1 - rotate.getMaxStretch()) <= SK_ScalarNearlyZero);
SkMatrix translate;
translate.setTranslate(10 * SK_Scalar1, -5 * SK_Scalar1);
REPORTER_ASSERT(reporter, SK_Scalar1 == translate.getMaxStretch());
SkMatrix perspX;
perspX.reset();
perspX.setPerspX(SK_Scalar1 / 1000);
REPORTER_ASSERT(reporter, -SK_Scalar1 == perspX.getMaxStretch());
SkMatrix perspY;
perspY.reset();
perspY.setPerspX(-SK_Scalar1 / 500);
REPORTER_ASSERT(reporter, -SK_Scalar1 == perspY.getMaxStretch());
SkMatrix baseMats[] = {scale, rot90Scale, rotate,
translate, perspX, perspY};
SkMatrix mats[2*SK_ARRAY_COUNT(baseMats)];
for (int i = 0; i < SK_ARRAY_COUNT(baseMats); ++i) {
mats[i] = baseMats[i];
bool invertable = mats[i].invert(&mats[i + SK_ARRAY_COUNT(baseMats)]);
REPORTER_ASSERT(reporter, invertable);
}
SkRandom rand;
for (int m = 0; m < 1000; ++m) {
SkMatrix mat;
mat.reset();
for (int i = 0; i < 4; ++i) {
int x = rand.nextU() % SK_ARRAY_COUNT(mats);
mat.postConcat(mats[x]);
}
SkScalar stretch = mat.getMaxStretch();
if ((stretch < 0) != mat.hasPerspective()) {
stretch = mat.getMaxStretch();
}
REPORTER_ASSERT(reporter, (stretch < 0) == mat.hasPerspective());
if (mat.hasPerspective()) {
m -= 1; // try another non-persp matrix
continue;
}
// test a bunch of vectors. None should be scaled by more than stretch
// (modulo some error) and we should find a vector that is scaled by
// almost stretch.
static const SkScalar gStretchTol = (105 * SK_Scalar1) / 100;
static const SkScalar gMaxStretchTol = (97 * SK_Scalar1) / 100;
SkScalar max = 0;
SkVector vectors[1000];
for (int i = 0; i < SK_ARRAY_COUNT(vectors); ++i) {
vectors[i].fX = rand.nextSScalar1();
vectors[i].fY = rand.nextSScalar1();
if (!vectors[i].normalize()) {
i -= 1;
continue;
}
}
mat.mapVectors(vectors, SK_ARRAY_COUNT(vectors));
for (int i = 0; i < SK_ARRAY_COUNT(vectors); ++i) {
SkScalar d = vectors[i].length();
REPORTER_ASSERT(reporter, SkScalarDiv(d, stretch) < gStretchTol);
if (max < d) {
max = d;
}
}
REPORTER_ASSERT(reporter, SkScalarDiv(max, stretch) >= gMaxStretchTol);
}
}
void TestMatrix(skiatest::Reporter* reporter) {
SkMatrix mat, inverse, iden1, iden2;
@ -146,6 +235,8 @@ void TestMatrix(skiatest::Reporter* reporter) {
mat.set(SkMatrix::kMPersp1, SkIntToScalar(1));
REPORTER_ASSERT(reporter, !mat.asAffine(affine));
test_matrix_max_stretch(reporter);
}
#include "TestClassDef.h"