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 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
* perspective -1 is returned.

View File

@ -358,13 +358,12 @@ GrEffectRef* SkBitmapProcShader::asNewEffect(GrContext* context, const SkPaint&
SkMatrix matrix;
matrix.setIDiv(fRawBitmap.width(), fRawBitmap.height());
if (this->hasLocalMatrix()) {
SkMatrix inverse;
if (!this->getLocalMatrix().invert(&inverse)) {
return NULL;
}
matrix.preConcat(inverse);
}
SkShader::TileMode tm[] = {
(TileMode)fState.fTileModeX,
(TileMode)fState.fTileModeY,
@ -384,9 +383,21 @@ GrEffectRef* SkBitmapProcShader::asNewEffect(GrContext* context, const SkPaint&
textureFilterMode = GrTextureParams::kMipMap_FilterMode;
break;
case SkPaint::kHigh_FilterLevel:
// Minification can look bad with the bicubic effect. This is an overly aggressive
// check for MIP fallbacks. It doesn't consider the fact that minification in the local
// 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;
default:
SkErrorInternals::SetError( kInvalidPaint_SkError,

View File

@ -1857,45 +1857,71 @@ bool SkMatrix::setPolyToPoly(const SkPoint src[], const SkPoint dst[],
///////////////////////////////////////////////////////////////////////////////
SkScalar SkMatrix::getMaxStretch() const {
TypeMask mask = this->getType();
enum MinOrMax {
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;
}
if (this->isIdentity()) {
if (SkMatrix::kIdentity_Mask == typeMask) {
return SK_Scalar1;
}
if (!(mask & kAffine_Mask)) {
return SkMaxScalar(SkScalarAbs(fMat[kMScaleX]),
SkScalarAbs(fMat[kMScaleY]));
if (!(typeMask & SkMatrix::kAffine_Mask)) {
if (kMin_MinOrMax == MIN_OR_MAX) {
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.
// compute singular values, take largest abs value.
// compute singular values, take largest or smallest 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]);
SkScalar a = SkScalarMul(m[SkMatrix::kMScaleX], m[SkMatrix::kMScaleX]) +
SkScalarMul(m[SkMatrix::kMSkewY], m[SkMatrix::kMSkewY]);
SkScalar b = SkScalarMul(m[SkMatrix::kMScaleX], m[SkMatrix::kMSkewX]) +
SkScalarMul(m[SkMatrix::kMScaleY], m[SkMatrix::kMSkewY]);
SkScalar c = SkScalarMul(m[SkMatrix::kMSkewX], m[SkMatrix::kMSkewX]) +
SkScalarMul(m[SkMatrix::kMScaleY], m[SkMatrix::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;
// and roots are guaranteed to be pos and real).
SkScalar chosenRoot;
SkScalar bSqd = SkScalarMul(b,b);
// if upper left 2x2 is orthogonal save some math
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 {
SkScalar aminusc = a - c;
SkScalar apluscdiv2 = SkScalarHalf(a + c);
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) {

View File

@ -1194,8 +1194,10 @@ void SkGpuDevice::drawBitmapCommon(const SkDraw& draw,
tileFilterPad = 1;
textureFilterMode = GrTextureParams::kMipMap_FilterMode;
break;
case SkPaint::kHigh_FilterLevel:
if (flags & SkCanvas::kBleed_DrawBitmapRectFlag) {
case SkPaint::kHigh_FilterLevel: {
// 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.
textureFilterMode = GrTextureParams::kNone_FilterMode;
tileFilterPad = GrBicubicEffect::kFilterTexelPad;
@ -1207,6 +1209,7 @@ void SkGpuDevice::drawBitmapCommon(const SkDraw& draw,
tileFilterPad = 1;
}
break;
}
default:
SkErrorInternals::SetError( kInvalidPaint_SkError,
"Sorry, I don't understand the filtering "

View File

@ -552,7 +552,12 @@ bool GrGpuGL::uploadTexData(const GrGLTexture::Desc& desc,
SkAutoSMalloc<128 * 128> tempStorage;
// 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 &&
this->glCaps().texStorageSupport();
@ -638,8 +643,7 @@ bool GrGpuGL::uploadTexData(const GrGLTexture::Desc& desc,
desc.fWidth == width && desc.fHeight == height) {
CLEAR_ERROR_BEFORE_ALLOC(this->glInterface());
if (useTexStorage) {
// We never resize or change formats of textures. We don't use
// mipmaps currently.
// We never resize or change formats of textures.
GL_ALLOC_CALL(this->glInterface(),
TexStorage2D(GR_GL_TEXTURE_2D,
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);
}
static void test_matrix_max_stretch(skiatest::Reporter* reporter) {
static void test_matrix_min_max_stretch(skiatest::Reporter* reporter) {
SkMatrix identity;
identity.reset();
REPORTER_ASSERT(reporter, SK_Scalar1 == identity.getMinStretch());
REPORTER_ASSERT(reporter, SK_Scalar1 == identity.getMaxStretch());
SkMatrix scale;
scale.setScale(SK_Scalar1 * 2, SK_Scalar1 * 4);
REPORTER_ASSERT(reporter, SK_Scalar1 * 2 == scale.getMinStretch());
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 / 4 == rot90Scale.getMinStretch());
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);
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(SK_Scalar1, rotate.getMinStretch() ,SK_ScalarNearlyZero));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(SK_Scalar1, rotate.getMaxStretch(), SK_ScalarNearlyZero));
SkMatrix translate;
translate.setTranslate(10 * SK_Scalar1, -5 * SK_Scalar1);
REPORTER_ASSERT(reporter, SK_Scalar1 == translate.getMinStretch());
REPORTER_ASSERT(reporter, SK_Scalar1 == translate.getMaxStretch());
SkMatrix perspX;
perspX.reset();
perspX.setPerspX(SkScalarToPersp(SK_Scalar1 / 1000));
REPORTER_ASSERT(reporter, -SK_Scalar1 == perspX.getMinStretch());
REPORTER_ASSERT(reporter, -SK_Scalar1 == perspX.getMaxStretch());
SkMatrix perspY;
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());
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);
mat.postConcat(mats[x]);
}
SkScalar stretch = mat.getMaxStretch();
if ((stretch < 0) != mat.hasPerspective()) {
stretch = mat.getMaxStretch();
}
REPORTER_ASSERT(reporter, (stretch < 0) == mat.hasPerspective());
SkScalar minStretch = mat.getMinStretch();
SkScalar maxStretch = mat.getMaxStretch();
REPORTER_ASSERT(reporter, (minStretch < 0) == (maxStretch < 0));
REPORTER_ASSERT(reporter, (maxStretch < 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;
// 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 almost each.
static const SkScalar gVectorStretchTol = (105 * SK_Scalar1) / 100;
static const SkScalar gClosestStretchTol = (97 * SK_Scalar1) / 100;
SkScalar max = 0, min = SK_ScalarMax;
SkVector vectors[1000];
for (size_t i = 0; i < SK_ARRAY_COUNT(vectors); ++i) {
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));
for (size_t i = 0; i < SK_ARRAY_COUNT(vectors); ++i) {
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) {
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));
#endif
test_matrix_max_stretch(reporter);
test_matrix_min_max_stretch(reporter);
test_matrix_is_similarity(reporter);
test_matrix_recttorect(reporter);
test_matrix_decomposition(reporter);