SkRemote

BUG=skia:

Review URL: https://codereview.chromium.org/1391023005
diff --git a/dm/DM.cpp b/dm/DM.cpp
index ccff84c..5d98fe7 100644
--- a/dm/DM.cpp
+++ b/dm/DM.cpp
@@ -611,6 +611,8 @@
     VIA("sp",        ViaSingletonPictures, wrapped);
     VIA("tiles",     ViaTiles, 256, 256, nullptr,            wrapped);
     VIA("tiles_rt",  ViaTiles, 256, 256, new SkRTreeFactory, wrapped);
+    VIA("remote",       ViaRemote, false, wrapped);
+    VIA("remote_cache", ViaRemote, true,  wrapped);
 
     if (FLAGS_matrix.count() == 4) {
         SkMatrix m;
diff --git a/dm/DMSrcSink.cpp b/dm/DMSrcSink.cpp
index 4137154..394b84f 100644
--- a/dm/DMSrcSink.cpp
+++ b/dm/DMSrcSink.cpp
@@ -23,6 +23,7 @@
 #include "SkRandom.h"
 #include "SkRecordDraw.h"
 #include "SkRecorder.h"
+#include "SkRemote.h"
 #include "SkSVGCanvas.h"
 #include "SkScaledCodec.h"
 #include "SkStream.h"
@@ -1016,6 +1017,16 @@
     });
 }
 
+Error ViaRemote::draw(const Src& src, SkBitmap* bitmap, SkWStream* stream, SkString* log) const {
+    return draw_to_canvas(fSink, bitmap, stream, log, src.size(), [&](SkCanvas* canvas) {
+        SkAutoTDelete<SkRemote::Cache> cache(fCache ? SkRemote::Cache::CreateAlwaysCache()
+                                                    : SkRemote::Cache::CreateNeverCache());
+        SkRemote::Server server(canvas);
+        SkRemote::Client client(cache, &server);
+        return src.draw(&client);
+    });
+}
+
 /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
 
 Error ViaSerialization::draw(
diff --git a/dm/DMSrcSink.h b/dm/DMSrcSink.h
index d881137..1beaec9 100644
--- a/dm/DMSrcSink.h
+++ b/dm/DMSrcSink.h
@@ -304,10 +304,12 @@
     Error draw(const Src&, SkBitmap*, SkWStream*, SkString*) const override;
 };
 
-class ViaDeferred : public Via {
+class ViaRemote : public Via {
 public:
-    explicit ViaDeferred(Sink* sink) : Via(sink) {}
+    ViaRemote(bool cache, Sink* sink) : Via(sink), fCache(cache) {}
     Error draw(const Src&, SkBitmap*, SkWStream*, SkString*) const override;
+private:
+    bool fCache;
 };
 
 class ViaSerialization : public Via {
diff --git a/gyp/core.gypi b/gyp/core.gypi
index dd00301..089a8c0 100644
--- a/gyp/core.gypi
+++ b/gyp/core.gypi
@@ -221,6 +221,7 @@
         '<(skia_src_path)/core/SkRegion.cpp',
         '<(skia_src_path)/core/SkRegionPriv.h',
         '<(skia_src_path)/core/SkRegion_path.cpp',
+        '<(skia_src_path)/core/SkRemote.cpp',
         '<(skia_src_path)/core/SkResourceCache.cpp',
         '<(skia_src_path)/core/SkRRect.cpp',
         '<(skia_src_path)/core/SkRTree.h',
diff --git a/src/core/SkRemote.cpp b/src/core/SkRemote.cpp
new file mode 100644
index 0000000..ae14a47
--- /dev/null
+++ b/src/core/SkRemote.cpp
@@ -0,0 +1,269 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkPath.h"
+#include "SkRect.h"
+#include "SkRemote.h"
+
+namespace SkRemote {
+
+    Misc Misc::CreateFrom(const SkPaint& paint) {
+        Misc misc = {
+            paint.getColor(),
+            paint.getFilterQuality(),
+            paint.isAntiAlias(),
+            paint.isDither(),
+        };
+        return misc;
+    }
+
+    void Misc::applyTo(SkPaint* paint) const {
+        paint->setColor        (fColor);
+        paint->setFilterQuality(fFilterQuality);
+        paint->setAntiAlias    (fAntiAlias);
+        paint->setDither       (fDither);
+    }
+
+    static bool operator==(const Misc& a, const Misc& b) {
+        return a.fColor         == b.fColor
+            && a.fFilterQuality == b.fFilterQuality
+            && a.fAntiAlias     == b.fAntiAlias
+            && a.fDither        == b.fDither;
+    }
+
+    Stroke Stroke::CreateFrom(const SkPaint& paint) {
+        Stroke stroke = {
+            paint.getStrokeWidth(),
+            paint.getStrokeMiter(),
+            paint.getStrokeCap(),
+            paint.getStrokeJoin(),
+        };
+        return stroke;
+    }
+
+    void Stroke::applyTo(SkPaint* paint) const {
+        paint->setStrokeWidth(fWidth);
+        paint->setStrokeMiter(fMiter);
+        paint->setStrokeCap  (fCap);
+        paint->setStrokeJoin (fJoin);
+    }
+
+    static bool operator==(const Stroke& a, const Stroke& b) {
+        return a.fWidth == b.fWidth
+            && a.fMiter == b.fMiter
+            && a.fCap   == b.fCap
+            && a.fJoin  == b.fJoin;
+    }
+
+    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
+
+    Cache* Cache::CreateNeverCache() {
+        struct NeverCache final : public Cache {
+            NeverCache()
+                : fNextMatrix(Type::kMatrix)
+                , fNextMisc  (Type::kMisc)
+                , fNextPath  (Type::kPath)
+                , fNextStroke(Type::kStroke)
+            {}
+            void cleanup(Encoder*) override {}
+
+            static bool Helper(ID* next, ID* id, LookupScope* ls) {
+                *id = (*next)++;
+                ls->undefineWhenDone(*id);
+                return false;
+            }
+
+            bool lookup(const SkMatrix&, ID* id, LookupScope* ls) override {
+                return Helper(&fNextMatrix, id, ls);
+            }
+            bool lookup(const Misc&, ID* id, LookupScope* ls) override {
+                return Helper(&fNextMisc, id, ls);
+            }
+            bool lookup(const SkPath&, ID* id, LookupScope* ls) override {
+                return Helper(&fNextPath, id, ls);
+            }
+            bool lookup(const Stroke&, ID* id, LookupScope* ls) override {
+                return Helper(&fNextStroke, id, ls);
+            }
+
+            ID fNextMatrix,
+               fNextMisc,
+               fNextPath,
+               fNextStroke;
+        };
+        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;
+        }
+        *id = (*next)++;
+        map->set(val, *id);
+        return false;
+    }
+
+    Cache* Cache::CreateAlwaysCache() {
+        struct AlwaysCache final : public Cache {
+            AlwaysCache()
+                : fNextMatrix(Type::kMatrix)
+                , fNextMisc  (Type::kMisc)
+                , fNextPath  (Type::kPath)
+                , fNextStroke(Type::kStroke)
+            {}
+
+            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); });
+            }
+
+
+            bool lookup(const SkMatrix& matrix, ID* id, LookupScope*) override {
+                return always_cache_helper(matrix, &fMatrix, &fNextMatrix, id);
+            }
+            bool lookup(const Misc& misc, ID* id, LookupScope*) override {
+                return always_cache_helper(misc, &fMisc, &fNextMisc, id);
+            }
+            bool lookup(const SkPath& path, ID* id, LookupScope*) override {
+                return always_cache_helper(path, &fPath, &fNextPath, id);
+            }
+            bool lookup(const Stroke& stroke, ID* id, LookupScope*) override {
+                return always_cache_helper(stroke, &fStroke, &fNextStroke, id);
+            }
+
+            SkTHashMap<SkMatrix, ID> fMatrix;
+            SkTHashMap<Misc,     ID> fMisc;
+            SkTHashMap<SkPath,   ID> fPath;
+            SkTHashMap<Stroke,   ID> fStroke;
+            ID fNextMatrix,
+               fNextMisc,
+               fNextPath,
+               fNextStroke;
+        };
+        return new AlwaysCache;
+    }
+
+    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
+
+    Client::Client(Cache* cache, Encoder* encoder)
+        : SkCanvas(1,1)
+        , fCache(cache)
+        , fEncoder(encoder)
+    {}
+
+    Client::~Client() {
+        fCache->cleanup(fEncoder);
+    }
+
+    template <typename T>
+    ID LookupScope::lookup(const T& val) {
+        ID id;
+        if (!fCache->lookup(val, &id, this)) {
+            fEncoder->define(id, val);
+        }
+        return id;
+    }
+
+    void Client::willSave()   { fEncoder->save(); }
+    void Client::didRestore() { fEncoder->restore(); }
+
+    void Client::didConcat   (const SkMatrix&) { this->didSetMatrix(this->getTotalMatrix()); }
+    void Client::didSetMatrix(const SkMatrix& matrix) {
+        LookupScope ls(fCache, fEncoder);
+        fEncoder->setMatrix(ls.lookup(matrix));
+    }
+
+    void Client::onDrawOval(const SkRect& oval, const SkPaint& paint) {
+        SkPath path;
+        path.addOval(oval);
+        this->onDrawPath(path, paint);
+    }
+
+    void Client::onDrawRect(const SkRect& rect, const SkPaint& paint) {
+        SkPath path;
+        path.addRect(rect);
+        this->onDrawPath(path, paint);
+    }
+
+    void Client::onDrawPath(const SkPath& path, const SkPaint& paint) {
+        LookupScope ls(fCache, fEncoder);
+        ID p = ls.lookup(path),
+           m = ls.lookup(Misc::CreateFrom(paint));
+
+        if (paint.getStyle() == SkPaint::kFill_Style) {
+            fEncoder->fillPath(p, m);
+        } else {
+            // TODO: handle kStrokeAndFill_Style
+            fEncoder->strokePath(p, m, ls.lookup(Stroke::CreateFrom(paint)));
+        }
+    }
+
+    void Client::onClipRect(const SkRect& rect, SkRegion::Op op, ClipEdgeStyle edgeStyle) {
+        SkPath path;
+        path.addRect(rect);
+        this->onClipPath(path, op, edgeStyle);
+    }
+
+    void Client::onClipRRect(const SkRRect& rrect, SkRegion::Op op, ClipEdgeStyle edgeStyle) {
+        SkPath path;
+        path.addRRect(rrect);
+        this->onClipPath(path, op, edgeStyle);
+    }
+
+    void Client::onClipPath(const SkPath& path, SkRegion::Op op, ClipEdgeStyle edgeStyle) {
+        LookupScope ls(fCache, fEncoder);
+        fEncoder->clipPath(ls.lookup(path), op, edgeStyle == kSoft_ClipEdgeStyle);
+    }
+
+    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
+
+    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::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::kNone: SkASSERT(false);
+        };
+    }
+
+    void Server::   save() { fCanvas->save(); }
+    void Server::restore() { fCanvas->restore(); }
+
+    void Server::setMatrix(ID matrix) { fCanvas->setMatrix(fMatrix.find(matrix)); }
+
+    void Server::clipPath(ID path, SkRegion::Op op, bool aa) {
+        fCanvas->clipPath(fPath.find(path), op, aa);
+    }
+    void Server::fillPath(ID path, ID misc) {
+        SkPaint paint;
+        paint.setStyle(SkPaint::kFill_Style);
+        fMisc.find(misc).applyTo(&paint);
+        fCanvas->drawPath(fPath.find(path), paint);
+    }
+    void Server::strokePath(ID path, ID misc, ID stroke) {
+        SkPaint paint;
+        paint.setStyle(SkPaint::kStroke_Style);
+        fMisc  .find(misc  ).applyTo(&paint);
+        fStroke.find(stroke).applyTo(&paint);
+        fCanvas->drawPath(fPath.find(path), paint);
+    }
+
+} // namespace SkRemote
diff --git a/src/core/SkRemote.h b/src/core/SkRemote.h
new file mode 100644
index 0000000..87333d8
--- /dev/null
+++ b/src/core/SkRemote.h
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkRemote_DEFINED
+#define SkRemote_DEFINED
+
+#include "SkCanvas.h"
+#include "SkPaint.h"
+#include "SkRemote_protocol.h"
+#include "SkTHash.h"
+#include "SkTypes.h"
+
+// TODO: document
+
+namespace SkRemote {
+    // TODO: document
+    struct Misc {
+        SkColor         fColor;
+        SkFilterQuality fFilterQuality;
+        bool fAntiAlias, fDither;
+
+        static Misc CreateFrom(const SkPaint&);
+        void applyTo(SkPaint*) const;
+    };
+
+    // TODO: document
+    struct Stroke {
+        SkScalar fWidth, fMiter;
+        SkPaint::Cap  fCap;
+        SkPaint::Join fJoin;
+
+        static Stroke CreateFrom(const SkPaint&);
+        void applyTo(SkPaint*) const;
+    };
+
+    // TODO: document
+    struct Encoder {
+        virtual ~Encoder() {}
+
+        virtual void define(ID, const SkMatrix&) = 0;
+        virtual void define(ID, const Misc&)     = 0;
+        virtual void define(ID, const SkPath&)   = 0;
+        virtual void define(ID, const Stroke&)   = 0;
+
+        virtual void undefine(ID) = 0;
+
+        virtual void    save() = 0;
+        virtual void restore() = 0;
+
+        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;
+    };
+
+    struct Cache;
+
+    // TODO: document
+    class LookupScope {
+    public:
+        LookupScope(Cache* cache, Encoder* encoder) : fCache(cache), fEncoder(encoder) {}
+        ~LookupScope() { for (ID id : fToUndefine) { fEncoder->undefine(id); } }
+        void undefineWhenDone(ID id) { fToUndefine.push_back(id); }
+
+        template <typename T>
+        ID lookup(const T&);
+    private:
+        Cache*   fCache;
+        Encoder* fEncoder;
+        SkSTArray<4, ID> fToUndefine;
+    };
+
+    // TODO: document
+    struct Cache {
+        virtual ~Cache() {}
+
+        static Cache* CreateNeverCache();
+        static Cache* CreateAlwaysCache();
+
+        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 void cleanup(Encoder*) = 0;
+    };
+
+    // TODO: document
+    class Client final : public SkCanvas {
+    public:
+        Client(Cache*, Encoder*);
+        ~Client();
+
+    private:
+        void   willSave() override;
+        void didRestore() override;
+
+        void    didConcat(const SkMatrix&) override;
+        void didSetMatrix(const SkMatrix&) override;
+
+        void onClipPath (const SkPath&,  SkRegion::Op, ClipEdgeStyle) override;
+        void onClipRRect(const SkRRect&, SkRegion::Op, ClipEdgeStyle) override;
+        void onClipRect (const SkRect&,  SkRegion::Op, ClipEdgeStyle) override;
+
+        void onDrawOval(const SkRect&, const SkPaint&) override;
+        void onDrawPath(const SkPath&, const SkPaint&) override;
+        void onDrawRect(const SkRect&, const SkPaint&) override;
+
+        Cache*   fCache;
+        Encoder* fEncoder;
+    };
+
+    // TODO: document
+    class Server final : public Encoder {
+    public:
+        explicit Server(SkCanvas*);
+
+    private:
+        void define(ID, const SkMatrix&) override;
+        void define(ID, const Misc&)     override;
+        void define(ID, const SkPath&)   override;
+        void define(ID, const Stroke&)   override;
+
+        void undefine(ID) override;
+
+        void    save() override;
+        void restore() override;
+
+        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;
+
+        template <typename T, Type kType>
+        class IDMap {
+        public:
+            void set(const ID& id, const T& val) {
+                SkASSERT(id.type() == kType);
+                fMap.set(id, val);
+            }
+
+            void remove(const ID& id) {
+                SkASSERT(id.type() == kType);
+                fMap.remove(id);
+            }
+
+            const T& find(const ID& id) const {
+                SkASSERT(id.type() == kType);
+                T* val = fMap.find(id);
+                SkASSERT(val != nullptr);
+                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;
+
+        SkCanvas* fCanvas;
+    };
+
+}  // namespace SkRemote
+
+#endif//SkRemote_DEFINED
diff --git a/src/core/SkRemote_protocol.h b/src/core/SkRemote_protocol.h
new file mode 100644
index 0000000..9975213
--- /dev/null
+++ b/src/core/SkRemote_protocol.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkRemote_protocol_DEFINED
+#define SkRemote_protocol_DEFINED
+
+// ATTENTION!  Changes to this file can break protocol compatibility.  Tread carefully.
+
+namespace SkRemote {
+
+    // It is safe to append to this enum without breaking protocol compatibility.
+    // Resorting, deleting, or inserting anywhere but the end will break compatibility.
+    enum class Type : uint8_t {
+        kNone,
+
+        kMatrix,
+        kMisc,
+        kPath,
+        kStroke,
+    };
+
+    class ID {
+    public:
+        explicit ID(Type type = Type::kNone) : fVal((uint64_t)type << 56) {}
+        ID(Type type, uint64_t val) {
+            fVal = (uint64_t)type << 56 | val;
+            SkASSERT(this->type() == type && this->val() == val);
+        }
+
+        Type    type() const { return (Type)(fVal >> 56); }
+        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++;
+            SkASSERT(this->val() != 0);  // Overflow is particularly bad as it'd change our Type.
+            return prev;
+        }
+
+    private:
+        // High 8 bits hold a Type.  Low 56 bits are unique within that Type.
+        // Any change to this format will break protocol compatibility.
+        uint64_t fVal;
+    };
+
+}  // namespace SkRemote
+
+#endif//SkRemote_protocol_DEFINED