Threaded generation of software paths
Re-land of: https://skia-review.googlesource.com/36560
All information needed by the thread is captured by the prepare
callback object, the lambda captures a pointer to that, and does the
mask render. Once it's done, it signals the semaphore (also owned by the
callback). The callback defers the semaphore wait even longer (into the
ASAP upload), so the odds of waiting for the thread are REALLY low.
Also did a bunch of cleanup along the way, and put in some trace markers
so we can monitor how well this is working.
Traces of a GM that includes GPU and SW path rendering (path-reverse):
Original:
https://screenshot.googleplex.com/f5BG3901tQg.png
Threaded, with wait in the callback (notice pre flush callback blocking):
https://screenshot.googleplex.com/htOSZFE2s04.png
Current version, with wait deferred to ASAP upload function:
https://screenshot.googleplex.com/GHjD0U3C34q.png
Bug: skia:
Change-Id: Idb92f385590749f41328a9aec65b2a93f4775079
Reviewed-on: https://skia-review.googlesource.com/40775
Reviewed-by: Brian Salomon <bsalomon@google.com>
Commit-Queue: Brian Osman <brianosman@google.com>
diff --git a/src/gpu/GrSWMaskHelper.cpp b/src/gpu/GrSWMaskHelper.cpp
index eb39865..af0d1bd 100644
--- a/src/gpu/GrSWMaskHelper.cpp
+++ b/src/gpu/GrSWMaskHelper.cpp
@@ -6,12 +6,12 @@
*/
#include "GrSWMaskHelper.h"
+
#include "GrContext.h"
#include "GrContextPriv.h"
#include "GrShape.h"
#include "GrSurfaceContext.h"
#include "GrTextureProxy.h"
-#include "SkDistanceFieldGen.h"
/*
* Convert a boolean operation into a transfer mode code
@@ -76,13 +76,13 @@
SkIRect bounds = SkIRect::MakeWH(resultBounds.width(), resultBounds.height());
const SkImageInfo bmImageInfo = SkImageInfo::MakeA8(bounds.width(), bounds.height());
- if (!fPixels.tryAlloc(bmImageInfo)) {
+ if (!fPixels->tryAlloc(bmImageInfo)) {
return false;
}
- fPixels.erase(0);
+ fPixels->erase(0);
sk_bzero(&fDraw, sizeof(fDraw));
- fDraw.fDst = fPixels;
+ fDraw.fDst = *fPixels;
fRasterClip.setRect(bounds);
fDraw.fRC = &fRasterClip;
fDraw.fMatrix = &fMatrix;
@@ -92,8 +92,8 @@
sk_sp<GrTextureProxy> GrSWMaskHelper::toTextureProxy(GrContext* context, SkBackingFit fit) {
GrSurfaceDesc desc;
desc.fOrigin = kTopLeft_GrSurfaceOrigin;
- desc.fWidth = fPixels.width();
- desc.fHeight = fPixels.height();
+ desc.fWidth = fPixels->width();
+ desc.fHeight = fPixels->height();
desc.fConfig = kAlpha_8_GrPixelConfig;
sk_sp<GrSurfaceContext> sContext = context->contextPriv().makeDeferredSurfaceContext(
@@ -105,39 +105,9 @@
}
SkImageInfo ii = SkImageInfo::MakeA8(desc.fWidth, desc.fHeight);
- if (!sContext->writePixels(ii, fPixels.addr(), fPixels.rowBytes(), 0, 0)) {
+ if (!sContext->writePixels(ii, fPixels->addr(), fPixels->rowBytes(), 0, 0)) {
return nullptr;
}
return sContext->asTextureProxyRef();
}
-
-/**
- * Convert mask generation results to a signed distance field
- */
-void GrSWMaskHelper::toSDF(unsigned char* sdf) {
- SkGenerateDistanceFieldFromA8Image(sdf, (const unsigned char*)fPixels.addr(),
- fPixels.width(), fPixels.height(), fPixels.rowBytes());
-}
-
-////////////////////////////////////////////////////////////////////////////////
-/**
- * Software rasterizes shape to A8 mask and uploads the result to a scratch texture. Returns the
- * resulting texture on success; nullptr on failure.
- */
-sk_sp<GrTextureProxy> GrSWMaskHelper::DrawShapeMaskToTexture(GrContext* context,
- const GrShape& shape,
- const SkIRect& resultBounds,
- GrAA aa,
- SkBackingFit fit,
- const SkMatrix* matrix) {
- GrSWMaskHelper helper;
-
- if (!helper.init(resultBounds, matrix)) {
- return nullptr;
- }
-
- helper.drawShape(shape, SkRegion::kReplace_Op, aa, 0xFF);
-
- return helper.toTextureProxy(context, fit);
-}
diff --git a/src/gpu/GrSWMaskHelper.h b/src/gpu/GrSWMaskHelper.h
index a98d58b..4e1363c 100644
--- a/src/gpu/GrSWMaskHelper.h
+++ b/src/gpu/GrSWMaskHelper.h
@@ -8,22 +8,15 @@
#ifndef GrSWMaskHelper_DEFINED
#define GrSWMaskHelper_DEFINED
-#include "GrColor.h"
-#include "GrRenderTargetContext.h"
#include "SkAutoPixmapStorage.h"
-#include "SkBitmap.h"
#include "SkDraw.h"
#include "SkMatrix.h"
#include "SkRasterClip.h"
#include "SkRegion.h"
#include "SkTypes.h"
-class GrClip;
-class GrPaint;
class GrShape;
-class GrStyle;
-class GrTexture;
-struct GrUserStencilSettings;
+class GrTextureProxy;
/**
* The GrSWMaskHelper helps generate clip masks using the software rendering
@@ -34,14 +27,15 @@
*
* draw one or more paths/rects specifying the required boolean ops
*
- * toTexture(); // to get it from the internal bitmap to the GPU
+ * toTextureProxy(); // to get it from the internal bitmap to the GPU
*
* The result of this process will be the final mask (on the GPU) in the
* upper left hand corner of the texture.
*/
class GrSWMaskHelper : SkNoncopyable {
public:
- GrSWMaskHelper() { }
+ GrSWMaskHelper(SkAutoPixmapStorage* pixels = nullptr)
+ : fPixels(pixels ? pixels : &fPixelsStorage) { }
// set up the internal state in preparation for draws. Since many masks
// may be accumulated in the helper during creation, "resultBounds"
@@ -57,28 +51,17 @@
sk_sp<GrTextureProxy> toTextureProxy(GrContext*, SkBackingFit fit);
- // Convert mask generation results to a signed distance field
- void toSDF(unsigned char* sdf);
-
// Reset the internal bitmap
void clear(uint8_t alpha) {
- fPixels.erase(SkColorSetARGB(alpha, 0xFF, 0xFF, 0xFF));
+ fPixels->erase(SkColorSetARGB(alpha, 0xFF, 0xFF, 0xFF));
}
- // Canonical usage utility that draws a single path and uploads it
- // to the GPU. The result is returned.
- static sk_sp<GrTextureProxy> DrawShapeMaskToTexture(GrContext*,
- const GrShape&,
- const SkIRect& resultBounds,
- GrAA,
- SkBackingFit,
- const SkMatrix* matrix);
-
private:
- SkMatrix fMatrix;
- SkAutoPixmapStorage fPixels;
- SkDraw fDraw;
- SkRasterClip fRasterClip;
+ SkMatrix fMatrix;
+ SkAutoPixmapStorage* fPixels;
+ SkAutoPixmapStorage fPixelsStorage;
+ SkDraw fDraw;
+ SkRasterClip fRasterClip;
typedef SkNoncopyable INHERITED;
};
diff --git a/src/gpu/GrSoftwarePathRenderer.cpp b/src/gpu/GrSoftwarePathRenderer.cpp
index 27ba8e6..e76af89 100644
--- a/src/gpu/GrSoftwarePathRenderer.cpp
+++ b/src/gpu/GrSoftwarePathRenderer.cpp
@@ -8,9 +8,16 @@
#include "GrSoftwarePathRenderer.h"
#include "GrAuditTrail.h"
#include "GrClip.h"
+#include "GrContextPriv.h"
#include "GrGpuResourcePriv.h"
+#include "GrOpFlushState.h"
+#include "GrOpList.h"
#include "GrResourceProvider.h"
#include "GrSWMaskHelper.h"
+#include "SkMakeUnique.h"
+#include "SkSemaphore.h"
+#include "SkTaskGroup.h"
+#include "SkTraceEvent.h"
#include "ops/GrDrawOp.h"
#include "ops/GrRectOpFactory.h"
@@ -145,10 +152,84 @@
maskMatrix.preConcat(viewMatrix);
paint.addCoverageFragmentProcessor(GrSimpleTextureEffect::Make(
std::move(proxy), nullptr, maskMatrix, GrSamplerParams::kNone_FilterMode));
- renderTargetContext->addDrawOp(clip,
- GrRectOpFactory::MakeNonAAFillWithLocalMatrix(
- std::move(paint), SkMatrix::I(), invert, dstRect,
- GrAAType::kNone, &userStencilSettings));
+ DrawNonAARect(renderTargetContext, std::move(paint), userStencilSettings, clip, SkMatrix::I(),
+ dstRect, invert);
+}
+
+static sk_sp<GrTextureProxy> make_deferred_mask_texture_proxy(GrContext* context, SkBackingFit fit,
+ int width, int height) {
+ GrSurfaceDesc desc;
+ desc.fOrigin = kTopLeft_GrSurfaceOrigin;
+ desc.fWidth = width;
+ desc.fHeight = height;
+ desc.fConfig = kAlpha_8_GrPixelConfig;
+
+ sk_sp<GrSurfaceContext> sContext =
+ context->contextPriv().makeDeferredSurfaceContext(desc, fit, SkBudgeted::kYes);
+ if (!sContext || !sContext->asTextureProxy()) {
+ return nullptr;
+ }
+ return sContext->asTextureProxyRef();
+}
+
+namespace {
+
+class GrMaskUploaderPrepareCallback : public GrPrepareCallback {
+public:
+ GrMaskUploaderPrepareCallback(sk_sp<GrTextureProxy> proxy, const SkIRect& maskBounds,
+ const SkMatrix& viewMatrix, const GrShape& shape, GrAA aa)
+ : fProxy(std::move(proxy))
+ , fMaskBounds(maskBounds)
+ , fViewMatrix(viewMatrix)
+ , fShape(shape)
+ , fAA(aa)
+ , fWaited(false) {}
+
+ ~GrMaskUploaderPrepareCallback() override {
+ if (!fWaited) {
+ // This can happen if our owning op list fails to instantiate (so it never prepares)
+ fPixelsReady.wait();
+ }
+ }
+
+ void operator()(GrOpFlushState* flushState) override {
+ TRACE_EVENT0("skia", "Mask Uploader Pre Flush Callback");
+ auto uploadMask = [this](GrDrawOp::WritePixelsFn& writePixelsFn) {
+ TRACE_EVENT0("skia", "Mask Upload");
+ this->fPixelsReady.wait();
+ this->fWaited = true;
+ // If the worker thread was unable to allocate pixels, this check will fail, and we'll
+ // end up drawing with an uninitialized mask texture, but at least we won't crash.
+ if (this->fPixels.addr()) {
+ writePixelsFn(this->fProxy.get(), 0, 0,
+ this->fPixels.width(), this->fPixels.height(),
+ kAlpha_8_GrPixelConfig,
+ this->fPixels.addr(), this->fPixels.rowBytes());
+ }
+ };
+ flushState->addASAPUpload(std::move(uploadMask));
+ }
+
+ SkAutoPixmapStorage* getPixels() { return &fPixels; }
+ SkSemaphore* getSemaphore() { return &fPixelsReady; }
+ const SkIRect& getMaskBounds() const { return fMaskBounds; }
+ const SkMatrix* getViewMatrix() const { return &fViewMatrix; }
+ const GrShape& getShape() const { return fShape; }
+ GrAA getAA() const { return fAA; }
+
+private:
+ // NOTE: This ref cnt isn't thread safe!
+ sk_sp<GrTextureProxy> fProxy;
+ SkAutoPixmapStorage fPixels;
+ SkSemaphore fPixelsReady;
+
+ SkIRect fMaskBounds;
+ SkMatrix fViewMatrix;
+ GrShape fShape;
+ GrAA fAA;
+ bool fWaited;
+};
+
}
////////////////////////////////////////////////////////////////////////////////
@@ -205,11 +286,6 @@
}
GrUniqueKey maskKey;
- struct KeyData {
- SkScalar fFractionalTranslateX;
- SkScalar fFractionalTranslateY;
- };
-
if (useCache) {
// We require the upper left 2x2 of the matrix to match exactly for a cache hit.
SkScalar sx = args.fViewMatrix->get(SkMatrix::kMScaleX);
@@ -240,8 +316,6 @@
builder[4] = fracX | (fracY >> 8);
args.fShape->writeUnstyledKey(&builder[5]);
#endif
- // FIXME: Doesn't the key need to consider whether we're using AA or not? In practice that
- // should always be true, though.
}
sk_sp<GrTextureProxy> proxy;
@@ -251,9 +325,42 @@
if (!proxy) {
SkBackingFit fit = useCache ? SkBackingFit::kExact : SkBackingFit::kApprox;
GrAA aa = GrAAType::kCoverage == args.fAAType ? GrAA::kYes : GrAA::kNo;
- proxy = GrSWMaskHelper::DrawShapeMaskToTexture(args.fContext, *args.fShape,
- *boundsForMask, aa,
- fit, args.fViewMatrix);
+
+ SkTaskGroup* taskGroup = args.fContext->contextPriv().getTaskGroup();
+ if (taskGroup) {
+ proxy = make_deferred_mask_texture_proxy(args.fContext, fit,
+ boundsForMask->width(),
+ boundsForMask->height());
+ if (!proxy) {
+ return false;
+ }
+
+ auto uploader = skstd::make_unique<GrMaskUploaderPrepareCallback>(
+ proxy, *boundsForMask, *args.fViewMatrix, *args.fShape, aa);
+ GrMaskUploaderPrepareCallback* uploaderRaw = uploader.get();
+
+ auto drawAndUploadMask = [uploaderRaw] {
+ TRACE_EVENT0("skia", "Threaded SW Mask Render");
+ GrSWMaskHelper helper(uploaderRaw->getPixels());
+ if (helper.init(uploaderRaw->getMaskBounds(), uploaderRaw->getViewMatrix())) {
+ helper.drawShape(uploaderRaw->getShape(), SkRegion::kReplace_Op,
+ uploaderRaw->getAA(), 0xFF);
+ } else {
+ SkDEBUGFAIL("Unable to allocate SW mask.");
+ }
+ uploaderRaw->getSemaphore()->signal();
+ };
+ taskGroup->add(std::move(drawAndUploadMask));
+ args.fRenderTargetContext->getOpList()->addPrepareCallback(std::move(uploader));
+ } else {
+ GrSWMaskHelper helper;
+ if (!helper.init(*boundsForMask, args.fViewMatrix)) {
+ return false;
+ }
+ helper.drawShape(*args.fShape, SkRegion::kReplace_Op, aa, 0xFF);
+ proxy = helper.toTextureProxy(args.fContext, fit);
+ }
+
if (!proxy) {
return false;
}