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