SkGlyphCache - update core routines.

- Allows different methods for creating SkGlyphCaches
- Enables passing FontMetrics for cache creation.
- Removes VisitCache
- Removes VisitAll
- Introduces SkExclusiveStrikePtr which should
  replaces SkAutoGlyphCache with simpler mechanism.

BUG=skia:7515

Change-Id: Ibada35e3985335179d2cc8284a837fc525224c92
Reviewed-on: https://skia-review.googlesource.com/111063
Reviewed-by: Ben Wagner <bungeman@google.com>
Reviewed-by: Herb Derby <herb@google.com>
Commit-Queue: Herb Derby <herb@google.com>
diff --git a/include/core/SkTypeface.h b/include/core/SkTypeface.h
index 61620a4..8fe8088 100644
--- a/include/core/SkTypeface.h
+++ b/include/core/SkTypeface.h
@@ -365,6 +365,9 @@
     };
     static SkFontStyle FromOldStyle(Style oldStyle);
     static SkTypeface* GetDefaultTypeface(Style style = SkTypeface::kNormal);
+    static SkTypeface* NormalizeTypeface(SkTypeface* typeface) {
+        return typeface != nullptr ? typeface : SkTypeface::GetDefaultTypeface();
+    }
     friend class GrPathRendering;  // GetDefaultTypeface
     friend class SkGlyphCache;     // GetDefaultTypeface
     friend class SkPaint;          // GetDefaultTypeface
diff --git a/src/core/SkGlyphCache.cpp b/src/core/SkGlyphCache.cpp
index 50a1b5f..087fb52 100644
--- a/src/core/SkGlyphCache.cpp
+++ b/src/core/SkGlyphCache.cpp
@@ -7,7 +7,6 @@
 
 
 #include "SkGlyphCache.h"
-#include "SkGlyphCache_Globals.h"
 #include "SkGraphics.h"
 #include "SkOnce.h"
 #include "SkPath.h"
@@ -34,14 +33,12 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
-SkGlyphCache::SkGlyphCache(const SkDescriptor* desc, std::unique_ptr<SkScalerContext> ctx)
-    : fDesc(desc->copy())
-    , fScalerContext(std::move(ctx)) {
-    SkASSERT(desc);
+SkGlyphCache::SkGlyphCache(const SkDescriptor& desc, std::unique_ptr<SkScalerContext> ctx)
+    : fDesc(desc.copy())
+    , fScalerContext(std::move(ctx))
+{
     SkASSERT(fScalerContext);
 
-    fPrev = fNext = nullptr;
-
     fScalerContext->getFontMetrics(&fFontMetrics);
 
     fMemoryUsed = sizeof(*this);
@@ -55,6 +52,10 @@
     });
 }
 
+void SkGlyphCache::PurgeAll() {
+    get_globals().purgeAll();
+}
+
 SkGlyphCache::CharGlyphRec* SkGlyphCache::getCharGlyphRec(SkPackedUnicharID packedUnicharID) {
     if (!fPackedUnicharIDToPackedGlyphID) {
         fPackedUnicharIDToPackedGlyphID.reset(new CharGlyphRec[kHashCount]);
@@ -102,6 +103,15 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
+bool SkGlyphCache::isGlyphCached(SkGlyphID glyphID, SkFixed x, SkFixed y) const {
+    SkPackedGlyphID packedGlyphID{glyphID, x, y};
+    return fGlyphMap.find(packedGlyphID) != nullptr;
+}
+
+SkGlyph* SkGlyphCache::getRawGlyphByID(SkPackedGlyphID id) {
+    return lookupByPackedGlyphID(id, kNothing_MetricsType);
+}
+
 const SkGlyph& SkGlyphCache::getUnicharAdvance(SkUnichar charCode) {
     VALIDATE();
     return *this->lookupByChar(charCode, kJustAdvance_MetricsType);
@@ -170,7 +180,9 @@
         glyphPtr = fGlyphMap.set(glyph);
     }
 
-    if (kJustAdvance_MetricsType == mtype) {
+    if (kNothing_MetricsType == mtype) {
+        return glyphPtr;
+    } else if (kJustAdvance_MetricsType == mtype) {
         fScalerContext->getAdvance(glyphPtr);
     } else {
         SkASSERT(kFull_MetricsType == mtype);
@@ -469,92 +481,43 @@
     this->internalPurge(fTotalMemoryUsed);
 }
 
-/*  This guy calls the visitor from within the mutext lock, so the visitor
-    cannot:
-    - take too much time
-    - try to acquire the mutext again
-    - call a fontscaler (which might call into the cache)
-*/
-SkGlyphCache* SkGlyphCache::VisitCache(SkTypeface* typeface,
-                                       const SkScalerContextEffects& effects,
-                                       const SkDescriptor* desc,
-                                       bool (*proc)(const SkGlyphCache*, void*),
-                                       void* context) {
-    if (!typeface) {
-        typeface = SkTypeface::GetDefaultTypeface();
-    }
-    SkASSERT(desc);
-
-    // Precondition: the typeface id must be the fFontID in the descriptor
-    SkDEBUGCODE(
-        uint32_t length = 0;
-        const SkScalerContextRec* rec = static_cast<const SkScalerContextRec*>(
-            desc->findEntry(kRec_SkDescriptorTag, &length));
-        SkASSERT(rec);
-        SkASSERT(length == sizeof(*rec));
-        SkASSERT(typeface->uniqueID() == rec->fFontID);
-    )
-
+SkExclusiveStrikePtr SkGlyphCache::FindStrikeExclusive(const SkDescriptor& desc) {
     SkGlyphCache_Globals& globals = get_globals();
     SkGlyphCache*         cache;
+    SkAutoExclusive       ac(globals.fLock);
 
-    {
-        SkAutoExclusive ac(globals.fLock);
-
-        globals.validate();
-
-        for (cache = globals.internalGetHead(); cache != nullptr; cache = cache->fNext) {
-            if (*cache->fDesc == *desc) {
-                globals.internalDetachCache(cache);
-                if (!proc(cache, context)) {
-                    globals.internalAttachCacheToHead(cache);
-                    cache = nullptr;
-                }
-                return cache;
-            }
+    for (cache = globals.internalGetHead(); cache != nullptr; cache = cache->fNext) {
+        if (*cache->fDesc == desc) {
+            globals.internalDetachCache(cache);
+            return SkExclusiveStrikePtr(cache);
         }
     }
 
-    // Check if we can create a scaler-context before creating the glyphcache.
-    // If not, we may have exhausted OS/font resources, so try purging the
-    // cache once and try again.
-    {
-        // pass true the first time, to notice if the scalercontext failed,
-        // so we can try the purge.
-        std::unique_ptr<SkScalerContext> ctx = typeface->createScalerContext(effects, desc, true);
-        if (!ctx) {
-            get_globals().purgeAll();
-            ctx = typeface->createScalerContext(effects, desc, false);
-            SkASSERT(ctx);
-        }
-        cache = new SkGlyphCache(desc, std::move(ctx));
-    }
+    return SkExclusiveStrikePtr(nullptr);
+}
 
-    AutoValidate av(cache);
-
-    if (!proc(cache, context)) {   // need to reattach
-        globals.attachCacheToHead(cache);
-        cache = nullptr;
-    }
-    return cache;
+SkExclusiveStrikePtr SkGlyphCache::FindOrCreateStrikeExclusive(
+    const SkDescriptor& desc, const SkScalerContextEffects& effects, const SkTypeface& typeface) {
+    auto creator = [&effects, &typeface](const SkDescriptor& descriptor, bool canFail) {
+        return typeface.createScalerContext(effects, &descriptor, canFail);
+    };
+    return FindOrCreateStrikeExclusive(desc, creator);
 }
 
 void SkGlyphCache::AttachCache(SkGlyphCache* cache) {
-    SkASSERT(cache);
-    SkASSERT(cache->fNext == nullptr);
-
-    get_globals().attachCacheToHead(cache);
+    SkGlyphCache_Globals::AttachCache(cache);
 }
 
-static void dump_visitor(const SkGlyphCache& cache, void* context) {
-    int* counter = (int*)context;
-    int index = *counter;
-    *counter += 1;
+void SkGlyphCache::ForEachStrike(std::function<void(const SkGlyphCache&)> visitor) {
+    SkGlyphCache_Globals& globals = get_globals();
+    SkAutoExclusive ac(globals.fLock);
+    SkGlyphCache* cache;
 
-    const SkScalerContextRec& rec = cache.getScalerContext()->getRec();
+    globals.validate();
 
-    SkDebugf("index %d\n", index);
-    SkDebugf("%s", rec.dump().c_str());
+    for (cache = globals.internalGetHead(); cache != nullptr; cache = cache->fNext) {
+        visitor(*cache);
+    }
 }
 
 void SkGlyphCache::Dump() {
@@ -565,30 +528,16 @@
              SkGraphics::GetFontCacheCountUsed(), SkGraphics::GetFontCacheCountLimit());
 
     int counter = 0;
-    SkGlyphCache::VisitAll(dump_visitor, &counter);
-}
 
-static void sk_trace_dump_visitor(const SkGlyphCache& cache, void* context) {
-    SkTraceMemoryDump* dump = static_cast<SkTraceMemoryDump*>(context);
+    auto visitor = [&counter](const SkGlyphCache& cache) {
+        const SkScalerContextRec& rec = cache.getScalerContext()->getRec();
 
-    const SkTypeface* face = cache.getScalerContext()->getTypeface();
-    const SkScalerContextRec& rec = cache.getScalerContext()->getRec();
+        SkDebugf("index %d\n", counter);
+        SkDebugf("%s", rec.dump().c_str());
+        counter += 1;
+    };
 
-    SkString fontName;
-    face->getFamilyName(&fontName);
-    // Replace all special characters with '_'.
-    for (size_t index = 0; index < fontName.size(); ++index) {
-        if (!std::isalnum(fontName[index])) {
-            fontName[index] = '_';
-        }
-    }
-
-    SkString dumpName = SkStringPrintf("%s/%s_%d/%p",
-                                       gGlyphCacheDumpName, fontName.c_str(), rec.fFontID, &cache);
-
-    dump->dumpNumericValue(dumpName.c_str(), "size", "bytes", cache.getMemoryUsed());
-    dump->dumpNumericValue(dumpName.c_str(), "glyph_count", "objects", cache.countCachedGlyphs());
-    dump->setMemoryBacking(dumpName.c_str(), "malloc", nullptr);
+    ForEachStrike(visitor);
 }
 
 void SkGlyphCache::DumpMemoryStatistics(SkTraceMemoryDump* dump) {
@@ -605,23 +554,53 @@
         return;
     }
 
-    SkGlyphCache::VisitAll(sk_trace_dump_visitor, dump);
-}
+    auto visitor = [&dump](const SkGlyphCache& cache) {
+        const SkTypeface* face = cache.getScalerContext()->getTypeface();
+        const SkScalerContextRec& rec = cache.getScalerContext()->getRec();
 
-void SkGlyphCache::VisitAll(Visitor visitor, void* context) {
-    SkGlyphCache_Globals& globals = get_globals();
-    SkAutoExclusive ac(globals.fLock);
-    SkGlyphCache*         cache;
+        SkString fontName;
+        face->getFamilyName(&fontName);
+        // Replace all special characters with '_'.
+        for (size_t index = 0; index < fontName.size(); ++index) {
+            if (!std::isalnum(fontName[index])) {
+                fontName[index] = '_';
+            }
+        }
 
-    globals.validate();
+        SkString dumpName = SkStringPrintf(
+            "%s/%s_%d/%p", gGlyphCacheDumpName, fontName.c_str(), rec.fFontID, &cache);
 
-    for (cache = globals.internalGetHead(); cache != nullptr; cache = cache->fNext) {
-        visitor(*cache, context);
-    }
+        dump->dumpNumericValue(dumpName.c_str(),
+                               "size", "bytes", cache.getMemoryUsed());
+        dump->dumpNumericValue(dumpName.c_str(),
+                               "glyph_count", "objects", cache.countCachedGlyphs());
+        dump->setMemoryBacking(dumpName.c_str(), "malloc", nullptr);
+    };
+
+    ForEachStrike(visitor);
 }
 
 ///////////////////////////////////////////////////////////////////////////////
 
+SkGlyphCache_Globals::~SkGlyphCache_Globals() {
+    SkGlyphCache* cache = fHead;
+    while (cache) {
+        SkGlyphCache* next = cache->fNext;
+        delete cache;
+        cache = next;
+    }
+}
+
+void SkGlyphCache_Globals::AttachCache(SkGlyphCache* cache) {
+    if (cache == nullptr) {
+        return;
+    }
+    SkASSERT(cache->fNext == nullptr);
+
+    get_globals().attachCacheToHead(cache);
+}
+
+
 void SkGlyphCache_Globals::attachCacheToHead(SkGlyphCache* cache) {
     SkAutoExclusive ac(fLock);
 
@@ -806,6 +785,15 @@
 size_t SkGraphics::GetTLSFontCacheLimit() { return 0; }
 void SkGraphics::SetTLSFontCacheLimit(size_t bytes) { }
 
+SkGlyphCache* SkGlyphCache::DetachCache(
+    SkTypeface* typeface, const SkScalerContextEffects& effects, const SkDescriptor* desc)
+{
+
+    auto cache = FindOrCreateStrikeExclusive(
+        *desc, effects, *SkTypeface::NormalizeTypeface(typeface));
+    return cache.release();
+}
+
 SkGlyphCache* SkGlyphCache::DetachCacheUsingPaint(const SkPaint& paint,
                                                   const SkSurfaceProps* surfaceProps,
                                                   SkScalerContextFlags scalerContextFlags,
diff --git a/src/core/SkGlyphCache.h b/src/core/SkGlyphCache.h
index 052e072..8c335af 100644
--- a/src/core/SkGlyphCache.h
+++ b/src/core/SkGlyphCache.h
@@ -8,19 +8,21 @@
 #define SkGlyphCache_DEFINED
 
 #include "SkArenaAlloc.h"
-#include "SkBitmap.h"
 #include "SkDescriptor.h"
 #include "SkGlyph.h"
+#include "SkGlyphCache_Globals.h"
 #include "SkPaint.h"
 #include "SkTHash.h"
 #include "SkScalerContext.h"
 #include "SkTemplates.h"
-#include "SkTDArray.h"
 #include <memory>
 
 class SkTraceMemoryDump;
 
-class SkGlyphCache_Globals;
+class SkGlyphCache;
+using SkExclusiveStrikePtr = std::unique_ptr<
+    SkGlyphCache,
+    SkFunctionWrapper<void, SkGlyphCache, SkGlyphCache_Globals::AttachCache>>;
 
 /** \class SkGlyphCache
 
@@ -30,10 +32,19 @@
     it and then adding it to the strike.
 
     The strikes are held in a global list, available to all threads. To interact with one, call
-    either VisitCache() or DetachCache().
+    either Find*() or (Deprecated)DetachCache().
+
+    The Find*Exclusive() method returns SkExclusiveStrikePtr, which releases exclusive ownership
+    when they go out of scope.
 */
 class SkGlyphCache {
 public:
+    /** Return true if glyph is cached. */
+    bool isGlyphCached(SkGlyphID glyphID, SkFixed x, SkFixed y) const;
+
+    /**  Return a glyph that has no information if it is not already filled out. */
+    SkGlyph* getRawGlyphByID(SkPackedGlyphID);
+
     /** Returns a glyph with valid fAdvance and fDevKern fields. The remaining fields may be
         valid, but that is not guaranteed. If you require those, call getUnicharMetrics or
         getGlyphIDMetrics instead.
@@ -112,19 +123,49 @@
 
     SkScalerContext* getScalerContext() const { return fScalerContext.get(); }
 
-    /** Find a matching cache entry, and call proc() with it. If none is found create a new one.
-        If the proc() returns true, detach the cache and return it, otherwise leave it and return
-        nullptr.
-    */
-    static SkGlyphCache* VisitCache(SkTypeface*, const SkScalerContextEffects&, const SkDescriptor*,
-                                    bool (*proc)(const SkGlyphCache*, void*),
-                                    void* context);
 
-    /** Given a strike that was returned by either VisitCache() or DetachCache() add it back into
+    /** Given a strike that was returned by DetachCache() add it back into
         the global cache list (after which the caller should not reference it anymore.
+        DEPRECATED - Use Find* and rely on RAII.
     */
     static void AttachCache(SkGlyphCache*);
-    using AttachCacheFunctor = SkFunctionWrapper<void, SkGlyphCache, AttachCache>;
+
+    static SkExclusiveStrikePtr FindStrikeExclusive(const SkDescriptor& desc);
+
+    template <typename ScalerContextCreator>
+    static SkExclusiveStrikePtr FindOrCreateStrikeExclusive(
+        const SkDescriptor& desc, ScalerContextCreator&& creator)
+    {
+        auto cache = FindStrikeExclusive(desc);
+        if (cache == nullptr) {
+            cache = CreateStrikeExclusive(desc, creator);
+        }
+        return cache;
+    }
+
+    static SkExclusiveStrikePtr FindOrCreateStrikeExclusive(
+        const SkDescriptor& desc,
+        const SkScalerContextEffects& effects,
+        const SkTypeface& typeface);
+
+    template <typename ScalerContextCreator>
+    static SkExclusiveStrikePtr CreateStrikeExclusive(
+        const SkDescriptor& desc, ScalerContextCreator creator)
+    {
+        // Check if we can create a scaler-context before creating the glyphcache.
+        // If not, we may have exhausted OS/font resources, so try purging the
+        // cache once and try again
+        // pass true the first time, to notice if the scalercontext failed,
+        // so we can try the purge.
+        auto context = creator(desc, true/* can fail */);
+        if (!context) {
+            PurgeAll();
+            context = creator(desc, false/* must succeed */);
+            SkASSERT(context);
+        }
+
+        return SkExclusiveStrikePtr(new SkGlyphCache(desc, std::move(context)));
+    }
 
     /** Detach a strike from the global cache matching the specified descriptor. Once detached,
         it can be queried/modified by the current thread, and when finished, be reattached to the
@@ -132,11 +173,10 @@
         descriptor, a different strike will be generated. This is fine. It does mean we can have
         more than 1 strike for the same descriptor, but that will eventually get purged, and the
         win is that different thread will never block each other while a strike is being used.
+        DEPRECATED
     */
-    static SkGlyphCache* DetachCache(SkTypeface* typeface, const SkScalerContextEffects& effects,
-                                     const SkDescriptor* desc) {
-        return VisitCache(typeface, effects, desc, DetachProc, nullptr);
-    }
+    static SkGlyphCache* DetachCache(
+        SkTypeface* typeface, const SkScalerContextEffects& effects, const SkDescriptor* desc);
 
     static SkGlyphCache* DetachCacheUsingPaint(const SkPaint& paint,
                                                const SkSurfaceProps* surfaceProps,
@@ -150,8 +190,7 @@
     */
     static void DumpMemoryStatistics(SkTraceMemoryDump* dump);
 
-    typedef void (*Visitor)(const SkGlyphCache&, void* context);
-    static void VisitAll(Visitor, void* context);
+    static void ForEachStrike(std::function<void(const SkGlyphCache&)> visitor);
 
 #ifdef SK_DEBUG
     void validate() const;
@@ -182,14 +221,15 @@
     friend class SkGlyphCache_Globals;
 
     enum MetricsType {
+        kNothing_MetricsType,
         kJustAdvance_MetricsType,
         kFull_MetricsType
     };
 
     enum {
-        kHashBits           = 8,
-        kHashCount          = 1 << kHashBits,
-        kHashMask           = kHashCount - 1
+        kHashBits  = 8,
+        kHashCount = 1 << kHashBits,
+        kHashMask  = kHashCount - 1
     };
 
     struct CharGlyphRec {
@@ -197,9 +237,12 @@
         SkPackedGlyphID fPackedGlyphID;
     };
 
-    SkGlyphCache(const SkDescriptor*, std::unique_ptr<SkScalerContext>);
+    SkGlyphCache(const SkDescriptor& desc, std::unique_ptr<SkScalerContext> scaler);
     ~SkGlyphCache();
 
+    // Purge all the things.
+    static void PurgeAll();
+
     // Return the SkGlyph* associated with MakeID. The id parameter is the
     // combined glyph/x/y id generated by MakeID. If it is just a glyph id
     // then x and y are assumed to be zero.
@@ -212,8 +255,6 @@
     // of work using type.
     SkGlyph* allocateNewGlyph(SkPackedGlyphID packedGlyphID, MetricsType type);
 
-    static bool DetachProc(const SkGlyphCache*, void*) { return true; }
-
     // The id arg is a combined id generated by MakeID.
     CharGlyphRec* getCharGlyphRec(SkPackedUnicharID id);
 
@@ -231,8 +272,8 @@
     static const SkGlyph::Intercept* MatchBounds(const SkGlyph* glyph,
                                                  const SkScalar bounds[2]);
 
-    SkGlyphCache*          fNext;
-    SkGlyphCache*          fPrev;
+    SkGlyphCache*          fNext{nullptr};
+    SkGlyphCache*          fPrev{nullptr};
     const std::unique_ptr<SkDescriptor> fDesc;
     const std::unique_ptr<SkScalerContext> fScalerContext;
     SkPaint::FontMetrics   fFontMetrics;
@@ -253,7 +294,7 @@
     size_t                  fMemoryUsed;
 };
 
-class SkAutoGlyphCache : public std::unique_ptr<SkGlyphCache, SkGlyphCache::AttachCacheFunctor> {
+class SkAutoGlyphCache : public SkExclusiveStrikePtr {
 public:
     /** deprecated: use get() */
     SkGlyphCache* getCache() const { return this->get(); }
@@ -280,7 +321,7 @@
             SkGlyphCache::DetachCacheUsingPaint(paint, surfaceProps, scalerContextFlags, matrix))
     {}
 private:
-    using INHERITED = std::unique_ptr<SkGlyphCache, SkGlyphCache::AttachCacheFunctor>;
+    using INHERITED = SkExclusiveStrikePtr;
 };
 
 class SkAutoGlyphCacheNoGamma : public SkAutoGlyphCache {
diff --git a/src/core/SkGlyphCache_Globals.h b/src/core/SkGlyphCache_Globals.h
index a1e0ac0..de37bbb 100644
--- a/src/core/SkGlyphCache_Globals.h
+++ b/src/core/SkGlyphCache_Globals.h
@@ -8,10 +8,10 @@
 #ifndef SkGlyphCache_Globals_DEFINED
 #define SkGlyphCache_Globals_DEFINED
 
-#include "SkGlyphCache.h"
 #include "SkMutex.h"
 #include "SkSpinlock.h"
-#include "SkTLS.h"
+
+class SkGlyphCache;
 
 #ifndef SK_DEFAULT_FONT_CACHE_COUNT_LIMIT
     #define SK_DEFAULT_FONT_CACHE_COUNT_LIMIT   2048
@@ -38,14 +38,9 @@
         fPointSizeLimit = SK_DEFAULT_FONT_CACHE_POINT_SIZE_LIMIT;
     }
 
-    ~SkGlyphCache_Globals() {
-        SkGlyphCache* cache = fHead;
-        while (cache) {
-            SkGlyphCache* next = cache->fNext;
-            delete cache;
-            cache = next;
-        }
-    }
+    ~SkGlyphCache_Globals();
+
+    static void AttachCache(SkGlyphCache* cache);
 
     mutable SkSpinlock     fLock;
 
diff --git a/src/core/SkPaint.cpp b/src/core/SkPaint.cpp
index f754c02..76f50ce 100644
--- a/src/core/SkPaint.cpp
+++ b/src/core/SkPaint.cpp
@@ -905,11 +905,6 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
-static bool FontMetricsCacheProc(const SkGlyphCache* cache, void* context) {
-    *(SkPaint::FontMetrics*)context = cache->getFontMetrics();
-    return false;   // don't detach the cache
-}
-
 SkScalar SkPaint::getFontMetrics(FontMetrics* metrics, SkScalar zoom) const {
     SkCanonicalizePaint canon(*this);
     const SkPaint& paint = canon.getPaint();
@@ -932,7 +927,11 @@
     auto desc = SkScalerContext::CreateDescriptorAndEffectsUsingPaint(
         paint, nullptr, SkScalerContextFlags::kNone, zoomPtr, &ad, &effects);
 
-    SkGlyphCache::VisitCache(paint.getTypeface(), effects, desc, FontMetricsCacheProc, metrics);
+    {
+        auto typeface = SkTypeface::NormalizeTypeface(paint.getTypeface());
+        auto cache = SkGlyphCache::FindOrCreateStrikeExclusive(*desc, effects, *typeface);
+        *metrics = cache->getFontMetrics();
+    }
 
     if (scale) {
         SkPaintPriv::ScaleFontMetrics(metrics, scale);