1
2/*
3 * Copyright 2011 Google Inc.
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
8#ifndef SkPictureFlat_DEFINED
9#define SkPictureFlat_DEFINED
10
11//#define SK_DEBUG_SIZE
12
13#include "SkBitmap.h"
14#include "SkBitmapHeap.h"
15#include "SkChecksum.h"
16#include "SkChunkAlloc.h"
17#include "SkMatrix.h"
18#include "SkOrderedReadBuffer.h"
19#include "SkOrderedWriteBuffer.h"
20#include "SkPaint.h"
21#include "SkPath.h"
22#include "SkPicture.h"
23#include "SkPtrRecorder.h"
24#include "SkRegion.h"
25#include "SkTDynamicHash.h"
26#include "SkTRefArray.h"
27#include "SkTSearch.h"
28
29enum DrawType {
30    UNUSED,
31    CLIP_PATH,
32    CLIP_REGION,
33    CLIP_RECT,
34    CLIP_RRECT,
35    CONCAT,
36    DRAW_BITMAP,
37    DRAW_BITMAP_MATRIX,
38    DRAW_BITMAP_NINE,
39    DRAW_BITMAP_RECT_TO_RECT,
40    DRAW_CLEAR,
41    DRAW_DATA,
42    DRAW_OVAL,
43    DRAW_PAINT,
44    DRAW_PATH,
45    DRAW_PICTURE,
46    DRAW_POINTS,
47    DRAW_POS_TEXT,
48    DRAW_POS_TEXT_TOP_BOTTOM, // fast variant of DRAW_POS_TEXT
49    DRAW_POS_TEXT_H,
50    DRAW_POS_TEXT_H_TOP_BOTTOM, // fast variant of DRAW_POS_TEXT_H
51    DRAW_RECT,
52    DRAW_RRECT,
53    DRAW_SPRITE,
54    DRAW_TEXT,
55    DRAW_TEXT_ON_PATH,
56    DRAW_TEXT_TOP_BOTTOM,   // fast variant of DRAW_TEXT
57    DRAW_VERTICES,
58    RESTORE,
59    ROTATE,
60    SAVE,
61    SAVE_LAYER,
62    SCALE,
63    SET_MATRIX,
64    SKEW,
65    TRANSLATE,
66    NOOP,
67    BEGIN_COMMENT_GROUP,
68    COMMENT,
69    END_COMMENT_GROUP,
70
71    LAST_DRAWTYPE_ENUM = END_COMMENT_GROUP
72};
73
74// In the 'match' method, this constant will match any flavor of DRAW_BITMAP*
75static const int kDRAW_BITMAP_FLAVOR = LAST_DRAWTYPE_ENUM+1;
76
77enum DrawVertexFlags {
78    DRAW_VERTICES_HAS_TEXS    = 0x01,
79    DRAW_VERTICES_HAS_COLORS  = 0x02,
80    DRAW_VERTICES_HAS_INDICES = 0x04
81};
82
83///////////////////////////////////////////////////////////////////////////////
84// clipparams are packed in 5 bits
85//  doAA:1 | regionOp:4
86
87static inline uint32_t ClipParams_pack(SkRegion::Op op, bool doAA) {
88    unsigned doAABit = doAA ? 1 : 0;
89    return (doAABit << 4) | op;
90}
91
92static inline SkRegion::Op ClipParams_unpackRegionOp(uint32_t packed) {
93    return (SkRegion::Op)(packed & 0xF);
94}
95
96static inline bool ClipParams_unpackDoAA(uint32_t packed) {
97    return SkToBool((packed >> 4) & 1);
98}
99
100///////////////////////////////////////////////////////////////////////////////
101
102class SkTypefacePlayback {
103public:
104    SkTypefacePlayback();
105    virtual ~SkTypefacePlayback();
106
107    int count() const { return fCount; }
108
109    void reset(const SkRefCntSet*);
110
111    void setCount(int count);
112    SkRefCnt* set(int index, SkRefCnt*);
113
114    void setupBuffer(SkOrderedReadBuffer& buffer) const {
115        buffer.setTypefaceArray((SkTypeface**)fArray, fCount);
116    }
117
118protected:
119    int fCount;
120    SkRefCnt** fArray;
121};
122
123class SkFactoryPlayback {
124public:
125    SkFactoryPlayback(int count) : fCount(count) {
126        fArray = SkNEW_ARRAY(SkFlattenable::Factory, count);
127    }
128
129    ~SkFactoryPlayback() {
130        SkDELETE_ARRAY(fArray);
131    }
132
133    SkFlattenable::Factory* base() const { return fArray; }
134
135    void setupBuffer(SkOrderedReadBuffer& buffer) const {
136        buffer.setFactoryPlayback(fArray, fCount);
137    }
138
139private:
140    int fCount;
141    SkFlattenable::Factory* fArray;
142};
143
144///////////////////////////////////////////////////////////////////////////////
145//
146//
147// The following templated classes provide an efficient way to store and compare
148// objects that have been flattened (i.e. serialized in an ordered binary
149// format).
150//
151// SkFlatData:       is a simple indexable container for the flattened data
152//                   which is agnostic to the type of data is is indexing. It is
153//                   also responsible for flattening/unflattening objects but
154//                   details of that operation are hidden in the provided procs
155// SkFlatDictionary: is an abstract templated dictionary that maintains a
156//                   searchable set of SkFlatData objects of type T.
157// SkFlatController: is an interface provided to SkFlatDictionary which handles
158//                   allocation (and unallocation in some cases). It also holds
159//                   ref count recorders and the like.
160//
161// NOTE: any class that wishes to be used in conjunction with SkFlatDictionary
162// must subclass the dictionary and provide the necessary flattening procs.
163// The end of this header contains dictionary subclasses for some common classes
164// like SkBitmap, SkMatrix, SkPaint, and SkRegion. SkFlatController must also
165// be implemented, or SkChunkFlatController can be used to use an
166// SkChunkAllocator and never do replacements.
167//
168//
169///////////////////////////////////////////////////////////////////////////////
170
171class SkFlatData;
172
173class SkFlatController : public SkRefCnt {
174public:
175    SK_DECLARE_INST_COUNT(SkFlatController)
176
177    SkFlatController();
178    virtual ~SkFlatController();
179    /**
180     * Return a new block of memory for the SkFlatDictionary to use.
181     * This memory is owned by the controller and has the same lifetime unless you
182     * call unalloc(), in which case it may be freed early.
183     */
184    virtual void* allocThrow(size_t bytes) = 0;
185
186    /**
187     * Hint that this block, which was allocated with allocThrow, is no longer needed.
188     * The implementation may choose to free this memory any time beteween now and destruction.
189     */
190    virtual void unalloc(void* ptr) = 0;
191
192    /**
193     * Used during creation and unflattening of SkFlatData objects. If the
194     * objects being flattened contain bitmaps they are stored in this heap
195     * and the flattenable stores the index to the bitmap on the heap.
196     * This should be set by the protected setBitmapHeap.
197     */
198    SkBitmapHeap* getBitmapHeap() { return fBitmapHeap; }
199
200    /**
201     * Used during creation of SkFlatData objects. If a typeface recorder is
202     * required to flatten the objects being flattened (i.e. for SkPaints), this
203     * should be set by the protected setTypefaceSet.
204     */
205    SkRefCntSet* getTypefaceSet() { return fTypefaceSet; }
206
207    /**
208     * Used during unflattening of the SkFlatData objects in the
209     * SkFlatDictionary. Needs to be set by the protected setTypefacePlayback
210     * and needs to be reset to the SkRefCntSet passed to setTypefaceSet.
211     */
212    SkTypefacePlayback* getTypefacePlayback() { return fTypefacePlayback; }
213
214    /**
215     * Optional factory recorder used during creation of SkFlatData objects. Set
216     * using the protected method setNamedFactorySet.
217     */
218    SkNamedFactorySet* getNamedFactorySet() { return fFactorySet; }
219
220    /**
221     * Flags to use during creation of SkFlatData objects. Defaults to zero.
222     */
223    uint32_t getWriteBufferFlags() { return fWriteBufferFlags; }
224
225protected:
226    /**
227     * Set an SkBitmapHeap to be used to store/read SkBitmaps. Ref counted.
228     */
229    void setBitmapHeap(SkBitmapHeap*);
230
231    /**
232     * Set an SkRefCntSet to be used to store SkTypefaces during flattening. Ref
233     * counted.
234     */
235    void setTypefaceSet(SkRefCntSet*);
236
237    /**
238     * Set an SkTypefacePlayback to be used to find references to SkTypefaces
239     * during unflattening. Should be reset to the set provided to
240     * setTypefaceSet.
241     */
242    void setTypefacePlayback(SkTypefacePlayback*);
243
244    /**
245     * Set an SkNamedFactorySet to be used to store Factorys and their
246     * corresponding names during flattening. Ref counted. Returns the same
247     * set as a convenience.
248     */
249    SkNamedFactorySet* setNamedFactorySet(SkNamedFactorySet*);
250
251    /**
252     * Set the flags to be used during flattening.
253     */
254    void setWriteBufferFlags(uint32_t flags) { fWriteBufferFlags = flags; }
255
256private:
257    SkBitmapHeap*       fBitmapHeap;
258    SkRefCntSet*        fTypefaceSet;
259    SkTypefacePlayback* fTypefacePlayback;
260    SkNamedFactorySet*  fFactorySet;
261    uint32_t            fWriteBufferFlags;
262
263    typedef SkRefCnt INHERITED;
264};
265
266class SkFlatData {
267public:
268    // Flatten obj into an SkFlatData with this index.  controller owns the SkFlatData*.
269    static SkFlatData* Create(SkFlatController* controller,
270                              const void* obj,
271                              int index,
272                              void (*flattenProc)(SkOrderedWriteBuffer&, const void*));
273
274    // Unflatten this into result, using bitmapHeap and facePlayback for bitmaps and fonts if given.
275    void unflatten(void* result,
276                   void (*unflattenProc)(SkOrderedReadBuffer&, void*),
277                   SkBitmapHeap* bitmapHeap = NULL,
278                   SkTypefacePlayback* facePlayback = NULL) const;
279
280    // Do these contain the same data?  Ignores index() and topBot().
281    bool operator==(const SkFlatData& that) const {
282        if (this->checksum() != that.checksum() || this->flatSize() != that.flatSize()) {
283            return false;
284        }
285        return memcmp(this->data(), that.data(), this->flatSize()) == 0;
286    }
287
288    int index() const { return fIndex; }
289    const uint8_t* data() const { return (const uint8_t*)this + sizeof(*this); }
290    size_t flatSize() const { return fFlatSize; }
291    uint32_t checksum() const { return fChecksum; }
292
293    // Returns true if fTopBot[] has been recorded.
294    bool isTopBotWritten() const {
295        return !SkScalarIsNaN(fTopBot[0]);
296    }
297
298    // Returns fTopBot array, so it can be passed to a routine to compute them.
299    // For efficiency, we assert that fTopBot have not been recorded yet.
300    SkScalar* writableTopBot() const {
301        SkASSERT(!this->isTopBotWritten());
302        return fTopBot;
303    }
304
305    // Return the topbot[] after it has been recorded.
306    const SkScalar* topBot() const {
307        SkASSERT(this->isTopBotWritten());
308        return fTopBot;
309    }
310
311private:
312    // For SkTDynamicHash.
313    static const SkFlatData& Identity(const SkFlatData& flat) { return flat; }
314    static uint32_t Hash(const SkFlatData& flat) { return flat.checksum(); }
315    static bool Equal(const SkFlatData& a, const SkFlatData& b) { return a == b; }
316
317    void setIndex(int index) { fIndex = index; }
318    uint8_t* data() { return (uint8_t*)this + sizeof(*this); }
319
320    // This assumes the payload flat data has already been written and does not modify it.
321    void stampHeader(int index, int32_t size) {
322        SkASSERT(SkIsAlign4(size));
323        fIndex     = index;
324        fFlatSize  = size;
325        fTopBot[0] = SK_ScalarNaN;  // Mark as unwritten.
326        fChecksum  = SkChecksum::Compute((uint32_t*)this->data(), size);
327    }
328
329    int fIndex;
330    int32_t fFlatSize;
331    uint32_t fChecksum;
332    mutable SkScalar fTopBot[2];  // Cache of FontMetrics fTop, fBottom.  Starts as [NaN,?].
333    // uint32_t flattenedData[] implicitly hangs off the end.
334
335    template <class T> friend class SkFlatDictionary;
336};
337
338template <class T>
339class SkFlatDictionary {
340    static const size_t kWriteBufferGrowthBytes = 1024;
341
342public:
343    SkFlatDictionary(SkFlatController* controller, size_t scratchSizeGuess = 0)
344    : fFlattenProc(NULL)
345    , fUnflattenProc(NULL)
346    , fController(SkRef(controller))
347    , fScratchSize(scratchSizeGuess)
348    , fScratch(AllocScratch(fScratchSize))
349    , fWriteBuffer(kWriteBufferGrowthBytes)
350    , fWriteBufferReady(false) {
351        this->reset();
352    }
353
354    /**
355     * Clears the dictionary of all entries. However, it does NOT free the
356     * memory that was allocated for each entry (that's owned by controller).
357     */
358    void reset() {
359        fIndexedData.rewind();
360        // TODO(mtklein): There's no reason to have the index start from 1.  Clean this up.
361        // index 0 is always empty since it is used as a signal that find failed
362        fIndexedData.push(NULL);
363        fNextIndex = 1;
364    }
365
366    ~SkFlatDictionary() {
367        sk_free(fScratch);
368    }
369
370    int count() const {
371        SkASSERT(fIndexedData.count() == fNextIndex);
372        SkASSERT(fHash.count() == fNextIndex - 1);
373        return fNextIndex - 1;
374    }
375
376    // For testing only.  Index is zero-based.
377    const SkFlatData* operator[](int index) {
378        return fIndexedData[index+1];
379    }
380
381    /**
382     * Given an element of type T return its 1-based index in the dictionary. If
383     * the element wasn't previously in the dictionary it is automatically
384     * added.
385     *
386     */
387    int find(const T& element) {
388        return this->findAndReturnFlat(element)->index();
389    }
390
391    /**
392     * Similar to find. Allows the caller to specify an SkFlatData to replace in
393     * the case of an add. Also tells the caller whether a new SkFlatData was
394     * added and whether the old one was replaced. The parameters added and
395     * replaced are required to be non-NULL. Rather than returning the index of
396     * the entry in the dictionary, it returns the actual SkFlatData.
397     */
398    const SkFlatData* findAndReplace(const T& element,
399                                     const SkFlatData* toReplace,
400                                     bool* added,
401                                     bool* replaced) {
402        SkASSERT(added != NULL && replaced != NULL);
403
404        const int oldCount = this->count();
405        SkFlatData* flat = this->findAndReturnMutableFlat(element);
406        *added = this->count() > oldCount;
407
408        // If we don't want to replace anything, we're done.
409        if (!*added || toReplace == NULL) {
410            *replaced = false;
411            return flat;
412        }
413
414        // If we don't have the thing to replace, we're done.
415        const SkFlatData* found = fHash.find(*toReplace);
416        if (found == NULL) {
417            *replaced = false;
418            return flat;
419        }
420
421        // findAndReturnMutableFlat gave us index (fNextIndex-1), but we'll use the old one.
422        fIndexedData.remove(flat->index());
423        fNextIndex--;
424        flat->setIndex(found->index());
425        fIndexedData[flat->index()] = flat;
426
427        // findAndReturnMutableFlat already called fHash.add(), so we just clean up the old entry.
428        fHash.remove(*found);
429        fController->unalloc((void*)found);
430        SkASSERT(this->count() == oldCount);
431
432        *replaced = true;
433        return flat;
434    }
435
436    /**
437     *  Unflatten the objects and return them in SkTRefArray, or return NULL
438     *  if there no objects.  Caller takes ownership of result.
439     */
440    SkTRefArray<T>* unflattenToArray() const {
441        const int count = this->count();
442        if (count == 0) {
443            return NULL;
444        }
445        SkTRefArray<T>* array = SkTRefArray<T>::Create(count);
446        for (int i = 0; i < count; i++) {
447            this->unflatten(&array->writableAt(i), fIndexedData[i+1]);
448        }
449        return array;
450    }
451
452    /**
453     * Unflatten the specific object at the given index.
454     * Caller takes ownership of the result.
455     */
456    T* unflatten(int index) const {
457        const SkFlatData* element = fIndexedData[index];
458        SkASSERT(index == element->index());
459
460        T* dst = new T;
461        this->unflatten(dst, element);
462        return dst;
463    }
464
465    /**
466     * Find or insert a flattened version of element into the dictionary.
467     * Caller does not take ownership of the result.  This will not return NULL.
468     */
469    const SkFlatData* findAndReturnFlat(const T& element) {
470        return this->findAndReturnMutableFlat(element);
471    }
472
473protected:
474    void (*fFlattenProc)(SkOrderedWriteBuffer&, const void*);
475    void (*fUnflattenProc)(SkOrderedReadBuffer&, void*);
476
477private:
478    // Layout: [ SkFlatData header, 20 bytes ] [ data ..., 4-byte aligned ]
479    static size_t SizeWithPadding(size_t flatDataSize) {
480        SkASSERT(SkIsAlign4(flatDataSize));
481        return sizeof(SkFlatData) + flatDataSize;
482    }
483
484    // Allocate a new scratch SkFlatData.  Must be sk_freed.
485    static SkFlatData* AllocScratch(size_t scratchSize) {
486        return (SkFlatData*) sk_malloc_throw(SizeWithPadding(scratchSize));
487    }
488
489    // We have to delay fWriteBuffer's initialization until its first use; fController might not
490    // be fully set up by the time we get it in the constructor.
491    void lazyWriteBufferInit() {
492        if (fWriteBufferReady) {
493            return;
494        }
495        // Without a bitmap heap, we'll flatten bitmaps into paints.  That's never what you want.
496        SkASSERT(fController->getBitmapHeap() != NULL);
497        fWriteBuffer.setBitmapHeap(fController->getBitmapHeap());
498        fWriteBuffer.setTypefaceRecorder(fController->getTypefaceSet());
499        fWriteBuffer.setNamedFactoryRecorder(fController->getNamedFactorySet());
500        fWriteBuffer.setFlags(fController->getWriteBufferFlags());
501        fWriteBufferReady = true;
502    }
503
504    // As findAndReturnFlat, but returns a mutable pointer for internal use.
505    SkFlatData* findAndReturnMutableFlat(const T& element) {
506        // Only valid until the next call to resetScratch().
507        const SkFlatData& scratch = this->resetScratch(element, fNextIndex);
508
509        SkFlatData* candidate = fHash.find(scratch);
510        if (candidate != NULL) return candidate;
511
512        SkFlatData* detached = this->detachScratch();
513        fHash.add(detached);
514        *fIndexedData.insert(fNextIndex) = detached;
515        fNextIndex++;
516        return detached;
517    }
518
519    // This reference is valid only until the next call to resetScratch() or detachScratch().
520    const SkFlatData& resetScratch(const T& element, int index) {
521        this->lazyWriteBufferInit();
522
523        // Flatten element into fWriteBuffer (using fScratch as storage).
524        fWriteBuffer.reset(fScratch->data(), fScratchSize);
525        fFlattenProc(fWriteBuffer, &element);
526        const size_t bytesWritten = fWriteBuffer.bytesWritten();
527
528        // If all the flattened bytes fit into fScratch, we can skip a call to writeToMemory.
529        if (!fWriteBuffer.wroteOnlyToStorage()) {
530            SkASSERT(bytesWritten > fScratchSize);
531            // It didn't all fit.  Copy into a larger replacement SkFlatData.
532            // We can't just realloc because it might move the pointer and confuse writeToMemory.
533            SkFlatData* larger = AllocScratch(bytesWritten);
534            fWriteBuffer.writeToMemory(larger->data());
535
536            // Carry on with this larger scratch to minimize the likelihood of future resizing.
537            sk_free(fScratch);
538            fScratchSize = bytesWritten;
539            fScratch = larger;
540        }
541
542        // The data is in fScratch now but we need to stamp its header.
543        fScratch->stampHeader(index, bytesWritten);
544        return *fScratch;
545    }
546
547    // This result is owned by fController and lives as long as it does (unless unalloc'd).
548    SkFlatData* detachScratch() {
549        // Allocate a new SkFlatData exactly big enough to hold our current scratch.
550        // We use the controller for this allocation to extend the allocation's lifetime and allow
551        // the controller to do whatever memory management it wants.
552        const size_t paddedSize = SizeWithPadding(fScratch->flatSize());
553        SkFlatData* detached = (SkFlatData*)fController->allocThrow(paddedSize);
554
555        // Copy scratch into the new SkFlatData.
556        memcpy(detached, fScratch, paddedSize);
557
558        // We can now reuse fScratch, and detached will live until fController dies.
559        return detached;
560    }
561
562    void unflatten(T* dst, const SkFlatData* element) const {
563        element->unflatten(dst,
564                           fUnflattenProc,
565                           fController->getBitmapHeap(),
566                           fController->getTypefacePlayback());
567    }
568
569    // All SkFlatData* stored in fIndexedData and fHash are owned by the controller.
570    SkAutoTUnref<SkFlatController> fController;
571    size_t fScratchSize;  // How many bytes fScratch has allocated for data itself.
572    SkFlatData* fScratch;  // Owned, must be freed with sk_free.
573    SkOrderedWriteBuffer fWriteBuffer;
574    bool fWriteBufferReady;
575
576    // We map between SkFlatData and a 1-based integer index.
577    int fNextIndex;
578
579    // For index -> SkFlatData.  fIndexedData[0] is always NULL.
580    SkTDArray<const SkFlatData*> fIndexedData;
581
582    // For SkFlatData -> cached SkFlatData, which has index().
583    SkTDynamicHash<SkFlatData, SkFlatData,
584                   SkFlatData::Identity, SkFlatData::Hash, SkFlatData::Equal> fHash;
585};
586
587///////////////////////////////////////////////////////////////////////////////
588// Some common dictionaries are defined here for both reference and convenience
589///////////////////////////////////////////////////////////////////////////////
590
591template <class T>
592static void SkFlattenObjectProc(SkOrderedWriteBuffer& buffer, const void* obj) {
593    ((T*)obj)->flatten(buffer);
594}
595
596template <class T>
597static void SkUnflattenObjectProc(SkOrderedReadBuffer& buffer, void* obj) {
598    ((T*)obj)->unflatten(buffer);
599}
600
601class SkChunkFlatController : public SkFlatController {
602public:
603    SkChunkFlatController(size_t minSize)
604    : fHeap(minSize)
605    , fTypefaceSet(SkNEW(SkRefCntSet))
606    , fLastAllocated(NULL) {
607        this->setTypefaceSet(fTypefaceSet);
608        this->setTypefacePlayback(&fTypefacePlayback);
609    }
610
611    virtual void* allocThrow(size_t bytes) SK_OVERRIDE {
612        fLastAllocated = fHeap.allocThrow(bytes);
613        return fLastAllocated;
614    }
615
616    virtual void unalloc(void* ptr) SK_OVERRIDE {
617        // fHeap can only free a pointer if it was the last one allocated.  Otherwise, we'll just
618        // have to wait until fHeap is destroyed.
619        if (ptr == fLastAllocated) (void)fHeap.unalloc(ptr);
620    }
621
622    void setupPlaybacks() const {
623        fTypefacePlayback.reset(fTypefaceSet.get());
624    }
625
626    void setBitmapStorage(SkBitmapHeap* heap) {
627        this->setBitmapHeap(heap);
628    }
629
630private:
631    SkChunkAlloc               fHeap;
632    SkAutoTUnref<SkRefCntSet>  fTypefaceSet;
633    void*                      fLastAllocated;
634    mutable SkTypefacePlayback fTypefacePlayback;
635};
636
637class SkMatrixDictionary : public SkFlatDictionary<SkMatrix> {
638 public:
639    // All matrices fit in 36 bytes.
640    SkMatrixDictionary(SkFlatController* controller)
641    : SkFlatDictionary<SkMatrix>(controller, 36) {
642        fFlattenProc = &flattenMatrix;
643        fUnflattenProc = &unflattenMatrix;
644    }
645
646    static void flattenMatrix(SkOrderedWriteBuffer& buffer, const void* obj) {
647        buffer.getWriter32()->writeMatrix(*(SkMatrix*)obj);
648    }
649
650    static void unflattenMatrix(SkOrderedReadBuffer& buffer, void* obj) {
651        buffer.getReader32()->readMatrix((SkMatrix*)obj);
652    }
653};
654
655class SkPaintDictionary : public SkFlatDictionary<SkPaint> {
656 public:
657    // The largest paint across ~60 .skps was 500 bytes.
658    SkPaintDictionary(SkFlatController* controller)
659    : SkFlatDictionary<SkPaint>(controller, 512) {
660        fFlattenProc = &SkFlattenObjectProc<SkPaint>;
661        fUnflattenProc = &SkUnflattenObjectProc<SkPaint>;
662    }
663};
664
665class SkRegionDictionary : public SkFlatDictionary<SkRegion> {
666 public:
667    SkRegionDictionary(SkFlatController* controller)
668    : SkFlatDictionary<SkRegion>(controller) {
669        fFlattenProc = &flattenRegion;
670        fUnflattenProc = &unflattenRegion;
671    }
672
673    static void flattenRegion(SkOrderedWriteBuffer& buffer, const void* obj) {
674        buffer.getWriter32()->writeRegion(*(SkRegion*)obj);
675    }
676
677    static void unflattenRegion(SkOrderedReadBuffer& buffer, void* obj) {
678        buffer.getReader32()->readRegion((SkRegion*)obj);
679    }
680};
681
682#endif
683