diff options
-rw-r--r-- | gn/tests.gni | 1 | ||||
-rw-r--r-- | src/gpu/GrShape.cpp | 10 | ||||
-rw-r--r-- | src/gpu/GrShape.h | 17 | ||||
-rw-r--r-- | src/gpu/ops/GrTessellatingPathRenderer.cpp | 1 | ||||
-rw-r--r-- | tests/GrShapeTest.cpp | 11 | ||||
-rw-r--r-- | tests/PathRendererCacheTests.cpp | 113 |
6 files changed, 150 insertions, 3 deletions
diff --git a/gn/tests.gni b/gn/tests.gni index 9cea3ea355..389d8769c2 100644 --- a/gn/tests.gni +++ b/gn/tests.gni @@ -156,6 +156,7 @@ tests_sources = [ "$_tests/PDFOpaqueSrcModeToSrcOverTest.cpp", "$_tests/PDFPrimitivesTest.cpp", "$_tests/OnFlushCallbackTest.cpp", + "$_tests/PathRendererCacheTests.cpp", "$_tests/PictureBBHTest.cpp", "$_tests/PictureShaderTest.cpp", "$_tests/PictureTest.cpp", diff --git a/src/gpu/GrShape.cpp b/src/gpu/GrShape.cpp index f7ed18f768..9636427c8e 100644 --- a/src/gpu/GrShape.cpp +++ b/src/gpu/GrShape.cpp @@ -9,6 +9,7 @@ GrShape& GrShape::operator=(const GrShape& that) { fStyle = that.fStyle; + fOriginalPath = that.fOriginalPath; this->changeType(that.fType, Type::kPath == that.fType ? &that.path() : nullptr); switch (fType) { case Type::kEmpty: @@ -66,6 +67,7 @@ GrShape GrShape::MakeFilled(const GrShape& original, FillInversion inversion) { return original; } GrShape result; + result.fOriginalPath = original.fOriginalPath; switch (original.fType) { case Type::kRRect: result.fType = original.fType; @@ -324,7 +326,11 @@ void GrShape::setInheritedKey(const GrShape &parent, GrStyle::Apply apply, SkSca } } -GrShape::GrShape(const GrShape& that) : fStyle(that.fStyle) { +void GrShape::addGenIDChangeListener(SkPathRef::GenIDChangeListener* listener) const { + SkPathPriv::AddGenIDChangeListener(fOriginalPath, listener); +} + +GrShape::GrShape(const GrShape& that) : fStyle(that.fStyle), fOriginalPath(that.fOriginalPath) { const SkPath* thatPath = Type::kPath == that.fType ? &that.fPathData.fPath : nullptr; this->initType(that.fType, thatPath); switch (fType) { @@ -380,6 +386,7 @@ GrShape::GrShape(const GrShape& parent, GrStyle::Apply apply, SkScalar scale) { scale)) { tmpParent.init(*srcForPathEffect, GrStyle(strokeRec, nullptr)); *this = tmpParent.get()->applyStyle(apply, scale); + fOriginalPath = parent.fOriginalPath; return; } // A path effect has access to change the res scale but we aren't expecting it to and it @@ -430,6 +437,7 @@ GrShape::GrShape(const GrShape& parent, GrStyle::Apply apply, SkScalar scale) { scale)); fStyle.resetToInitStyle(fillOrHairline); } + fOriginalPath = parent.fOriginalPath; this->attemptToSimplifyPath(); this->setInheritedKey(*parentForKey, apply, scale); } diff --git a/src/gpu/GrShape.h b/src/gpu/GrShape.h index 2b48eca633..dd8aa850d6 100644 --- a/src/gpu/GrShape.h +++ b/src/gpu/GrShape.h @@ -46,7 +46,7 @@ public: explicit GrShape(const SkRect& rect) : GrShape(rect, GrStyle::SimpleFill()) {} - GrShape(const SkPath& path, const GrStyle& style) : fStyle(style) { + GrShape(const SkPath& path, const GrStyle& style) : fStyle(style), fOriginalPath(path) { this->initType(Type::kPath, &path); this->attemptToSimplifyPath(); } @@ -91,7 +91,7 @@ public: this->attemptToSimplifyRRect(); } - GrShape(const SkPath& path, const SkPaint& paint) : fStyle(paint) { + GrShape(const SkPath& path, const SkPaint& paint) : fStyle(paint), fOriginalPath(path) { this->initType(Type::kPath, &path); this->attemptToSimplifyPath(); } @@ -365,6 +365,18 @@ public: */ void writeUnstyledKey(uint32_t* key) const; + /** + * Adds a listener to the *original* path. Typically used to invalidate cached resources when + * a path is no longer in-use. If the shape started out as something other than a path, this + * does nothing (but will delete the listener). + */ + void addGenIDChangeListener(SkPathRef::GenIDChangeListener* listener) const; + + /** + * Gets the generation ID of the *original* path. This is only exposed for unit tests. + */ + uint32_t testingOnly_getOriginalGenerationID() const; + private: enum class Type { @@ -492,6 +504,7 @@ private: } fLineData; }; GrStyle fStyle; + SkPath fOriginalPath; SkAutoSTArray<8, uint32_t> fInheritedKey; }; #endif diff --git a/src/gpu/ops/GrTessellatingPathRenderer.cpp b/src/gpu/ops/GrTessellatingPathRenderer.cpp index 099cb1151f..13e4af5015 100644 --- a/src/gpu/ops/GrTessellatingPathRenderer.cpp +++ b/src/gpu/ops/GrTessellatingPathRenderer.cpp @@ -280,6 +280,7 @@ private: info.fCount = count; key.setCustomData(SkData::MakeWithCopy(&info, sizeof(info))); rp->assignUniqueKeyToResource(key, allocator.vertexBuffer()); + fShape.addGenIDChangeListener(new PathInvalidator(key)); } void drawAA(Target* target, const GrGeometryProcessor* gp) { diff --git a/tests/GrShapeTest.cpp b/tests/GrShapeTest.cpp index 087ac525c8..2b2a169477 100644 --- a/tests/GrShapeTest.cpp +++ b/tests/GrShapeTest.cpp @@ -17,6 +17,10 @@ #include "SkSurface.h" #include "SkClipOpPriv.h" +uint32_t GrShape::testingOnly_getOriginalGenerationID() const { + return fOriginalPath.getGenerationID(); +} + using Key = SkTArray<uint32_t>; static bool make_key(Key* key, const GrShape& shape) { @@ -493,6 +497,13 @@ private: make_key(&fAppliedPEThenStrokeKey, fAppliedPEThenStroke); make_key(&fAppliedFullKey, fAppliedFull); + // All shapes should report the same "original" path, so that path renderers can get to it + // if necessary. + uint32_t baseGenID = fBase.testingOnly_getOriginalGenerationID(); + REPORTER_ASSERT(r, baseGenID == fAppliedPE.testingOnly_getOriginalGenerationID()); + REPORTER_ASSERT(r, baseGenID == fAppliedPEThenStroke.testingOnly_getOriginalGenerationID()); + REPORTER_ASSERT(r, baseGenID == fAppliedFull.testingOnly_getOriginalGenerationID()); + // Applying the path effect and then the stroke should always be the same as applying // both in one go. REPORTER_ASSERT(r, fAppliedPEThenStrokeKey == fAppliedFullKey); diff --git a/tests/PathRendererCacheTests.cpp b/tests/PathRendererCacheTests.cpp new file mode 100644 index 0000000000..da58fa4d7d --- /dev/null +++ b/tests/PathRendererCacheTests.cpp @@ -0,0 +1,113 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "Test.h" + +#include "SkPath.h" + +#if SK_SUPPORT_GPU +#include "GrClip.h" +#include "GrContext.h" +#include "GrContextPriv.h" +#include "GrResourceCache.h" +#include "effects/GrPorterDuffXferProcessor.h" +#include "ops/GrTessellatingPathRenderer.h" + +static SkPath create_concave_path() { + SkPath path; + path.moveTo(100, 0); + path.lineTo(200, 200); + path.lineTo(100, 150); + path.lineTo(0, 200); + path.close(); + return path; +} + +static void draw_path(GrContext* ctx, + GrRenderTargetContext* renderTargetContext, + const SkPath& path, + GrPathRenderer* pr, + GrAAType aaType = GrAAType::kNone, + GrStyle style = GrStyle(SkStrokeRec::kFill_InitStyle)) { + GrPaint paint; + paint.setXPFactory(GrPorterDuffXPFactory::Get(SkBlendMode::kSrc)); + + GrNoClip noClip; + SkIRect clipConservativeBounds = SkIRect::MakeWH(renderTargetContext->width(), + renderTargetContext->height()); + GrShape shape(path, style); + if (shape.style().applies()) { + shape = shape.applyStyle(GrStyle::Apply::kPathEffectAndStrokeRec, 1.0f); + } + SkMatrix matrix = SkMatrix::I(); + GrPathRenderer::DrawPathArgs args{ctx, + std::move(paint), + &GrUserStencilSettings::kUnused, + renderTargetContext, + &noClip, + &clipConservativeBounds, + &matrix, + &shape, + aaType, + false}; + pr->drawPath(args); +} + +static void test_path(skiatest::Reporter* reporter, + std::function<SkPath(void)> createPath, + GrPathRenderer* pathRenderer, + int expectedResources = 1, + GrAAType aaType = GrAAType::kNone, + GrStyle style = GrStyle(SkStrokeRec::kFill_InitStyle)) { + sk_sp<GrContext> ctx = GrContext::MakeMock(nullptr); + ctx->setResourceCacheLimits(100, 10000); + GrResourceCache* cache = ctx->getResourceCache(); + + sk_sp<GrRenderTargetContext> rtc(ctx->makeDeferredRenderTargetContext( + SkBackingFit::kApprox, 800, 800, kRGBA_8888_GrPixelConfig, nullptr, 0, + kTopLeft_GrSurfaceOrigin)); + if (!rtc) { + return; + } + + SkPath path = createPath(); + + // Initially, cache only has the render target context + REPORTER_ASSERT(reporter, 1 == cache->getResourceCount()); + + // Draw the path, check that new resource count matches expectations + draw_path(ctx.get(), rtc.get(), path, pathRenderer); + ctx->flush(); + REPORTER_ASSERT(reporter, (1 + expectedResources) == cache->getResourceCount()); + + // Nothing should be purgeable yet + cache->purgeAsNeeded(); + REPORTER_ASSERT(reporter, (1 + expectedResources) == cache->getResourceCount()); + + // Reset the path to change the GenID, which should invalidate any resources in the cache + path.reset(); + cache->purgeAsNeeded(); + REPORTER_ASSERT(reporter, 1 == cache->getResourceCount()); +} + +// Test that deleting the original path invalidates the VBs cached by the tessellating path renderer +DEF_GPUTEST(TessellatingPathRendererCacheTest, reporter, factory) { + GrTessellatingPathRenderer tess; + + // Tessellating path renderer stores vertex buffers in the cache (for non-AA paths) + test_path(reporter, create_concave_path, &tess); + + // Test with a shape that applies. This needs to attach the invalidation logic to the original + // path, not the modified path produced by the style. + SkPaint paint; + paint.setStyle(SkPaint::kStroke_Style); + paint.setStrokeWidth(1); + GrStyle style(paint); + test_path(reporter, create_concave_path, &tess, 1, GrAAType::kNone, style); +} + +#endif |