michael@0: michael@0: /* michael@0: * Copyright 2011 Google Inc. michael@0: * michael@0: * Use of this source code is governed by a BSD-style license that can be michael@0: * found in the LICENSE file. michael@0: */ michael@0: #ifndef SkPictureFlat_DEFINED michael@0: #define SkPictureFlat_DEFINED michael@0: michael@0: //#define SK_DEBUG_SIZE michael@0: michael@0: #include "SkBitmapHeap.h" michael@0: #include "SkChecksum.h" michael@0: #include "SkChunkAlloc.h" michael@0: #include "SkReadBuffer.h" michael@0: #include "SkWriteBuffer.h" michael@0: #include "SkPaint.h" michael@0: #include "SkPicture.h" michael@0: #include "SkPtrRecorder.h" michael@0: #include "SkTDynamicHash.h" michael@0: #include "SkTRefArray.h" michael@0: michael@0: enum DrawType { michael@0: UNUSED, michael@0: CLIP_PATH, michael@0: CLIP_REGION, michael@0: CLIP_RECT, michael@0: CLIP_RRECT, michael@0: CONCAT, michael@0: DRAW_BITMAP, michael@0: DRAW_BITMAP_MATRIX, michael@0: DRAW_BITMAP_NINE, michael@0: DRAW_BITMAP_RECT_TO_RECT, michael@0: DRAW_CLEAR, michael@0: DRAW_DATA, michael@0: DRAW_OVAL, michael@0: DRAW_PAINT, michael@0: DRAW_PATH, michael@0: DRAW_PICTURE, michael@0: DRAW_POINTS, michael@0: DRAW_POS_TEXT, michael@0: DRAW_POS_TEXT_TOP_BOTTOM, // fast variant of DRAW_POS_TEXT michael@0: DRAW_POS_TEXT_H, michael@0: DRAW_POS_TEXT_H_TOP_BOTTOM, // fast variant of DRAW_POS_TEXT_H michael@0: DRAW_RECT, michael@0: DRAW_RRECT, michael@0: DRAW_SPRITE, michael@0: DRAW_TEXT, michael@0: DRAW_TEXT_ON_PATH, michael@0: DRAW_TEXT_TOP_BOTTOM, // fast variant of DRAW_TEXT michael@0: DRAW_VERTICES, michael@0: RESTORE, michael@0: ROTATE, michael@0: SAVE, michael@0: SAVE_LAYER, michael@0: SCALE, michael@0: SET_MATRIX, michael@0: SKEW, michael@0: TRANSLATE, michael@0: NOOP, michael@0: BEGIN_COMMENT_GROUP, michael@0: COMMENT, michael@0: END_COMMENT_GROUP, michael@0: michael@0: // new ops -- feel free to re-alphabetize on next version bump michael@0: DRAW_DRRECT, michael@0: PUSH_CULL, michael@0: POP_CULL, michael@0: michael@0: LAST_DRAWTYPE_ENUM = POP_CULL michael@0: }; michael@0: michael@0: // In the 'match' method, this constant will match any flavor of DRAW_BITMAP* michael@0: static const int kDRAW_BITMAP_FLAVOR = LAST_DRAWTYPE_ENUM+1; michael@0: michael@0: enum DrawVertexFlags { michael@0: DRAW_VERTICES_HAS_TEXS = 0x01, michael@0: DRAW_VERTICES_HAS_COLORS = 0x02, michael@0: DRAW_VERTICES_HAS_INDICES = 0x04, michael@0: DRAW_VERTICES_HAS_XFER = 0x08, michael@0: }; michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: // clipparams are packed in 5 bits michael@0: // doAA:1 | regionOp:4 michael@0: michael@0: static inline uint32_t ClipParams_pack(SkRegion::Op op, bool doAA) { michael@0: unsigned doAABit = doAA ? 1 : 0; michael@0: return (doAABit << 4) | op; michael@0: } michael@0: michael@0: static inline SkRegion::Op ClipParams_unpackRegionOp(uint32_t packed) { michael@0: return (SkRegion::Op)(packed & 0xF); michael@0: } michael@0: michael@0: static inline bool ClipParams_unpackDoAA(uint32_t packed) { michael@0: return SkToBool((packed >> 4) & 1); michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: class SkTypefacePlayback { michael@0: public: michael@0: SkTypefacePlayback(); michael@0: virtual ~SkTypefacePlayback(); michael@0: michael@0: int count() const { return fCount; } michael@0: michael@0: void reset(const SkRefCntSet*); michael@0: michael@0: void setCount(int count); michael@0: SkRefCnt* set(int index, SkRefCnt*); michael@0: michael@0: void setupBuffer(SkReadBuffer& buffer) const { michael@0: buffer.setTypefaceArray((SkTypeface**)fArray, fCount); michael@0: } michael@0: michael@0: protected: michael@0: int fCount; michael@0: SkRefCnt** fArray; michael@0: }; michael@0: michael@0: class SkFactoryPlayback { michael@0: public: michael@0: SkFactoryPlayback(int count) : fCount(count) { michael@0: fArray = SkNEW_ARRAY(SkFlattenable::Factory, count); michael@0: } michael@0: michael@0: ~SkFactoryPlayback() { michael@0: SkDELETE_ARRAY(fArray); michael@0: } michael@0: michael@0: SkFlattenable::Factory* base() const { return fArray; } michael@0: michael@0: void setupBuffer(SkReadBuffer& buffer) const { michael@0: buffer.setFactoryPlayback(fArray, fCount); michael@0: } michael@0: michael@0: private: michael@0: int fCount; michael@0: SkFlattenable::Factory* fArray; michael@0: }; michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: // michael@0: // michael@0: // The following templated classes provide an efficient way to store and compare michael@0: // objects that have been flattened (i.e. serialized in an ordered binary michael@0: // format). michael@0: // michael@0: // SkFlatData: is a simple indexable container for the flattened data michael@0: // which is agnostic to the type of data is is indexing. It is michael@0: // also responsible for flattening/unflattening objects but michael@0: // details of that operation are hidden in the provided traits michael@0: // SkFlatDictionary: is an abstract templated dictionary that maintains a michael@0: // searchable set of SkFlatData objects of type T. michael@0: // SkFlatController: is an interface provided to SkFlatDictionary which handles michael@0: // allocation (and unallocation in some cases). It also holds michael@0: // ref count recorders and the like. michael@0: // michael@0: // NOTE: any class that wishes to be used in conjunction with SkFlatDictionary must subclass the michael@0: // dictionary and provide the necessary flattening traits. SkFlatController must also be michael@0: // implemented, or SkChunkFlatController can be used to use an SkChunkAllocator and never do michael@0: // replacements. michael@0: // michael@0: // michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: class SkFlatData; michael@0: michael@0: class SkFlatController : public SkRefCnt { michael@0: public: michael@0: SK_DECLARE_INST_COUNT(SkFlatController) michael@0: michael@0: SkFlatController(uint32_t writeBufferFlags = 0); michael@0: virtual ~SkFlatController(); michael@0: /** michael@0: * Return a new block of memory for the SkFlatDictionary to use. michael@0: * This memory is owned by the controller and has the same lifetime unless you michael@0: * call unalloc(), in which case it may be freed early. michael@0: */ michael@0: virtual void* allocThrow(size_t bytes) = 0; michael@0: michael@0: /** michael@0: * Hint that this block, which was allocated with allocThrow, is no longer needed. michael@0: * The implementation may choose to free this memory any time beteween now and destruction. michael@0: */ michael@0: virtual void unalloc(void* ptr) = 0; michael@0: michael@0: /** michael@0: * Used during creation and unflattening of SkFlatData objects. If the michael@0: * objects being flattened contain bitmaps they are stored in this heap michael@0: * and the flattenable stores the index to the bitmap on the heap. michael@0: * This should be set by the protected setBitmapHeap. michael@0: */ michael@0: SkBitmapHeap* getBitmapHeap() { return fBitmapHeap; } michael@0: michael@0: /** michael@0: * Used during creation of SkFlatData objects. If a typeface recorder is michael@0: * required to flatten the objects being flattened (i.e. for SkPaints), this michael@0: * should be set by the protected setTypefaceSet. michael@0: */ michael@0: SkRefCntSet* getTypefaceSet() { return fTypefaceSet; } michael@0: michael@0: /** michael@0: * Used during unflattening of the SkFlatData objects in the michael@0: * SkFlatDictionary. Needs to be set by the protected setTypefacePlayback michael@0: * and needs to be reset to the SkRefCntSet passed to setTypefaceSet. michael@0: */ michael@0: SkTypefacePlayback* getTypefacePlayback() { return fTypefacePlayback; } michael@0: michael@0: /** michael@0: * Optional factory recorder used during creation of SkFlatData objects. Set michael@0: * using the protected method setNamedFactorySet. michael@0: */ michael@0: SkNamedFactorySet* getNamedFactorySet() { return fFactorySet; } michael@0: michael@0: /** michael@0: * Flags to use during creation of SkFlatData objects. Defaults to zero. michael@0: */ michael@0: uint32_t getWriteBufferFlags() { return fWriteBufferFlags; } michael@0: michael@0: protected: michael@0: /** michael@0: * Set an SkBitmapHeap to be used to store/read SkBitmaps. Ref counted. michael@0: */ michael@0: void setBitmapHeap(SkBitmapHeap*); michael@0: michael@0: /** michael@0: * Set an SkRefCntSet to be used to store SkTypefaces during flattening. Ref michael@0: * counted. michael@0: */ michael@0: void setTypefaceSet(SkRefCntSet*); michael@0: michael@0: /** michael@0: * Set an SkTypefacePlayback to be used to find references to SkTypefaces michael@0: * during unflattening. Should be reset to the set provided to michael@0: * setTypefaceSet. michael@0: */ michael@0: void setTypefacePlayback(SkTypefacePlayback*); michael@0: michael@0: /** michael@0: * Set an SkNamedFactorySet to be used to store Factorys and their michael@0: * corresponding names during flattening. Ref counted. Returns the same michael@0: * set as a convenience. michael@0: */ michael@0: SkNamedFactorySet* setNamedFactorySet(SkNamedFactorySet*); michael@0: michael@0: private: michael@0: SkBitmapHeap* fBitmapHeap; michael@0: SkRefCntSet* fTypefaceSet; michael@0: SkTypefacePlayback* fTypefacePlayback; michael@0: SkNamedFactorySet* fFactorySet; michael@0: const uint32_t fWriteBufferFlags; michael@0: michael@0: typedef SkRefCnt INHERITED; michael@0: }; michael@0: michael@0: class SkFlatData { michael@0: public: michael@0: // Flatten obj into an SkFlatData with this index. controller owns the SkFlatData*. michael@0: template michael@0: static SkFlatData* Create(SkFlatController* controller, const T& obj, int index) { michael@0: // A buffer of 256 bytes should fit most paints, regions, and matrices. michael@0: uint32_t storage[64]; michael@0: SkWriteBuffer buffer(storage, sizeof(storage), controller->getWriteBufferFlags()); michael@0: michael@0: buffer.setBitmapHeap(controller->getBitmapHeap()); michael@0: buffer.setTypefaceRecorder(controller->getTypefaceSet()); michael@0: buffer.setNamedFactoryRecorder(controller->getNamedFactorySet()); michael@0: michael@0: Traits::Flatten(buffer, obj); michael@0: size_t size = buffer.bytesWritten(); michael@0: SkASSERT(SkIsAlign4(size)); michael@0: michael@0: // Allocate enough memory to hold SkFlatData struct and the flat data itself. michael@0: size_t allocSize = sizeof(SkFlatData) + size; michael@0: SkFlatData* result = (SkFlatData*) controller->allocThrow(allocSize); michael@0: michael@0: // Put the serialized contents into the data section of the new allocation. michael@0: buffer.writeToMemory(result->data()); michael@0: // Stamp the index, size and checksum in the header. michael@0: result->stampHeader(index, SkToS32(size)); michael@0: return result; michael@0: } michael@0: michael@0: // Unflatten this into result, using bitmapHeap and facePlayback for bitmaps and fonts if given michael@0: template michael@0: void unflatten(T* result, michael@0: SkBitmapHeap* bitmapHeap = NULL, michael@0: SkTypefacePlayback* facePlayback = NULL) const { michael@0: SkReadBuffer buffer(this->data(), fFlatSize); michael@0: michael@0: if (bitmapHeap) { michael@0: buffer.setBitmapStorage(bitmapHeap); michael@0: } michael@0: if (facePlayback) { michael@0: facePlayback->setupBuffer(buffer); michael@0: } michael@0: michael@0: Traits::Unflatten(buffer, result); michael@0: SkASSERT(fFlatSize == (int32_t)buffer.offset()); michael@0: } michael@0: michael@0: // Do these contain the same data? Ignores index() and topBot(). michael@0: bool operator==(const SkFlatData& that) const { michael@0: if (this->checksum() != that.checksum() || this->flatSize() != that.flatSize()) { michael@0: return false; michael@0: } michael@0: return memcmp(this->data(), that.data(), this->flatSize()) == 0; michael@0: } michael@0: michael@0: int index() const { return fIndex; } michael@0: const uint8_t* data() const { return (const uint8_t*)this + sizeof(*this); } michael@0: size_t flatSize() const { return fFlatSize; } michael@0: uint32_t checksum() const { return fChecksum; } michael@0: michael@0: // Returns true if fTopBot[] has been recorded. michael@0: bool isTopBotWritten() const { michael@0: return !SkScalarIsNaN(fTopBot[0]); michael@0: } michael@0: michael@0: // Returns fTopBot array, so it can be passed to a routine to compute them. michael@0: // For efficiency, we assert that fTopBot have not been recorded yet. michael@0: SkScalar* writableTopBot() const { michael@0: SkASSERT(!this->isTopBotWritten()); michael@0: return fTopBot; michael@0: } michael@0: michael@0: // Return the topbot[] after it has been recorded. michael@0: const SkScalar* topBot() const { michael@0: SkASSERT(this->isTopBotWritten()); michael@0: return fTopBot; michael@0: } michael@0: michael@0: private: michael@0: // For SkTDynamicHash. michael@0: static const SkFlatData& Identity(const SkFlatData& flat) { return flat; } michael@0: static uint32_t Hash(const SkFlatData& flat) { return flat.checksum(); } michael@0: static bool Equal(const SkFlatData& a, const SkFlatData& b) { return a == b; } michael@0: michael@0: void setIndex(int index) { fIndex = index; } michael@0: uint8_t* data() { return (uint8_t*)this + sizeof(*this); } michael@0: michael@0: // This assumes the payload flat data has already been written and does not modify it. michael@0: void stampHeader(int index, int32_t size) { michael@0: SkASSERT(SkIsAlign4(size)); michael@0: fIndex = index; michael@0: fFlatSize = size; michael@0: fTopBot[0] = SK_ScalarNaN; // Mark as unwritten. michael@0: fChecksum = SkChecksum::Compute((uint32_t*)this->data(), size); michael@0: } michael@0: michael@0: int fIndex; michael@0: int32_t fFlatSize; michael@0: uint32_t fChecksum; michael@0: mutable SkScalar fTopBot[2]; // Cache of FontMetrics fTop, fBottom. Starts as [NaN,?]. michael@0: // uint32_t flattenedData[] implicitly hangs off the end. michael@0: michael@0: template friend class SkFlatDictionary; michael@0: }; michael@0: michael@0: template michael@0: class SkFlatDictionary { michael@0: public: michael@0: explicit SkFlatDictionary(SkFlatController* controller) michael@0: : fController(SkRef(controller)) michael@0: , fScratch(controller->getWriteBufferFlags()) michael@0: , fReady(false) { michael@0: this->reset(); michael@0: } michael@0: michael@0: /** michael@0: * Clears the dictionary of all entries. However, it does NOT free the michael@0: * memory that was allocated for each entry (that's owned by controller). michael@0: */ michael@0: void reset() { michael@0: fIndexedData.rewind(); michael@0: } michael@0: michael@0: int count() const { michael@0: SkASSERT(fHash.count() == fIndexedData.count()); michael@0: return fHash.count(); michael@0: } michael@0: michael@0: // For testing only. Index is zero-based. michael@0: const SkFlatData* operator[](int index) { michael@0: return fIndexedData[index]; michael@0: } michael@0: michael@0: /** michael@0: * Given an element of type T return its 1-based index in the dictionary. If michael@0: * the element wasn't previously in the dictionary it is automatically michael@0: * added. michael@0: * michael@0: */ michael@0: int find(const T& element) { michael@0: return this->findAndReturnFlat(element)->index(); michael@0: } michael@0: michael@0: /** michael@0: * Similar to find. Allows the caller to specify an SkFlatData to replace in michael@0: * the case of an add. Also tells the caller whether a new SkFlatData was michael@0: * added and whether the old one was replaced. The parameters added and michael@0: * replaced are required to be non-NULL. Rather than returning the index of michael@0: * the entry in the dictionary, it returns the actual SkFlatData. michael@0: */ michael@0: const SkFlatData* findAndReplace(const T& element, michael@0: const SkFlatData* toReplace, michael@0: bool* added, michael@0: bool* replaced) { michael@0: SkASSERT(added != NULL && replaced != NULL); michael@0: michael@0: const int oldCount = this->count(); michael@0: SkFlatData* flat = this->findAndReturnMutableFlat(element); michael@0: *added = this->count() > oldCount; michael@0: michael@0: // If we don't want to replace anything, we're done. michael@0: if (!*added || toReplace == NULL) { michael@0: *replaced = false; michael@0: return flat; michael@0: } michael@0: michael@0: // If we don't have the thing to replace, we're done. michael@0: const SkFlatData* found = fHash.find(*toReplace); michael@0: if (found == NULL) { michael@0: *replaced = false; michael@0: return flat; michael@0: } michael@0: michael@0: // findAndReturnMutableFlat put flat at the back. Swap it into found->index() instead. michael@0: // indices in SkFlatData are 1-based, while fIndexedData is 0-based. Watch out! michael@0: SkASSERT(flat->index() == this->count()); michael@0: flat->setIndex(found->index()); michael@0: fIndexedData.removeShuffle(found->index()-1); michael@0: SkASSERT(flat == fIndexedData[found->index()-1]); michael@0: michael@0: // findAndReturnMutableFlat already called fHash.add(), so we just clean up the old entry. michael@0: fHash.remove(*found); michael@0: fController->unalloc((void*)found); michael@0: SkASSERT(this->count() == oldCount); michael@0: michael@0: *replaced = true; michael@0: return flat; michael@0: } michael@0: michael@0: /** michael@0: * Unflatten the objects and return them in SkTRefArray, or return NULL michael@0: * if there no objects. Caller takes ownership of result. michael@0: */ michael@0: SkTRefArray* unflattenToArray() const { michael@0: const int count = this->count(); michael@0: if (count == 0) { michael@0: return NULL; michael@0: } michael@0: SkTRefArray* array = SkTRefArray::Create(count); michael@0: for (int i = 0; i < count; i++) { michael@0: this->unflatten(&array->writableAt(i), fIndexedData[i]); michael@0: } michael@0: return array; michael@0: } michael@0: michael@0: /** michael@0: * Unflatten the specific object at the given index. michael@0: * Caller takes ownership of the result. michael@0: */ michael@0: T* unflatten(int index) const { michael@0: // index is 1-based, while fIndexedData is 0-based. michael@0: const SkFlatData* element = fIndexedData[index-1]; michael@0: SkASSERT(index == element->index()); michael@0: michael@0: T* dst = new T; michael@0: this->unflatten(dst, element); michael@0: return dst; michael@0: } michael@0: michael@0: /** michael@0: * Find or insert a flattened version of element into the dictionary. michael@0: * Caller does not take ownership of the result. This will not return NULL. michael@0: */ michael@0: const SkFlatData* findAndReturnFlat(const T& element) { michael@0: return this->findAndReturnMutableFlat(element); michael@0: } michael@0: michael@0: private: michael@0: // We have to delay fScratch's initialization until its first use; fController might not michael@0: // be fully set up by the time we get it in the constructor. michael@0: void lazyInit() { michael@0: if (fReady) { michael@0: return; michael@0: } michael@0: michael@0: // Without a bitmap heap, we'll flatten bitmaps into paints. That's never what you want. michael@0: SkASSERT(fController->getBitmapHeap() != NULL); michael@0: fScratch.setBitmapHeap(fController->getBitmapHeap()); michael@0: fScratch.setTypefaceRecorder(fController->getTypefaceSet()); michael@0: fScratch.setNamedFactoryRecorder(fController->getNamedFactorySet()); michael@0: fReady = true; michael@0: } michael@0: michael@0: // As findAndReturnFlat, but returns a mutable pointer for internal use. michael@0: SkFlatData* findAndReturnMutableFlat(const T& element) { michael@0: // Only valid until the next call to resetScratch(). michael@0: const SkFlatData& scratch = this->resetScratch(element, this->count()+1); michael@0: michael@0: SkFlatData* candidate = fHash.find(scratch); michael@0: if (candidate != NULL) { michael@0: return candidate; michael@0: } michael@0: michael@0: SkFlatData* detached = this->detachScratch(); michael@0: fHash.add(detached); michael@0: *fIndexedData.append() = detached; michael@0: SkASSERT(fIndexedData.top()->index() == this->count()); michael@0: return detached; michael@0: } michael@0: michael@0: // This reference is valid only until the next call to resetScratch() or detachScratch(). michael@0: const SkFlatData& resetScratch(const T& element, int index) { michael@0: this->lazyInit(); michael@0: michael@0: // Layout of fScratch: [ SkFlatData header, 20 bytes ] [ data ..., 4-byte aligned ] michael@0: fScratch.reset(); michael@0: fScratch.reserve(sizeof(SkFlatData)); michael@0: Traits::Flatten(fScratch, element); michael@0: const size_t dataSize = fScratch.bytesWritten() - sizeof(SkFlatData); michael@0: michael@0: // Reinterpret data in fScratch as an SkFlatData. michael@0: SkFlatData* scratch = (SkFlatData*)fScratch.getWriter32()->contiguousArray(); michael@0: SkASSERT(scratch != NULL); michael@0: scratch->stampHeader(index, dataSize); michael@0: return *scratch; michael@0: } michael@0: michael@0: // This result is owned by fController and lives as long as it does (unless unalloc'd). michael@0: SkFlatData* detachScratch() { michael@0: // Allocate a new SkFlatData exactly big enough to hold our current scratch. michael@0: // We use the controller for this allocation to extend the allocation's lifetime and allow michael@0: // the controller to do whatever memory management it wants. michael@0: SkFlatData* detached = (SkFlatData*)fController->allocThrow(fScratch.bytesWritten()); michael@0: michael@0: // Copy scratch into the new SkFlatData. michael@0: SkFlatData* scratch = (SkFlatData*)fScratch.getWriter32()->contiguousArray(); michael@0: SkASSERT(scratch != NULL); michael@0: memcpy(detached, scratch, fScratch.bytesWritten()); michael@0: michael@0: // We can now reuse fScratch, and detached will live until fController dies. michael@0: return detached; michael@0: } michael@0: michael@0: void unflatten(T* dst, const SkFlatData* element) const { michael@0: element->unflatten(dst, michael@0: fController->getBitmapHeap(), michael@0: fController->getTypefacePlayback()); michael@0: } michael@0: michael@0: // All SkFlatData* stored in fIndexedData and fHash are owned by the controller. michael@0: SkAutoTUnref fController; michael@0: SkWriteBuffer fScratch; michael@0: bool fReady; michael@0: michael@0: // For index -> SkFlatData. 0-based, while all indices in the API are 1-based. Careful! michael@0: SkTDArray fIndexedData; michael@0: michael@0: // For SkFlatData -> cached SkFlatData, which has index(). michael@0: SkTDynamicHash fHash; michael@0: }; michael@0: michael@0: typedef SkFlatDictionary SkPaintDictionary; michael@0: michael@0: class SkChunkFlatController : public SkFlatController { michael@0: public: michael@0: SkChunkFlatController(size_t minSize) michael@0: : fHeap(minSize) michael@0: , fTypefaceSet(SkNEW(SkRefCntSet)) michael@0: , fLastAllocated(NULL) { michael@0: this->setTypefaceSet(fTypefaceSet); michael@0: this->setTypefacePlayback(&fTypefacePlayback); michael@0: } michael@0: michael@0: virtual void* allocThrow(size_t bytes) SK_OVERRIDE { michael@0: fLastAllocated = fHeap.allocThrow(bytes); michael@0: return fLastAllocated; michael@0: } michael@0: michael@0: virtual void unalloc(void* ptr) SK_OVERRIDE { michael@0: // fHeap can only free a pointer if it was the last one allocated. Otherwise, we'll just michael@0: // have to wait until fHeap is destroyed. michael@0: if (ptr == fLastAllocated) (void)fHeap.unalloc(ptr); michael@0: } michael@0: michael@0: void setupPlaybacks() const { michael@0: fTypefacePlayback.reset(fTypefaceSet.get()); michael@0: } michael@0: michael@0: void setBitmapStorage(SkBitmapHeap* heap) { michael@0: this->setBitmapHeap(heap); michael@0: } michael@0: michael@0: private: michael@0: SkChunkAlloc fHeap; michael@0: SkAutoTUnref fTypefaceSet; michael@0: void* fLastAllocated; michael@0: mutable SkTypefacePlayback fTypefacePlayback; michael@0: }; michael@0: michael@0: #endif