Reland: add SkPath::shrinkToFit

Original CL: https://skia-review.googlesource.com/c/skia/+/150967

  * expanded tests
  * fixed shared pathref copying

Change-Id: I4bfe89f597485aa2db68f58d99112188615faceb
Reviewed-on: https://skia-review.googlesource.com/153553
Commit-Queue: Florin Malita <fmalita@chromium.org>
Reviewed-by: Mike Reed <reed@google.com>
Auto-Submit: Florin Malita <fmalita@chromium.org>
This commit is contained in:
Florin Malita 2018-09-11 14:01:42 -04:00 committed by Skia Commit-Bot
parent efa6bcb022
commit 3d413c5761
4 changed files with 112 additions and 11 deletions

View File

@ -549,6 +549,8 @@ public:
*/
void incReserve(unsigned extraPtCount);
void shrinkToFit();
/** Adds beginning of contour at SkPoint (x, y).
@param x x-axis value of contour start
@ -1667,6 +1669,8 @@ public:
bool pathRefIsValid() const { return fPathRef->isValid(); }
#endif
SkDEBUGCODE(size_t debugging_private_getFreeSpace() const;)
private:
sk_sp<SkPathRef> fPathRef;
int fLastMoveToIndex;

View File

@ -407,7 +407,7 @@ private:
fFreeSpace = 0;
fVerbCnt = 0;
fPointCnt = 0;
this->makeSpace(minSize);
this->makeSpace(minSize, true);
fVerbCnt = verbCount;
fPointCnt = pointCount;
fFreeSpace -= newSize;
@ -437,24 +437,28 @@ private:
/**
* Ensures that the free space available in the path ref is >= size. The verb and point counts
* are not changed.
* are not changed. May allocate extra capacity, unless |exact| is true.
*/
void makeSpace(size_t size) {
void makeSpace(size_t size, bool exact = false) {
SkDEBUGCODE(this->validate();)
if (size <= fFreeSpace) {
return;
}
size_t growSize = size - fFreeSpace;
size_t oldSize = this->currSize();
// round to next multiple of 8 bytes
growSize = (growSize + 7) & ~static_cast<size_t>(7);
// we always at least double the allocation
if (growSize < oldSize) {
growSize = oldSize;
}
if (growSize < kMinSize) {
growSize = kMinSize;
if (!exact) {
// round to next multiple of 8 bytes
growSize = (growSize + 7) & ~static_cast<size_t>(7);
// we always at least double the allocation
if (growSize < oldSize) {
growSize = oldSize;
}
if (growSize < kMinSize) {
growSize = kMinSize;
}
}
constexpr size_t maxSize = std::numeric_limits<size_t>::max();
size_t newSize;
if (growSize <= maxSize - oldSize) {
@ -555,6 +559,7 @@ private:
friend class PathRefTest_Private;
friend class ForceIsRRect_Private; // unit test isRRect
friend class SkPath;
};
#endif

View File

@ -43,6 +43,52 @@ SkPathRef::Editor::Editor(sk_sp<SkPathRef>* pathRef,
SkDEBUGCODE(sk_atomic_inc(&fPathRef->fEditorsAttached);)
}
// Sort of like makeSpace(0) but the the additional requirement that we actively shrink the
// allocations to just fit the current needs. makeSpace() will only grow, but never shrinks.
//
void SkPath::shrinkToFit() {
const size_t kMinFreeSpaceForShrink = 8; // just made up a small number
if (fPathRef->fFreeSpace <= kMinFreeSpaceForShrink) {
return;
}
if (fPathRef->unique()) {
int pointCount = fPathRef->fPointCnt;
int verbCount = fPathRef->fVerbCnt;
size_t ptsSize = sizeof(SkPoint) * pointCount;
size_t vrbSize = sizeof(uint8_t) * verbCount;
size_t minSize = ptsSize + vrbSize;
void* newAlloc = sk_malloc_canfail(minSize);
if (!newAlloc) {
return; // couldn't allocate the smaller buffer, but that's ok
}
sk_careful_memcpy(newAlloc, fPathRef->fPoints, ptsSize);
sk_careful_memcpy((char*)newAlloc + minSize - vrbSize, fPathRef->verbsMemBegin(), vrbSize);
sk_free(fPathRef->fPoints);
fPathRef->fPoints = static_cast<SkPoint*>(newAlloc);
fPathRef->fVerbs = (uint8_t*)newAlloc + minSize;
fPathRef->fFreeSpace = 0;
fPathRef->fConicWeights.shrinkToFit();
} else {
sk_sp<SkPathRef> pr(new SkPathRef);
pr->copy(*fPathRef, 0, 0);
fPathRef = std::move(pr);
}
SkDEBUGCODE(fPathRef->validate();)
}
#ifdef SK_DEBUG
size_t SkPath::debugging_private_getFreeSpace() const {
return fPathRef->fFreeSpace;
}
#endif
//////////////////////////////////////////////////////////////////////////////
SkPathRef::~SkPathRef() {

View File

@ -5103,3 +5103,49 @@ DEF_TEST(triangle_big, reporter) {
draw_triangle(surface->getCanvas(), pts);
}
static void add_verbs(SkPath* path, int count) {
path->moveTo(0, 0);
for (int i = 0; i < count; ++i) {
switch (i & 3) {
case 0: path->lineTo(10, 20); break;
case 1: path->quadTo(5, 6, 7, 8); break;
case 2: path->conicTo(1, 2, 3, 4, 0.5f); break;
case 3: path->cubicTo(2, 4, 6, 8, 10, 12); break;
}
}
}
// Make sure when we call shrinkToFit() that we always shrink (or stay the same)
// and that if we call twice, we stay the same.
DEF_TEST(Path_shrinkToFit, reporter) {
size_t max_free = 0;
for (int verbs = 0; verbs < 100; ++verbs) {
SkPath unique_path, shared_path;
add_verbs(&unique_path, verbs);
add_verbs(&shared_path, verbs);
const SkPath copy = shared_path;
REPORTER_ASSERT(reporter, shared_path == unique_path);
REPORTER_ASSERT(reporter, shared_path == copy);
#ifdef SK_DEBUG
size_t before = unique_path.debugging_private_getFreeSpace();
#endif
unique_path.shrinkToFit();
shared_path.shrinkToFit();
REPORTER_ASSERT(reporter, shared_path == unique_path);
REPORTER_ASSERT(reporter, shared_path == copy);
#ifdef SK_DEBUG
size_t after = unique_path.debugging_private_getFreeSpace();
REPORTER_ASSERT(reporter, before >= after);
max_free = std::max(max_free, before - after);
size_t after2 = unique_path.debugging_private_getFreeSpace();
REPORTER_ASSERT(reporter, after == after2);
#endif
}
if (false) {
SkDebugf("max_free %zu\n", max_free);
}
}