SkPDF: stop using linked list of dynamic memory streams.

also:
     delete src/core/SkSinglyLinkedList.h
     add prependToAndReset() to SkDynamicMemoryWStream.

All test PDFs are identical.

Change-Id: I528873f2f5061f07dd416a71f39d97ee97ef3c7d
Reviewed-on: https://skia-review.googlesource.com/c/159323
Reviewed-by: Ben Wagner <bungeman@google.com>
Reviewed-by: Cary Clark <caryclark@google.com>
Commit-Queue: Cary Clark <caryclark@google.com>
Auto-Submit: Hal Canary <halcanary@google.com>
This commit is contained in:
Hal Canary 2018-10-08 16:00:37 -04:00 committed by Skia Commit-Bot
parent 3d6390e7bb
commit 42137de2b2
6 changed files with 103 additions and 160 deletions

View File

@ -281,7 +281,6 @@ skia_core_sources = [
"$_src/core/SkSemaphore.cpp",
"$_src/core/SkSharedMutex.cpp",
"$_src/core/SkSharedMutex.h",
"$_src/core/SkSinglyLinkedList.h",
"$_src/core/SkSpan.h",
"$_src/core/SkSpecialImage.cpp",
"$_src/core/SkSpecialImage.h",

View File

@ -477,6 +477,13 @@ public:
/** Equivalent to writeToStream() followed by reset(), but may save memory use. */
bool writeToAndReset(SkWStream* dst);
/** Equivalent to writeToStream() followed by reset(), but may save memory use.
When the dst is also a SkDynamicMemoryWStream, the implementation is constant time. */
bool writeToAndReset(SkDynamicMemoryWStream* dst);
/** Prepend this stream to dst, resetting this. */
void prependToAndReset(SkDynamicMemoryWStream* dst);
/** Return the contents as SkData, and then reset the stream. */
sk_sp<SkData> detachAsData();

View File

@ -1,102 +0,0 @@
/*
* Copyright 2016 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef SkSinglyLinkedList_DEFINED
#define SkSinglyLinkedList_DEFINED
#include <utility>
#include "SkMakeUnique.h"
#include "SkTypes.h"
template <typename T> class SkSinglyLinkedList {
struct Node;
public:
SkSinglyLinkedList() {}
~SkSinglyLinkedList() { this->reset(); }
void reset() {
SkASSERT(fHead != nullptr || nullptr == fTail);
// Use a while loop rather than recursion to avoid stack overflow.
std::unique_ptr<Node> node = std::move(fHead);
while (node) {
std::unique_ptr<Node> next = std::move(node->fNext);
SkASSERT(next || node.get() == fTail);
node = std::move(next);
}
fTail = nullptr;
}
T* back() { return fTail ? &fTail->fData : nullptr; }
T* front() { return fHead ? &fHead->fData : nullptr; }
bool empty() const { return fHead == nullptr; }
#ifdef SK_DEBUG
int count() { // O(n), debug only.
int count = 0;
for (Node* node = fHead.get(); node; node = node->fNext.get()) {
++count;
}
return count;
}
#endif
void pop_front() {
if (fHead) {
fHead = std::move(fHead->fNext);
if (!fHead) {
fTail = nullptr;
}
}
}
template <class... Args> T* emplace_front(Args&&... args) {
fHead = skstd::make_unique<Node>(std::move(fHead), std::forward<Args>(args)...);
if (!fTail) {
fTail = fHead.get();
}
return &fHead->fData;
}
template <class... Args> T* emplace_back(Args&&... args) {
std::unique_ptr<Node>* dst = fTail ? &fTail->fNext : &fHead;
*dst = skstd::make_unique<Node>(nullptr, std::forward<Args>(args)...);
fTail = dst->get();
return &fTail->fData;
}
class Iter {
public:
void operator++() { fNode = fNode->fNext.get(); }
T& operator*() { return fNode->fData; }
bool operator!=(const Iter& rhs) const { return fNode != rhs.fNode; }
Iter(Node* n) : fNode(n) {}
private:
Node* fNode;
};
Iter begin() { return Iter(fHead.get()); }
Iter end() { return Iter(nullptr); }
class ConstIter {
public:
void operator++() { fNode = fNode->fNext.get(); }
const T& operator*() const { return fNode->fData; }
bool operator!=(const ConstIter& rhs) const { return fNode != rhs.fNode; }
ConstIter(const Node* n) : fNode(n) {}
private:
const Node* fNode;
};
ConstIter begin() const { return ConstIter(fHead.get()); }
ConstIter end() const { return ConstIter(nullptr); }
private:
struct Node {
T fData;
std::unique_ptr<Node> fNext;
template <class... Args>
Node(std::unique_ptr<Node> n, Args&&... args)
: fData(std::forward<Args>(args)...), fNext(std::move(n)) {}
};
std::unique_ptr<Node> fHead;
Node* fTail = nullptr;
SkSinglyLinkedList(const SkSinglyLinkedList<T>&) = delete;
SkSinglyLinkedList& operator=(const SkSinglyLinkedList<T>&) = delete;
};
#endif // SkSinglyLinkedList_DEFINED

View File

@ -555,6 +555,43 @@ bool SkDynamicMemoryWStream::write(const void* buffer, size_t count) {
return true;
}
bool SkDynamicMemoryWStream::writeToAndReset(SkDynamicMemoryWStream* dst) {
SkASSERT(dst);
SkASSERT(dst != this);
if (0 == this->bytesWritten()) {
return true;
}
if (0 == dst->bytesWritten()) {
*dst = std::move(*this);
return true;
}
dst->fTail->fNext = fHead;
dst->fBytesWrittenBeforeTail += fBytesWrittenBeforeTail + dst->fTail->written();
dst->fTail = fTail;
fHead = fTail = nullptr;
fBytesWrittenBeforeTail = 0;
return true;
}
void SkDynamicMemoryWStream::prependToAndReset(SkDynamicMemoryWStream* dst) {
SkASSERT(dst);
SkASSERT(dst != this);
if (0 == this->bytesWritten()) {
return;
}
if (0 == dst->bytesWritten()) {
*dst = std::move(*this);
return;
}
fTail->fNext = dst->fHead;
dst->fHead = fHead;
dst->fBytesWrittenBeforeTail += fBytesWrittenBeforeTail + fTail->written();
fHead = fTail = nullptr;
fBytesWrittenBeforeTail = 0;
return;
}
bool SkDynamicMemoryWStream::read(void* buffer, size_t offset, size_t count) {
if (offset + count > this->bytesWritten()) {
return false; // test does not partially modify
@ -676,9 +713,7 @@ void SkDynamicMemoryWStream::validate() const {
const Block* block = fHead;
while (block) {
if (block->fNext) {
SkASSERT(block->avail() == 0);
bytes += block->written();
SkASSERT(bytes == SkAlign4(bytes)); // see padToAlign4()
}
block = block->fNext;
}

View File

@ -171,6 +171,8 @@ void remove_color_filter(SkPaint* paint) {
}
}
SkPDFDevice::GraphicStackState::GraphicStackState(SkDynamicMemoryWStream* s) : fContentStream(s) {
}
void SkPDFDevice::GraphicStackState::drainStack() {
if (fContentStream) {
@ -408,9 +410,7 @@ public:
const SkPaint& paint,
bool hasText = false)
: fDevice(device)
, fContentEntry(nullptr)
, fBlendMode(SkBlendMode::kSrcOver)
, fDstFormXObject(nullptr)
, fClipStack(clipStack)
{
if (matrix.hasPerspective()) {
@ -418,14 +418,14 @@ public:
return;
}
fBlendMode = paint.getBlendMode();
fContentEntry =
fContentStream =
fDevice->setUpContentEntry(clipStack, matrix, paint, hasText, &fDstFormXObject);
}
ScopedContentEntry(SkPDFDevice* dev, const SkPaint& paint, bool hasText = false)
: ScopedContentEntry(dev, &dev->cs(), dev->ctm(), paint, hasText) {}
~ScopedContentEntry() {
if (fContentEntry) {
if (fContentStream) {
SkPath* shape = &fShape;
if (shape->isEmpty()) {
shape = nullptr;
@ -434,8 +434,8 @@ public:
}
}
SkDynamicMemoryWStream* entry() { return fContentEntry; }
SkDynamicMemoryWStream* stream() { return fContentEntry; }
explicit operator bool() const { return fContentStream != nullptr; }
SkDynamicMemoryWStream* stream() { return fContentStream; }
/* Returns true when we explicitly need the shape of the drawing. */
bool needShape() {
@ -472,8 +472,8 @@ public:
}
private:
SkPDFDevice* fDevice;
SkDynamicMemoryWStream* fContentEntry;
SkPDFDevice* fDevice = nullptr;
SkDynamicMemoryWStream* fContentStream = nullptr;
SkBlendMode fBlendMode;
sk_sp<SkPDFObject> fDstFormXObject;
SkPath fShape;
@ -502,7 +502,7 @@ void SkPDFDevice::reset() {
fXObjectResources = std::vector<sk_sp<SkPDFObject>>();
fShaderResources = std::vector<sk_sp<SkPDFObject>>();
fFontResources = std::vector<sk_sp<SkPDFFont>>();
fContentEntries.reset();
fContent.reset();
fActiveStackState = GraphicStackState();
}
@ -610,7 +610,7 @@ void SkPDFDevice::drawPoints(SkCanvas::PointMode mode,
}
ScopedContentEntry content(this, *paint);
if (!content.entry()) {
if (!content) {
return;
}
SkDynamicMemoryWStream* contentStream = content.stream();
@ -695,7 +695,7 @@ void SkPDFDevice::drawRect(const SkRect& rect,
}
ScopedContentEntry content(this, paint);
if (!content.entry()) {
if (!content) {
return;
}
SkPDFUtils::AppendRectangle(r, content.stream());
@ -773,7 +773,7 @@ void SkPDFDevice::internalDrawPathWithFilter(const SkClipStack& clipStack,
transform_shader(paint.writable(), ctm); // Since we are using identity matrix.
}
ScopedContentEntry content(this, &clipStack, SkMatrix::I(), *paint);
if (!content.entry()) {
if (!content) {
return;
}
this->addSMaskGraphicState(std::move(maskDevice), content.stream());
@ -875,7 +875,7 @@ void SkPDFDevice::internalDrawPath(const SkClipStack& clipStack,
}
ScopedContentEntry content(this, &clipStack, matrix, paint);
if (!content.entry()) {
if (!content) {
return;
}
constexpr SkScalar kToleranceScale = 0.0625f; // smaller = better conics (circles).
@ -1124,7 +1124,7 @@ void SkPDFDevice::internalDrawGlyphRun(const SkGlyphRun& glyphRun, SkPoint offse
SkRect clipStackBounds = this->cs().bounds(this->bounds());
{
ScopedContentEntry content(this, paint, true);
if (!content.entry()) {
if (!content) {
return;
}
SkDynamicMemoryWStream* out = content.stream();
@ -1300,7 +1300,7 @@ void SkPDFDevice::drawDevice(SkBaseDevice* device, int x, int y, const SkPaint&
SkMatrix matrix = SkMatrix::MakeTrans(SkIntToScalar(x), SkIntToScalar(y));
ScopedContentEntry content(this, &this->cs(), matrix, paint);
if (!content.entry()) {
if (!content) {
return;
}
if (content.needShape()) {
@ -1330,26 +1330,22 @@ std::unique_ptr<SkStreamAsset> SkPDFDevice::content() {
fActiveStackState.drainStack();
fActiveStackState = GraphicStackState();
}
if (fContent.bytesWritten() == 0) {
return skstd::make_unique<SkMemoryStream>();
}
SkDynamicMemoryWStream buffer;
if (fInitialTransform.getType() != SkMatrix::kIdentity_Mask) {
append_transform(fInitialTransform, &buffer);
}
if (fContentEntries.back() && fContentEntries.back() == fContentEntries.front()) {
fContentEntries.front()->writeToAndReset(&buffer);
} else {
for (SkDynamicMemoryWStream& entry : fContentEntries) {
buffer.writeText("q\n");
entry.writeToAndReset(&buffer);
buffer.writeText("Q\n");
}
if (fNeedsExtraSave) {
buffer.writeText("q\n");
}
fContentEntries.reset();
if (buffer.bytesWritten() > 0) {
return std::unique_ptr<SkStreamAsset>(buffer.detachAsStream());
} else {
return skstd::make_unique<SkMemoryStream>();
fContent.writeToAndReset(&buffer);
if (fNeedsExtraSave) {
buffer.writeText("Q\n");
}
fNeedsExtraSave = false;
return std::unique_ptr<SkStreamAsset>(buffer.detachAsStream());
}
/* Draws an inverse filled path by using Path Ops to compute the positive
@ -1480,7 +1476,7 @@ void SkPDFDevice::drawFormXObjectWithMask(sk_sp<SkPDFObject> xObject,
SkPaint paint;
paint.setBlendMode(mode);
ScopedContentEntry content(this, nullptr, SkMatrix::I(), paint);
if (!content.entry()) {
if (!content) {
return;
}
this->setGraphicState(SkPDFGraphicState::GetSMaskGraphicState(
@ -1526,15 +1522,17 @@ SkDynamicMemoryWStream* SkPDFDevice::setUpContentEntry(const SkClipStack* clipSt
if (treat_as_regular_pdf_blend_mode(blendMode)) {
if (!fActiveStackState.fContentStream) {
fActiveStackState = GraphicStackState(fContentEntries.emplace_back());
if (fContent.bytesWritten() != 0) {
fContent.writeText("Q\nq\n");
fNeedsExtraSave = true;
}
fActiveStackState = GraphicStackState(&fContent);
} else {
SkASSERT(fActiveStackState.fContentStream = &fContent);
}
} else {
fActiveStackState.drainStack();
if (blendMode != SkBlendMode::kDstOver) {
fActiveStackState = GraphicStackState(fContentEntries.emplace_back());
} else {
fActiveStackState = GraphicStackState(fContentEntries.emplace_front());
}
fActiveStackState = GraphicStackState(&fContentBuffer);
}
SkASSERT(fActiveStackState.fContentStream);
GraphicStateEntry entry;
@ -1563,14 +1561,25 @@ void SkPDFDevice::finishContentEntry(const SkClipStack* clipStack,
if (blendMode == SkBlendMode::kDstOver) {
SkASSERT(!dst);
if (fContentEntries.front()->bytesWritten() == 0) {
// For DstOver, an empty content entry was inserted before the rest
// of the content entries. If nothing was drawn, it needs to be
// removed.
fContentEntries.pop_front();
if (fContentBuffer.bytesWritten() != 0) {
if (fContent.bytesWritten() != 0) {
fContentBuffer.writeText("Q\nq\n");
fNeedsExtraSave = true;
}
fContentBuffer.prependToAndReset(&fContent);
SkASSERT(fContentBuffer.bytesWritten() == 0);
}
return;
}
if (fContentBuffer.bytesWritten() != 0) {
if (fContent.bytesWritten() != 0) {
fContent.writeText("Q\nq\n");
fNeedsExtraSave = true;
}
fContentBuffer.writeToAndReset(&fContent);
SkASSERT(fContentBuffer.bytesWritten() == 0);
}
if (!dst) {
SkASSERT(blendMode == SkBlendMode::kSrc ||
blendMode == SkBlendMode::kSrcOut);
@ -1578,7 +1587,6 @@ void SkPDFDevice::finishContentEntry(const SkClipStack* clipStack,
}
SkASSERT(dst);
SkASSERT(fContentEntries.count() == 1);
// Changing the current content into a form-xobject will destroy the clip
// objects which is fine since the xobject will already be clipped. However
// if source has shape, we need to clip it too, so a copy of the clip is
@ -1602,7 +1610,6 @@ void SkPDFDevice::finishContentEntry(const SkClipStack* clipStack,
blendMode = SkBlendMode::kClear;
}
} else {
SkASSERT(fContentEntries.count() == 1);
srcFormXObject = this->makeFormXObjectFromDevice();
}
@ -1636,7 +1643,7 @@ void SkPDFDevice::finishContentEntry(const SkClipStack* clipStack,
} else if (blendMode == SkBlendMode::kSrc ||
blendMode == SkBlendMode::kDstATop) {
ScopedContentEntry content(this, nullptr, SkMatrix::I(), stockPaint);
if (content.entry()) {
if (content) {
this->drawFormXObject(srcFormXObject, content.stream());
}
if (blendMode == SkBlendMode::kSrc) {
@ -1644,7 +1651,7 @@ void SkPDFDevice::finishContentEntry(const SkClipStack* clipStack,
}
} else if (blendMode == SkBlendMode::kSrcATop) {
ScopedContentEntry content(this, nullptr, SkMatrix::I(), stockPaint);
if (content.entry()) {
if (content) {
this->drawFormXObject(dst, content.stream());
}
}
@ -1676,11 +1683,7 @@ void SkPDFDevice::finishContentEntry(const SkClipStack* clipStack,
}
bool SkPDFDevice::isContentEmpty() {
if (!fContentEntries.front() || fContentEntries.front()->bytesWritten() == 0) {
SkASSERT(fContentEntries.count() <= 1);
return true;
}
return false;
return fContent.bytesWritten() == 0 && fContentBuffer.bytesWritten() == 0;
}
void SkPDFDevice::populateGraphicStateEntryFromPaint(
@ -1879,7 +1882,7 @@ void SkPDFDevice::internalDrawImageRect(SkKeyedImage imageSubset,
transform_shader(&paint, ctm); // Since we are using identity matrix.
}
ScopedContentEntry content(this, &this->cs(), SkMatrix::I(), paint);
if (!content.entry()) {
if (!content) {
return;
}
this->addSMaskGraphicState(std::move(maskDevice), content.stream());
@ -1986,7 +1989,7 @@ void SkPDFDevice::internalDrawImageRect(SkKeyedImage imageSubset,
SkIntToScalar(subset.height()));
scaled.postConcat(matrix);
ScopedContentEntry content(this, &this->cs(), scaled, paint);
if (!content.entry()) {
if (!content) {
return;
}
if (content.needShape()) {

View File

@ -16,7 +16,6 @@
#include "SkPaint.h"
#include "SkRect.h"
#include "SkRefCnt.h"
#include "SkSinglyLinkedList.h"
#include "SkStream.h"
#include "SkTextBlobPriv.h"
#include "SkKeyedImage.h"
@ -172,9 +171,11 @@ private:
std::vector<sk_sp<SkPDFFont>> fFontResources;
int fNodeId;
SkSinglyLinkedList<SkDynamicMemoryWStream> fContentEntries;
SkDynamicMemoryWStream fContent;
SkDynamicMemoryWStream fContentBuffer;
bool fNeedsExtraSave = false;
struct GraphicStackState {
GraphicStackState(SkDynamicMemoryWStream* s = nullptr) : fContentStream(s) {}
GraphicStackState(SkDynamicMemoryWStream* s = nullptr);
void updateClip(const SkClipStack* clipStack, const SkIRect& bounds);
void updateMatrix(const SkMatrix& matrix);
void updateDrawingState(const SkPDFDevice::GraphicStateEntry& state);