Linear-time implementation of willPlaybackBitmaps(), computed & cached

on construction in SkPicture. Unit test.

Template trickery thanks to mtklein@.

BUG=skia:2702
R=mtklein@google.com, reed@android.com, reed@google.com, tomhudson@google.com, mtklein, reed

Author: tomhudson@chromium.org

Review URL: https://codereview.chromium.org/366443002
This commit is contained in:
tomhudson 2014-07-05 13:37:53 -07:00 committed by Commit bot
parent 2830632ce9
commit d930511ee7
6 changed files with 134 additions and 6 deletions

View File

@ -151,6 +151,8 @@
'<(skia_src_path)/core/SkRasterClip.cpp', '<(skia_src_path)/core/SkRasterClip.cpp',
'<(skia_src_path)/core/SkRasterizer.cpp', '<(skia_src_path)/core/SkRasterizer.cpp',
'<(skia_src_path)/core/SkReadBuffer.cpp', '<(skia_src_path)/core/SkReadBuffer.cpp',
'<(skia_src_path)/core/SkRecordAnalysis.cpp',
'<(skia_src_path)/core/SkRecordAnalysis.h',
'<(skia_src_path)/core/SkRecordDraw.cpp', '<(skia_src_path)/core/SkRecordDraw.cpp',
'<(skia_src_path)/core/SkRecordOpts.cpp', '<(skia_src_path)/core/SkRecordOpts.cpp',
'<(skia_src_path)/core/SkRecorder.cpp', '<(skia_src_path)/core/SkRecorder.cpp',

View File

@ -313,6 +313,7 @@ private:
SkPicture(int width, int height, SkRecord*); // Takes ownership. SkPicture(int width, int height, SkRecord*); // Takes ownership.
SkAutoTDelete<SkRecord> fRecord; SkAutoTDelete<SkRecord> fRecord;
bool fRecordWillPlayBackBitmaps; // TODO: const
}; };
#endif #endif

View File

@ -19,6 +19,7 @@
#include "SkDrawPictureCallback.h" #include "SkDrawPictureCallback.h"
#include "SkPaintPriv.h" #include "SkPaintPriv.h"
#include "SkPicture.h" #include "SkPicture.h"
#include "SkRecordAnalysis.h"
#include "SkRegion.h" #include "SkRegion.h"
#include "SkStream.h" #include "SkStream.h"
#include "SkTDArray.h" #include "SkTDArray.h"
@ -132,7 +133,8 @@ static void validateMatrix(const SkMatrix* matrix) {
// fRecord OK // fRecord OK
SkPicture::SkPicture() SkPicture::SkPicture()
: fWidth(0) : fWidth(0)
, fHeight(0) { , fHeight(0)
, fRecordWillPlayBackBitmaps(false) {
this->needsNewGenID(); this->needsNewGenID();
} }
@ -141,7 +143,8 @@ SkPicture::SkPicture(int width, int height,
const SkPictureRecord& record, const SkPictureRecord& record,
bool deepCopyOps) bool deepCopyOps)
: fWidth(width) : fWidth(width)
, fHeight(height) { , fHeight(height)
, fRecordWillPlayBackBitmaps(false) {
this->needsNewGenID(); this->needsNewGenID();
SkPictInfo info; SkPictInfo info;
@ -170,6 +173,7 @@ SkPicture::SkPicture(const SkPicture& src) : INHERITED() {
this->needsNewGenID(); this->needsNewGenID();
fWidth = src.fWidth; fWidth = src.fWidth;
fHeight = src.fHeight; fHeight = src.fHeight;
fRecordWillPlayBackBitmaps = src.fRecordWillPlayBackBitmaps;
if (NULL != src.fData.get()) { if (NULL != src.fData.get()) {
fData.reset(SkNEW_ARGS(SkPictureData, (*src.fData))); fData.reset(SkNEW_ARGS(SkPictureData, (*src.fData)));
@ -204,6 +208,7 @@ void SkPicture::clone(SkPicture* pictures, int count) const {
clone->fWidth = fWidth; clone->fWidth = fWidth;
clone->fHeight = fHeight; clone->fHeight = fHeight;
clone->fData.reset(NULL); clone->fData.reset(NULL);
clone->fRecordWillPlayBackBitmaps = fRecordWillPlayBackBitmaps;
/* We want to copy the src's playback. However, if that hasn't been built /* 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 yet, we need to fake a call to endRecording() without actually calling
@ -381,7 +386,8 @@ bool SkPicture::InternalOnly_BufferIsSKP(SkReadBuffer& buffer, SkPictInfo* pInfo
SkPicture::SkPicture(SkPictureData* data, int width, int height) SkPicture::SkPicture(SkPictureData* data, int width, int height)
: fData(data) : fData(data)
, fWidth(width) , fWidth(width)
, fHeight(height) { , fHeight(height)
, fRecordWillPlayBackBitmaps(false) {
this->needsNewGenID(); this->needsNewGenID();
} }
@ -521,8 +527,11 @@ bool SkPicture::suitableForGpuRasterization(GrContext* context, const char **rea
} }
#endif #endif
// fRecord TODO // fRecord OK
bool SkPicture::willPlayBackBitmaps() const { bool SkPicture::willPlayBackBitmaps() const {
if (fRecord.get()) {
return fRecordWillPlayBackBitmaps;
}
if (!fData.get()) { if (!fData.get()) {
return false; return false;
} }
@ -563,6 +572,7 @@ uint32_t SkPicture::uniqueID() const {
SkPicture::SkPicture(int width, int height, SkRecord* record) SkPicture::SkPicture(int width, int height, SkRecord* record)
: fWidth(width) : fWidth(width)
, fHeight(height) , fHeight(height)
, fRecord(record) { , fRecord(record)
, fRecordWillPlayBackBitmaps(SkRecordWillPlaybackBitmaps(*record)) {
this->needsNewGenID(); this->needsNewGenID();
} }

View File

@ -0,0 +1,66 @@
#include "SkRecordAnalysis.h"
#include "SkShader.h"
#include "SkTLogic.h"
/** 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.
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);
// 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; }
// Main entry for visitor:
// 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.
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;
}
// 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; }
};
bool SkRecordWillPlaybackBitmaps(const SkRecord& record) {
BitmapTester tester;
for (unsigned i = 0; i < record.count(); i++) {
if (record.visit<bool>(i, tester)) {
return true;
}
}
return false;
}

View File

@ -0,0 +1,8 @@
#ifndef SkRecordAnalysis_DEFINED
#define SkRecordAnalysis_DEFINED
#include "SkRecord.h"
bool SkRecordWillPlaybackBitmaps(const SkRecord& record);
#endif // SkRecordAnalysis_DEFINED

View File

@ -7,7 +7,11 @@
#include "Test.h" #include "Test.h"
#include "SkBitmap.h"
#include "SkImageInfo.h"
#include "SkShader.h"
#include "SkRecord.h" #include "SkRecord.h"
#include "SkRecordAnalysis.h"
#include "SkRecords.h" #include "SkRecords.h"
// Sums the area of any DrawRect command it sees. // Sums the area of any DrawRect command it sees.
@ -48,6 +52,8 @@ struct Stretch {
} }
}; };
#define APPEND(record, type, ...) SkNEW_PLACEMENT_ARGS(record.append<type>(), type, (__VA_ARGS__))
// Basic tests for the low-level SkRecord code. // Basic tests for the low-level SkRecord code.
DEF_TEST(Record, r) { DEF_TEST(Record, r) {
SkRecord record; SkRecord record;
@ -55,7 +61,7 @@ DEF_TEST(Record, r) {
// Add a simple DrawRect command. // Add a simple DrawRect command.
SkRect rect = SkRect::MakeWH(10, 10); SkRect rect = SkRect::MakeWH(10, 10);
SkPaint paint; SkPaint paint;
SkNEW_PLACEMENT_ARGS(record.append<SkRecords::DrawRect>(), SkRecords::DrawRect, (paint, rect)); APPEND(record, SkRecords::DrawRect, paint, rect);
// Its area should be 100. // Its area should be 100.
AreaSummer summer; AreaSummer summer;
@ -70,3 +76,38 @@ DEF_TEST(Record, r) {
summer.apply(record); summer.apply(record);
REPORTER_ASSERT(r, summer.area() == 500); REPORTER_ASSERT(r, summer.area() == 500);
} }
DEF_TEST(RecordAnalysis, r) {
SkRecord record;
SkRect rect = SkRect::MakeWH(10, 10);
SkPaint paint;
APPEND(record, SkRecords::DrawRect, paint, rect);
REPORTER_ASSERT(r, !SkRecordWillPlaybackBitmaps(record));
SkBitmap bitmap;
APPEND(record, SkRecords::DrawBitmap, &paint, bitmap, 0.0f, 0.0f);
REPORTER_ASSERT(r, SkRecordWillPlaybackBitmaps(record));
SkNEW_PLACEMENT_ARGS(record.replace<SkRecords::DrawRect>(1),
SkRecords::DrawRect, (paint, rect));
REPORTER_ASSERT(r, !SkRecordWillPlaybackBitmaps(record));
SkPaint paint2;
// CreateBitmapShader is too smart for us; an empty (or 1x1) bitmap shader
// gets optimized into a non-bitmap form, so we create a 2x2 bitmap here.
SkBitmap bitmap2;
bitmap2.allocPixels(SkImageInfo::MakeN32Premul(2, 2));
bitmap2.eraseColor(SK_ColorBLUE);
*(bitmap2.getAddr32(0, 0)) = SK_ColorGREEN;
SkShader* shader = SkShader::CreateBitmapShader(bitmap2, SkShader::kClamp_TileMode,
SkShader::kClamp_TileMode);
paint2.setShader(shader);
REPORTER_ASSERT(r, shader->asABitmap(NULL, NULL, NULL) == SkShader::kDefault_BitmapType);
APPEND(record, SkRecords::DrawRect, paint2, rect);
REPORTER_ASSERT(r, SkRecordWillPlaybackBitmaps(record));
}
#undef APPEND