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:
parent
3d6390e7bb
commit
42137de2b2
@ -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",
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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
|
@ -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;
|
||||
}
|
||||
|
@ -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()) {
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user