*/
virtual Factory getFactory() const = 0;
- /** Returns the name of the object's class
- */
- const char* getTypeName() const { return FactoryToName(getFactory()); }
+ /**
+ * Returns the name of the object's class.
+ *
+ * Subclasses should override this function if they intend to provide
+ * support for flattening without using the global registry.
+ *
+ * If the flattenable is registered, there is no need to override.
+ */
+ virtual const char* getTypeName() const { return FactoryToName(getFactory()); }
static Factory NameToFactory(const char name[]);
static const char* FactoryToName(Factory);
#include "SkPixelSerializer.h"
#include "SkRefCnt.h"
#include "SkWriter32.h"
+#include "../private/SkTHash.h"
class SkBitmap;
class SkBitmapHeap;
public:
enum Flags {
kCrossProcess_Flag = 1 << 0,
- kValidation_Flag = 1 << 1,
};
SkWriteBuffer(uint32_t flags = 0);
~SkWriteBuffer();
bool isCrossProcess() const {
- return this->isValidating() || SkToBool(fFlags & kCrossProcess_Flag);
+ return SkToBool(fFlags & kCrossProcess_Flag);
}
SkWriter32* getWriter32() { return &fWriter; }
SkPixelSerializer* getPixelSerializer() const { return fPixelSerializer; }
private:
- bool isValidating() const { return SkToBool(fFlags & kValidation_Flag); }
-
const uint32_t fFlags;
SkFactorySet* fFactorySet;
SkWriter32 fWriter;
SkRefCntSet* fTFSet;
SkAutoTUnref<SkPixelSerializer> fPixelSerializer;
+
+ // Only used if we do not have an fFactorySet
+ SkTHashMap<SkString, uint32_t> fFlattenableDict;
};
#endif // SkWriteBuffer_DEFINED
#include "SkWriteBuffer.h"
SkData* SkValidatingSerializeFlattenable(SkFlattenable* flattenable) {
- SkWriteBuffer writer(SkWriteBuffer::kValidation_Flag);
+ SkWriteBuffer writer;
writer.writeFlattenable(flattenable);
size_t size = writer.bytesWritten();
auto data = SkData::MakeUninitialized(size);
return fReader.readInt();
}
+uint8_t SkReadBuffer::peekByte() {
+ SkASSERT(fReader.available() > 0);
+ return *((uint8_t*) fReader.peek());
+}
+
void SkReadBuffer::readString(SkString* string) {
size_t len;
const char* strContents = fReader.readString(&len);
}
factory = fFactoryArray[index];
} else {
- factory = (SkFlattenable::Factory)readFunctionPtr();
- if (nullptr == factory) {
- return nullptr; // writer failed to give us the flattenable
+ SkString name;
+ if (this->peekByte()) {
+ // If the first byte is non-zero, the flattenable is specified by a string.
+ this->readString(&name);
+
+ // Add the string to the dictionary.
+ fFlattenableDict.set(fFlattenableDict.count() + 1, name);
+ } else {
+ // Read the index. We are guaranteed that the first byte
+ // is zeroed, so we must shift down a byte.
+ uint32_t index = fReader.readU32() >> 8;
+ if (0 == index) {
+ return nullptr; // writer failed to give us the flattenable
+ }
+
+ SkString* namePtr = fFlattenableDict.find(index);
+ SkASSERT(namePtr);
+ name = *namePtr;
+ }
+
+ // Check if a custom Factory has been specified for this flattenable.
+ if (!(factory = this->getCustomFactory(name))) {
+ // If there is no custom Factory, check for a default.
+ if (!(factory = SkFlattenable::NameToFactory(name.c_str()))) {
+ return nullptr; // writer failed to give us the flattenable
+ }
}
}
#include "SkReader32.h"
#include "SkRefCnt.h"
#include "SkShader.h"
+#include "SkTHash.h"
#include "SkWriteBuffer.h"
#include "SkXfermode.h"
virtual uint32_t readUInt();
virtual int32_t read32();
+ // peek
+ virtual uint8_t peekByte();
+
// strings -- the caller is responsible for freeing the string contents
virtual void readString(SkString* string);
virtual void* readEncodedString(size_t* length, SkPaint::TextEncoding encoding);
fFactoryCount = count;
}
+ /**
+ * For an input flattenable (specified by name), set a custom factory proc
+ * to use when unflattening. Will make a copy of |name|.
+ *
+ * If the global registry already has a default factory for the flattenable,
+ * this will override that factory. If a custom factory has already been
+ * set for the flattenable, this will override that factory.
+ *
+ * Custom factories can be removed by calling setCustomFactory("...", nullptr).
+ */
+ void setCustomFactory(const SkString& name, SkFlattenable::Factory factory) {
+ fCustomFactory.set(name, factory);
+ }
+
/**
* Provide a function to decode an SkBitmap from encoded data. Only used if the writer
* encoded the SkBitmap. If the proper decoder cannot be used, a red bitmap with the
}
protected:
+ /**
+ * Allows subclass to check if we are using factories for expansion
+ * of flattenables.
+ */
+ int factoryCount() { return fFactoryCount; }
+
+
+ /**
+ * Checks if a custom factory has been set for a given flattenable.
+ * Returns the custom factory if it exists, or nullptr otherwise.
+ */
+ SkFlattenable::Factory getCustomFactory(const SkString& name) {
+ SkFlattenable::Factory* factoryPtr = fCustomFactory.find(name);
+ return factoryPtr ? *factoryPtr : nullptr;
+ }
+
SkReader32 fReader;
+ // Only used if we do not have an fFactoryArray.
+ SkTHashMap<uint32_t, SkString> fFlattenableDict;
+
private:
bool readArray(void* value, size_t size, size_t elementSize);
SkFlattenable::Factory* fFactoryArray;
int fFactoryCount;
+ // Only used if we do not have an fFactoryArray.
+ SkTHashMap<SkString, SkFlattenable::Factory> fCustomFactory;
+
SkPicture::InstallPixelRefProc fBitmapDecoder;
#ifdef DEBUG_NON_DETERMINISTIC_ASSERT
return this->readInt();
}
+uint8_t SkValidatingReadBuffer::peekByte() {
+ if (fReader.available() <= 0) {
+ fError = true;
+ return 0;
+ }
+ return *((uint8_t*) fReader.peek());
+}
+
void SkValidatingReadBuffer::readString(SkString* string) {
const size_t len = this->readUInt();
const void* ptr = fReader.peek();
}
SkFlattenable* SkValidatingReadBuffer::readFlattenable(SkFlattenable::Type type) {
- SkString name;
- this->readString(&name);
+ // The validating read buffer always uses strings and string-indices for unflattening.
+ SkASSERT(0 == this->factoryCount());
+
+ uint8_t firstByte = this->peekByte();
if (fError) {
return nullptr;
}
+ SkString name;
+ if (firstByte) {
+ // If the first byte is non-zero, the flattenable is specified by a string.
+ this->readString(&name);
+ if (fError) {
+ return nullptr;
+ }
+
+ // Add the string to the dictionary.
+ fFlattenableDict.set(fFlattenableDict.count() + 1, name);
+ } else {
+ // Read the index. We are guaranteed that the first byte
+ // is zeroed, so we must shift down a byte.
+ uint32_t index = fReader.readU32() >> 8;
+ if (0 == index) {
+ return nullptr; // writer failed to give us the flattenable
+ }
+
+ SkString* namePtr = fFlattenableDict.find(index);
+ SkASSERT(namePtr);
+ name = *namePtr;
+ }
+
// Is this the type we wanted ?
const char* cname = name.c_str();
SkFlattenable::Type baseType;
return nullptr;
}
- SkFlattenable::Factory factory = SkFlattenable::NameToFactory(cname);
- if (nullptr == factory) {
- return nullptr; // writer failed to give us the flattenable
+ // Get the factory for this flattenable.
+ SkFlattenable::Factory factory = this->getCustomFactory(name);
+ if (!factory) {
+ factory = SkFlattenable::NameToFactory(cname);
+ if (!factory) {
+ return nullptr; // writer failed to give us the flattenable
+ }
}
- // if we get here, factory may still be null, but if that is the case, the
- // failure was ours, not the writer.
+ // If we get here, the factory is non-null.
sk_sp<SkFlattenable> obj;
uint32_t sizeRecorded = this->readUInt();
- if (factory) {
- size_t offset = fReader.offset();
- obj = (*factory)(*this);
- // check that we read the amount we expected
- size_t sizeRead = fReader.offset() - offset;
- this->validate(sizeRecorded == sizeRead);
- if (fError) {
- obj = nullptr;
- }
- } else {
- // we must skip the remaining data
- this->skip(sizeRecorded);
- SkASSERT(false);
+ size_t offset = fReader.offset();
+ obj = (*factory)(*this);
+ // check that we read the amount we expected
+ size_t sizeRead = fReader.offset() - offset;
+ this->validate(sizeRecorded == sizeRead);
+ if (fError) {
+ obj = nullptr;
}
return obj.release();
}
uint32_t readUInt() override;
int32_t read32() override;
+ // peek
+ uint8_t peekByte() override;
+
// strings -- the caller is responsible for freeing the string contents
void readString(SkString* string) override;
void* readEncodedString(size_t* length, SkPaint::TextEncoding encoding) override;
void SkWriteBuffer::writeFlattenable(const SkFlattenable* flattenable) {
/*
- * If we have a factoryset, then the first 32bits tell us...
+ * The first 32 bits tell us...
* 0: failure to write the flattenable
- * >0: (1-based) index into the SkFactorySet or SkNamedFactorySet
- * If we don't have a factoryset, then the first "ptr" is either the
- * factory, or null for failure.
- *
- * The distinction is important, since 0-index is 32bits (always), but a
- * 0-functionptr might be 32 or 64 bits.
+ * >0: index (1-based) into fFactorySet or fFlattenableDict or
+ * the first character of a string
*/
if (nullptr == flattenable) {
- if (this->isValidating()) {
- this->writeString("");
- } else if (fFactorySet != nullptr) {
- this->write32(0);
- } else {
- this->writeFunctionPtr(nullptr);
- }
+ this->write32(0);
return;
}
- SkFlattenable::Factory factory = flattenable->getFactory();
- SkASSERT(factory != nullptr);
-
/*
- * We can write 1 of 3 versions of the flattenable:
- * 1. function-ptr : this is the fastest for the reader, but assumes that
- * the writer and reader are in the same process.
- * 2. index into fFactorySet : This is assumes the writer will later
+ * We can write 1 of 2 versions of the flattenable:
+ * 1. index into fFactorySet : This assumes the writer will later
* resolve the function-ptrs into strings for its reader. SkPicture
* does exactly this, by writing a table of names (matching the indices)
* up front in its serialized form.
- * 3. index into fNamedFactorySet. fNamedFactorySet will also store the
- * name. SkGPipe uses this technique so it can write the name to its
- * stream before writing the flattenable.
+ * 2. string name of the flattenable or index into fFlattenableDict: We
+ * store the string to allow the reader to specify its own factories
+ * after write time. In order to improve compression, if we have
+ * already written the string, we write its index instead.
*/
- if (this->isValidating()) {
- this->writeString(flattenable->getTypeName());
- } else if (fFactorySet) {
+ if (fFactorySet) {
+ SkFlattenable::Factory factory = flattenable->getFactory();
+ SkASSERT(factory);
this->write32(fFactorySet->add(factory));
} else {
- this->writeFunctionPtr((void*)factory);
+ const char* name = flattenable->getTypeName();
+ SkASSERT(name);
+ SkString key(name);
+ if (uint32_t* indexPtr = fFlattenableDict.find(key)) {
+ // We will write the index as a 32-bit int. We want the first byte
+ // that we send to be zero - this will act as a sentinel that we
+ // have an index (not a string). This means that we will send the
+ // the index shifted left by 8. The remaining 24-bits should be
+ // plenty to store the index. Note that this strategy depends on
+ // being little endian.
+ SkASSERT(0 == *indexPtr >> 24);
+ this->write32(*indexPtr << 8);
+ } else {
+ // Otherwise write the string. Clients should not use the empty
+ // string as a name, or we will have a problem.
+ SkASSERT(strcmp("", name));
+ this->writeString(name);
+
+ // Add key to dictionary.
+ fFlattenableDict.set(key, fFlattenableDict.count() + 1);
+ }
}
// make room for the size of the flattened object
index = dictionary.find(*bitmapShader);
heap.endAddingOwnersDeferral(false);
- // The dictionary should report the same index since the new entry is identical.
// The bitmap heap should contain the bitmap, but with no references.
- REPORTER_ASSERT(reporter, 1 == index);
REPORTER_ASSERT(reporter, heap.count() == 1);
REPORTER_ASSERT(reporter, SkBitmapHeapTester::GetRefCount(heap.getEntry(0)) == 0);
}
--- /dev/null
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkFlattenable.h"
+#include "SkReadBuffer.h"
+#include "SkWriteBuffer.h"
+#include "Test.h"
+
+class IntFlattenable : public SkFlattenable {
+public:
+ IntFlattenable(uint32_t a, uint32_t b, uint32_t c, uint32_t d)
+ : fA(a)
+ , fB(b)
+ , fC(c)
+ , fD(d)
+ {}
+
+ void flatten(SkWriteBuffer& buffer) const override {
+ buffer.writeUInt(fA);
+ buffer.writeUInt(fB);
+ buffer.writeUInt(fC);
+ buffer.writeUInt(fD);
+ }
+
+ Factory getFactory() const override { return nullptr; }
+
+ uint32_t a() const { return fA; }
+ uint32_t b() const { return fB; }
+ uint32_t c() const { return fC; }
+ uint32_t d() const { return fD; }
+
+ const char* getTypeName() const override { return "IntFlattenable"; }
+
+private:
+ uint32_t fA;
+ uint32_t fB;
+ uint32_t fC;
+ uint32_t fD;
+};
+
+static sk_sp<SkFlattenable> custom_create_proc(SkReadBuffer& buffer) {
+ uint32_t a = buffer.readUInt();
+ uint32_t b = buffer.readUInt();
+ uint32_t c = buffer.readUInt();
+ uint32_t d = buffer.readUInt();
+ return sk_sp<SkFlattenable>(new IntFlattenable(a + 1, b + 1, c + 1, d + 1));
+}
+
+DEF_TEST(UnflattenWithCustomFactory, r) {
+ // Create and flatten the test flattenable
+ SkWriteBuffer writeBuffer;
+ SkAutoTUnref<SkFlattenable> flattenable1(new IntFlattenable(1, 2, 3, 4));
+ writeBuffer.writeFlattenable(flattenable1);
+ SkAutoTUnref<SkFlattenable> flattenable2(new IntFlattenable(2, 3, 4, 5));
+ writeBuffer.writeFlattenable(flattenable2);
+ SkAutoTUnref<SkFlattenable> flattenable3(new IntFlattenable(3, 4, 5, 6));
+ writeBuffer.writeFlattenable(flattenable3);
+
+ // Copy the contents of the write buffer into a read buffer
+ sk_sp<SkData> data = SkData::MakeUninitialized(writeBuffer.bytesWritten());
+ writeBuffer.writeToMemory(data->writable_data());
+ SkReadBuffer readBuffer(data->data(), data->size());
+
+ // Register a custom factory with the read buffer
+ readBuffer.setCustomFactory(SkString("IntFlattenable"), &custom_create_proc);
+
+ // Unflatten and verify the flattenables
+ SkAutoTUnref<IntFlattenable> out1((IntFlattenable*) readBuffer.readFlattenable(
+ SkFlattenable::kSkUnused_Type));
+ REPORTER_ASSERT(r, out1);
+ REPORTER_ASSERT(r, 2 == out1->a());
+ REPORTER_ASSERT(r, 3 == out1->b());
+ REPORTER_ASSERT(r, 4 == out1->c());
+ REPORTER_ASSERT(r, 5 == out1->d());
+
+ SkAutoTUnref<IntFlattenable> out2((IntFlattenable*) readBuffer.readFlattenable(
+ SkFlattenable::kSkUnused_Type));
+ REPORTER_ASSERT(r, out2);
+ REPORTER_ASSERT(r, 3 == out2->a());
+ REPORTER_ASSERT(r, 4 == out2->b());
+ REPORTER_ASSERT(r, 5 == out2->c());
+ REPORTER_ASSERT(r, 6 == out2->d());
+
+ SkAutoTUnref<IntFlattenable> out3((IntFlattenable*) readBuffer.readFlattenable(
+ SkFlattenable::kSkUnused_Type));
+ REPORTER_ASSERT(r, out3);
+ REPORTER_ASSERT(r, 4 == out3->a());
+ REPORTER_ASSERT(r, 5 == out3->b());
+ REPORTER_ASSERT(r, 6 == out3->c());
+ REPORTER_ASSERT(r, 7 == out3->d());
+}
template<typename T, bool testInvalid>
static void TestObjectSerializationNoAlign(T* testObj, skiatest::Reporter* reporter) {
- SkWriteBuffer writer(SkWriteBuffer::kValidation_Flag);
+ SkWriteBuffer writer;
SerializationUtils<T>::Write(writer, testObj);
size_t bytesWritten = writer.bytesWritten();
REPORTER_ASSERT(reporter, SkAlign4(bytesWritten) == bytesWritten);
template<typename T>
static T* TestFlattenableSerialization(T* testObj, bool shouldSucceed,
skiatest::Reporter* reporter) {
- SkWriteBuffer writer(SkWriteBuffer::kValidation_Flag);
+ SkWriteBuffer writer;
SerializationUtils<T>::Write(writer, testObj);
size_t bytesWritten = writer.bytesWritten();
REPORTER_ASSERT(reporter, SkAlign4(bytesWritten) == bytesWritten);
template<typename T>
static void TestArraySerialization(T* data, skiatest::Reporter* reporter) {
- SkWriteBuffer writer(SkWriteBuffer::kValidation_Flag);
+ SkWriteBuffer writer;
SerializationUtils<T>::Write(writer, data, kArraySize);
size_t bytesWritten = writer.bytesWritten();
// This should write the length (in 4 bytes) and the array
sk_sp<SkPicture> pict(recorder.finishRecordingAsPicture());
// Serialize picture
- SkWriteBuffer writer(SkWriteBuffer::kValidation_Flag);
+ SkWriteBuffer writer;
pict->flatten(writer);
size_t size = writer.bytesWritten();
SkAutoTMalloc<unsigned char> data(size);