Bug 1479196 - draw missing glyphs from an atlas instead of rectangles. r=jfkthame

This commit is contained in:
Lee Salzman 2018-08-21 12:40:36 -04:00
Родитель daa9f58360
Коммит bf12f26ad9
4 изменённых файлов: 349 добавлений и 168 удалений

Просмотреть файл

@ -78,7 +78,9 @@ public:
{
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++) {

Просмотреть файл

@ -9,107 +9,31 @@
#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 |
@ -148,67 +72,317 @@ static const int BOX_BORDER_WIDTH = 1;
* opacity being used to draw the text.
*/
static const Float BOX_BORDER_OPACITY = 0.5;
/**
* 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...
*/
#ifndef MOZ_GFX_OPTIMIZE_MOBILE
static void
DrawHexChar(uint32_t aDigit, const Point& aPt, DrawTarget& aDrawTarget,
const Pattern &aPattern, const Matrix* aMat)
static RefPtr<DrawTarget> gGlyphDrawTarget;
static RefPtr<SourceSurface> gGlyphMask;
static RefPtr<SourceSurface> gGlyphAtlas;
static Color gGlyphColor;
/**
* Generates a new colored mini-font atlas from the mini-font mask.
*/
static bool
MakeGlyphAtlas(const Color& aColor)
{
uint32_t glyphBits = glyphMicroFont[aDigit];
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);
}
return;
}
// 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();
}
glyphBits >>= 1;
gGlyphAtlas = nullptr;
if (!gGlyphDrawTarget) {
gGlyphDrawTarget = gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
IntSize(MINIFONT_WIDTH * 16, MINIFONT_HEIGHT), SurfaceFormat::B8G8R8A8);
if (!gGlyphDrawTarget) {
return false;
}
}
RefPtr<Path> path = builder->Finish();
aDrawTarget.Fill(path, aPattern);
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;
}
/**
* 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));
}
}
void
Remove()
{
remove();
mManager->RemoveUserData(&sWRUserDataKey);
}
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});
}
}
}
// 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);
}
}
// 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();
}
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();
}
#endif // MOZ_GFX_OPTIMIZE_MOBILE
void
gfxFontMissingGlyphs::DrawMissingGlyph(uint32_t aChar,
@ -228,9 +402,9 @@ gfxFontMissingGlyphs::DrawMissingGlyph(uint32_t aChar,
// 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
@ -242,7 +416,7 @@ gfxFontMissingGlyphs::DrawMissingGlyph(uint32_t aChar,
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);
@ -253,6 +427,14 @@ gfxFontMissingGlyphs::DrawMissingGlyph(uint32_t aChar,
}
#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);
@ -284,14 +466,10 @@ gfxFontMissingGlyphs::DrawMissingGlyph(uint32_t aChar,
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) &&
@ -300,18 +478,12 @@ gfxFontMissingGlyphs::DrawMissingGlyph(uint32_t aChar,
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);
}
}

Просмотреть файл

@ -54,6 +54,10 @@ public:
*/
static Float GetDesiredMinWidth(uint32_t aChar,
uint32_t aAppUnitsPerDevUnit);
static void Purge();
static void Shutdown();
};
#endif

Просмотреть файл

@ -73,6 +73,7 @@
#include "gfx2DGlue.h"
#include "gfxGradientCache.h"
#include "gfxUtils.h" // for NextPowerOfTwo
#include "gfxFontMissingGlyphs.h"
#include "nsExceptionHandler.h"
#include "nsUnicodeRange.h"
@ -470,6 +471,7 @@ gfxPlatform::OnMemoryPressure(layers::MemoryPressureReason aWhy)
{
Factory::PurgeAllCaches();
gfxGradientCache::PurgeAllCaches();
gfxFontMissingGlyphs::Purge();
PurgeSkiaFontCache();
PurgeSkiaGPUCache();
if (XRE_IsParentProcess()) {
@ -964,6 +966,7 @@ gfxPlatform::Shutdown()
gfxAlphaBoxBlur::ShutdownBlurCache();
gfxGraphiteShaper::Shutdown();
gfxPlatformFontList::Shutdown();
gfxFontMissingGlyphs::Shutdown();
ShutdownTileCache();
// Free the various non-null transforms and loaded profiles