SkPDF: SkPDFGraphicState Lookup hashtabled

In Release, running `dm --src skp --config pdf`, I get a
speedup of about 1.2%.

SkPDFGraphicState class:
-   Holds the subset of SkPaint that maps to a PDF Graphics
    State
-   These fields are easily comparable, making hashtable
    comparisons easy.

SkPDFCanon:
-   findGraphicState() takes a SkPDFGraphicState, not a SkPaint
-   fGraphicStateRecords is a SkHashSet, not a SkTDArray

SkPDFGraphicState:
-   mode_for_pdf() replaces logic inside equivalent(), but is
    only called once per lookup.
-   emitObject() no longer modifies the SkPDFGraphicState to
    cache the SkPDFDict stucture.  (Since it is de-duped,
    this get no speedup).
-   Static Functions that don't use the canon return a plain
    SkPDFDict now.  No need for fPopulated.

SkTHash.h
-   SkHashSet::forall added

SkPDFDevice; SkPDFShader
-   Updated for new SkPDFGraphicState interface.

BUG=skia:3585

Review URL: https://codereview.chromium.org/1046293002
This commit is contained in:
halcanary 2015-04-01 13:31:19 -07:00 committed by Commit bot
parent 03e5161bed
commit be27a118c2
8 changed files with 152 additions and 150 deletions

View File

@ -237,6 +237,12 @@ public:
// This pointer remains valid until the next call to add().
const T* find(const T& item) const { return fTable.find(item); }
// Call fn on every item in the set. You may not mutate anything.
template <typename Fn> // f(T), f(const T&)
void foreach (Fn&& fn) const {
fTable.foreach (fn);
}
private:
struct Traits {
static const T& GetKey(const T& item) { return item; }

View File

@ -8,7 +8,6 @@
#include "SkPDFBitmap.h"
#include "SkPDFCanon.h"
#include "SkPDFFont.h"
#include "SkPDFGraphicState.h"
#include "SkPDFShader.h"
////////////////////////////////////////////////////////////////////////////////
@ -24,7 +23,7 @@ void SkPDFCanon::reset() {
fAlphaShaderRecords.reset();
fImageShaderRecords.unrefAll();
fImageShaderRecords.reset();
fGraphicStateRecords.unrefAll();
fGraphicStateRecords.foreach ([](WrapGS w) { w.fPtr->unref(); });
fGraphicStateRecords.reset();
fBitmapRecords.unrefAll();
fBitmapRecords.reset();
@ -107,12 +106,17 @@ void SkPDFCanon::addImageShader(SkPDFImageShader* pdfShader) {
////////////////////////////////////////////////////////////////////////////////
SkPDFGraphicState* SkPDFCanon::findGraphicState(const SkPaint& paint) const {
return find_item(fGraphicStateRecords, paint);
const SkPDFGraphicState* SkPDFCanon::findGraphicState(
const SkPDFGraphicState& key) const {
const WrapGS* ptr = fGraphicStateRecords.find(WrapGS(&key));
return ptr ? ptr->fPtr : NULL;
}
void SkPDFCanon::addGraphicState(SkPDFGraphicState* state) {
fGraphicStateRecords.push(SkRef(state));
void SkPDFCanon::addGraphicState(const SkPDFGraphicState* state) {
SkASSERT(state);
WrapGS w(SkRef(state));
SkASSERT(!fGraphicStateRecords.contains(w));
fGraphicStateRecords.add(w);
}
////////////////////////////////////////////////////////////////////////////////

View File

@ -7,12 +7,13 @@
#ifndef SkPDFCanon_DEFINED
#define SkPDFCanon_DEFINED
#include "SkPDFGraphicState.h"
#include "SkPDFShader.h"
#include "SkTDArray.h"
#include "SkTHash.h"
class SkBitmap;
class SkPDFFont;
class SkPDFGraphicState;
class SkPDFBitmap;
class SkPaint;
@ -56,8 +57,8 @@ public:
SkPDFImageShader* findImageShader(const SkPDFShader::State&) const;
void addImageShader(SkPDFImageShader*);
SkPDFGraphicState* findGraphicState(const SkPaint&) const;
void addGraphicState(SkPDFGraphicState*);
const SkPDFGraphicState* findGraphicState(const SkPDFGraphicState&) const;
void addGraphicState(const SkPDFGraphicState*);
SkPDFBitmap* findBitmap(const SkBitmap&) const;
void addBitmap(SkPDFBitmap*);
@ -76,7 +77,20 @@ private:
SkTDArray<SkPDFImageShader*> fImageShaderRecords;
SkTDArray<SkPDFGraphicState*> fGraphicStateRecords;
struct WrapGS {
explicit WrapGS(const SkPDFGraphicState* ptr = NULL) : fPtr(ptr) {}
const SkPDFGraphicState* fPtr;
bool operator==(const WrapGS& rhs) const {
SkASSERT(fPtr);
SkASSERT(rhs.fPtr);
return *fPtr == *rhs.fPtr;
}
static uint32_t Hash(const WrapGS& w) {
SkASSERT(w.fPtr);
return w.fPtr->hash();
}
};
SkTHashSet<WrapGS, WrapGS::Hash> fGraphicStateRecords;
SkTDArray<SkPDFBitmap*> fBitmapRecords;
};

View File

@ -1569,8 +1569,7 @@ void SkPDFDevice::drawFormXObjectWithMask(int xObjectIndex,
return;
}
SkAutoTUnref<SkPDFGraphicState> sMaskGS(
SkPDFGraphicState::GetSMaskGraphicState(
SkAutoTUnref<SkPDFObject> sMaskGS(SkPDFGraphicState::GetSMaskGraphicState(
mask, invertClip, SkPDFGraphicState::kAlpha_SMaskMode));
SkMatrix identity;
@ -1941,7 +1940,7 @@ void SkPDFDevice::populateGraphicStateEntryFromPaint(
}
}
int SkPDFDevice::addGraphicStateResource(SkPDFGraphicState* gs) {
int SkPDFDevice::addGraphicStateResource(SkPDFObject* gs) {
// Assumes that gs has been canonicalized (so we can directly compare
// pointers).
int result = fGraphicStateResources.find(gs);

View File

@ -203,7 +203,7 @@ private:
SkPDFArray* fAnnotations;
SkTDArray<NamedDestination*> fNamedDestinations;
SkTDArray<SkPDFGraphicState*> fGraphicStateResources;
SkTDArray<SkPDFObject*> fGraphicStateResources;
SkTDArray<SkPDFObject*> fXObjectResources;
SkTDArray<SkPDFFont*> fFontResources;
SkTDArray<SkPDFObject*> fShaderResources;
@ -271,7 +271,7 @@ private:
const SkPaint& paint,
bool hasText,
GraphicStateEntry* entry);
int addGraphicStateResource(SkPDFGraphicState* gs);
int addGraphicStateResource(SkPDFObject* gs);
int addXObjectResource(SkPDFObject* xObject);
void updateFont(const SkPaint& paint, uint16_t glyphID,

View File

@ -70,66 +70,58 @@ static const char* as_blend_mode(SkXfermode::Mode mode) {
return NULL;
}
static bool equivalent(const SkPaint& a, const SkPaint& b) {
// We're only interested in some fields of the SkPaint, so we have
// a custom equality function.
if (SkColorGetA(a.getColor()) != SkColorGetA(b.getColor()) ||
a.getStrokeCap() != b.getStrokeCap() ||
a.getStrokeJoin() != b.getStrokeJoin() ||
a.getStrokeWidth() != b.getStrokeWidth() ||
a.getStrokeMiter() != b.getStrokeMiter()) {
return false;
// If a SkXfermode is unsupported in PDF, this function returns
// SrcOver, otherwise, it returns that Xfermode as a Mode.
static SkXfermode::Mode mode_for_pdf(const SkXfermode* xfermode) {
SkXfermode::Mode mode = SkXfermode::kSrcOver_Mode;
if (xfermode) {
xfermode->asMode(&mode);
}
SkXfermode::Mode aXfermodeName = SkXfermode::kSrcOver_Mode;
SkXfermode* aXfermode = a.getXfermode();
if (aXfermode) {
aXfermode->asMode(&aXfermodeName);
switch (mode) {
case SkXfermode::kSrcOver_Mode:
case SkXfermode::kMultiply_Mode:
case SkXfermode::kScreen_Mode:
case SkXfermode::kOverlay_Mode:
case SkXfermode::kDarken_Mode:
case SkXfermode::kLighten_Mode:
case SkXfermode::kColorDodge_Mode:
case SkXfermode::kColorBurn_Mode:
case SkXfermode::kHardLight_Mode:
case SkXfermode::kSoftLight_Mode:
case SkXfermode::kDifference_Mode:
case SkXfermode::kExclusion_Mode:
case SkXfermode::kHue_Mode:
case SkXfermode::kSaturation_Mode:
case SkXfermode::kColor_Mode:
case SkXfermode::kLuminosity_Mode:
// Mode is suppported and handled by pdf graphics state.
return mode;
default:
return SkXfermode::kSrcOver_Mode; // Default mode.
}
if (aXfermodeName < 0 || aXfermodeName > SkXfermode::kLastMode ||
as_blend_mode(aXfermodeName) == NULL) {
aXfermodeName = SkXfermode::kSrcOver_Mode;
}
const char* aXfermodeString = as_blend_mode(aXfermodeName);
SkASSERT(aXfermodeString != NULL);
SkXfermode::Mode bXfermodeName = SkXfermode::kSrcOver_Mode;
SkXfermode* bXfermode = b.getXfermode();
if (bXfermode) {
bXfermode->asMode(&bXfermodeName);
}
if (bXfermodeName < 0 || bXfermodeName > SkXfermode::kLastMode ||
as_blend_mode(bXfermodeName) == NULL) {
bXfermodeName = SkXfermode::kSrcOver_Mode;
}
const char* bXfermodeString = as_blend_mode(bXfermodeName);
SkASSERT(bXfermodeString != NULL);
return strcmp(aXfermodeString, bXfermodeString) == 0;
}
bool SkPDFGraphicState::equals(const SkPaint& paint) const {
return equivalent(paint, fPaint);
}
SkPDFGraphicState::~SkPDFGraphicState() {}
void SkPDFGraphicState::emitObject(SkWStream* stream,
const SkPDFObjNumMap& objNumMap,
const SkPDFSubstituteMap& substitutes) {
populateDict();
SkPDFDict::emitObject(stream, objNumMap, substitutes);
}
SkPDFGraphicState::SkPDFGraphicState(const SkPaint& p)
: fStrokeWidth(p.getStrokeWidth())
, fStrokeMiter(p.getStrokeMiter())
, fAlpha(p.getAlpha())
, fStrokeCap(SkToU8(p.getStrokeCap()))
, fStrokeJoin(SkToU8(p.getStrokeJoin()))
, fMode(SkToU8(mode_for_pdf(p.getXfermode()))) {}
// static
SkPDFGraphicState* SkPDFGraphicState::GetGraphicStateForPaint(
SkPDFCanon* canon, const SkPaint& paint) {
SkASSERT(canon);
SkPDFGraphicState* pdfGraphicState = canon->findGraphicState(paint);
if (pdfGraphicState) {
return SkRef(pdfGraphicState);
SkPDFGraphicState key(paint);
if (const SkPDFGraphicState* canonGS = canon->findGraphicState(key)) {
// The returned SkPDFGraphicState must be made non-const,
// since the emitObject() interface is non-const. But We
// promise that there is no way to mutate this object from
// here on out.
return SkRef(const_cast<SkPDFGraphicState*>(canonGS));
}
pdfGraphicState = new SkPDFGraphicState(paint);
SkPDFGraphicState* pdfGraphicState = new SkPDFGraphicState(paint);
canon->addGraphicState(pdfGraphicState);
return pdfGraphicState;
}
@ -159,12 +151,15 @@ SkPDFObject* create_invert_function() {
template <typename T> void unref(T* ptr) { ptr->unref(); }
} // namespace
SK_DECLARE_STATIC_LAZY_PTR(SkPDFObject, invertFunction,
create_invert_function, unref<SkPDFObject>);
SK_DECLARE_STATIC_LAZY_PTR(SkPDFObject,
invertFunction,
create_invert_function,
unref<SkPDFObject>);
// static
SkPDFGraphicState* SkPDFGraphicState::GetSMaskGraphicState(
SkPDFFormXObject* sMask, bool invert, SkPDFSMaskMode sMaskMode) {
SkPDFDict* SkPDFGraphicState::GetSMaskGraphicState(SkPDFFormXObject* sMask,
bool invert,
SkPDFSMaskMode sMaskMode) {
// The practical chances of using the same mask more than once are unlikely
// enough that it's not worth canonicalizing.
SkAutoTUnref<SkPDFDict> sMaskDict(new SkPDFDict("Mask"));
@ -174,81 +169,64 @@ SkPDFGraphicState* SkPDFGraphicState::GetSMaskGraphicState(
sMaskDict->insertName("S", "Luminosity");
}
sMaskDict->insert("G", new SkPDFObjRef(sMask))->unref();
SkPDFGraphicState* result = new SkPDFGraphicState;
result->fPopulated = true;
result->insertName("Type", "ExtGState");
result->insert("SMask", sMaskDict.get());
if (invert) {
sMaskDict->insert("TR", new SkPDFObjRef(invertFunction.get()))->unref();
}
SkPDFDict* result = new SkPDFDict("ExtGState");
result->insert("SMask", sMaskDict.get());
return result;
}
SkPDFGraphicState* SkPDFGraphicState::CreateNoSMaskGraphicState() {
SkPDFGraphicState* noSMaskGS = SkNEW(SkPDFGraphicState);
noSMaskGS->fPopulated = true;
noSMaskGS->insertName("Type", "ExtGState");
namespace {
SkPDFDict* create_no_smask_graphic_state() {
SkPDFDict* noSMaskGS = new SkPDFDict("ExtGState");
noSMaskGS->insertName("SMask", "None");
return noSMaskGS;
}
SK_DECLARE_STATIC_LAZY_PTR(
SkPDFGraphicState, noSMaskGraphicState,
SkPDFGraphicState::CreateNoSMaskGraphicState, unref<SkPDFGraphicState>);
} // namespace
SK_DECLARE_STATIC_LAZY_PTR(SkPDFDict,
noSMaskGraphicState,
create_no_smask_graphic_state,
unref<SkPDFDict>);
// static
SkPDFGraphicState* SkPDFGraphicState::GetNoSMaskGraphicState() {
SkPDFDict* SkPDFGraphicState::GetNoSMaskGraphicState() {
return SkRef(noSMaskGraphicState.get());
}
SkPDFGraphicState::SkPDFGraphicState()
: fPopulated(false) {}
void SkPDFGraphicState::emitObject(SkWStream* stream,
const SkPDFObjNumMap& objNumMap,
const SkPDFSubstituteMap& substitutes) {
SkAutoTUnref<SkPDFDict> dict(SkNEW_ARGS(SkPDFDict, ("ExtGState")));
dict->insertName("Type", "ExtGState");
SkPDFGraphicState::SkPDFGraphicState(const SkPaint& paint)
: fPaint(paint), fPopulated(false) {}
SkAutoTUnref<SkPDFScalar> alpha(new SkPDFScalar(SkScalarDiv(fAlpha, 0xFF)));
dict->insert("CA", alpha.get());
dict->insert("ca", alpha.get());
// populateDict and operator== have to stay in sync with each other.
void SkPDFGraphicState::populateDict() {
if (!fPopulated) {
fPopulated = true;
insertName("Type", "ExtGState");
SkPaint::Cap strokeCap = (SkPaint::Cap)fStrokeCap;
SkPaint::Join strokeJoin = (SkPaint::Join)fStrokeJoin;
SkXfermode::Mode xferMode = (SkXfermode::Mode)fMode;
SkAutoTUnref<SkPDFScalar> alpha(
new SkPDFScalar(SkScalarDiv(fPaint.getAlpha(), 0xFF)));
insert("CA", alpha.get());
insert("ca", alpha.get());
SK_COMPILE_ASSERT(SkPaint::kButt_Cap == 0, paint_cap_mismatch);
SK_COMPILE_ASSERT(SkPaint::kRound_Cap == 1, paint_cap_mismatch);
SK_COMPILE_ASSERT(SkPaint::kSquare_Cap == 2, paint_cap_mismatch);
SK_COMPILE_ASSERT(SkPaint::kCapCount == 3, paint_cap_mismatch);
SkASSERT(strokeCap >= 0 && strokeCap <= 2);
dict->insertInt("LC", strokeCap);
SK_COMPILE_ASSERT(SkPaint::kButt_Cap == 0, paint_cap_mismatch);
SK_COMPILE_ASSERT(SkPaint::kRound_Cap == 1, paint_cap_mismatch);
SK_COMPILE_ASSERT(SkPaint::kSquare_Cap == 2, paint_cap_mismatch);
SK_COMPILE_ASSERT(SkPaint::kCapCount == 3, paint_cap_mismatch);
SkASSERT(fPaint.getStrokeCap() >= 0 && fPaint.getStrokeCap() <= 2);
insertInt("LC", fPaint.getStrokeCap());
SK_COMPILE_ASSERT(SkPaint::kMiter_Join == 0, paint_join_mismatch);
SK_COMPILE_ASSERT(SkPaint::kRound_Join == 1, paint_join_mismatch);
SK_COMPILE_ASSERT(SkPaint::kBevel_Join == 2, paint_join_mismatch);
SK_COMPILE_ASSERT(SkPaint::kJoinCount == 3, paint_join_mismatch);
SkASSERT(strokeJoin >= 0 && strokeJoin <= 2);
dict->insertInt("LJ", strokeJoin);
SK_COMPILE_ASSERT(SkPaint::kMiter_Join == 0, paint_join_mismatch);
SK_COMPILE_ASSERT(SkPaint::kRound_Join == 1, paint_join_mismatch);
SK_COMPILE_ASSERT(SkPaint::kBevel_Join == 2, paint_join_mismatch);
SK_COMPILE_ASSERT(SkPaint::kJoinCount == 3, paint_join_mismatch);
SkASSERT(fPaint.getStrokeJoin() >= 0 && fPaint.getStrokeJoin() <= 2);
insertInt("LJ", fPaint.getStrokeJoin());
insertScalar("LW", fPaint.getStrokeWidth());
insertScalar("ML", fPaint.getStrokeMiter());
insert("SA", new SkPDFBool(true))->unref(); // Auto stroke adjustment.
SkXfermode::Mode xfermode = SkXfermode::kSrcOver_Mode;
// If asMode fails, default to kSrcOver_Mode.
if (fPaint.getXfermode())
fPaint.getXfermode()->asMode(&xfermode);
// If we don't support the mode, just use kSrcOver_Mode.
if (xfermode < 0 || xfermode > SkXfermode::kLastMode ||
as_blend_mode(xfermode) == NULL) {
xfermode = SkXfermode::kSrcOver_Mode;
NOT_IMPLEMENTED("unsupported xfermode", false);
}
insertName("BM", as_blend_mode(xfermode));
}
dict->insertScalar("LW", fStrokeWidth);
dict->insertScalar("ML", fStrokeMiter);
// SA = Auto stroke adjustment.
dict->insert("SA", new SkPDFBool(true))->unref();
dict->insertName("BM", as_blend_mode(xferMode));
dict->emitObject(stream, objNumMap, substitutes);
}

View File

@ -13,19 +13,17 @@
#include "SkPaint.h"
#include "SkPDFTypes.h"
#include "SkTemplates.h"
#include "SkChecksum.h"
class SkPDFCanon;
class SkPDFFormXObject;
/** \class SkPDFGraphicState
SkPaint objects roughly correspond to graphic state dictionaries that can
be installed. So that a given dictionary is only output to the pdf file
once, we want to canonicalize them. Static methods in this class manage
a weakly referenced set of SkPDFGraphicState objects: when the last
reference to a SkPDFGraphicState is removed, it removes itself from the
static set of objects.
once, we want to canonicalize them.
*/
class SkPDFGraphicState : public SkPDFDict {
class SkPDFGraphicState : public SkPDFObject {
SK_DECLARE_INST_COUNT(SkPDFGraphicState)
public:
enum SkPDFSMaskMode {
@ -33,8 +31,6 @@ public:
kLuminosity_SMaskMode
};
virtual ~SkPDFGraphicState();
// Override emitObject so that we can populate the dictionary on
// demand.
virtual void emitObject(SkWStream* stream,
@ -57,32 +53,37 @@ public:
* @param sMask The form xobject to use as a soft mask.
* @param invert Indicates if the alpha of the sMask should be inverted.
* @param sMaskMode Whether to use alpha or luminosity for the sMask.
*
* These are not de-duped.
*/
static SkPDFGraphicState* GetSMaskGraphicState(SkPDFFormXObject* sMask,
bool invert,
SkPDFSMaskMode sMaskMode);
static SkPDFDict* GetSMaskGraphicState(SkPDFFormXObject* sMask,
bool invert,
SkPDFSMaskMode sMaskMode);
/** Get a graphic state that only unsets the soft mask. The reference
* count of the object is incremented and it is the caller's responsibility
* to unreference it when done. This is needed to accommodate the weak
* reference pattern used when the returned object is new and has no
* other references.
*
* The returned object is a singleton.
*/
static SkPDFGraphicState* GetNoSMaskGraphicState();
static SkPDFDict* GetNoSMaskGraphicState();
bool equals(const SkPaint&) const;
// Only public for SK_DECLARE_STATIC_LAZY_PTR
static SkPDFGraphicState* CreateNoSMaskGraphicState();
bool operator==(const SkPDFGraphicState& rhs) const {
return 0 == memcmp(&fStrokeWidth, &rhs.fStrokeWidth, 12);
}
uint32_t hash() const { return SkChecksum::Murmur3(&fStrokeWidth, 12); }
private:
const SkPaint fPaint;
bool fPopulated;
const SkScalar fStrokeWidth;
const SkScalar fStrokeMiter;
const uint8_t fAlpha;
const uint8_t fStrokeCap; // SkPaint::Cap
const uint8_t fStrokeJoin; // SkPaint::Join
const uint8_t fMode; // SkXfermode::Mode
SkPDFGraphicState();
SkPDFGraphicState(const SkPaint& paint);
void populateDict();
SkPDFGraphicState(const SkPaint&);
typedef SkPDFDict INHERITED;
};

View File

@ -635,7 +635,7 @@ static SkStream* create_pattern_fill_content(int gsIndex, SkRect& bounds) {
* Creates a ExtGState with the SMask set to the luminosityShader in
* luminosity mode. The shader pattern extends to the bbox.
*/
static SkPDFGraphicState* create_smask_graphic_state(
static SkPDFObject* create_smask_graphic_state(
SkPDFCanon* canon, SkScalar dpi, const SkPDFShader::State& state) {
SkRect bbox;
bbox.set(state.fBBox);
@ -676,7 +676,7 @@ SkPDFAlphaFunctionShader* SkPDFAlphaFunctionShader::Create(
// Create resource dict with alpha graphics state as G0 and
// pattern shader as P0, then write content stream.
SkAutoTUnref<SkPDFGraphicState> alphaGs(
SkAutoTUnref<SkPDFObject> alphaGs(
create_smask_graphic_state(canon, dpi, state));
SkPDFAlphaFunctionShader* alphaFunctionShader =