#include "SkPictureFlat.h"
+#include "SkPictureData.h"
#include "SkPicturePlayback.h"
#include "SkPictureRecord.h"
+#include "SkPictureRecorder.h"
-#include "SkBBHFactory.h"
#include "SkBitmapDevice.h"
#include "SkCanvas.h"
#include "SkChunkAlloc.h"
+#include "SkDrawPictureCallback.h"
#include "SkPaintPriv.h"
+#include "SkPathEffect.h"
#include "SkPicture.h"
#include "SkRegion.h"
+#include "SkShader.h"
#include "SkStream.h"
#include "SkTDArray.h"
+#include "SkTLogic.h"
#include "SkTSearch.h"
#include "SkTime.h"
#include "SkReader32.h"
#include "SkWriter32.h"
#include "SkRTree.h"
-#include "SkBBoxHierarchyRecord.h"
#if SK_SUPPORT_GPU
#include "GrContext.h"
#endif
+#include "SkRecord.h"
+#include "SkRecordDraw.h"
+#include "SkRecordOpts.h"
+#include "SkRecorder.h"
+
template <typename T> int SafeCount(const T* obj) {
return obj ? obj->count() : 0;
}
-#define DUMP_BUFFER_SIZE 65536
+///////////////////////////////////////////////////////////////////////////////
-//#define ENABLE_TIME_DRAW // dumps milliseconds for each draw
+namespace {
+// Some commands have a paint, some have an optional paint. Either way, get back a pointer.
+static const SkPaint* AsPtr(const SkPaint& p) { return &p; }
+static const SkPaint* AsPtr(const SkRecords::Optional<SkPaint>& p) { return p; }
-#ifdef SK_DEBUG
-// enable SK_DEBUG_TRACE to trace DrawType elements when
-// recorded and played back
-// #define SK_DEBUG_TRACE
-// enable SK_DEBUG_SIZE to see the size of picture components
-// #define SK_DEBUG_SIZE
-// enable SK_DEBUG_DUMP to see the contents of recorded elements
-// #define SK_DEBUG_DUMP
-// enable SK_DEBUG_VALIDATE to check internal structures for consistency
-// #define SK_DEBUG_VALIDATE
-#endif
+/** SkRecords visitor to determine whether an instance may require an
+ "external" bitmap to rasterize. May return false positives.
+ Does not return true for bitmap text.
-#if defined SK_DEBUG_TRACE || defined SK_DEBUG_DUMP
-const char* DrawTypeToString(DrawType drawType) {
- switch (drawType) {
- case UNUSED: SkDebugf("DrawType UNUSED\n"); SkASSERT(0); break;
- case CLIP_PATH: return "CLIP_PATH";
- case CLIP_REGION: return "CLIP_REGION";
- case CLIP_RECT: return "CLIP_RECT";
- case CLIP_RRECT: return "CLIP_RRECT";
- case CONCAT: return "CONCAT";
- case DRAW_BITMAP: return "DRAW_BITMAP";
- case DRAW_BITMAP_MATRIX: return "DRAW_BITMAP_MATRIX";
- case DRAW_BITMAP_NINE: return "DRAW_BITMAP_NINE";
- case DRAW_BITMAP_RECT_TO_RECT: return "DRAW_BITMAP_RECT_TO_RECT";
- case DRAW_CLEAR: return "DRAW_CLEAR";
- case DRAW_DATA: return "DRAW_DATA";
- case DRAW_OVAL: return "DRAW_OVAL";
- case DRAW_PAINT: return "DRAW_PAINT";
- case DRAW_PATH: return "DRAW_PATH";
- case DRAW_PICTURE: return "DRAW_PICTURE";
- case DRAW_POINTS: return "DRAW_POINTS";
- case DRAW_POS_TEXT: return "DRAW_POS_TEXT";
- case DRAW_POS_TEXT_TOP_BOTTOM: return "DRAW_POS_TEXT_TOP_BOTTOM";
- case DRAW_POS_TEXT_H: return "DRAW_POS_TEXT_H";
- case DRAW_POS_TEXT_H_TOP_BOTTOM: return "DRAW_POS_TEXT_H_TOP_BOTTOM";
- case DRAW_RECT: return "DRAW_RECT";
- case DRAW_RRECT: return "DRAW_RRECT";
- case DRAW_SPRITE: return "DRAW_SPRITE";
- case DRAW_TEXT: return "DRAW_TEXT";
- case DRAW_TEXT_ON_PATH: return "DRAW_TEXT_ON_PATH";
- case DRAW_TEXT_TOP_BOTTOM: return "DRAW_TEXT_TOP_BOTTOM";
- case DRAW_VERTICES: return "DRAW_VERTICES";
- case RESTORE: return "RESTORE";
- case ROTATE: return "ROTATE";
- case SAVE: return "SAVE";
- case SAVE_LAYER: return "SAVE_LAYER";
- case SCALE: return "SCALE";
- case SET_MATRIX: return "SET_MATRIX";
- case SKEW: return "SKEW";
- case TRANSLATE: return "TRANSLATE";
- case NOOP: return "NOOP";
- default:
- SkDebugf("DrawType error 0x%08x\n", drawType);
- SkASSERT(0);
- break;
+ Expected use is to determine whether images need to be decoded before
+ rasterizing a particular SkRecord.
+ */
+struct BitmapTester {
+ // Helpers. These create HasMember_bitmap and HasMember_paint.
+ SK_CREATE_MEMBER_DETECTOR(bitmap);
+ SK_CREATE_MEMBER_DETECTOR(paint);
+
+
+ // Main entry for visitor:
+ // If the command is a DrawPicture, recurse.
+ // If the command has a bitmap directly, return true.
+ // If the command has a paint and the paint has a bitmap, return true.
+ // Otherwise, return false.
+ bool operator()(const SkRecords::DrawPicture& op) { return op.picture->willPlayBackBitmaps(); }
+
+ template <typename T>
+ bool operator()(const T& r) { return CheckBitmap(r); }
+
+
+ // If the command has a bitmap, of course we're going to play back bitmaps.
+ template <typename T>
+ static SK_WHEN(HasMember_bitmap<T>, bool) CheckBitmap(const T&) { return true; }
+
+ // If not, look for one in its paint (if it has a paint).
+ template <typename T>
+ static SK_WHEN(!HasMember_bitmap<T>, bool) CheckBitmap(const T& r) { return CheckPaint(r); }
+
+ // If we have a paint, dig down into the effects looking for a bitmap.
+ template <typename T>
+ static SK_WHEN(HasMember_paint<T>, bool) CheckPaint(const T& r) {
+ const SkPaint* paint = AsPtr(r.paint);
+ if (paint) {
+ const SkShader* shader = paint->getShader();
+ if (shader &&
+ shader->asABitmap(NULL, NULL, NULL) == SkShader::kDefault_BitmapType) {
+ return true;
+ }
+ }
+ return false;
}
- SkASSERT(0);
- return NULL;
-}
-#endif
-#ifdef SK_DEBUG_VALIDATE
-static void validateMatrix(const SkMatrix* matrix) {
- SkScalar scaleX = matrix->getScaleX();
- SkScalar scaleY = matrix->getScaleY();
- SkScalar skewX = matrix->getSkewX();
- SkScalar skewY = matrix->getSkewY();
- SkScalar perspX = matrix->getPerspX();
- SkScalar perspY = matrix->getPerspY();
- if (scaleX != 0 && skewX != 0)
- SkDebugf("scaleX != 0 && skewX != 0\n");
- SkASSERT(scaleX == 0 || skewX == 0);
- SkASSERT(scaleY == 0 || skewY == 0);
- SkASSERT(perspX == 0);
- SkASSERT(perspY == 0);
-}
-#endif
-
-
-///////////////////////////////////////////////////////////////////////////////
+ // If we don't have a paint, that non-paint has no bitmap.
+ template <typename T>
+ static SK_WHEN(!HasMember_paint<T>, bool) CheckPaint(const T&) { return false; }
+};
-SkPicture::SkPicture()
- : fAccelData(NULL) {
- this->needsNewGenID();
- fRecord = NULL;
- fPlayback = NULL;
- fWidth = fHeight = 0;
-}
-
-SkPicture::SkPicture(const SkPicture& src)
- : INHERITED()
- , fAccelData(NULL)
- , fContentInfo(src.fContentInfo) {
- this->needsNewGenID();
- fWidth = src.fWidth;
- fHeight = src.fHeight;
- fRecord = NULL;
-
- /* We want to copy the src's playback. However, if that hasn't been built
- yet, we need to fake a call to endRecording() without actually calling
- it (since it is destructive, and we don't want to change src).
- */
- if (src.fPlayback) {
- fPlayback = SkNEW_ARGS(SkPicturePlayback, (this, *src.fPlayback));
- SkASSERT(NULL == src.fRecord);
- fUniqueID = src.uniqueID(); // need to call method to ensure != 0
- } else if (src.fRecord) {
- SkPictInfo info;
- this->createHeader(&info);
- // here we do a fake src.endRecording()
- fPlayback = SkNEW_ARGS(SkPicturePlayback, (this, *src.fRecord, info));
- } else {
- fPlayback = NULL;
+bool WillPlaybackBitmaps(const SkRecord& record) {
+ BitmapTester tester;
+ for (unsigned i = 0; i < record.count(); i++) {
+ if (record.visit<bool>(i, tester)) {
+ return true;
+ }
}
-
- fPathHeap.reset(SkSafeRef(src.fPathHeap.get()));
+ return false;
}
-const SkPath& SkPicture::getPath(int index) const {
- return (*fPathHeap.get())[index];
-}
+// SkRecord visitor to find recorded text.
+struct TextHunter {
+ // All ops with text have that text as a char array member named "text".
+ SK_CREATE_MEMBER_DETECTOR(text);
+ bool operator()(const SkRecords::DrawPicture& op) { return op.picture->hasText(); }
+ template <typename T> SK_WHEN(HasMember_text<T>, bool) operator()(const T&) { return true; }
+ template <typename T> SK_WHEN(!HasMember_text<T>, bool) operator()(const T&) { return false; }
+};
-int SkPicture::addPathToHeap(const SkPath& path) {
- if (NULL == fPathHeap) {
- fPathHeap.reset(SkNEW(SkPathHeap));
- }
-#ifdef SK_DEDUP_PICTURE_PATHS
- return fPathHeap->insert(path);
-#else
- return fPathHeap->append(path);
-#endif
-}
+} // namespace
-void SkPicture::initForPlayback() const {
- // ensure that the paths bounds are pre-computed
- if (NULL != fPathHeap.get()) {
- for (int i = 0; i < fPathHeap->count(); i++) {
- (*fPathHeap.get())[i].updateBoundsCache();
+/** SkRecords visitor to determine heuristically whether or not a SkPicture
+ will be performant when rasterized on the GPU.
+ */
+struct SkPicture::PathCounter {
+ SK_CREATE_MEMBER_DETECTOR(paint);
+
+ PathCounter()
+ : numPaintWithPathEffectUses (0)
+ , numFastPathDashEffects (0)
+ , numAAConcavePaths (0)
+ , numAAHairlineConcavePaths (0)
+ , numAADFEligibleConcavePaths(0) {
+ }
+
+ // Recurse into nested pictures.
+ void operator()(const SkRecords::DrawPicture& op) {
+ const SkPicture::Analysis& analysis = op.picture->fAnalysis;
+ numPaintWithPathEffectUses += analysis.fNumPaintWithPathEffectUses;
+ numFastPathDashEffects += analysis.fNumFastPathDashEffects;
+ numAAConcavePaths += analysis.fNumAAConcavePaths;
+ numAAHairlineConcavePaths += analysis.fNumAAHairlineConcavePaths;
+ numAADFEligibleConcavePaths += analysis.fNumAADFEligibleConcavePaths;
+ }
+
+ void checkPaint(const SkPaint* paint) {
+ if (paint && paint->getPathEffect()) {
+ numPaintWithPathEffectUses++;
}
}
-}
-void SkPicture::dumpSize() const {
- SkDebugf("--- picture size: paths=%d\n",
- SafeCount(fPathHeap.get()));
-}
-
-SkPicture::~SkPicture() {
- SkSafeUnref(fRecord);
- SkDELETE(fPlayback);
- SkSafeUnref(fAccelData);
-}
-
-void SkPicture::internalOnly_EnableOpts(bool enableOpts) {
- if (NULL != fRecord) {
- fRecord->internalOnly_EnableOpts(enableOpts);
+ void operator()(const SkRecords::DrawPoints& op) {
+ this->checkPaint(&op.paint);
+ const SkPathEffect* effect = op.paint.getPathEffect();
+ if (effect) {
+ SkPathEffect::DashInfo info;
+ SkPathEffect::DashType dashType = effect->asADash(&info);
+ if (2 == op.count && SkPaint::kRound_Cap != op.paint.getStrokeCap() &&
+ SkPathEffect::kDash_DashType == dashType && 2 == info.fCount) {
+ numFastPathDashEffects++;
+ }
+ }
}
-}
-
-void SkPicture::swap(SkPicture& other) {
- SkTSwap(fUniqueID, other.fUniqueID);
- SkTSwap(fRecord, other.fRecord);
- SkTSwap(fPlayback, other.fPlayback);
- SkTSwap(fAccelData, other.fAccelData);
- SkTSwap(fWidth, other.fWidth);
- SkTSwap(fHeight, other.fHeight);
- fPathHeap.swap(&other.fPathHeap);
- fContentInfo.swap(&other.fContentInfo);
-}
-SkPicture* SkPicture::clone() const {
- SkPicture* clonedPicture = SkNEW(SkPicture);
- this->clone(clonedPicture, 1);
- return clonedPicture;
-}
+ void operator()(const SkRecords::DrawPath& op) {
+ this->checkPaint(&op.paint);
+ if (op.paint.isAntiAlias() && !op.path.isConvex()) {
+ numAAConcavePaths++;
-void SkPicture::clone(SkPicture* pictures, int count) const {
- SkPictCopyInfo copyInfo;
- SkPictInfo info;
- this->createHeader(&info);
-
- for (int i = 0; i < count; i++) {
- SkPicture* clone = &pictures[i];
-
- clone->needsNewGenID();
- clone->fWidth = fWidth;
- clone->fHeight = fHeight;
- SkSafeSetNull(clone->fRecord);
- SkDELETE(clone->fPlayback);
- clone->fContentInfo.set(fContentInfo);
-
- /* We want to copy the src's playback. However, if that hasn't been built
- yet, we need to fake a call to endRecording() without actually calling
- it (since it is destructive, and we don't want to change src).
- */
- if (fPlayback) {
- if (!copyInfo.initialized) {
- int paintCount = SafeCount(fPlayback->fPaints);
-
- /* The alternative to doing this is to have a clone method on the paint and have it
- * make the deep copy of its internal structures as needed. The holdup to doing
- * that is at this point we would need to pass the SkBitmapHeap so that we don't
- * unnecessarily flatten the pixels in a bitmap shader.
- */
- copyInfo.paintData.setCount(paintCount);
-
- /* Use an SkBitmapHeap to avoid flattening bitmaps in shaders. If there already is
- * one, use it. If this SkPicturePlayback was created from a stream, fBitmapHeap
- * will be NULL, so create a new one.
- */
- if (fPlayback->fBitmapHeap.get() == NULL) {
- // FIXME: Put this on the stack inside SkPicture::clone.
- SkBitmapHeap* heap = SkNEW(SkBitmapHeap);
- copyInfo.controller.setBitmapStorage(heap);
- heap->unref();
- } else {
- copyInfo.controller.setBitmapStorage(fPlayback->fBitmapHeap);
- }
-
- SkDEBUGCODE(int heapSize = SafeCount(fPlayback->fBitmapHeap.get());)
- for (int i = 0; i < paintCount; i++) {
- if (NeedsDeepCopy(fPlayback->fPaints->at(i))) {
- copyInfo.paintData[i] =
- SkFlatData::Create<SkPaint::FlatteningTraits>(©Info.controller,
- fPlayback->fPaints->at(i), 0);
-
- } else {
- // this is our sentinel, which we use in the unflatten loop
- copyInfo.paintData[i] = NULL;
- }
- }
- SkASSERT(SafeCount(fPlayback->fBitmapHeap.get()) == heapSize);
-
- // needed to create typeface playback
- copyInfo.controller.setupPlaybacks();
- copyInfo.initialized = true;
+ SkPaint::Style paintStyle = op.paint.getStyle();
+ const SkRect& pathBounds = op.path.getBounds();
+ if (SkPaint::kStroke_Style == paintStyle &&
+ 0 == op.paint.getStrokeWidth()) {
+ numAAHairlineConcavePaths++;
+ } else if (SkPaint::kFill_Style == paintStyle && pathBounds.width() < 64.f &&
+ pathBounds.height() < 64.f && !op.path.isVolatile()) {
+ numAADFEligibleConcavePaths++;
}
-
- clone->fPlayback = SkNEW_ARGS(SkPicturePlayback, (clone, *fPlayback, ©Info));
- SkASSERT(NULL == fRecord);
- clone->fUniqueID = this->uniqueID(); // need to call method to ensure != 0
- } else if (fRecord) {
- // here we do a fake src.endRecording()
- clone->fPlayback = SkNEW_ARGS(SkPicturePlayback, (clone, *fRecord, info, true));
- } else {
- clone->fPlayback = NULL;
}
-
- clone->fPathHeap.reset(SkSafeRef(fPathHeap.get()));
}
-}
-SkPicture::AccelData::Domain SkPicture::AccelData::GenerateDomain() {
- static int32_t gNextID = 0;
-
- int32_t id = sk_atomic_inc(&gNextID);
- if (id >= 1 << (8 * sizeof(Domain))) {
- SK_CRASH();
+ template <typename T>
+ SK_WHEN(HasMember_paint<T>, void) operator()(const T& op) {
+ this->checkPaint(AsPtr(op.paint));
}
- return static_cast<Domain>(id);
-}
+ template <typename T>
+ SK_WHEN(!HasMember_paint<T>, void) operator()(const T& op) { /* do nothing */ }
-///////////////////////////////////////////////////////////////////////////////
+ int numPaintWithPathEffectUses;
+ int numFastPathDashEffects;
+ int numAAConcavePaths;
+ int numAAHairlineConcavePaths;
+ int numAADFEligibleConcavePaths;
+};
-#ifdef SK_SUPPORT_LEGACY_DERIVED_PICTURE_CLASSES
+SkPicture::Analysis::Analysis(const SkRecord& record) {
+ fWillPlaybackBitmaps = WillPlaybackBitmaps(record);
-SkCanvas* SkPicture::beginRecording(int width, int height,
- uint32_t recordingFlags) {
- if (fPlayback) {
- SkDELETE(fPlayback);
- fPlayback = NULL;
+ PathCounter counter;
+ for (unsigned i = 0; i < record.count(); i++) {
+ record.visit<void>(i, counter);
}
- SkSafeUnref(fAccelData);
- SkSafeSetNull(fRecord);
- fContentInfo.reset();
-
- this->needsNewGenID();
-
- // Must be set before calling createBBoxHierarchy
- fWidth = width;
- fHeight = height;
+ fNumPaintWithPathEffectUses = counter.numPaintWithPathEffectUses;
+ fNumFastPathDashEffects = counter.numFastPathDashEffects;
+ fNumAAConcavePaths = counter.numAAConcavePaths;
+ fNumAAHairlineConcavePaths = counter.numAAHairlineConcavePaths;
+ fNumAADFEligibleConcavePaths = counter.numAADFEligibleConcavePaths;
- const SkISize size = SkISize::Make(width, height);
-
- if (recordingFlags & kOptimizeForClippedPlayback_RecordingFlag) {
- SkBBoxHierarchy* tree = this->createBBoxHierarchy();
- SkASSERT(NULL != tree);
- fRecord = SkNEW_ARGS(SkBBoxHierarchyRecord, (this, size, recordingFlags, tree));
- tree->unref();
- } else {
- fRecord = SkNEW_ARGS(SkPictureRecord, (this, size, recordingFlags));
+ fHasText = false;
+ TextHunter text;
+ for (unsigned i = 0; i < record.count(); i++) {
+ if (record.visit<bool>(i, text)) {
+ fHasText = true;
+ break;
+ }
}
- fRecord->beginRecording();
-
- return fRecord;
}
-#endif
-
-SkCanvas* SkPicture::beginRecording(int width, int height,
- SkBBHFactory* bbhFactory,
- uint32_t recordingFlags) {
- if (fPlayback) {
- SkDELETE(fPlayback);
- fPlayback = NULL;
- }
- SkSafeUnref(fAccelData);
- SkSafeSetNull(fRecord);
- SkASSERT(NULL == fPathHeap);
- fContentInfo.reset();
-
- this->needsNewGenID();
-
- fWidth = width;
- fHeight = height;
-
- const SkISize size = SkISize::Make(width, height);
-
- if (NULL != bbhFactory) {
- SkAutoTUnref<SkBBoxHierarchy> tree((*bbhFactory)(width, height));
- SkASSERT(NULL != tree);
- fRecord = SkNEW_ARGS(SkBBoxHierarchyRecord, (this, size,
- recordingFlags|
- kOptimizeForClippedPlayback_RecordingFlag,
- tree.get()));
- } else {
- fRecord = SkNEW_ARGS(SkPictureRecord, (this, size, recordingFlags));
+bool SkPicture::Analysis::suitableForGpuRasterization(const char** reason,
+ int sampleCount) const {
+ // TODO: the heuristic used here needs to be refined
+ static const int kNumPaintWithPathEffectsUsesTol = 1;
+ static const int kNumAAConcavePathsTol = 5;
+
+ int numNonDashedPathEffects = fNumPaintWithPathEffectUses -
+ fNumFastPathDashEffects;
+ bool suitableForDash = (0 == fNumPaintWithPathEffectUses) ||
+ (numNonDashedPathEffects < kNumPaintWithPathEffectsUsesTol
+ && 0 == sampleCount);
+
+ bool ret = suitableForDash &&
+ (fNumAAConcavePaths - fNumAAHairlineConcavePaths - fNumAADFEligibleConcavePaths)
+ < kNumAAConcavePathsTol;
+
+ if (!ret && reason) {
+ if (!suitableForDash) {
+ if (0 != sampleCount) {
+ *reason = "Can't use multisample on dash effect.";
+ } else {
+ *reason = "Too many non dashed path effects.";
+ }
+ } else if ((fNumAAConcavePaths - fNumAAHairlineConcavePaths - fNumAADFEligibleConcavePaths)
+ >= kNumAAConcavePathsTol)
+ *reason = "Too many anti-aliased concave paths.";
+ else
+ *reason = "Unknown reason for GPU unsuitability.";
}
- fRecord->beginRecording();
-
- return fRecord;
+ return ret;
}
+///////////////////////////////////////////////////////////////////////////////
-#ifdef SK_SUPPORT_LEGACY_DERIVED_PICTURE_CLASSES
-
-SkBBoxHierarchy* SkPicture::createBBoxHierarchy() const {
- // TODO: this code is now replicated in SkRTreePicture. Once all external
- // clients have been weaned off of kOptimizeForClippedPlayback_RecordingFlag,
- // this code can be removed.
-
- // These values were empirically determined to produce reasonable
- // performance in most cases.
- static const int kRTreeMinChildren = 6;
- static const int kRTreeMaxChildren = 11;
+// fRecord OK
+SkPicture::SkPicture(SkScalar width, SkScalar height,
+ const SkPictureRecord& record,
+ bool deepCopyOps)
+ : fCullWidth(width)
+ , fCullHeight(height)
+ , fAnalysis() {
+ this->needsNewGenID();
- SkScalar aspectRatio = SkScalarDiv(SkIntToScalar(fWidth),
- SkIntToScalar(fHeight));
- bool sortDraws = false; // Do not sort draw calls when bulk loading.
+ SkPictInfo info;
+ this->createHeader(&info);
+ fData.reset(SkNEW_ARGS(SkPictureData, (record, info, deepCopyOps)));
+}
- return SkRTree::Create(kRTreeMinChildren, kRTreeMaxChildren,
- aspectRatio, sortDraws);
+// Create an SkPictureData-backed SkPicture from an SkRecord.
+// This for compatibility with serialization code only. This is not cheap.
+SkPicture* SkPicture::Backport(const SkRecord& src, const SkRect& cullRect) {
+ SkPictureRecord rec(SkISize::Make(cullRect.width(), cullRect.height()), 0/*flags*/);
+ rec.beginRecording();
+ SkRecordDraw(src, &rec, NULL/*bbh*/, NULL/*callback*/);
+ rec.endRecording();
+ return SkNEW_ARGS(SkPicture, (cullRect.width(), cullRect.height(), rec, false/*deepCopyOps*/));
}
-#endif
+// fRecord OK
+SkPicture::~SkPicture() {
+ this->callDeletionListeners();
+}
-SkCanvas* SkPicture::getRecordingCanvas() const {
- // will be null if we are not recording
- return fRecord;
+// fRecord OK
+void SkPicture::EXPERIMENTAL_addAccelData(const SkPicture::AccelData* data) const {
+ fAccelData.reset(SkRef(data));
}
-void SkPicture::endRecording() {
- if (NULL == fPlayback) {
- if (NULL != fRecord) {
- fRecord->endRecording();
- SkPictInfo info;
- this->createHeader(&info);
- fPlayback = SkNEW_ARGS(SkPicturePlayback, (this, *fRecord, info));
- SkSafeSetNull(fRecord);
- }
+// fRecord OK
+const SkPicture::AccelData* SkPicture::EXPERIMENTAL_getAccelData(
+ SkPicture::AccelData::Key key) const {
+ if (fAccelData.get() && fAccelData->getKey() == key) {
+ return fAccelData.get();
}
- SkASSERT(NULL == fRecord);
+ return NULL;
}
-const SkPicture::OperationList& SkPicture::OperationList::InvalidList() {
- static OperationList gInvalid;
- return gInvalid;
-}
+// fRecord OK
+SkPicture::AccelData::Domain SkPicture::AccelData::GenerateDomain() {
+ static int32_t gNextID = 0;
-const SkPicture::OperationList& SkPicture::EXPERIMENTAL_getActiveOps(const SkIRect& queryRect) {
- this->endRecording(); // TODO: remove eventually
- if (NULL != fPlayback) {
- return fPlayback->getActiveOps(queryRect);
+ int32_t id = sk_atomic_inc(&gNextID);
+ if (id >= 1 << (8 * sizeof(Domain))) {
+ SK_CRASH();
}
- return OperationList::InvalidList();
+
+ return static_cast<Domain>(id);
}
-size_t SkPicture::EXPERIMENTAL_curOpID() const {
- if (NULL != fPlayback) {
- return fPlayback->curOpID();
+///////////////////////////////////////////////////////////////////////////////
+
+// fRecord OK
+void SkPicture::playback(SkCanvas* canvas, SkDrawPictureCallback* callback) const {
+ SkASSERT(canvas);
+ SkASSERT(fData.get() || fRecord.get());
+
+ if (fData.get()) {
+ SkPicturePlayback playback(this);
+ playback.draw(canvas, callback);
}
- return 0;
-}
+ if (fRecord.get()) {
+ // If the query contains the whole picture, don't bother with the BBH.
+ SkRect clipBounds = { 0, 0, 0, 0 };
+ (void)canvas->getClipBounds(&clipBounds);
+ const bool useBBH = !clipBounds.contains(this->cullRect());
-void SkPicture::draw(SkCanvas* surface, SkDrawPictureCallback* callback) {
- this->endRecording(); // TODO: remove eventually
- if (NULL != fPlayback) {
- fPlayback->draw(*surface, callback);
+ SkRecordDraw(*fRecord, canvas, useBBH ? fBBH.get() : NULL, callback);
}
}
static const char kMagic[] = { 's', 'k', 'i', 'a', 'p', 'i', 'c', 't' };
+// fRecord OK
bool SkPicture::IsValidPictInfo(const SkPictInfo& info) {
if (0 != memcmp(info.fMagic, kMagic, sizeof(kMagic))) {
return false;
return true;
}
+// fRecord OK
bool SkPicture::InternalOnly_StreamIsSKP(SkStream* stream, SkPictInfo* pInfo) {
if (NULL == stream) {
return false;
// Check magic bytes.
SkPictInfo info;
SkASSERT(sizeof(kMagic) == sizeof(info.fMagic));
- if (!stream->read(&info, sizeof(info)) || !IsValidPictInfo(info)) {
+
+ if (!stream->read(&info.fMagic, sizeof(kMagic))) {
+ return false;
+ }
+
+ info.fVersion = stream->readU32();
+
+#ifndef V35_COMPATIBILITY_CODE
+ if (info.fVersion < 35) {
+ info.fCullRect.fLeft = 0;
+ info.fCullRect.fTop = 0;
+ info.fCullRect.fRight = SkIntToScalar(stream->readU32());
+ info.fCullRect.fBottom = SkIntToScalar(stream->readU32());
+ } else {
+#endif
+ info.fCullRect.fLeft = stream->readScalar();
+ info.fCullRect.fTop = stream->readScalar();
+ info.fCullRect.fRight = stream->readScalar();
+ info.fCullRect.fBottom = stream->readScalar();
+#ifndef V35_COMPATIBILITY_CODE
+ }
+#endif
+
+ info.fFlags = stream->readU32();
+
+ if (!IsValidPictInfo(info)) {
return false;
}
return true;
}
-bool SkPicture::InternalOnly_BufferIsSKP(SkReadBuffer& buffer, SkPictInfo* pInfo) {
+// fRecord OK
+bool SkPicture::InternalOnly_BufferIsSKP(SkReadBuffer* buffer, SkPictInfo* pInfo) {
// Check magic bytes.
SkPictInfo info;
SkASSERT(sizeof(kMagic) == sizeof(info.fMagic));
- if (!buffer.readByteArray(&info, sizeof(info)) || !IsValidPictInfo(info)) {
+
+ if (!buffer->readByteArray(&info.fMagic, sizeof(kMagic))) {
+ return false;
+ }
+
+ info.fVersion = buffer->readUInt();
+
+#ifndef V35_COMPATIBILITY_CODE
+ if (info.fVersion < 35) {
+ info.fCullRect.fLeft = 0;
+ info.fCullRect.fTop = 0;
+ info.fCullRect.fRight = SkIntToScalar(buffer->readUInt());
+ info.fCullRect.fBottom = SkIntToScalar(buffer->readUInt());
+ } else {
+#endif
+ buffer->readRect(&info.fCullRect);
+#ifndef V35_COMPATIBILITY_CODE
+ }
+#endif
+
+ info.fFlags = buffer->readUInt();
+
+ if (!IsValidPictInfo(info)) {
return false;
}
return true;
}
-SkPicture::SkPicture(SkPicturePlayback* playback, int width, int height)
- : fPlayback(playback)
- , fRecord(NULL)
- , fWidth(width)
- , fHeight(height)
- , fAccelData(NULL) {
+// fRecord OK
+SkPicture::SkPicture(SkPictureData* data, SkScalar width, SkScalar height)
+ : fData(data)
+ , fCullWidth(width)
+ , fCullHeight(height)
+ , fAnalysis() {
this->needsNewGenID();
}
+SkPicture* SkPicture::Forwardport(const SkPicture& src) {
+ SkAutoTDelete<SkRecord> record(SkNEW(SkRecord));
+ SkRecorder canvas(record.get(), src.cullRect().width(), src.cullRect().height());
+ src.playback(&canvas);
+ return SkNEW_ARGS(SkPicture, (src.cullRect().width(), src.cullRect().height(),
+ record.detach(), NULL/*bbh*/));
+}
+
+// fRecord OK
SkPicture* SkPicture::CreateFromStream(SkStream* stream, InstallPixelRefProc proc) {
SkPictInfo info;
return NULL;
}
- SkPicture* newPict = SkNEW_ARGS(SkPicture, (NULL, info.fWidth, info.fHeight));
-
// Check to see if there is a playback to recreate.
if (stream->readBool()) {
- SkPicturePlayback* playback = SkPicturePlayback::CreateFromStream(newPict, stream,
- info, proc);
- if (NULL == playback) {
- SkDELETE(newPict);
+ SkPictureData* data = SkPictureData::CreateFromStream(stream, info, proc);
+ if (NULL == data) {
return NULL;
}
- newPict->fPlayback = playback;
+ const SkPicture src(data, info.fCullRect.width(), info.fCullRect.height());
+ return Forwardport(src);
}
- return newPict;
+ return NULL;
}
+// fRecord OK
SkPicture* SkPicture::CreateFromBuffer(SkReadBuffer& buffer) {
SkPictInfo info;
- if (!InternalOnly_BufferIsSKP(buffer, &info)) {
+ if (!InternalOnly_BufferIsSKP(&buffer, &info)) {
return NULL;
}
- SkPicture* newPict = SkNEW_ARGS(SkPicture, (NULL, info.fWidth, info.fHeight));
-
// Check to see if there is a playback to recreate.
if (buffer.readBool()) {
- SkPicturePlayback* playback = SkPicturePlayback::CreateFromBuffer(newPict, buffer, info);
- if (NULL == playback) {
- SkDELETE(newPict);
+ SkPictureData* data = SkPictureData::CreateFromBuffer(buffer, info);
+ if (NULL == data) {
return NULL;
}
- newPict->fPlayback = playback;
+ const SkPicture src(data, info.fCullRect.width(), info.fCullRect.height());
+ return Forwardport(src);
}
- return newPict;
+ return NULL;
}
+// fRecord OK
void SkPicture::createHeader(SkPictInfo* info) const {
// Copy magic bytes at the beginning of the header
SkASSERT(sizeof(kMagic) == 8);
// Set picture info after magic bytes in the header
info->fVersion = CURRENT_PICTURE_VERSION;
- info->fWidth = fWidth;
- info->fHeight = fHeight;
+ info->fCullRect = this->cullRect();
info->fFlags = SkPictInfo::kCrossProcess_Flag;
// TODO: remove this flag, since we're always float (now)
info->fFlags |= SkPictInfo::kScalarIsFloat_Flag;
}
}
+// fRecord OK
void SkPicture::serialize(SkWStream* stream, EncodeBitmap encoder) const {
- SkPicturePlayback* playback = fPlayback;
+ const SkPictureData* data = fData.get();
- SkPictInfo info;
- this->createHeader(&info);
- if (NULL == playback && fRecord) {
- playback = SkNEW_ARGS(SkPicturePlayback, (this, *fRecord, info));
+ // If we're a new-format picture, backport to old format for serialization.
+ SkAutoTDelete<SkPicture> oldFormat;
+ if (NULL == data && fRecord.get()) {
+ oldFormat.reset(Backport(*fRecord, this->cullRect()));
+ data = oldFormat->fData.get();
+ SkASSERT(data);
}
+ SkPictInfo info;
+ this->createHeader(&info);
+ SkASSERT(sizeof(SkPictInfo) == 32);
stream->write(&info, sizeof(info));
- if (playback) {
+
+ if (data) {
stream->writeBool(true);
- playback->serialize(stream, encoder);
- // delete playback if it is a local version (i.e. cons'd up just now)
- if (playback != fPlayback) {
- SkDELETE(playback);
- }
+ data->serialize(stream, encoder);
} else {
stream->writeBool(false);
}
}
-void SkPicture::WriteTagSize(SkWriteBuffer& buffer, uint32_t tag, size_t size) {
- buffer.writeUInt(tag);
- buffer.writeUInt(SkToU32(size));
-}
-
-void SkPicture::WriteTagSize(SkWStream* stream, uint32_t tag, size_t size) {
- stream->write32(tag);
- stream->write32(SkToU32(size));
-}
-
-bool SkPicture::parseBufferTag(SkReadBuffer& buffer,
- uint32_t tag,
- uint32_t size) {
- switch (tag) {
- case SK_PICT_PATH_BUFFER_TAG:
- if (size > 0) {
- fPathHeap.reset(SkNEW_ARGS(SkPathHeap, (buffer)));
- }
- break;
- default:
- // The tag was invalid.
- return false;
- }
-
- return true; // success
-}
-
-void SkPicture::flattenToBuffer(SkWriteBuffer& buffer) const {
- int n;
+// fRecord OK
+void SkPicture::flatten(SkWriteBuffer& buffer) const {
+ const SkPictureData* data = fData.get();
- if ((n = SafeCount(fPathHeap.get())) > 0) {
- WriteTagSize(buffer, SK_PICT_PATH_BUFFER_TAG, n);
- fPathHeap->flatten(buffer);
+ // If we're a new-format picture, backport to old format for serialization.
+ SkAutoTDelete<SkPicture> oldFormat;
+ if (NULL == data && fRecord.get()) {
+ oldFormat.reset(Backport(*fRecord, this->cullRect()));
+ data = oldFormat->fData.get();
+ SkASSERT(data);
}
-}
-
-void SkPicture::flatten(SkWriteBuffer& buffer) const {
- SkPicturePlayback* playback = fPlayback;
SkPictInfo info;
this->createHeader(&info);
- if (NULL == playback && fRecord) {
- playback = SkNEW_ARGS(SkPicturePlayback, (this, *fRecord, info));
- }
+ buffer.writeByteArray(&info.fMagic, sizeof(info.fMagic));
+ buffer.writeUInt(info.fVersion);
+ buffer.writeRect(info.fCullRect);
+ buffer.writeUInt(info.fFlags);
- buffer.writeByteArray(&info, sizeof(info));
- if (playback) {
+ if (data) {
buffer.writeBool(true);
- playback->flatten(buffer);
- // delete playback if it is a local version (i.e. cons'd up just now)
- if (playback != fPlayback) {
- SkDELETE(playback);
- }
+ data->flatten(buffer);
} else {
buffer.writeBool(false);
}
}
#if SK_SUPPORT_GPU
-bool SkPicture::suitableForGpuRasterization(GrContext* context) const {
- // TODO: the heuristic used here needs to be refined
- static const int kNumPaintWithPathEffectUsesTol = 1;
- static const int kNumAAConcavePaths = 5;
-
- SkASSERT(this->numAAHairlineConcavePaths() <= this->numAAConcavePaths());
+// fRecord OK
+bool SkPicture::suitableForGpuRasterization(GrContext* context, const char **reason) const {
+ if (fRecord.get()) {
+ return fAnalysis.suitableForGpuRasterization(reason, 0);
+ }
+ if (NULL == fData.get()) {
+ if (reason) {
+ *reason = "Missing internal data.";
+ }
+ return false;
+ }
- return this->numPaintWithPathEffectUses() < kNumPaintWithPathEffectUsesTol &&
- (this->numAAConcavePaths()-this->numAAHairlineConcavePaths()) < kNumAAConcavePaths;
+ return fData->suitableForGpuRasterization(context, reason);
}
#endif
-bool SkPicture::willPlayBackBitmaps() const {
- if (!fPlayback) {
- return false;
+// fRecord OK
+bool SkPicture::hasText() const {
+ if (fRecord.get()) {
+ return fAnalysis.fHasText;
+ }
+ if (fData.get()) {
+ return fData->hasText();
}
- return fPlayback->containsBitmaps();
+ SkFAIL("Unreachable");
+ return false;
}
-#ifdef SK_BUILD_FOR_ANDROID
-void SkPicture::abortPlayback() {
- if (NULL == fPlayback) {
- return;
+// fRecord OK
+bool SkPicture::willPlayBackBitmaps() const {
+ if (fRecord.get()) {
+ return fAnalysis.fWillPlaybackBitmaps;
+ }
+ if (fData.get()) {
+ return fData->containsBitmaps();
}
- fPlayback->abort();
+ SkFAIL("Unreachable");
+ return false;
}
-#endif
+// fRecord OK
static int32_t next_picture_generation_id() {
static int32_t gPictureGenerationID = 0;
// do a loop in case our global wraps around, as we never want to
return genID;
}
+// fRecord OK
uint32_t SkPicture::uniqueID() const {
- if (NULL != fRecord) {
- SkASSERT(NULL == fPlayback);
- return SK_InvalidGenID;
- }
-
if (SK_InvalidGenID == fUniqueID) {
fUniqueID = next_picture_generation_id();
}
return fUniqueID;
}
+
+
+static SkRecord* optimized(SkRecord* r) {
+ SkRecordOptimize(r);
+ return r;
+}
+
+// fRecord OK
+SkPicture::SkPicture(SkScalar width, SkScalar height, SkRecord* record, SkBBoxHierarchy* bbh)
+ : fCullWidth(width)
+ , fCullHeight(height)
+ , fRecord(optimized(record))
+ , fBBH(SkSafeRef(bbh))
+ , fAnalysis(*fRecord) {
+ // TODO: delay as much of this work until just before first playback?
+ if (fBBH.get()) {
+ SkRecordFillBounds(this->cullRect(), *fRecord, fBBH.get());
+ }
+ this->needsNewGenID();
+}
+
+// Note that we are assuming that this entry point will only be called from
+// one thread. Currently the only client of this method is
+// SkGpuDevice::EXPERIMENTAL_optimize which should be only called from a single
+// thread.
+void SkPicture::addDeletionListener(DeletionListener* listener) const {
+ SkASSERT(listener);
+
+ *fDeletionListeners.append() = SkRef(listener);
+}
+
+void SkPicture::callDeletionListeners() {
+ for (int i = 0; i < fDeletionListeners.count(); ++i) {
+ fDeletionListeners[i]->onDeletion(this->uniqueID());
+ }
+
+ fDeletionListeners.unrefAll();
+}
+
+// fRecord OK
+int SkPicture::approximateOpCount() const {
+ SkASSERT(fRecord.get() || fData.get());
+ if (fRecord.get()) {
+ return fRecord->count();
+ }
+ if (fData.get()) {
+ return fData->opCount();
+ }
+ return 0;
+}