SkPDF: refactor streams, experimental threading
All PDF Streams are immediatly serialized, and are never long-lived
in memory.
if EXPERIMENTAL fExecutor is set on the Document, streams are
compressed in parallel.
Results for PDFBigDocBench:
without patch 1807885.01 μs
with patch without executor 1802808.35 μs
with patch with executor 246313.72 μs
SkPDFStreamOut() function replaces SkPDFStream classes.
Page resources are all serialized early.
Several Document-level objects are serialzied early.
SkUUID introduced as top-level object.
Many {insert|append}ObjRef() converted to memory efficient
{insert|append}Ref().
Bug: skia:8630
Change-Id: Ic336917d0c8b9ac1c2423b43bfe9b49a3533fbff
Reviewed-on: https://skia-review.googlesource.com/c/176588
Auto-Submit: Hal Canary <halcanary@google.com>
Reviewed-by: Herb Derby <herb@google.com>
Commit-Queue: Hal Canary <halcanary@google.com>
diff --git a/src/pdf/SkPDFDevice.cpp b/src/pdf/SkPDFDevice.cpp
index 7475009..95d65d4 100644
--- a/src/pdf/SkPDFDevice.cpp
+++ b/src/pdf/SkPDFDevice.cpp
@@ -107,6 +107,11 @@
return SkImage::MakeFromBitmap(greyBitmap);
}
+static int add_resource(SkTHashSet<SkPDFIndirectReference>& resources, SkPDFIndirectReference ref) {
+ resources.add(ref);
+ return ref.fValue;
+}
+
static void draw_points(SkCanvas::PointMode mode,
size_t count,
const SkPoint* points,
@@ -429,7 +434,7 @@
if (shape->isEmpty()) {
shape = nullptr;
}
- fDevice->finishContentEntry(fClipStack, fBlendMode, std::move(fDstFormXObject), shape);
+ fDevice->finishContentEntry(fClipStack, fBlendMode, fDstFormXObject, shape);
}
}
@@ -474,7 +479,7 @@
SkPDFDevice* fDevice = nullptr;
SkDynamicMemoryWStream* fContentStream = nullptr;
SkBlendMode fBlendMode;
- sk_sp<SkPDFObject> fDstFormXObject;
+ SkPDFIndirectReference fDstFormXObject;
SkPath fShape;
const SkClipStack* fClipStack;
};
@@ -497,9 +502,9 @@
fLinkToURLs = std::vector<RectWithData>();
fLinkToDestinations = std::vector<RectWithData>();
fNamedDestinations = std::vector<NamedDestination>();
- fGraphicStateResources = std::vector<sk_sp<SkPDFObject>>();
- fXObjectResources = std::vector<sk_sp<SkPDFObject>>();
- fShaderResources = std::vector<sk_sp<SkPDFObject>>();
+ fGraphicStateResources.reset();
+ fXObjectResources.reset();
+ fShaderResources.reset();
fFontResources.reset();
fContent.reset();
fActiveStackState = GraphicStackState();
@@ -797,23 +802,24 @@
return index;
}
-void SkPDFDevice::setGraphicState(sk_sp<SkPDFObject> gs, SkDynamicMemoryWStream* content) {
- SkPDFUtils::ApplyGraphicState(find_or_add(&fGraphicStateResources, std::move(gs)), content);
+void SkPDFDevice::setGraphicState(SkPDFIndirectReference gs, SkDynamicMemoryWStream* content) {
+ SkPDFUtils::ApplyGraphicState(add_resource(fGraphicStateResources, gs), content);
}
void SkPDFDevice::addSMaskGraphicState(sk_sp<SkPDFDevice> maskDevice,
SkDynamicMemoryWStream* contentStream) {
this->setGraphicState(SkPDFGraphicState::GetSMaskGraphicState(
maskDevice->makeFormXObjectFromDevice(true), false,
- SkPDFGraphicState::kLuminosity_SMaskMode, this->getCanon()), contentStream);
+ SkPDFGraphicState::kLuminosity_SMaskMode, fDocument), contentStream);
}
void SkPDFDevice::clearMaskOnGraphicState(SkDynamicMemoryWStream* contentStream) {
// The no-softmask graphic state is used to "turn off" the mask for later draw calls.
- sk_sp<SkPDFDict>& noSMaskGS = this->getCanon()->fNoSmaskGraphicState;
+ SkPDFIndirectReference& noSMaskGS = this->getCanon()->fNoSmaskGraphicState;
if (!noSMaskGS) {
- noSMaskGS = sk_make_sp<SkPDFDict>("ExtGState");
- noSMaskGS->insertName("SMask", "None");
+ SkPDFDict tmp("ExtGState");
+ tmp.insertName("SMask", "None");
+ noSMaskGS = fDocument->emit(tmp);
}
this->setGraphicState(noSMaskGS, contentStream);
}
@@ -1240,12 +1246,10 @@
// Not yet specified font or need to switch font.
font = SkPDFFont::GetFontResource(fDocument, glyphCache.get(), typeface, gid);
SkASSERT(font); // All preconditions for SkPDFFont::GetFontResource are met.
- SkPDFIndirectReference ref = font->indirectReference();
- fFontResources.add(ref);
-
glyphPositioner.flush();
glyphPositioner.setWideChars(font->multiByteGlyphs());
- SkPDFWriteResourceName(out, SkPDFResourceType::kFont, ref.fValue);
+ SkPDFWriteResourceName(out, SkPDFResourceType::kFont,
+ add_resource(fFontResources, font->indirectReference()));
out->writeText(" ");
SkPDFUtils::AppendScalar(textSize, out);
out->writeText(" Tf\n");
@@ -1275,9 +1279,10 @@
// TODO: implement drawVertices
}
-void SkPDFDevice::drawFormXObject(sk_sp<SkPDFObject> xObject, SkDynamicMemoryWStream* content) {
+void SkPDFDevice::drawFormXObject(SkPDFIndirectReference xObject, SkDynamicMemoryWStream* content) {
+ SkASSERT(xObject);
SkPDFWriteResourceName(content, SkPDFResourceType::kXObject,
- find_or_add(&fXObjectResources, std::move(xObject)));
+ add_resource(fXObjectResources, xObject));
content->writeText(" Do\n");
}
@@ -1335,18 +1340,20 @@
return SkSurface::MakeRaster(info, &props);
}
+static std::vector<SkPDFIndirectReference> sort(const SkTHashSet<SkPDFIndirectReference>& src) {
+ std::vector<SkPDFIndirectReference> dst;
+ dst.reserve(src.count());
+ src.foreach([&dst](SkPDFIndirectReference ref) { dst.push_back(ref); } );
+ std::sort(dst.begin(), dst.end(),
+ [](SkPDFIndirectReference a, SkPDFIndirectReference b) { return a.fValue < b.fValue; });
+ return dst;
+}
sk_sp<SkPDFDict> SkPDFDevice::makeResourceDict() {
- std::vector<SkPDFIndirectReference> fonts;
- fonts.reserve(fFontResources.count());
- fFontResources.foreach([&fonts](SkPDFIndirectReference ref) { fonts.push_back(ref); } );
- fFontResources.reset();
- std::sort(fonts.begin(), fonts.end(),
- [](SkPDFIndirectReference a, SkPDFIndirectReference b) { return a.fValue < b.fValue; });
- return SkPDFMakeResourceDict(std::move(fGraphicStateResources),
- std::move(fShaderResources),
- std::move(fXObjectResources),
- std::move(fonts));
+ return SkPDFMakeResourceDict(sort(fGraphicStateResources),
+ sort(fShaderResources),
+ sort(fXObjectResources),
+ sort(fFontResources));
}
std::unique_ptr<SkStreamAsset> SkPDFDevice::content() {
@@ -1446,13 +1453,13 @@
for (const RectWithData& rectWithURL : fLinkToURLs) {
SkRect r;
fInitialTransform.mapRect(&r, rectWithURL.rect);
- array->appendObjRef(create_link_to_url(rectWithURL.data.get(), r));
+ array->appendRef(fDocument->emit(*create_link_to_url(rectWithURL.data.get(), r)));
}
for (const RectWithData& linkToDestination : fLinkToDestinations) {
SkRect r;
fInitialTransform.mapRect(&r, linkToDestination.rect);
- array->appendObjRef(
- create_link_named_dest(linkToDestination.data.get(), r));
+ array->appendRef(
+ fDocument->emit(*create_link_named_dest(linkToDestination.data.get(), r)));
}
return array;
}
@@ -1461,6 +1468,7 @@
for (const NamedDestination& dest : fNamedDestinations) {
auto pdfDest = sk_make_sp<SkPDFArray>();
pdfDest->reserve(5);
+ // TODO(halcanry) reserve an IndirectReference for each page with beginPage()
pdfDest->appendObjRef(sk_ref_sp(page));
pdfDest->appendName("XYZ");
SkPoint p = fInitialTransform.mapXY(dest.point.x(), dest.point.y());
@@ -1472,7 +1480,7 @@
}
}
-sk_sp<SkPDFObject> SkPDFDevice::makeFormXObjectFromDevice(bool alpha) {
+SkPDFIndirectReference SkPDFDevice::makeFormXObjectFromDevice(bool alpha) {
SkMatrix inverseTransform = SkMatrix::I();
if (!fInitialTransform.isIdentity()) {
if (!fInitialTransform.invert(&inverseTransform)) {
@@ -1482,8 +1490,8 @@
}
const char* colorSpace = alpha ? "DeviceGray" : nullptr;
- sk_sp<SkPDFObject> xobject =
- SkPDFMakeFormXObject(this->content(),
+ SkPDFIndirectReference xobject =
+ SkPDFMakeFormXObject(fDocument, this->content(),
SkPDFMakeArray(0, 0, this->width(), this->height()),
this->makeResourceDict(), inverseTransform, colorSpace);
// We always draw the form xobjects that we create back into the device, so
@@ -1493,10 +1501,11 @@
return xobject;
}
-void SkPDFDevice::drawFormXObjectWithMask(sk_sp<SkPDFObject> xObject,
- sk_sp<SkPDFObject> mask,
+void SkPDFDevice::drawFormXObjectWithMask(SkPDFIndirectReference xObject,
+ SkPDFIndirectReference sMask,
SkBlendMode mode,
bool invertClip) {
+ SkASSERT(sMask);
SkPaint paint;
paint.setBlendMode(mode);
ScopedContentEntry content(this, nullptr, SkMatrix::I(), paint);
@@ -1504,9 +1513,9 @@
return;
}
this->setGraphicState(SkPDFGraphicState::GetSMaskGraphicState(
- std::move(mask), invertClip, SkPDFGraphicState::kAlpha_SMaskMode,
- fDocument->canon()), content.stream());
- this->drawFormXObject(std::move(xObject), content.stream());
+ sMask, invertClip, SkPDFGraphicState::kAlpha_SMaskMode,
+ fDocument), content.stream());
+ this->drawFormXObject(xObject, content.stream());
this->clearMaskOnGraphicState(content.stream());
}
@@ -1519,8 +1528,8 @@
const SkMatrix& matrix,
const SkPaint& paint,
bool hasText,
- sk_sp<SkPDFObject>* dst) {
- *dst = nullptr;
+ SkPDFIndirectReference* dst) {
+ SkASSERT(!*dst);
SkBlendMode blendMode = paint.getBlendMode();
// Dst xfer mode doesn't draw source at all.
@@ -1570,7 +1579,7 @@
void SkPDFDevice::finishContentEntry(const SkClipStack* clipStack,
SkBlendMode blendMode,
- sk_sp<SkPDFObject> dst,
+ SkPDFIndirectReference dst,
SkPath* shape) {
SkASSERT(blendMode != SkBlendMode::kDst);
if (treat_as_regular_pdf_blend_mode(blendMode)) {
@@ -1618,7 +1627,7 @@
SkPaint stockPaint;
- sk_sp<SkPDFObject> srcFormXObject;
+ SkPDFIndirectReference srcFormXObject;
if (this->isContentEmpty()) {
// If nothing was drawn and there's no shape, then the draw was a
// no-op, but dst needs to be restored for that to be true.
@@ -1628,7 +1637,7 @@
if (shape == nullptr || blendMode == SkBlendMode::kDstOut ||
blendMode == SkBlendMode::kSrcATop) {
ScopedContentEntry content(this, nullptr, SkMatrix::I(), stockPaint);
- this->drawFormXObject(std::move(dst), content.stream());
+ this->drawFormXObject(dst, content.stream());
return;
} else {
blendMode = SkBlendMode::kClear;
@@ -1691,8 +1700,8 @@
if (blendMode == SkBlendMode::kSrcIn ||
blendMode == SkBlendMode::kSrcOut ||
blendMode == SkBlendMode::kSrcATop) {
- this->drawFormXObjectWithMask(std::move(srcFormXObject), std::move(dst),
- SkBlendMode::kSrcOver, blendMode == SkBlendMode::kSrcOut);
+ this->drawFormXObjectWithMask(srcFormXObject, dst, SkBlendMode::kSrcOver,
+ blendMode == SkBlendMode::kSrcOut);
return;
} else {
SkBlendMode mode = SkBlendMode::kSrcOver;
@@ -1700,8 +1709,7 @@
this->drawFormXObjectWithMask(srcFormXObject, dst, SkBlendMode::kSrcOver, false);
mode = SkBlendMode::kMultiply;
}
- this->drawFormXObjectWithMask(std::move(dst), std::move(srcFormXObject), mode,
- blendMode == SkBlendMode::kDstOut);
+ this->drawFormXObjectWithMask(dst, srcFormXObject, mode, blendMode == SkBlendMode::kDstOut);
return;
}
}
@@ -1728,7 +1736,6 @@
entry->fShaderIndex = -1;
// PDF treats a shader as a color, so we only set one or the other.
- sk_sp<SkPDFObject> pdfShader;
SkShader* shader = paint.getShader();
if (shader) {
if (SkShader::kColor_GradientType == shader->asAGradient(nullptr)) {
@@ -1759,24 +1766,25 @@
SkIRect bounds;
clipStackBounds.roundOut(&bounds);
- pdfShader = SkPDFMakeShader(fDocument, shader, transform, bounds, paint.getColor());
+ SkPDFIndirectReference pdfShader
+ = SkPDFMakeShader(fDocument, shader, transform, bounds, paint.getColor());
if (pdfShader) {
// pdfShader has been canonicalized so we can directly compare pointers.
- entry->fShaderIndex = find_or_add(&fShaderResources, std::move(pdfShader));
+ entry->fShaderIndex = add_resource(fShaderResources, pdfShader);
}
}
}
- sk_sp<SkPDFDict> newGraphicState;
+ SkPDFIndirectReference newGraphicState;
if (color == paint.getColor4f()) {
- newGraphicState = SkPDFGraphicState::GetGraphicStateForPaint(fDocument->canon(), paint);
+ newGraphicState = SkPDFGraphicState::GetGraphicStateForPaint(fDocument, paint);
} else {
SkPaint newPaint = paint;
newPaint.setColor4f(color, nullptr);
- newGraphicState = SkPDFGraphicState::GetGraphicStateForPaint(fDocument->canon(), newPaint);
+ newGraphicState = SkPDFGraphicState::GetGraphicStateForPaint(fDocument, newPaint);
}
- entry->fGraphicStateIndex = find_or_add(&fGraphicStateResources, std::move(newGraphicState));
+ entry->fGraphicStateIndex = add_resource(fGraphicStateResources, std::move(newGraphicState));
if (hasText) {
entry->fTextScaleX = paint.getTextScaleX();
@@ -1814,15 +1822,6 @@
is_integer(r.bottom());
}
-namespace {
-// This struct will go away when fIndirectReference goes away.
-struct PDFObj final : public SkPDFObject {
- PDFObj(SkPDFIndirectReference ref) { fIndirectReference = ref; }
- // emitObject() is never called since the Object already has a indirect ref.
- void emitObject(SkWStream*) const override { SK_ABORT("DO NOT REACH HERE"); }
-};
-} // namespace
-
void SkPDFDevice::internalDrawImageRect(SkKeyedImage imageSubset,
const SkRect* src,
const SkRect& dst,
@@ -2046,18 +2045,17 @@
}
SkBitmapKey key = imageSubset.key();
- sk_sp<SkPDFObject>* pdfimagePtr = fDocument->canon()->fPDFBitmapMap.find(key);
- sk_sp<SkPDFObject> pdfimage = pdfimagePtr ? *pdfimagePtr : nullptr;
- if (!pdfimage) {
+ SkPDFIndirectReference* pdfimagePtr = fDocument->canon()->fPDFBitmapMap.find(key);
+ SkPDFIndirectReference pdfimage = pdfimagePtr ? *pdfimagePtr : SkPDFIndirectReference();
+ if (!pdfimagePtr) {
SkASSERT(imageSubset);
- auto ref = SkPDFSerializeImage(imageSubset.image().get(), fDocument,
+ pdfimage = SkPDFSerializeImage(imageSubset.image().get(), fDocument,
fDocument->metadata().fEncodingQuality);
- SkASSERT(ref.fValue > 0);
- pdfimage = sk_make_sp<PDFObj>(ref);
SkASSERT((key != SkBitmapKey{{0, 0, 0, 0}, 0}));
fDocument->canon()->fPDFBitmapMap.set(key, pdfimage);
}
- this->drawFormXObject(std::move(pdfimage), content.stream());
+ SkASSERT(pdfimage != SkPDFIndirectReference());
+ this->drawFormXObject(pdfimage, content.stream());
}
///////////////////////////////////////////////////////////////////////////////////////////////////