Do not use GrBicubic effect when downscaling. Also, don't use glTexStorage as it interferes with deleyed mipmap generation.

R=robertphillips@google.com

Author: bsalomon@google.com

Review URL: https://codereview.chromium.org/105353002

git-svn-id: http://skia.googlecode.com/svn/trunk@12576 2bbb7eff-a529-9590-31e7-b0007b416f81
This commit is contained in:
commit-bot@chromium.org 2013-12-09 19:15:37 +00:00
parent c9c0b1e90e
commit cea9abb001
6 changed files with 112 additions and 51 deletions

View File

@ -573,6 +573,14 @@ public:
SkDEVCODE(void dump() const;) SkDEVCODE(void dump() const;)
SkDEVCODE(void toString(SkString*) const;) SkDEVCODE(void toString(SkString*) const;)
/**
* Calculates the minimum stretching factor of the matrix. If the matrix has
* perspective -1 is returned.
*
* @return minumum strecthing factor
*/
SkScalar getMinStretch() const;
/** /**
* Calculates the maximum stretching factor of the matrix. If the matrix has * Calculates the maximum stretching factor of the matrix. If the matrix has
* perspective -1 is returned. * perspective -1 is returned.

View File

@ -358,13 +358,12 @@ GrEffectRef* SkBitmapProcShader::asNewEffect(GrContext* context, const SkPaint&
SkMatrix matrix; SkMatrix matrix;
matrix.setIDiv(fRawBitmap.width(), fRawBitmap.height()); matrix.setIDiv(fRawBitmap.width(), fRawBitmap.height());
if (this->hasLocalMatrix()) { SkMatrix inverse;
SkMatrix inverse; if (!this->getLocalMatrix().invert(&inverse)) {
if (!this->getLocalMatrix().invert(&inverse)) { return NULL;
return NULL;
}
matrix.preConcat(inverse);
} }
matrix.preConcat(inverse);
SkShader::TileMode tm[] = { SkShader::TileMode tm[] = {
(TileMode)fState.fTileModeX, (TileMode)fState.fTileModeX,
(TileMode)fState.fTileModeY, (TileMode)fState.fTileModeY,
@ -384,9 +383,21 @@ GrEffectRef* SkBitmapProcShader::asNewEffect(GrContext* context, const SkPaint&
textureFilterMode = GrTextureParams::kMipMap_FilterMode; textureFilterMode = GrTextureParams::kMipMap_FilterMode;
break; break;
case SkPaint::kHigh_FilterLevel: case SkPaint::kHigh_FilterLevel:
// fall back to no filtering here; we will install another // Minification can look bad with the bicubic effect. This is an overly aggressive
// shader that will do the HQ filtering. // check for MIP fallbacks. It doesn't consider the fact that minification in the local
textureFilterMode = GrTextureParams::kNone_FilterMode; // matrix could be offset by the view matrix and vice versa. We also don't know whether
// the draw has explicit local coords (e.g. drawVertices) where the scale factor is
// unknown and varies.
if (context->getMatrix().getMinStretch() >= SK_Scalar1 &&
this->getLocalMatrix().getMaxStretch() <= SK_Scalar1) {
// fall back to no filtering here; we will install another
// shader that will do the HQ filtering.
textureFilterMode = GrTextureParams::kNone_FilterMode;
} else {
// Fall back to mip-mapping.
paintFilterLevel = SkPaint::kMedium_FilterLevel;
textureFilterMode = GrTextureParams::kMipMap_FilterMode;
}
break; break;
default: default:
SkErrorInternals::SetError( kInvalidPaint_SkError, SkErrorInternals::SetError( kInvalidPaint_SkError,

View File

@ -1857,45 +1857,71 @@ bool SkMatrix::setPolyToPoly(const SkPoint src[], const SkPoint dst[],
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
SkScalar SkMatrix::getMaxStretch() const { enum MinOrMax {
TypeMask mask = this->getType(); kMin_MinOrMax,
kMax_MinOrMax
};
if (this->hasPerspective()) { template <MinOrMax MIN_OR_MAX> SkScalar get_stretch_factor(SkMatrix::TypeMask typeMask,
const SkScalar m[9]) {
if (typeMask & SkMatrix::kPerspective_Mask) {
return -SK_Scalar1; return -SK_Scalar1;
} }
if (this->isIdentity()) { if (SkMatrix::kIdentity_Mask == typeMask) {
return SK_Scalar1; return SK_Scalar1;
} }
if (!(mask & kAffine_Mask)) { if (!(typeMask & SkMatrix::kAffine_Mask)) {
return SkMaxScalar(SkScalarAbs(fMat[kMScaleX]), if (kMin_MinOrMax == MIN_OR_MAX) {
SkScalarAbs(fMat[kMScaleY])); return SkMinScalar(SkScalarAbs(m[SkMatrix::kMScaleX]),
SkScalarAbs(m[SkMatrix::kMScaleY]));
} else {
return SkMaxScalar(SkScalarAbs(m[SkMatrix::kMScaleX]),
SkScalarAbs(m[SkMatrix::kMScaleY]));
}
} }
// ignore the translation part of the matrix, just look at 2x2 portion. // ignore the translation part of the matrix, just look at 2x2 portion.
// compute singular values, take largest abs value. // compute singular values, take largest or smallest abs value.
// [a b; b c] = A^T*A // [a b; b c] = A^T*A
SkScalar a = SkScalarMul(fMat[kMScaleX], fMat[kMScaleX]) + SkScalar a = SkScalarMul(m[SkMatrix::kMScaleX], m[SkMatrix::kMScaleX]) +
SkScalarMul(fMat[kMSkewY], fMat[kMSkewY]); SkScalarMul(m[SkMatrix::kMSkewY], m[SkMatrix::kMSkewY]);
SkScalar b = SkScalarMul(fMat[kMScaleX], fMat[kMSkewX]) + SkScalar b = SkScalarMul(m[SkMatrix::kMScaleX], m[SkMatrix::kMSkewX]) +
SkScalarMul(fMat[kMScaleY], fMat[kMSkewY]); SkScalarMul(m[SkMatrix::kMScaleY], m[SkMatrix::kMSkewY]);
SkScalar c = SkScalarMul(fMat[kMSkewX], fMat[kMSkewX]) + SkScalar c = SkScalarMul(m[SkMatrix::kMSkewX], m[SkMatrix::kMSkewX]) +
SkScalarMul(fMat[kMScaleY], fMat[kMScaleY]); SkScalarMul(m[SkMatrix::kMScaleY], m[SkMatrix::kMScaleY]);
// eigenvalues of A^T*A are the squared singular values of A. // eigenvalues of A^T*A are the squared singular values of A.
// characteristic equation is det((A^T*A) - l*I) = 0 // characteristic equation is det((A^T*A) - l*I) = 0
// l^2 - (a + c)l + (ac-b^2) // l^2 - (a + c)l + (ac-b^2)
// solve using quadratic equation (divisor is non-zero since l^2 has 1 coeff // solve using quadratic equation (divisor is non-zero since l^2 has 1 coeff
// and roots are guaraunteed to be pos and real). // and roots are guaranteed to be pos and real).
SkScalar largerRoot; SkScalar chosenRoot;
SkScalar bSqd = SkScalarMul(b,b); SkScalar bSqd = SkScalarMul(b,b);
// if upper left 2x2 is orthogonal save some math // if upper left 2x2 is orthogonal save some math
if (bSqd <= SK_ScalarNearlyZero*SK_ScalarNearlyZero) { if (bSqd <= SK_ScalarNearlyZero*SK_ScalarNearlyZero) {
largerRoot = SkMaxScalar(a, c); if (kMin_MinOrMax == MIN_OR_MAX) {
chosenRoot = SkMinScalar(a, c);
} else {
chosenRoot = SkMaxScalar(a, c);
}
} else { } else {
SkScalar aminusc = a - c; SkScalar aminusc = a - c;
SkScalar apluscdiv2 = SkScalarHalf(a + c); SkScalar apluscdiv2 = SkScalarHalf(a + c);
SkScalar x = SkScalarHalf(SkScalarSqrt(SkScalarMul(aminusc, aminusc) + 4 * bSqd)); SkScalar x = SkScalarHalf(SkScalarSqrt(SkScalarMul(aminusc, aminusc) + 4 * bSqd));
largerRoot = apluscdiv2 + x; if (kMin_MinOrMax == MIN_OR_MAX) {
chosenRoot = apluscdiv2 - x;
} else {
chosenRoot = apluscdiv2 + x;
}
} }
return SkScalarSqrt(largerRoot); SkASSERT(chosenRoot >= 0);
return SkScalarSqrt(chosenRoot);
}
SkScalar SkMatrix::getMinStretch() const {
return get_stretch_factor<kMin_MinOrMax>(this->getType(), fMat);
}
SkScalar SkMatrix::getMaxStretch() const {
return get_stretch_factor<kMax_MinOrMax>(this->getType(), fMat);
} }
static void reset_identity_matrix(SkMatrix* identity) { static void reset_identity_matrix(SkMatrix* identity) {

View File

@ -1194,8 +1194,10 @@ void SkGpuDevice::drawBitmapCommon(const SkDraw& draw,
tileFilterPad = 1; tileFilterPad = 1;
textureFilterMode = GrTextureParams::kMipMap_FilterMode; textureFilterMode = GrTextureParams::kMipMap_FilterMode;
break; break;
case SkPaint::kHigh_FilterLevel: case SkPaint::kHigh_FilterLevel: {
if (flags & SkCanvas::kBleed_DrawBitmapRectFlag) { // Minification can look bad with the bicubic effect.
if (fContext->getMatrix().getMinStretch() >= SK_Scalar1 &&
(flags & SkCanvas::kBleed_DrawBitmapRectFlag)) {
// We will install an effect that does the filtering in the shader. // We will install an effect that does the filtering in the shader.
textureFilterMode = GrTextureParams::kNone_FilterMode; textureFilterMode = GrTextureParams::kNone_FilterMode;
tileFilterPad = GrBicubicEffect::kFilterTexelPad; tileFilterPad = GrBicubicEffect::kFilterTexelPad;
@ -1207,6 +1209,7 @@ void SkGpuDevice::drawBitmapCommon(const SkDraw& draw,
tileFilterPad = 1; tileFilterPad = 1;
} }
break; break;
}
default: default:
SkErrorInternals::SetError( kInvalidPaint_SkError, SkErrorInternals::SetError( kInvalidPaint_SkError,
"Sorry, I don't understand the filtering " "Sorry, I don't understand the filtering "

View File

@ -552,7 +552,12 @@ bool GrGpuGL::uploadTexData(const GrGLTexture::Desc& desc,
SkAutoSMalloc<128 * 128> tempStorage; SkAutoSMalloc<128 * 128> tempStorage;
// paletted textures cannot be partially updated // paletted textures cannot be partially updated
bool useTexStorage = isNewTexture && // We currently lazily create MIPMAPs when the we see a draw with
// GrTextureParams::kMipMap_FilterMode. Using texture storage requires that the
// MIP levels are all created when the texture is created. So for now we don't use
// texture storage.
bool useTexStorage = false &&
isNewTexture &&
desc.fConfig != kIndex_8_GrPixelConfig && desc.fConfig != kIndex_8_GrPixelConfig &&
this->glCaps().texStorageSupport(); this->glCaps().texStorageSupport();
@ -638,8 +643,7 @@ bool GrGpuGL::uploadTexData(const GrGLTexture::Desc& desc,
desc.fWidth == width && desc.fHeight == height) { desc.fWidth == width && desc.fHeight == height) {
CLEAR_ERROR_BEFORE_ALLOC(this->glInterface()); CLEAR_ERROR_BEFORE_ALLOC(this->glInterface());
if (useTexStorage) { if (useTexStorage) {
// We never resize or change formats of textures. We don't use // We never resize or change formats of textures.
// mipmaps currently.
GL_ALLOC_CALL(this->glInterface(), GL_ALLOC_CALL(this->glInterface(),
TexStorage2D(GR_GL_TEXTURE_2D, TexStorage2D(GR_GL_TEXTURE_2D,
1, // levels 1, // levels

View File

@ -130,36 +130,43 @@ static void test_flatten(skiatest::Reporter* reporter, const SkMatrix& m) {
REPORTER_ASSERT(reporter, memcmp(buffer, buffer2, size1) == 0); REPORTER_ASSERT(reporter, memcmp(buffer, buffer2, size1) == 0);
} }
static void test_matrix_max_stretch(skiatest::Reporter* reporter) { static void test_matrix_min_max_stretch(skiatest::Reporter* reporter) {
SkMatrix identity; SkMatrix identity;
identity.reset(); identity.reset();
REPORTER_ASSERT(reporter, SK_Scalar1 == identity.getMinStretch());
REPORTER_ASSERT(reporter, SK_Scalar1 == identity.getMaxStretch()); REPORTER_ASSERT(reporter, SK_Scalar1 == identity.getMaxStretch());
SkMatrix scale; SkMatrix scale;
scale.setScale(SK_Scalar1 * 2, SK_Scalar1 * 4); scale.setScale(SK_Scalar1 * 2, SK_Scalar1 * 4);
REPORTER_ASSERT(reporter, SK_Scalar1 * 2 == scale.getMinStretch());
REPORTER_ASSERT(reporter, SK_Scalar1 * 4 == scale.getMaxStretch()); REPORTER_ASSERT(reporter, SK_Scalar1 * 4 == scale.getMaxStretch());
SkMatrix rot90Scale; SkMatrix rot90Scale;
rot90Scale.setRotate(90 * SK_Scalar1); rot90Scale.setRotate(90 * SK_Scalar1);
rot90Scale.postScale(SK_Scalar1 / 4, SK_Scalar1 / 2); rot90Scale.postScale(SK_Scalar1 / 4, SK_Scalar1 / 2);
REPORTER_ASSERT(reporter, SK_Scalar1 / 4 == rot90Scale.getMinStretch());
REPORTER_ASSERT(reporter, SK_Scalar1 / 2 == rot90Scale.getMaxStretch()); REPORTER_ASSERT(reporter, SK_Scalar1 / 2 == rot90Scale.getMaxStretch());
SkMatrix rotate; SkMatrix rotate;
rotate.setRotate(128 * SK_Scalar1); rotate.setRotate(128 * SK_Scalar1);
REPORTER_ASSERT(reporter, SkScalarAbs(SK_Scalar1 - rotate.getMaxStretch()) <= SK_ScalarNearlyZero); REPORTER_ASSERT(reporter, SkScalarNearlyEqual(SK_Scalar1, rotate.getMinStretch() ,SK_ScalarNearlyZero));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(SK_Scalar1, rotate.getMaxStretch(), SK_ScalarNearlyZero));
SkMatrix translate; SkMatrix translate;
translate.setTranslate(10 * SK_Scalar1, -5 * SK_Scalar1); translate.setTranslate(10 * SK_Scalar1, -5 * SK_Scalar1);
REPORTER_ASSERT(reporter, SK_Scalar1 == translate.getMinStretch());
REPORTER_ASSERT(reporter, SK_Scalar1 == translate.getMaxStretch()); REPORTER_ASSERT(reporter, SK_Scalar1 == translate.getMaxStretch());
SkMatrix perspX; SkMatrix perspX;
perspX.reset(); perspX.reset();
perspX.setPerspX(SkScalarToPersp(SK_Scalar1 / 1000)); perspX.setPerspX(SkScalarToPersp(SK_Scalar1 / 1000));
REPORTER_ASSERT(reporter, -SK_Scalar1 == perspX.getMinStretch());
REPORTER_ASSERT(reporter, -SK_Scalar1 == perspX.getMaxStretch()); REPORTER_ASSERT(reporter, -SK_Scalar1 == perspX.getMaxStretch());
SkMatrix perspY; SkMatrix perspY;
perspY.reset(); perspY.reset();
perspY.setPerspX(SkScalarToPersp(-SK_Scalar1 / 500)); perspY.setPerspY(SkScalarToPersp(-SK_Scalar1 / 500));
REPORTER_ASSERT(reporter, -SK_Scalar1 == perspY.getMinStretch());
REPORTER_ASSERT(reporter, -SK_Scalar1 == perspY.getMaxStretch()); REPORTER_ASSERT(reporter, -SK_Scalar1 == perspY.getMaxStretch());
SkMatrix baseMats[] = {scale, rot90Scale, rotate, SkMatrix baseMats[] = {scale, rot90Scale, rotate,
@ -178,25 +185,22 @@ static void test_matrix_max_stretch(skiatest::Reporter* reporter) {
int x = rand.nextU() % SK_ARRAY_COUNT(mats); int x = rand.nextU() % SK_ARRAY_COUNT(mats);
mat.postConcat(mats[x]); mat.postConcat(mats[x]);
} }
SkScalar stretch = mat.getMaxStretch();
if ((stretch < 0) != mat.hasPerspective()) { SkScalar minStretch = mat.getMinStretch();
stretch = mat.getMaxStretch(); SkScalar maxStretch = mat.getMaxStretch();
} REPORTER_ASSERT(reporter, (minStretch < 0) == (maxStretch < 0));
REPORTER_ASSERT(reporter, (maxStretch < 0) == mat.hasPerspective());
REPORTER_ASSERT(reporter, (stretch < 0) == mat.hasPerspective());
if (mat.hasPerspective()) { if (mat.hasPerspective()) {
m -= 1; // try another non-persp matrix m -= 1; // try another non-persp matrix
continue; continue;
} }
// test a bunch of vectors. None should be scaled by more than stretch // test a bunch of vectors. All should be scaled by between minStretch and maxStretch
// (modulo some error) and we should find a vector that is scaled by // (modulo some error) and we should find a vector that is scaled by almost each.
// almost stretch. static const SkScalar gVectorStretchTol = (105 * SK_Scalar1) / 100;
static const SkScalar gStretchTol = (105 * SK_Scalar1) / 100; static const SkScalar gClosestStretchTol = (97 * SK_Scalar1) / 100;
static const SkScalar gMaxStretchTol = (97 * SK_Scalar1) / 100; SkScalar max = 0, min = SK_ScalarMax;
SkScalar max = 0;
SkVector vectors[1000]; SkVector vectors[1000];
for (size_t i = 0; i < SK_ARRAY_COUNT(vectors); ++i) { for (size_t i = 0; i < SK_ARRAY_COUNT(vectors); ++i) {
vectors[i].fX = rand.nextSScalar1(); vectors[i].fX = rand.nextSScalar1();
@ -209,12 +213,17 @@ static void test_matrix_max_stretch(skiatest::Reporter* reporter) {
mat.mapVectors(vectors, SK_ARRAY_COUNT(vectors)); mat.mapVectors(vectors, SK_ARRAY_COUNT(vectors));
for (size_t i = 0; i < SK_ARRAY_COUNT(vectors); ++i) { for (size_t i = 0; i < SK_ARRAY_COUNT(vectors); ++i) {
SkScalar d = vectors[i].length(); SkScalar d = vectors[i].length();
REPORTER_ASSERT(reporter, SkScalarDiv(d, stretch) < gStretchTol); REPORTER_ASSERT(reporter, SkScalarDiv(d, maxStretch) < gVectorStretchTol);
REPORTER_ASSERT(reporter, SkScalarDiv(minStretch, d) < gVectorStretchTol);
if (max < d) { if (max < d) {
max = d; max = d;
} }
if (min > d) {
min = d;
}
} }
REPORTER_ASSERT(reporter, SkScalarDiv(max, stretch) >= gMaxStretchTol); REPORTER_ASSERT(reporter, SkScalarDiv(max, maxStretch) >= gClosestStretchTol);
REPORTER_ASSERT(reporter, SkScalarDiv(minStretch, min) >= gClosestStretchTol);
} }
} }
@ -797,7 +806,7 @@ static void TestMatrix(skiatest::Reporter* reporter) {
REPORTER_ASSERT(reporter, !are_equal(reporter, mat, mat2)); REPORTER_ASSERT(reporter, !are_equal(reporter, mat, mat2));
#endif #endif
test_matrix_max_stretch(reporter); test_matrix_min_max_stretch(reporter);
test_matrix_is_similarity(reporter); test_matrix_is_similarity(reporter);
test_matrix_recttorect(reporter); test_matrix_recttorect(reporter);
test_matrix_decomposition(reporter); test_matrix_decomposition(reporter);