SkRemote: add xfermodes

Note this changes the default ID for each type from a valid non-null value to a nullptr.
All the ()++ are now ++(), so we always work with non-null IDs when we define things.

Some of this is prematurely generalized with an eye for supporting other effects and ref-counted things.

https://gold.skia.org/search2?issue=1412223002&unt=true&query=source_type%3Dgm&master=false&include=true

BUG=skia:

Review URL: https://codereview.chromium.org/1412223002
diff --git a/src/core/SkRemote.cpp b/src/core/SkRemote.cpp
index 3966aac..8efeb8b 100644
--- a/src/core/SkRemote.cpp
+++ b/src/core/SkRemote.cpp
@@ -97,15 +97,16 @@
     Cache* Cache::CreateNeverCache() {
         struct NeverCache final : public Cache {
             NeverCache()
-                : fNextMatrix(Type::kMatrix)
-                , fNextMisc  (Type::kMisc)
-                , fNextPath  (Type::kPath)
-                , fNextStroke(Type::kStroke)
+                : fNextMatrix  (Type::kMatrix)
+                , fNextMisc    (Type::kMisc)
+                , fNextPath    (Type::kPath)
+                , fNextStroke  (Type::kStroke)
+                , fNextXfermode(Type::kXfermode)
             {}
             void cleanup(Encoder*) override {}
 
             static bool Helper(ID* next, ID* id, LookupScope* ls) {
-                *id = (*next)++;
+                *id = ++(*next);
                 ls->undefineWhenDone(*id);
                 return false;
             }
@@ -122,65 +123,120 @@
             bool lookup(const Stroke&, ID* id, LookupScope* ls) override {
                 return Helper(&fNextStroke, id, ls);
             }
+            bool lookup(const SkXfermode* xfermode, ID* id, LookupScope* ls) override {
+                if (!xfermode) {
+                    *id = ID(Type::kXfermode);
+                    return true;  // Null IDs are always defined.
+                }
+                return Helper(&fNextXfermode, id, ls);
+            }
 
             ID fNextMatrix,
                fNextMisc,
                fNextPath,
-               fNextStroke;
+               fNextStroke,
+               fNextXfermode;
         };
         return new NeverCache;
     }
 
-    // Can't be declared locally inside AlwaysCache because of the templating.  :(
-    template <typename T, typename Map>
-    static bool always_cache_helper(const T& val, Map* map, ID* next, ID* id) {
-        if (ID* found = map->find(val)) {
-            *id = *found;
-            return true;
+    // These can't be declared locally inside AlwaysCache because of the templating.  :(
+    namespace {
+        template <typename T, typename Map>
+        static bool always_cache_lookup(const T& val, Map* map, ID* next, ID* id) {
+            if (const ID* found = map->find(val)) {
+                *id = *found;
+                return true;
+            }
+            *id = ++(*next);
+            map->set(val, *id);
+            return false;
         }
-        *id = (*next)++;
-        map->set(val, *id);
-        return false;
-    }
+
+        struct Undefiner {
+            Encoder* fEncoder;
+
+            template <typename T>
+            void operator()(const T&, ID* id) const { fEncoder->undefine(*id); }
+        };
+
+        // Maps const T* -> ID, and refs the key.  nullptr always maps to ID(kType).
+        template <typename T, Type kType>
+        class RefKeyMap {
+        public:
+            RefKeyMap() {}
+            ~RefKeyMap() { fMap.foreach([](const T* key, ID*) { key->unref(); }); }
+
+            void set(const T* key, const ID& id) {
+                SkASSERT(key && id.type() == kType);
+                fMap.set(SkRef(key), id);
+            }
+
+            void remove(const T* key) {
+                SkASSERT(key);
+                fMap.remove(key);
+                key->unref();
+            }
+
+            const ID* find(const T* key) const {
+                static const ID nullID(kType);
+                return key ? fMap.find(key) : &nullID;
+            }
+
+            template <typename Fn>
+            void foreach(const Fn& fn) { fMap.foreach(fn); }
+        private:
+            SkTHashMap<const T*, ID> fMap;
+        };
+    } // namespace
 
     Cache* Cache::CreateAlwaysCache() {
         struct AlwaysCache final : public Cache {
             AlwaysCache()
-                : fNextMatrix(Type::kMatrix)
-                , fNextMisc  (Type::kMisc)
-                , fNextPath  (Type::kPath)
-                , fNextStroke(Type::kStroke)
+                : fNextMatrix  (Type::kMatrix)
+                , fNextMisc    (Type::kMisc)
+                , fNextPath    (Type::kPath)
+                , fNextStroke  (Type::kStroke)
+                , fNextXfermode(Type::kXfermode)
             {}
 
             void cleanup(Encoder* encoder) override {
-                fMatrix.foreach([=](const SkMatrix&, ID* id) { encoder->undefine(*id); });
-                fMisc  .foreach([=](const Misc&,     ID* id) { encoder->undefine(*id); });
-                fPath  .foreach([=](const SkPath&,   ID* id) { encoder->undefine(*id); });
-                fStroke.foreach([=](const Stroke&,   ID* id) { encoder->undefine(*id); });
+                Undefiner undef{encoder};
+                fMatrix  .foreach(undef);
+                fMisc    .foreach(undef);
+                fPath    .foreach(undef);
+                fStroke  .foreach(undef);
+                fXfermode.foreach(undef);
             }
 
 
             bool lookup(const SkMatrix& matrix, ID* id, LookupScope*) override {
-                return always_cache_helper(matrix, &fMatrix, &fNextMatrix, id);
+                return always_cache_lookup(matrix, &fMatrix, &fNextMatrix, id);
             }
             bool lookup(const Misc& misc, ID* id, LookupScope*) override {
-                return always_cache_helper(misc, &fMisc, &fNextMisc, id);
+                return always_cache_lookup(misc, &fMisc, &fNextMisc, id);
             }
             bool lookup(const SkPath& path, ID* id, LookupScope*) override {
-                return always_cache_helper(path, &fPath, &fNextPath, id);
+                return always_cache_lookup(path, &fPath, &fNextPath, id);
             }
             bool lookup(const Stroke& stroke, ID* id, LookupScope*) override {
-                return always_cache_helper(stroke, &fStroke, &fNextStroke, id);
+                return always_cache_lookup(stroke, &fStroke, &fNextStroke, id);
+            }
+            bool lookup(const SkXfermode* xfermode, ID* id, LookupScope*) override {
+                return always_cache_lookup(xfermode, &fXfermode, &fNextXfermode, id);
             }
 
-            SkTHashMap<SkMatrix, ID>           fMatrix;
-            SkTHashMap<Misc,     ID, MiscHash> fMisc;
-            SkTHashMap<SkPath,   ID>           fPath;
-            SkTHashMap<Stroke,   ID>           fStroke;
+            SkTHashMap<SkMatrix, ID>               fMatrix;
+            SkTHashMap<Misc,     ID, MiscHash>     fMisc;
+            SkTHashMap<SkPath,   ID>               fPath;
+            SkTHashMap<Stroke,   ID>               fStroke;
+            RefKeyMap<SkXfermode, Type::kXfermode> fXfermode;
+
             ID fNextMatrix,
                fNextMisc,
                fNextPath,
-               fNextStroke;
+               fNextStroke,
+               fNextXfermode;
         };
         return new AlwaysCache;
     }
@@ -236,13 +292,14 @@
     void Client::onDrawPath(const SkPath& path, const SkPaint& paint) {
         LookupScope ls(fCache, fEncoder);
         ID p = ls.lookup(path),
-           m = ls.lookup(Misc::CreateFrom(paint));
+           m = ls.lookup(Misc::CreateFrom(paint)),
+           x = ls.lookup(paint.getXfermode());
 
         if (paint.getStyle() == SkPaint::kFill_Style) {
-            fEncoder->fillPath(p, m);
+            fEncoder->fillPath(p, m, x);
         } else {
             // TODO: handle kStrokeAndFill_Style
-            fEncoder->strokePath(p, m, ls.lookup(Stroke::CreateFrom(paint)));
+            fEncoder->strokePath(p, m, x, ls.lookup(Stroke::CreateFrom(paint)));
         }
     }
 
@@ -302,17 +359,19 @@
 
     Server::Server(SkCanvas* canvas) : fCanvas(canvas) {}
 
-    void Server::define(ID id, const SkMatrix& v) { fMatrix.set(id, v); }
-    void Server::define(ID id, const Misc&     v) { fMisc  .set(id, v); }
-    void Server::define(ID id, const SkPath&   v) { fPath  .set(id, v); }
-    void Server::define(ID id, const Stroke&   v) { fStroke.set(id, v); }
+    void Server::define(ID id, const SkMatrix& v) { fMatrix  .set(id, v); }
+    void Server::define(ID id, const Misc&     v) { fMisc    .set(id, v); }
+    void Server::define(ID id, const SkPath&   v) { fPath    .set(id, v); }
+    void Server::define(ID id, const Stroke&   v) { fStroke  .set(id, v); }
+    void Server::define(ID id, SkXfermode*     v) { fXfermode.set(id, v); }
 
     void Server::undefine(ID id) {
         switch(id.type()) {
-            case Type::kMatrix: return fMatrix.remove(id);
-            case Type::kMisc:   return fMisc  .remove(id);
-            case Type::kPath:   return fPath  .remove(id);
-            case Type::kStroke: return fStroke.remove(id);
+            case Type::kMatrix:   return fMatrix  .remove(id);
+            case Type::kMisc:     return fMisc    .remove(id);
+            case Type::kPath:     return fPath    .remove(id);
+            case Type::kStroke:   return fStroke  .remove(id);
+            case Type::kXfermode: return fXfermode.remove(id);
 
             case Type::kNone: SkASSERT(false);
         };
@@ -326,17 +385,19 @@
     void Server::clipPath(ID path, SkRegion::Op op, bool aa) {
         fCanvas->clipPath(fPath.find(path), op, aa);
     }
-    void Server::fillPath(ID path, ID misc) {
+    void Server::fillPath(ID path, ID misc, ID xfermode) {
         SkPaint paint;
         paint.setStyle(SkPaint::kFill_Style);
         fMisc.find(misc).applyTo(&paint);
+        paint.setXfermode(fXfermode.find(xfermode));
         fCanvas->drawPath(fPath.find(path), paint);
     }
-    void Server::strokePath(ID path, ID misc, ID stroke) {
+    void Server::strokePath(ID path, ID misc, ID xfermode, ID stroke) {
         SkPaint paint;
         paint.setStyle(SkPaint::kStroke_Style);
         fMisc  .find(misc  ).applyTo(&paint);
         fStroke.find(stroke).applyTo(&paint);
+        paint.setXfermode(fXfermode.find(xfermode));
         fCanvas->drawPath(fPath.find(path), paint);
     }
 
diff --git a/src/core/SkRemote.h b/src/core/SkRemote.h
index 72914d4..ed99c0f 100644
--- a/src/core/SkRemote.h
+++ b/src/core/SkRemote.h
@@ -45,6 +45,7 @@
         virtual void define(ID, const Misc&)     = 0;
         virtual void define(ID, const SkPath&)   = 0;
         virtual void define(ID, const Stroke&)   = 0;
+        virtual void define(ID, SkXfermode*)     = 0;
 
         virtual void undefine(ID) = 0;
 
@@ -53,24 +54,37 @@
 
         virtual void setMatrix(ID matrix) = 0;
 
-        virtual void   clipPath(ID path, SkRegion::Op, bool aa) = 0;
-        virtual void   fillPath(ID path, ID misc)               = 0;
-        virtual void strokePath(ID path, ID misc, ID stroke)    = 0;
+        // TODO: struct CommonIDs { ID misc; ID xfermode; ... }
+        // for IDs that affect both fill + stroke?
+
+        virtual void   clipPath(ID path, SkRegion::Op, bool aa)           = 0;
+        virtual void   fillPath(ID path, ID misc, ID xfermode)            = 0;
+        virtual void strokePath(ID path, ID misc, ID xfermode, ID stroke) = 0;
     };
 
     class LookupScope;
 
-    // TODO: document
+    // The Cache interface encapsulates the caching logic of the Client.
+    //
+    // Each lookup() method must always fill ID* with a valid value,
+    // but ID may be cached.  If so, the lookup() method returns true;
+    // if not the lookup() method returns false and the Client must
+    // then define() this ID -> Thing mapping before using the ID.
+    //
+    // The Caches may also add IDs to the LookupScope's list of IDs to
+    // undefine() on destruction.  This lets the Cache purge IDs.
     struct Cache {
         virtual ~Cache() {}
 
-        static Cache* CreateNeverCache();
-        static Cache* CreateAlwaysCache();
+        static Cache* CreateNeverCache();   // Never caches anything.
+        static Cache* CreateAlwaysCache();  // Caches by value (not deep pointer equality).
+        // TODO: static Cache* CreateDeepCache();  // Caches by deep value.
 
-        virtual bool lookup(const SkMatrix&, ID*, LookupScope*) = 0;
-        virtual bool lookup(const Misc&,     ID*, LookupScope*) = 0;
-        virtual bool lookup(const SkPath&,   ID*, LookupScope*) = 0;
-        virtual bool lookup(const Stroke&,   ID*, LookupScope*) = 0;
+        virtual bool lookup(const SkMatrix&,   ID*, LookupScope*) = 0;
+        virtual bool lookup(const Misc&,       ID*, LookupScope*) = 0;
+        virtual bool lookup(const SkPath&,     ID*, LookupScope*) = 0;
+        virtual bool lookup(const Stroke&,     ID*, LookupScope*) = 0;
+        virtual bool lookup(const SkXfermode*, ID*, LookupScope*) = 0;
 
         virtual void cleanup(Encoder*) = 0;
     };
@@ -120,6 +134,7 @@
         void define(ID, const Misc&)     override;
         void define(ID, const SkPath&)   override;
         void define(ID, const Stroke&)   override;
+        void define(ID, SkXfermode*)     override;
 
         void undefine(ID) override;
 
@@ -128,13 +143,19 @@
 
         void setMatrix(ID matrix) override;
 
-        void   clipPath(ID path, SkRegion::Op, bool aa) override;
-        void   fillPath(ID path, ID misc) override;
-        void strokePath(ID path, ID misc, ID stroke) override;
+        void   clipPath(ID path, SkRegion::Op, bool aa)           override;
+        void   fillPath(ID path, ID misc, ID xfermode)            override;
+        void strokePath(ID path, ID misc, ID xfermode, ID stroke) override;
 
+        // Maps ID -> T.
         template <typename T, Type kType>
         class IDMap {
         public:
+            ~IDMap() {
+                // A well-behaved client always cleans up its definitions.
+                SkASSERT(fMap.count() == 0);
+            }
+
             void set(const ID& id, const T& val) {
                 SkASSERT(id.type() == kType);
                 fMap.set(id, val);
@@ -156,10 +177,48 @@
             SkTHashMap<ID, T> fMap;
         };
 
-        IDMap<SkMatrix, Type::kMatrix> fMatrix;
-        IDMap<Misc    , Type::kMisc  > fMisc;
-        IDMap<SkPath  , Type::kPath  > fPath;
-        IDMap<Stroke  , Type::kStroke> fStroke;
+        // Maps ID -> T*, and keeps the T alive by reffing it.
+        template <typename T, Type kType>
+        class ReffedIDMap {
+        public:
+            ReffedIDMap() {
+                // A null ID always maps to nullptr.
+                fMap.set(ID(kType), nullptr);
+            }
+            ~ReffedIDMap() {
+                // A well-behaved client always cleans up its definitions.
+                SkASSERT(fMap.count() == 1);
+            }
+
+            void set(const ID& id, T* val) {
+                SkASSERT(id.type() == kType && val);
+                fMap.set(id, SkRef(val));
+            }
+
+            void remove(const ID& id) {
+                SkASSERT(id.type() == kType);
+                T** val = fMap.find(id);
+                SkASSERT(val && *val);
+                (*val)->unref();
+                fMap.remove(id);
+            }
+
+            T* find(const ID& id) const {
+                SkASSERT(id.type() == kType);
+                T** val = fMap.find(id);
+                SkASSERT(val);
+                return *val;
+            }
+
+        private:
+            SkTHashMap<ID, T*> fMap;
+        };
+
+        IDMap<SkMatrix, Type::kMatrix>           fMatrix;
+        IDMap<Misc    , Type::kMisc  >           fMisc;
+        IDMap<SkPath  , Type::kPath  >           fPath;
+        IDMap<Stroke  , Type::kStroke>           fStroke;
+        ReffedIDMap<SkXfermode, Type::kXfermode> fXfermode;
 
         SkCanvas* fCanvas;
     };
diff --git a/src/core/SkRemote_protocol.h b/src/core/SkRemote_protocol.h
index 9975213..23fa60c 100644
--- a/src/core/SkRemote_protocol.h
+++ b/src/core/SkRemote_protocol.h
@@ -21,6 +21,7 @@
         kMisc,
         kPath,
         kStroke,
+        kXfermode,
     };
 
     class ID {
@@ -35,11 +36,10 @@
         uint64_t val() const { return fVal & ~((uint64_t)0xFF << 56); }
 
         bool operator==(ID o) const { return fVal == o.fVal; }
-        ID operator++(int) {
-            ID prev = *this;
-            fVal++;
+        ID operator++() {
+            ++fVal;
             SkASSERT(this->val() != 0);  // Overflow is particularly bad as it'd change our Type.
-            return prev;
+            return *this;
         }
 
     private: