Bug 1479196 - draw missing glyphs from an atlas instead of rectangles. r=jfkthame
authorLee Salzman <lsalzman@mozilla.com>
Tue, 21 Aug 2018 12:40:36 -0400
changeset 432625 07dabfefeef1
parent 432624 9c610000042a
child 432626 d8a7212c51a4
push id34482
push usertoros@mozilla.com
push dateTue, 21 Aug 2018 21:56:56 +0000
treeherdermozilla-central@7c96ad3ab673 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjfkthame
bugs1479196
milestone63.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1479196 - draw missing glyphs from an atlas instead of rectangles. r=jfkthame
gfx/2d/UserData.h
gfx/thebes/gfxFontMissingGlyphs.cpp
gfx/thebes/gfxFontMissingGlyphs.h
gfx/thebes/gfxPlatform.cpp
--- a/gfx/2d/UserData.h
+++ b/gfx/2d/UserData.h
@@ -73,17 +73,19 @@ public:
     return nullptr;
   }
 
   /* Remove and destroy a given key */
   void RemoveAndDestroy(UserDataKey *key)
   {
     for (int i=0; i<count; i++) {
       if (key == entries[i].key) {
-        entries[i].destroy(entries[i].userData);
+        if (entries[i].destroy) {
+          entries[i].destroy(entries[i].userData);
+        }
         // decrement before looping so entries[i+1] doesn't read past the end:
         --count;
         for (;i<count; i++) {
           entries[i] = entries[i+1];
         }
       }
     }
   }
--- a/gfx/thebes/gfxFontMissingGlyphs.cpp
+++ b/gfx/thebes/gfxFontMissingGlyphs.cpp
@@ -4,117 +4,41 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "gfxFontMissingGlyphs.h"
 
 #include "gfxUtils.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/gfx/Helpers.h"
 #include "mozilla/gfx/PathHelpers.h"
+#include "mozilla/LinkedList.h"
 #include "mozilla/RefPtr.h"
 #include "nsDeviceContext.h"
 #include "nsLayoutUtils.h"
+#include "TextDrawTarget.h"
 
 using namespace mozilla;
 using namespace mozilla::gfx;
 
-#define CHAR_BITS(b00, b01, b02, b10, b11, b12, b20, b21, b22, b30, b31, b32, b40, b41, b42) \
-  ((b00 << 0) | (b01 << 1) | (b02 << 2) | (b10 << 3) | (b11 << 4) | (b12 << 5) | \
-   (b20 << 6) | (b21 << 7) | (b22 << 8) | (b30 << 9) | (b31 << 10) | (b32 << 11) | \
-   (b40 << 12) | (b41 << 13) | (b42 << 14))
-
-static const uint16_t glyphMicroFont[16] = {
-  CHAR_BITS(0, 1, 0,
-            1, 0, 1,
-            1, 0, 1,
-            1, 0, 1,
-            0, 1, 0),
-  CHAR_BITS(0, 1, 0,
-            0, 1, 0,
-            0, 1, 0,
-            0, 1, 0,
-            0, 1, 0),
-  CHAR_BITS(1, 1, 1,
-            0, 0, 1,
-            1, 1, 1,
-            1, 0, 0,
-            1, 1, 1),
-  CHAR_BITS(1, 1, 1,
-            0, 0, 1,
-            1, 1, 1,
-            0, 0, 1,
-            1, 1, 1),
-  CHAR_BITS(1, 0, 1,
-            1, 0, 1,
-            1, 1, 1,
-            0, 0, 1,
-            0, 0, 1),
-  CHAR_BITS(1, 1, 1,
-            1, 0, 0,
-            1, 1, 1,
-            0, 0, 1,
-            1, 1, 1),
-  CHAR_BITS(1, 1, 1,
-            1, 0, 0,
-            1, 1, 1,
-            1, 0, 1,
-            1, 1, 1),
-  CHAR_BITS(1, 1, 1,
-            0, 0, 1,
-            0, 0, 1,
-            0, 0, 1,
-            0, 0, 1),
-  CHAR_BITS(0, 1, 0,
-            1, 0, 1,
-            0, 1, 0,
-            1, 0, 1,
-            0, 1, 0),
-  CHAR_BITS(1, 1, 1,
-            1, 0, 1,
-            1, 1, 1,
-            0, 0, 1,
-            0, 0, 1),
-  CHAR_BITS(1, 1, 1,
-            1, 0, 1,
-            1, 1, 1,
-            1, 0, 1,
-            1, 0, 1),
-  CHAR_BITS(1, 1, 0,
-            1, 0, 1,
-            1, 1, 0,
-            1, 0, 1,
-            1, 1, 0),
-  CHAR_BITS(0, 1, 1,
-            1, 0, 0,
-            1, 0, 0,
-            1, 0, 0,
-            0, 1, 1),
-  CHAR_BITS(1, 1, 0,
-            1, 0, 1,
-            1, 0, 1,
-            1, 0, 1,
-            1, 1, 0),
-  CHAR_BITS(1, 1, 1,
-            1, 0, 0,
-            1, 1, 1,
-            1, 0, 0,
-            1, 1, 1),
-  CHAR_BITS(1, 1, 1,
-            1, 0, 0,
-            1, 1, 1,
-            1, 0, 0,
-            1, 0, 0)
+#define X 255
+static const uint8_t gMiniFontData[] = {
+    0,X,0, 0,X,0, X,X,X, X,X,X, X,0,X, X,X,X, X,X,X, X,X,X, X,X,X, X,X,X, X,X,X, X,X,0, 0,X,X, X,X,0, X,X,X, X,X,X,
+    X,0,X, 0,X,0, 0,0,X, 0,0,X, X,0,X, X,0,0, X,0,0, 0,0,X, X,0,X, X,0,X, X,0,X, X,0,X, X,0,0, X,0,X, X,0,0, X,0,0,
+    X,0,X, 0,X,0, X,X,X, X,X,X, X,X,X, X,X,X, X,X,X, 0,0,X, X,X,X, X,X,X, X,X,X, X,X,0, X,0,0, X,0,X, X,X,X, X,X,X,
+    X,0,X, 0,X,0, X,0,0, 0,0,X, 0,0,X, 0,0,X, X,0,X, 0,0,X, X,0,X, 0,0,X, X,0,X, X,0,X, X,0,0, X,0,X, X,0,0, X,0,0,
+    0,X,0, 0,X,0, X,X,X, X,X,X, 0,0,X, X,X,X, X,X,X, 0,0,X, X,X,X, 0,0,X, X,0,X, X,X,0, 0,X,X, X,X,0, X,X,X, X,0,0,
 };
+#undef X
 
 /* Parameters that control the rendering of hexboxes. They look like this:
 
         BMP codepoints           non-BMP codepoints
       (U+0000 - U+FFFF)         (U+10000 - U+10FFFF)
 
-         +---------+              +-------------+ 
+         +---------+              +-------------+
          |         |              |             |
          | HHH HHH |              | HHH HHH HHH |
          | HHH HHH |              | HHH HHH HHH |
          | HHH HHH |              | HHH HHH HHH |
          | HHH HHH |              | HHH HHH HHH |
          | HHH HHH |              | HHH HHH HHH |
          |         |              |             |
          | HHH HHH |              | HHH HHH HHH |
@@ -143,77 +67,327 @@ static const int HEX_CHAR_GAP = 1;
 static const int BOX_HORIZONTAL_INSET = 1;
 /** The width of the border */
 static const int BOX_BORDER_WIDTH = 1;
 /**
  * The scaling factor for the border opacity; this is multiplied by the current
  * opacity being used to draw the text.
  */
 static const Float BOX_BORDER_OPACITY = 0.5;
+
+#ifndef MOZ_GFX_OPTIMIZE_MOBILE
+
+static RefPtr<DrawTarget> gGlyphDrawTarget;
+static RefPtr<SourceSurface> gGlyphMask;
+static RefPtr<SourceSurface> gGlyphAtlas;
+static Color gGlyphColor;
+
 /**
- * Draw a single hex character using the current color. A nice way to do this
- * would be to fill in an A8 image surface and then use it as a mask
- * to paint the current color. Tragically this doesn't currently work with the
- * Quartz cairo backend which doesn't generally support masking with surfaces.
- * So for now we just paint a bunch of rectangles...
+ * Generates a new colored mini-font atlas from the mini-font mask.
  */
-#ifndef MOZ_GFX_OPTIMIZE_MOBILE
-static void
-DrawHexChar(uint32_t aDigit, const Point& aPt, DrawTarget& aDrawTarget,
-            const Pattern &aPattern, const Matrix* aMat)
+static bool
+MakeGlyphAtlas(const Color& aColor)
 {
-    uint32_t glyphBits = glyphMicroFont[aDigit];
+    gGlyphAtlas = nullptr;
+    if (!gGlyphDrawTarget) {
+        gGlyphDrawTarget = gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
+            IntSize(MINIFONT_WIDTH * 16, MINIFONT_HEIGHT), SurfaceFormat::B8G8R8A8);
+        if (!gGlyphDrawTarget) {
+            return false;
+        }
+    }
+    if (!gGlyphMask) {
+        gGlyphMask = gGlyphDrawTarget->CreateSourceSurfaceFromData(
+            const_cast<uint8_t*>(gMiniFontData), IntSize(MINIFONT_WIDTH * 16, MINIFONT_HEIGHT),
+            MINIFONT_WIDTH * 16, SurfaceFormat::A8);
+        if (!gGlyphMask) {
+            return false;
+        }
+    }
+    gGlyphDrawTarget->MaskSurface(ColorPattern(aColor), gGlyphMask, Point(0, 0),
+                                  DrawOptions(1.0f, CompositionOp::OP_SOURCE));
+    gGlyphAtlas = gGlyphDrawTarget->Snapshot();
+    if (!gGlyphAtlas) {
+        return false;
+    }
+    gGlyphColor = aColor;
+    return true;
+}
 
-    if (aMat) {
-        // If using an orientation matrix instead of a DT transform, step
-        // with the matrix basis vectors, filling individual rectangles of
-        // the size indicated by the matrix.
-        Point stepX(aMat->_11, aMat->_12);
-        Point stepY(aMat->_21, aMat->_22);
-        Point corner = stepX + stepY;
-        // Get the rectangle at the origin that will be stepped into place.
-        Rect startRect(std::min(corner.x, 0.0f), std::min(corner.y, 0.0f),
-                       fabs(corner.x), fabs(corner.y));
-        startRect.MoveBy(aMat->TransformPoint(aPt));
-        for (int y = 0; y < MINIFONT_HEIGHT; ++y) {
-            Rect curRect = startRect;
-            for (int x = 0; x < MINIFONT_WIDTH; ++x) {
-                if (glyphBits & 1) {
-                    aDrawTarget.FillRect(curRect, aPattern);
-                }
-                glyphBits >>= 1;
-                curRect.MoveBy(stepX);
-            }
-            startRect.MoveBy(stepY);
+/**
+ * Reuse the current mini-font atlas if the color matches, otherwise regenerate it.
+ */
+static inline already_AddRefed<SourceSurface>
+GetGlyphAtlas(const Color& aColor)
+{
+    // Get the opaque color, ignoring any transparency which will be handled later.
+    Color color(aColor.r, aColor.g, aColor.b);
+    if ((gGlyphAtlas && gGlyphColor == color) || MakeGlyphAtlas(color)) {
+        return do_AddRef(gGlyphAtlas);
+    }
+    return nullptr;
+}
+
+/**
+ * Clear any cached glyph atlas resources.
+ */
+static void
+PurgeGlyphAtlas()
+{
+    gGlyphAtlas = nullptr;
+    gGlyphDrawTarget = nullptr;
+    gGlyphMask = nullptr;
+}
+
+// WebRender layer manager user data that will get signaled when the layer
+// manager is destroyed.
+class WRUserData : public layers::LayerUserData, public LinkedListElement<WRUserData>
+{
+public:
+    explicit WRUserData(layers::WebRenderLayerManager* aManager);
+
+    ~WRUserData();
+
+    static void
+    Assign(layers::WebRenderLayerManager* aManager)
+    {
+        if (!aManager->HasUserData(&sWRUserDataKey)) {
+            aManager->SetUserData(&sWRUserDataKey, new WRUserData(aManager));
         }
-        return;
+    }
+
+    void
+    Remove()
+    {
+        remove();
+        mManager->RemoveUserData(&sWRUserDataKey);
     }
 
-    // To avoid the potential for seams showing between rects when we're under
-    // a transform we concat all the rects into a PathBuilder and fill the
-    // resulting Path (rather than using DrawTarget::FillRect).
-    RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder();
-    for (int y = 0; y < MINIFONT_HEIGHT; ++y) {
-        for (int x = 0; x < MINIFONT_WIDTH; ++x) {
-            if (glyphBits & 1) {
-                Rect r(aPt.x + x, aPt.y + y, 1, 1);
-                MaybeSnapToDevicePixels(r, aDrawTarget, true);
-                builder->MoveTo(r.TopLeft());
-                builder->LineTo(r.TopRight());
-                builder->LineTo(r.BottomRight());
-                builder->LineTo(r.BottomLeft());
-                builder->Close();
+    layers::WebRenderLayerManager* mManager;
+
+    static UserDataKey sWRUserDataKey;
+};
+
+static RefPtr<SourceSurface> gWRGlyphAtlas[8];
+static LinkedList<WRUserData> gWRUsers;
+UserDataKey WRUserData::sWRUserDataKey;
+
+/**
+ * Generates a transformed WebRender mini-font atlas for a given orientation.
+ */
+static already_AddRefed<SourceSurface>
+MakeWRGlyphAtlas(const Matrix* aMat)
+{
+    IntSize size(MINIFONT_WIDTH * 16, MINIFONT_HEIGHT);
+    // If the orientation is transposed, width/height are swapped.
+    if (aMat && aMat->_11 == 0) {
+        std::swap(size.width, size.height);
+    }
+    RefPtr<DrawTarget> ref = gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
+    RefPtr<DrawTarget> dt = gfxPlatform::GetPlatform()->CreateSimilarSoftwareDrawTarget(
+            ref, size, SurfaceFormat::B8G8R8A8);
+    if (!dt) {
+        return nullptr;
+    }
+    if (aMat) {
+        // Select appropriate transform matrix based on whether the
+        // orientation is transposed.
+        dt->SetTransform(aMat->_11 == 0 ?
+            Matrix(0.0f, copysign(1.0f, aMat->_12),
+                   copysign(1.0f, aMat->_21), 0.0f,
+                   aMat->_21 < 0 ? MINIFONT_HEIGHT : 0.0f,
+                   aMat->_12 < 0 ? MINIFONT_WIDTH * 16 : 0.0f) :
+            Matrix(copysign(1.0f, aMat->_11), 0.0f,
+                   0.0f, copysign(1.0f, aMat->_22),
+                   aMat->_11 < 0 ? MINIFONT_WIDTH * 16 : 0.0f,
+                   aMat->_22 < 0 ? MINIFONT_HEIGHT : 0.0f));
+    }
+    RefPtr<SourceSurface> mask = dt->CreateSourceSurfaceFromData(
+        const_cast<uint8_t*>(gMiniFontData), IntSize(MINIFONT_WIDTH * 16, MINIFONT_HEIGHT),
+        MINIFONT_WIDTH * 16, SurfaceFormat::A8);
+    if (!mask) {
+        return nullptr;
+    }
+    dt->MaskSurface(ColorPattern(Color(1.0f, 1.0f, 1.0f)), mask, Point(0, 0));
+    return dt->Snapshot();
+}
+
+/**
+ * Clear any cached WebRender glyph atlas resources.
+ */
+static void
+PurgeWRGlyphAtlas()
+{
+    // For each WR layer manager, we need go through each atlas orientation
+    // and see if it has a stashed image key. If it does, remove the image
+    // from the layer manager.
+    for (WRUserData* user : gWRUsers) {
+        auto* manager = user->mManager;
+        for (size_t i = 0; i < 8; i++) {
+            if (gWRGlyphAtlas[i]) {
+                uint32_t handle =
+                    (uint32_t)(uintptr_t)gWRGlyphAtlas[i]->GetUserData(
+                        reinterpret_cast<UserDataKey*>(manager));
+                if (handle) {
+                    manager->AddImageKeyForDiscard(
+                        wr::ImageKey{manager->WrBridge()->GetNamespace(), handle});
+                }
             }
-            glyphBits >>= 1;
+        }
+        // Remove the layer manager's destroy notification.
+        user->Remove();
+    }
+    // Finally, clear out the atlases.
+    for (size_t i = 0; i < 8; i++) {
+        gWRGlyphAtlas[i] = nullptr;
+    }
+}
+
+WRUserData::WRUserData(layers::WebRenderLayerManager* aManager)
+    : mManager(aManager)
+{
+    gWRUsers.insertFront(this);
+}
+
+WRUserData::~WRUserData()
+{
+    // When the layer manager is destroyed, we need go through each
+    // atlas and remove any assigned image keys.
+    if (isInList()) {
+        for (size_t i = 0; i < 8; i++) {
+            if (gWRGlyphAtlas[i]) {
+                gWRGlyphAtlas[i]->RemoveUserData(reinterpret_cast<UserDataKey*>(mManager));
+            }
+        }
+    }
+}
+
+static already_AddRefed<SourceSurface>
+GetWRGlyphAtlas(DrawTarget& aDrawTarget, const Matrix* aMat)
+{
+    uint32_t key = 0;
+    // Encode orientation in the key.
+    if (aMat) {
+        if (aMat->_11 == 0) {
+            key |= 4 | (aMat->_12 < 0 ? 1 : 0) | (aMat->_21 < 0 ? 2 : 0);
+        } else {
+            key |= (aMat->_11 < 0 ? 1 : 0) | (aMat->_22 < 0 ? 2 : 0);
         }
     }
-    RefPtr<Path> path = builder->Finish();
-    aDrawTarget.Fill(path, aPattern);
+    // Check if an atlas was already created, or create one if necessary.
+    RefPtr<SourceSurface> atlas = gWRGlyphAtlas[key];
+    if (!atlas) {
+        atlas = MakeWRGlyphAtlas(aMat);
+        gWRGlyphAtlas[key] = atlas;
+    }
+    // The atlas may exist, but an image key may not be assigned for it to
+    // the given layer manager.
+    auto* tdt = static_cast<layout::TextDrawTarget*>(&aDrawTarget);
+    auto* manager = tdt->WrLayerManager();
+    if (!atlas->GetUserData(reinterpret_cast<UserDataKey*>(manager))) {
+        // No image key, so we need to map the atlas' data for transfer to WR.
+        RefPtr<DataSourceSurface> dataSurface = atlas->GetDataSurface();
+        if (!dataSurface) {
+            return nullptr;
+        }
+        DataSourceSurface::ScopedMap map(dataSurface, DataSourceSurface::READ);
+        if (!map.IsMapped()) {
+            return nullptr;
+        }
+        // Transfer the data and get an image key for it.
+        Maybe<wr::ImageKey> result =
+            tdt->DefineImage(atlas->GetSize(),
+                             map.GetStride(),
+                             atlas->GetFormat(),
+                             map.GetData());
+        if (!result.isSome()) {
+            return nullptr;
+        }
+        // Assign the image key to the atlas.
+        atlas->AddUserData(reinterpret_cast<UserDataKey*>(manager),
+                           (void*)(uintptr_t)result.value().mHandle,
+                           nullptr);
+        // Create a user data notification for when the layer manager is
+        // destroyed so we can clean up any assigned image keys.
+        WRUserData::Assign(manager);
+    }
+    return atlas.forget();
 }
-#endif // MOZ_GFX_OPTIMIZE_MOBILE
+
+static void
+DrawHexChar(uint32_t aDigit, Float aLeft, Float aTop, DrawTarget& aDrawTarget,
+            SourceSurface* aAtlas, const Color& aColor,
+            const Matrix* aMat = nullptr)
+{
+    Rect dest(aLeft, aTop, MINIFONT_WIDTH, MINIFONT_HEIGHT);
+    if (aDrawTarget.GetBackendType() == BackendType::WEBRENDER_TEXT) {
+        // For WR, we need to get the image key assigned to the given WR layer manager
+        // for referencing the image.
+        auto* tdt = static_cast<layout::TextDrawTarget*>(&aDrawTarget);
+        auto* manager = tdt->WrLayerManager();
+        wr::ImageKey key = {
+            manager->WrBridge()->GetNamespace(),
+            (uint32_t)(uintptr_t)aAtlas->GetUserData(reinterpret_cast<UserDataKey*>(manager))
+        };
+        // Transform the bounds of the atlas into the given orientation, and then also transform
+        // a small clip rect which will be used to select the given digit from the atlas.
+        Rect bounds(aLeft - aDigit * MINIFONT_WIDTH, aTop, MINIFONT_WIDTH * 16, MINIFONT_HEIGHT);
+        if (aMat) {
+            // Width and height may be negative after the transform, so move the rect
+            // if necessary and fix size.
+            bounds = aMat->TransformRect(bounds);
+            bounds.x += std::min(bounds.width, 0.0f);
+            bounds.y += std::min(bounds.height, 0.0f);
+            bounds.width = fabs(bounds.width);
+            bounds.height = fabs(bounds.height);
+            dest = aMat->TransformRect(dest);
+            dest.x += std::min(dest.width, 0.0f);
+            dest.y += std::min(dest.height, 0.0f);
+            dest.width = fabs(dest.width);
+            dest.height = fabs(dest.height);
+        }
+        // Finally, push the colored image with point filtering.
+        tdt->PushImage(key,
+                       wr::ToLayoutRect(bounds),
+                       wr::ToLayoutRect(dest),
+                       wr::ImageRendering::Pixelated,
+                       wr::ToColorF(aColor));
+    } else {
+        // For the normal case, just draw the given digit from the atlas. Point filtering is used
+        // to ensure the mini-font rectangles stay sharp with any scaling. Handle any transparency
+        // here as well.
+        aDrawTarget.DrawSurface(aAtlas,
+                                dest,
+                                Rect(aDigit * MINIFONT_WIDTH, 0, MINIFONT_WIDTH, MINIFONT_HEIGHT),
+                                DrawSurfaceOptions(SamplingFilter::POINT),
+                                DrawOptions(aColor.a, CompositionOp::OP_OVER, AntialiasMode::NONE));
+    }
+}
+
+void
+gfxFontMissingGlyphs::Purge()
+{
+    PurgeGlyphAtlas();
+    PurgeWRGlyphAtlas();
+}
+
+#else // MOZ_GFX_OPTIMIZE_MOBILE
+
+void
+gfxFontMissingGlyphs::Purge()
+{
+}
+
+#endif
+
+void
+gfxFontMissingGlyphs::Shutdown()
+{
+    Purge();
+}
 
 void
 gfxFontMissingGlyphs::DrawMissingGlyph(uint32_t aChar,
                                        const Rect& aRect,
                                        DrawTarget& aDrawTarget,
                                        const Pattern& aPattern,
                                        uint32_t aAppUnitsPerDevPixel,
                                        const Matrix* aMat)
@@ -223,41 +397,49 @@ gfxFontMissingGlyphs::DrawMissingGlyph(u
     if (aMat) {
         rect.MoveBy(-aRect.BottomLeft());
         rect = aMat->TransformBounds(rect);
         rect.MoveBy(aRect.BottomLeft());
     }
 
     // If we're currently drawing with some kind of pattern, we just draw the
     // missing-glyph data in black.
-    ColorPattern color = aPattern.GetType() == PatternType::COLOR ?
-        static_cast<const ColorPattern&>(aPattern) :
-        ColorPattern(ToDeviceColor(Color(0.f, 0.f, 0.f, 1.f)));
+    Color color = aPattern.GetType() == PatternType::COLOR ?
+        static_cast<const ColorPattern&>(aPattern).mColor :
+        ToDeviceColor(Color(0.f, 0.f, 0.f, 1.f));
 
     // Stroke a rectangle so that the stroke's left edge is inset one pixel
     // from the left edge of the glyph box and the stroke's right edge
     // is inset one pixel from the right edge of the glyph box.
     Float halfBorderWidth = BOX_BORDER_WIDTH / 2.0;
     Float borderLeft = rect.X() + BOX_HORIZONTAL_INSET + halfBorderWidth;
     Float borderRight = rect.XMost() - BOX_HORIZONTAL_INSET - halfBorderWidth;
     Rect borderStrokeRect(borderLeft, rect.Y() + halfBorderWidth,
                           borderRight - borderLeft,
                           rect.Height() - 2.0 * halfBorderWidth);
     if (!borderStrokeRect.IsEmpty()) {
-        ColorPattern adjustedColor = color;
+        ColorPattern adjustedColor(color);
         adjustedColor.mColor.a *= BOX_BORDER_OPACITY;
 #ifdef MOZ_GFX_OPTIMIZE_MOBILE
         aDrawTarget.FillRect(borderStrokeRect, adjustedColor);
 #else
         StrokeOptions strokeOptions(BOX_BORDER_WIDTH);
         aDrawTarget.StrokeRect(borderStrokeRect, adjustedColor, strokeOptions);
 #endif
     }
 
 #ifndef MOZ_GFX_OPTIMIZE_MOBILE
+    RefPtr<SourceSurface> atlas =
+        aDrawTarget.GetBackendType() == BackendType::WEBRENDER_TEXT ?
+            GetWRGlyphAtlas(aDrawTarget, aMat) :
+            GetGlyphAtlas(color);
+    if (!atlas) {
+        return;
+    }
+
     Point center = rect.Center();
     Float halfGap = HEX_CHAR_GAP / 2.f;
     Float top = -(MINIFONT_HEIGHT + halfGap);
     // We always want integer scaling, otherwise the "bitmap" glyphs will look
     // even uglier than usual when zoomed
     int32_t devPixelsPerCSSPx =
         std::max<int32_t>(1, AppUnitsPerCSSPixel() /
                              aAppUnitsPerDevPixel);
@@ -279,44 +461,34 @@ gfxFontMissingGlyphs::DrawMissingGlyph(u
                            .PreScale(devPixelsPerCSSPx, devPixelsPerCSSPx));
     }
 
     if (aChar < 0x10000) {
         if (rect.Width() >= 2 * (MINIFONT_WIDTH + HEX_CHAR_GAP) &&
             rect.Height() >= 2 * MINIFONT_HEIGHT + HEX_CHAR_GAP) {
             // Draw 4 digits for BMP
             Float left = -(MINIFONT_WIDTH + halfGap);
-            DrawHexChar((aChar >> 12) & 0xF,
-                        Point(left, top), aDrawTarget, color, aMat);
-            DrawHexChar((aChar >> 8) & 0xF,
-                        Point(halfGap, top), aDrawTarget, color, aMat);
-            DrawHexChar((aChar >> 4) & 0xF,
-                        Point(left, halfGap), aDrawTarget, color, aMat);
-            DrawHexChar(aChar & 0xF,
-                        Point(halfGap, halfGap), aDrawTarget, color, aMat);
+            DrawHexChar((aChar >> 12) & 0xF, left, top, aDrawTarget, atlas, color, aMat);
+            DrawHexChar((aChar >> 8) & 0xF, halfGap, top, aDrawTarget, atlas, color, aMat);
+            DrawHexChar((aChar >> 4) & 0xF, left, halfGap, aDrawTarget, atlas, color, aMat);
+            DrawHexChar(aChar & 0xF, halfGap, halfGap, aDrawTarget, atlas, color, aMat);
         }
     } else {
         if (rect.Width() >= 3 * (MINIFONT_WIDTH + HEX_CHAR_GAP) &&
             rect.Height() >= 2 * MINIFONT_HEIGHT + HEX_CHAR_GAP) {
             // Draw 6 digits for non-BMP
             Float first = -(MINIFONT_WIDTH * 1.5 + HEX_CHAR_GAP);
             Float second = -(MINIFONT_WIDTH / 2.0);
             Float third = (MINIFONT_WIDTH / 2.0 + HEX_CHAR_GAP);
-            DrawHexChar((aChar >> 20) & 0xF,
-                        Point(first, top), aDrawTarget, color, aMat);
-            DrawHexChar((aChar >> 16) & 0xF,
-                        Point(second, top), aDrawTarget, color, aMat);
-            DrawHexChar((aChar >> 12) & 0xF,
-                        Point(third, top), aDrawTarget, color, aMat);
-            DrawHexChar((aChar >> 8) & 0xF,
-                        Point(first, halfGap), aDrawTarget, color, aMat);
-            DrawHexChar((aChar >> 4) & 0xF,
-                        Point(second, halfGap), aDrawTarget, color, aMat);
-            DrawHexChar(aChar & 0xF,
-                        Point(third, halfGap), aDrawTarget, color, aMat);
+            DrawHexChar((aChar >> 20) & 0xF, first, top, aDrawTarget, atlas, color, aMat);
+            DrawHexChar((aChar >> 16) & 0xF, second, top, aDrawTarget, atlas, color, aMat);
+            DrawHexChar((aChar >> 12) & 0xF, third, top, aDrawTarget, atlas, color, aMat);
+            DrawHexChar((aChar >> 8) & 0xF, first, halfGap, aDrawTarget, atlas, color, aMat);
+            DrawHexChar((aChar >> 4) & 0xF, second, halfGap, aDrawTarget, atlas, color, aMat);
+            DrawHexChar(aChar & 0xF, third, halfGap, aDrawTarget, atlas, color, aMat);
         }
     }
 
     if (!aMat) {
         // The draw target transform was changed, so it must be restored to
         // the original value.
         aDrawTarget.SetTransform(tempMat);
     }
--- a/gfx/thebes/gfxFontMissingGlyphs.h
+++ b/gfx/thebes/gfxFontMissingGlyphs.h
@@ -49,11 +49,15 @@ public:
                                  uint32_t aAppUnitsPerDevPixel,
                                  const Matrix* aMat = nullptr);
     /**
      * @return the desired minimum width for a glyph-box that will allow
      * the hexboxes to be drawn reasonably.
      */
     static Float GetDesiredMinWidth(uint32_t aChar,
                                     uint32_t aAppUnitsPerDevUnit);
+
+    static void Purge();
+
+    static void Shutdown();
 };
 
 #endif
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -68,16 +68,17 @@
 #include "gfxContext.h"
 #include "gfxImageSurface.h"
 #include "nsUnicodeProperties.h"
 #include "harfbuzz/hb.h"
 #include "gfxGraphiteShaper.h"
 #include "gfx2DGlue.h"
 #include "gfxGradientCache.h"
 #include "gfxUtils.h" // for NextPowerOfTwo
+#include "gfxFontMissingGlyphs.h"
 
 #include "nsExceptionHandler.h"
 #include "nsUnicodeRange.h"
 #include "nsServiceManagerUtils.h"
 #include "nsTArray.h"
 #include "nsIObserverService.h"
 #include "nsIScreenManager.h"
 #include "FrameMetrics.h"
@@ -465,16 +466,17 @@ FontPrefChanged(const char* aPref, void*
     gfxPlatform::GetPlatform()->FontsPrefsChanged(aPref);
 }
 
 void
 gfxPlatform::OnMemoryPressure(layers::MemoryPressureReason aWhy)
 {
     Factory::PurgeAllCaches();
     gfxGradientCache::PurgeAllCaches();
+    gfxFontMissingGlyphs::Purge();
     PurgeSkiaFontCache();
     PurgeSkiaGPUCache();
     if (XRE_IsParentProcess()) {
       layers::CompositorManagerChild* manager = CompositorManagerChild::GetInstance();
       if (manager) {
         manager->SendNotifyMemoryPressure();
       }
     }
@@ -959,16 +961,17 @@ gfxPlatform::Shutdown()
 
     // These may be called before the corresponding subsystems have actually
     // started up. That's OK, they can handle it.
     gfxFontCache::Shutdown();
     gfxGradientCache::Shutdown();
     gfxAlphaBoxBlur::ShutdownBlurCache();
     gfxGraphiteShaper::Shutdown();
     gfxPlatformFontList::Shutdown();
+    gfxFontMissingGlyphs::Shutdown();
     ShutdownTileCache();
 
     // Free the various non-null transforms and loaded profiles
     ShutdownCMS();
 
     /* Unregister our CMS Override callback. */
     NS_ASSERTION(gPlatform->mSRGBOverrideObserver, "mSRGBOverrideObserver has alreay gone");
     Preferences::RemoveObserver(gPlatform->mSRGBOverrideObserver, GFX_PREF_CMS_FORCE_SRGB);