Bug 1773936 - Apply Skia-equivalent subpixel rounding in DrawTargetWebgl. r=aosmond,gfx-reviewers

Depending on the current transform, Skia applies either subpixel rounding or integer
rounding to different coordinate axes of the transformed glyph position. If we don't
correctly predict which of these are applied, we may have aliased cache entries that
round to the same value in DrawTargetWebgl but for which Skia under the hood rounds
in entirely different directions. When this happens, glyphs can get hinted to the
wrong direction. To fix this, we need to ensure that we appropriately apply either
subpixel rounding or integer rounding in the same manner as Skia.

Differential Revision: https://phabricator.services.mozilla.com/D149350
This commit is contained in:
Lee Salzman
2022-06-16 21:39:07 +00:00
parent 582946eef1
commit a7e8d0ffb7
8 changed files with 47 additions and 7 deletions

View File

@@ -2361,13 +2361,15 @@ void DrawTargetWebgl::StrokeGlyphs(ScaledFont* aFont,
// Skia only supports subpixel positioning to the nearest 1/4 fraction. It
// would be wasteful to attempt to cache text runs with positioning that is
// anymore precise than this. To prevent this cache bloat, we quantize the
// transformed glyph positions to the nearest 1/4.
// transformed glyph positions to the nearest 1/4. The scaling factor for
// the quantization is baked into the transform, so that if subpixel rounding
// is used on a given axis, then the axis will be multiplied by 4 before
// rounding. Since the quantized position is not used for rasterization, the
// transform is safe to modify as such.
static inline IntPoint QuantizePosition(const Matrix& aTransform,
const IntPoint& aOffset,
const Point& aPosition) {
IntPoint pos =
RoundedToInt(aTransform.TransformPoint(aPosition) * 4.0f) - aOffset * 4;
return IntPoint(pos.x & 3, pos.y & 3);
return RoundedToInt(aTransform.TransformPoint(aPosition)) - aOffset;
}
// Hashes a glyph buffer to a single hash value that can be used for quick
@@ -2539,6 +2541,34 @@ bool DrawTargetWebgl::SharedContext::FillGlyphsAccel(
// as for color emoji.
bool useBitmaps = aFont->MayUseBitmaps();
// Depending on whether we enable subpixel position for a given font, Skia may
// round transformed coordinates differently on each axis. By default, text is
// subpixel quantized horizontally and snapped to a whole integer vertical
// baseline. Axis-flip transforms instead snap to horizontal boundaries while
// subpixel quantizing along the vertical. For other types of transforms, Skia
// just applies subpixel quantization to both axes.
// We must duplicate the amount of quantization Skia applies carefully as a
// boundary value such as 0.49 may round to 0.5 with subpixel quantization,
// but if Skia actually snapped it to a whole integer instead, it would round
// down to 0. If a subsequent glyph with offset 0.51 came in, we might
// mistakenly round it down to 0.5, whereas Skia would round it up to 1. Thus
// we would alias 0.49 and 0.51 to the same cache entry, while Skia would
// actually snap the offset to 0 or 1, depending, resulting in mismatched
// hinting.
Matrix quantizeTransform = currentTransform;
if (aFont->UseSubpixelPosition()) {
if (currentTransform._12 == 0) {
// Glyphs are rendered subpixel horizontally, so snap vertically.
quantizeTransform.PostScale(4, 1);
} else if (currentTransform._11 == 0) {
// Glyphs are rendered subpixel vertically, so snap horizontally.
quantizeTransform.PostScale(1, 4);
} else {
// The transform isn't aligned, so don't snap.
quantizeTransform.PostScale(4, 4);
}
}
// Look for an existing glyph cache on the font. If not there, create it.
GlyphCache* cache =
static_cast<GlyphCache*>(aFont->GetUserData(&mGlyphCacheKey));
@@ -2555,7 +2585,7 @@ bool DrawTargetWebgl::SharedContext::FillGlyphsAccel(
RefPtr<GlyphCacheEntry> entry = cache->FindOrInsertEntry(
aBuffer,
useBitmaps ? color : DeviceColor::Mask(aUseSubpixelAA ? 1 : 0, 1),
currentTransform, intBounds);
quantizeTransform, intBounds);
if (!entry) {
return false;
}

View File

@@ -308,6 +308,8 @@ class GlyphCacheEntry : public CacheEntryImpl<GlyphCacheEntry> {
HashNumber aHash);
~GlyphCacheEntry();
const GlyphBuffer& GetGlyphBuffer() const { return mBuffer; }
bool MatchesGlyphs(const GlyphBuffer& aBuffer, const DeviceColor& aColor,
const Matrix& aTransform, const IntRect& aBounds,
HashNumber aHash);

View File

@@ -1217,6 +1217,8 @@ class ScaledFont : public SupportsThreadSafeWeakPtr<ScaledFont> {
virtual bool MayUseBitmaps() { return false; }
virtual bool UseSubpixelPosition() const { return false; }
void AddUserData(UserDataKey* key, void* userData, void (*destroy)(void*)) {
mUserData.Add(key, userData, destroy);
}

View File

@@ -57,6 +57,8 @@ class ScaledFontDWrite final : public ScaledFontBase {
bool UseMultistrikeBold() const { return mUseMultistrikeBold; }
bool ForceGDIMode() const { return mGDIForced; }
bool UseSubpixelPosition() const override { return !ForceGDIMode(); }
bool HasBoldSimulation() const {
return (mFontFace->GetSimulations() & DWRITE_FONT_SIMULATIONS_BOLD) != 0;
}

View File

@@ -30,7 +30,7 @@ class ScaledFontFontconfig : public ScaledFontBase {
AntialiasMode GetDefaultAAMode() override;
bool UseSubpixelPosition() const;
bool UseSubpixelPosition() const override;
bool CanSerialize() override { return true; }

View File

@@ -31,7 +31,7 @@ class ScaledFontFreeType : public ScaledFontBase {
AntialiasMode GetDefaultAAMode() override { return AntialiasMode::GRAY; }
bool UseSubpixelPosition() const;
bool UseSubpixelPosition() const override;
bool CanSerialize() override { return true; }

View File

@@ -56,6 +56,8 @@ class ScaledFontMac : public ScaledFontBase {
bool MayUseBitmaps() override { return mHasColorGlyphs; }
bool UseSubpixelPosition() const override { return true; }
DeviceColor FontSmoothingBackgroundColor() {
return mFontSmoothingBackgroundColor;
}

View File

@@ -29,6 +29,8 @@ class ScaledFontWin : public ScaledFontBase {
bool MayUseBitmaps() override { return true; }
bool UseSubpixelPosition() const override { return false; }
protected:
cairo_font_face_t* CreateCairoFontFace(
cairo_font_options_t* aFontOptions) override;