Roll external/skia 61ffd53a9..b49d7b011 (5 commits)
https://skia.googlesource.com/skia.git/+log/61ffd53a9..b49d7b011
2017-11-07 liyuqian@google.com This is a reland of 67340. This CL fixes the broken layout tests by preserving the containedInClip boolean. We will eventually remove it and rebaseline the layout tests.
2017-11-07 csmartdalton@google.com Fix dangling pointers when Ganesh culls CCPR Ops early
2017-11-07 bsalomon@google.com Make GrAtlasTextBlob return to caller when a flush is required during subrun tessellation.
2017-11-07 rmistry@google.com Add section for how to connect to Skia swarming bots
2017-11-07 csmartdalton@google.com Add clipping options to path text bench and samples
The AutoRoll server is located here: https://android-roll.skia.org
Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+/master/autoroll/README.md
If the roll is causing failures, please contact the current sheriff, who should
be CC'd on the roll, and stop the roller if necessary.
Test: Presubmit checks will test this change.
Change-Id: Ia5fc3396761cfe7f508a55d08cddf3decf1d9c89
Exempt-From-Owner-Approval: The autoroll bot does not require owner approval.
diff --git a/Android.bp b/Android.bp
index 1b70283..e668fb5 100644
--- a/Android.bp
+++ b/Android.bp
@@ -601,7 +601,7 @@
"src/gpu/ops/GrTextureOp.cpp",
"src/gpu/text/GrAtlasGlyphCache.cpp",
"src/gpu/text/GrAtlasTextBlob.cpp",
- "src/gpu/text/GrAtlasTextBlob_regenInOp.cpp",
+ "src/gpu/text/GrAtlasTextBlobVertexRegenerator.cpp",
"src/gpu/text/GrAtlasTextContext.cpp",
"src/gpu/text/GrDistanceFieldAdjustTable.cpp",
"src/gpu/text/GrStencilAndCoverTextContext.cpp",
diff --git a/bench/PathTextBench.cpp b/bench/PathTextBench.cpp
index 6551fa5..8920223 100644
--- a/bench/PathTextBench.cpp
+++ b/bench/PathTextBench.cpp
@@ -11,6 +11,7 @@
#include "SkPaint.h"
#include "SkPath.h"
#include "SkRandom.h"
+#include "sk_tool_utils.h"
static constexpr int kScreenWidth = 1500;
static constexpr int kScreenHeight = 1500;
@@ -27,12 +28,19 @@
*/
class PathTextBench : public Benchmark {
public:
- PathTextBench(bool cached) : fCached(cached) {}
+ PathTextBench(bool clipped, bool uncached) : fClipped(clipped), fUncached(uncached) {}
bool isVisual() override { return true; }
private:
const char* onGetName() override {
- return fCached ? "path_text" : "path_text_uncached";
+ fName = "path_text";
+ if (fClipped) {
+ fName.append("_clipped");
+ }
+ if (fUncached) {
+ fName.append("_uncached");
+ }
+ return fName.c_str();
}
SkIPoint onGetSize() override { return SkIPoint::Make(kScreenWidth, kScreenHeight); }
@@ -43,7 +51,7 @@
for (int i = 0; i < kNumGlyphs; ++i) {
SkGlyphID id = cache->unicharToGlyph(kGlyphs[i]);
cache->getScalerContext()->getPath(SkPackedGlyphID(id), &fGlyphs[i]);
- fGlyphs[i].setIsVolatile(!fCached);
+ fGlyphs[i].setIsVolatile(fUncached);
}
SkRandom rand;
@@ -67,10 +75,18 @@
fPaints[i].setAntiAlias(true);
fPaints[i].setColor(rand.nextU() | 0x80808080);
}
+
+ if (fClipped) {
+ fClipPath = sk_tool_utils::make_star(SkRect::MakeIWH(kScreenWidth,kScreenHeight), 11,3);
+ fClipPath.setIsVolatile(fUncached);
+ }
}
void onDraw(int loops, SkCanvas* canvas) override {
SkAutoCanvasRestore acr(canvas, true);
+ if (fClipped) {
+ canvas->clipPath(fClipPath, SkClipOp::kIntersect, true);
+ }
for (int i = 0; i < kNumDraws; ++i) {
const SkPath& glyph = fGlyphs[i % kNumGlyphs];
canvas->setMatrix(fXforms[i]);
@@ -78,13 +94,17 @@
}
}
- const bool fCached;
+ const bool fClipped;
+ const bool fUncached;
+ SkString fName;
SkPath fGlyphs[kNumGlyphs];
SkPaint fPaints[kNumDraws];
SkMatrix fXforms[kNumDraws];
+ SkPath fClipPath;
typedef Benchmark INHERITED;
};
-DEF_BENCH(return new PathTextBench(false);)
-DEF_BENCH(return new PathTextBench(true);)
+DEF_BENCH(return new PathTextBench(false, false);)
+DEF_BENCH(return new PathTextBench(false, true);)
+DEF_BENCH(return new PathTextBench(true, true);)
diff --git a/gn/gpu.gni b/gn/gpu.gni
index ef60312..e67b2ac 100644
--- a/gn/gpu.gni
+++ b/gn/gpu.gni
@@ -387,7 +387,7 @@
"$_src/gpu/text/GrAtlasGlyphCache.cpp",
"$_src/gpu/text/GrAtlasGlyphCache.h",
"$_src/gpu/text/GrAtlasTextBlob.cpp",
- "$_src/gpu/text/GrAtlasTextBlob_regenInOp.cpp",
+ "$_src/gpu/text/GrAtlasTextBlobVertexRegenerator.cpp",
"$_src/gpu/text/GrAtlasTextBlob.h",
"$_src/gpu/text/GrAtlasTextContext.cpp",
"$_src/gpu/text/GrAtlasTextContext.h",
diff --git a/samplecode/SamplePathText.cpp b/samplecode/SamplePathText.cpp
index 5df223b..12bb282 100644
--- a/samplecode/SamplePathText.cpp
+++ b/samplecode/SamplePathText.cpp
@@ -13,6 +13,7 @@
#include "SkPath.h"
#include "SkRandom.h"
#include "SkTaskGroup.h"
+#include "sk_tool_utils.h"
////////////////////////////////////////////////////////////////////////////////////////////////////
// Static text from paths.
@@ -21,7 +22,7 @@
constexpr static int kNumPaths = 1500;
virtual const char* getName() const { return "PathText"; }
- PathText() : fRand(25) {
+ PathText() {
SkPaint defaultPaint;
SkAutoGlyphCache agc(defaultPaint, nullptr, &SkMatrix::I());
SkGlyphCache* cache = agc.getCache();
@@ -53,10 +54,32 @@
SampleCode::TitleR(evt, this->getName());
return true;
}
+ SkUnichar unichar;
+ if (SampleCode::CharQ(*evt, &unichar)) {
+ if (unichar == 'X') {
+ fDoClip = !fDoClip;
+ this->inval(nullptr);
+ return true;
+ }
+ }
return this->INHERITED::onQuery(evt);
}
void onDrawContent(SkCanvas* canvas) override {
+ if (fDoClip) {
+ SkMatrix oldMatrix = canvas->getTotalMatrix();
+ canvas->setMatrix(SkMatrix::MakeScale(this->width(), this->height()));
+ canvas->save();
+ canvas->clipPath(fClipPath, SkClipOp::kDifference, true);
+ canvas->clear(SK_ColorBLACK);
+ canvas->restore();
+ canvas->clipPath(fClipPath, SkClipOp::kIntersect, true);
+ canvas->setMatrix(oldMatrix);
+ }
+ this->drawGlyphs(canvas);
+ }
+
+ virtual void drawGlyphs(SkCanvas* canvas) {
for (Glyph& glyph : fGlyphs) {
SkAutoCanvasRestore acr(canvas, true);
canvas->translate(glyph.fPosition.x(), glyph.fPosition.y());
@@ -81,7 +104,9 @@
};
Glyph fGlyphs[kNumPaths];
- SkRandom fRand;
+ SkRandom fRand{25};
+ SkPath fClipPath = sk_tool_utils::make_star(SkRect{0,0,1,1}, 11, 3);
+ bool fDoClip = false;
typedef SampleView INHERITED;
};
@@ -193,7 +218,7 @@
std::swap(fFrontMatrices, fBackMatrices);
}
- void onDrawContent(SkCanvas* canvas) override {
+ void drawGlyphs(SkCanvas* canvas) override {
for (int i = 0; i < kNumPaths; ++i) {
SkAutoCanvasRestore acr(canvas, true);
canvas->concat(fFrontMatrices[i]);
@@ -299,7 +324,7 @@
fFrontPaths.swap(fBackPaths);
}
- void onDrawContent(SkCanvas* canvas) override {
+ void drawGlyphs(SkCanvas* canvas) override {
for (int i = 0; i < kNumPaths; ++i) {
canvas->drawPath(fFrontPaths[i], fGlyphs[i].fPaint);
}
diff --git a/site/dev/sheriffing/trooper.md b/site/dev/sheriffing/trooper.md
index e49d7cf..c21af03 100644
--- a/site/dev/sheriffing/trooper.md
+++ b/site/dev/sheriffing/trooper.md
@@ -49,31 +49,12 @@
- Install the Skia trooper Chrome extension (available [here](https://chrome.google.com/webstore/a/google.com/detail/alerts-for-skia-troopers/fpljhfiomnfioecagooiekldeolcpief)) to be able to see alerts quickly in the browser.
-- Where machines are located:
- - Machine name like "skia-gce-NNN", "skia-i-gce-NNN", "ct-gce-NNN", "skia-ct-gce-NNN", "ct-xxx-builder-NNN" -> GCE
- - Machine name ends with "a9", "m3" -> Chrome Golo/Labs
- - Machine name ends with "m5" -> CT bare-metal bots in Chrome Golo
- - Machine name starts with "skia-e-", "skia-i-" (other than "skia-i-gce-NNN"), "skia-rpi-" -> Chapel Hill lab
+- See [this section](../testing/swarmingbots#connecting-to-swarming-bots) for details on how to connect to the different Skia bots.
- The [chrome-infra hangout](https://goto.google.com/cit-hangout) is useful for
questions regarding bots managed by the Chrome Infra team and to get
visibility into upstream failures that cause problems for us.
-- To log in to a Linux bot in GCE, use `gcloud compute ssh default@<machine
- name>`. Choose the zone listed for the
- [GCE VM](https://console.cloud.google.com/project/31977622648/compute/instances)
- (or specify it using the `--zone` command-line flag).
-
-- To log in to a Windows bot in GCE, use
- [Chrome RDP Extension](https://chrome.google.com/webstore/detail/chrome-rdp/cbkkbcmdlboombapidmoeolnmdacpkch?hl=en-US)
- with the
- [IP address of the GCE VM](https://console.cloud.google.com/project/31977622648/compute/instances)
- shown on the [host info page](https://status.skia.org/hosts) for that bot. The
- username is chrome-bot and the password can be found on
- [Valentine](https://valentine.corp.google.com/) as "chrome-bot (Win GCE)".
-
-- To log in to other bots, see the [Skolo maintenance doc](https://docs.google.com/document/d/1zTR1YtrIFBo-fRWgbUgvJNVJ-s_4_sNjTrHIoX2vulo/edit#heading=h.2nq3yd1axg0n) remote access section.
-
- If there is a problem with a bot in the Chrome Golo or Chrome infra GCE, the
best course of action is to
[file a bug](https://code.google.com/p/chromium/issues/entry?template=Build%20Infrastructure)
diff --git a/site/dev/testing/swarmingbots.md b/site/dev/testing/swarmingbots.md
index 4f28ddd..4aa9115 100644
--- a/site/dev/testing/swarmingbots.md
+++ b/site/dev/testing/swarmingbots.md
@@ -20,6 +20,24 @@
[go/skbl](https://goto.google.com/skbl) lists all Skia Swarming bots.
+<a name="connecting-to-swarming-bots"></a>
+Connecting to Swarming Bots
+---------------------------
+
+- Machine name like “skia-gce-NNN”, “skia-i-gce-NNN”, “ct-gce-NNN”, “skia-ct-gce-NNN”, “ct-xxx-builder-NNN” -> GCE
+ * To log in to a Linux bot in GCE, use `gcloud compute ssh default@<machine name>`. Choose the zone listed for the [GCE VM][gce instances link] (or specify it using the `--zone` command-line flag).
+ * To log in to a Windows bot in GCE, use [Chrome RDP Extension][chrome rdp extension] with the [IP address of the GCE VM][gce instances link]. The username is chrome-bot and the password can be found on [Valentine](https://goto.google.com/valentine) as "chrome-bot (Win GCE)".
+
+- Machine name ends with “a9”, “m3”, "m5" -> Chrome Golo/Labs
+ * To log in to Golo bots, see [go/swarming-ssh](https://goto.google.com/swarming-ssh).
+
+- Machine name starts with “skia-e-”, “skia-i-” (other than “skia-i-gce-NNN”), “skia-rpi-” -> Chapel Hill lab (aka Skolo)<br/>
+ To log in to Skolo bots, see the [Skolo maintenance doc][remote access] remote access section. See the following for OS specific instructions:<br/>
+ * [Remotely debug an Android device in Skolo][remotely debug android]
+ * [VNC to Skolo Windows bots][vnc to skolo windows]
+ * [ChromeOS Debugging][chromeos debugging]
+
+
Debugging
---------
@@ -27,11 +45,9 @@
run tryjobs (after adding debugging output to the relevant code). In some cases you may also need to
[create or modify tryjobs](automated_testing#adding-new-jobs).
-For Googlers: If you need more control (e.g. to run GDB), the [current Trooper][current trooper] can
-loan a machine/device from the Skolo. All bots are accessible via either SSH or VNC -- see the
-[Skolo maintenance doc remote access section][remote access] and/or get help from the Trooper. You
-can also bring the device back to your desk and connect it to GoogleGuest WiFi or the [Google Test
-Network](http://go/gtn-criteria).
+For Googlers: If you need more control (e.g. to run GDB) and need run to directly on a swarming bot then you can use [leasing.skia.org](https://leasing.skia.org).<br/>
+If that does not work then the [current trooper][current trooper] can help you bring the device back to your desk and connect
+it to GoogleGuest Wifi or the [Google Test Network](http://go/gtn-criteria).
If you need to make changes on a Skolo device, please check with an Infra team member. Most can be
flashed/imaged back to a clean state, but others can not.
@@ -39,10 +55,18 @@
If a permanent change needs to be made on the machine (such as an OS or driver update), please [file
a bug][infra bug] and assign to jcgregorio for reassignment.
+[chrome rdp extension]:
+ https://chrome.google.com/webstore/detail/chrome-rdp/cbkkbcmdlboombapidmoeolnmdacpkch?hl=en-US
[current trooper]: http://skia-tree-status.appspot.com/trooper
[remote access]:
https://docs.google.com/document/d/1zTR1YtrIFBo-fRWgbUgvJNVJ-s_4_sNjTrHIoX2vulo/edit#heading=h.2nq3yd1axg0n
[infra bug]: https://bugs.chromium.org/p/skia/issues/entry?template=Infrastructure+Bug
+[gce instances link]: https://console.cloud.google.com/project/31977622648/compute/instances
+[remotely debug android]: https://docs.google.com/document/d/1nxn7TobfaLNNfhSTiwstOnjV0jCxYUI1uwW0T_V7BYg/
+[vnc to skolo windows]:
+ https://docs.google.com/document/d/1zTR1YtrIFBo-fRWgbUgvJNVJ-s_4_sNjTrHIoX2vulo/edit#heading=h.7cqd856ft0s
+[chromeos debugging]:
+ https://docs.google.com/document/d/1yJ2LLfLzV6pXKjiameid1LHEz1mj71Ob4wySIYxlBdw/edit#heading=h.9arg79l59xrf
Maintenance Tasks
-----------------
diff --git a/src/core/SkScan.h b/src/core/SkScan.h
index 8bb9d1f..2212d0d 100644
--- a/src/core/SkScan.h
+++ b/src/core/SkScan.h
@@ -89,10 +89,12 @@
const SkRegion*, SkBlitter*);
static void HairLineRgn(const SkPoint[], int count, const SkRegion*, SkBlitter*);
static void AntiHairLineRgn(const SkPoint[], int count, const SkRegion*, SkBlitter*);
- static void AAAFillPath(const SkPath& path, const SkRegion& origClip, SkBlitter* blitter,
- bool forceRLE = false); // SkAAClip uses forceRLE
- static void DAAFillPath(const SkPath& path, const SkRegion& origClip, SkBlitter* blitter,
- bool forceRLE = false);
+ static void AAAFillPath(const SkPath& path, SkBlitter* blitter, const SkIRect& pathIR,
+ const SkIRect& clipBounds, bool containedInClip, bool forceRLE);
+ static void DAAFillPath(const SkPath& path, SkBlitter* blitter, const SkIRect& pathIR,
+ const SkIRect& clipBounds, bool containedInClip, bool forceRLE);
+ static void SAAFillPath(const SkPath& path, SkBlitter* blitter, const SkIRect& pathIR,
+ const SkIRect& clipBounds, bool containedInClip, bool forceRLE);
};
/** Assign an SkXRect from a SkIRect, by promoting the src rect's coordinates
diff --git a/src/core/SkScanPriv.h b/src/core/SkScanPriv.h
index 96ee695..544f5f5 100644
--- a/src/core/SkScanPriv.h
+++ b/src/core/SkScanPriv.h
@@ -78,45 +78,6 @@
return prev;
}
-static bool fitsInsideLimit(const SkRect& r, SkScalar max) {
- const SkScalar min = -max;
- return r.fLeft > min && r.fTop > min &&
- r.fRight < max && r.fBottom < max;
-}
-
-static int overflows_short_shift(int value, int shift) {
- const int s = 16 + shift;
- return (SkLeftShift(value, s) >> s) - value;
-}
-
-/**
- Would any of the coordinates of this rectangle not fit in a short,
- when left-shifted by shift?
-*/
-static int rect_overflows_short_shift(SkIRect rect, int shift) {
- SkASSERT(!overflows_short_shift(8191, shift));
- SkASSERT(overflows_short_shift(8192, shift));
- SkASSERT(!overflows_short_shift(32767, 0));
- SkASSERT(overflows_short_shift(32768, 0));
-
- // Since we expect these to succeed, we bit-or together
- // for a tiny extra bit of speed.
- return overflows_short_shift(rect.fLeft, shift) |
- overflows_short_shift(rect.fRight, shift) |
- overflows_short_shift(rect.fTop, shift) |
- overflows_short_shift(rect.fBottom, shift);
-}
-
-static bool safeRoundOut(const SkRect& src, SkIRect* dst, int32_t maxInt) {
- const SkScalar maxScalar = SkIntToScalar(maxInt);
-
- if (fitsInsideLimit(src, maxScalar)) {
- src.roundOut(dst);
- return true;
- }
- return false;
-}
-
// Check if the path is a rect and fat enough after clipping; if so, blit it.
static inline bool TryBlitFatAntiRect(SkBlitter* blitter, const SkPath& path, const SkIRect& clip) {
SkRect rect;
@@ -134,93 +95,4 @@
return true;
}
-using FillPathFunc = std::function<void(const SkPath& path, SkBlitter* blitter, bool isInverse,
- const SkIRect& ir, const SkIRect& clipBounds, bool containedInClip, bool forceRLE)>;
-
-static inline void do_fill_path(const SkPath& path, const SkRegion& origClip, SkBlitter* blitter,
- bool forceRLE, const int SHIFT, FillPathFunc fillPathFunc) {
- if (origClip.isEmpty()) {
- return;
- }
-
- const bool isInverse = path.isInverseFillType();
- SkIRect ir;
-
- if (!safeRoundOut(path.getBounds(), &ir, SK_MaxS32 >> SHIFT)) {
- // Bounds can't fit in SkIRect; we'll return without drawing
- return;
- }
- if (ir.isEmpty()) {
- if (isInverse) {
- blitter->blitRegion(origClip);
- }
- return;
- }
-
- // If the intersection of the path bounds and the clip bounds
- // will overflow 32767 when << by SHIFT, we can't supersample,
- // so draw without antialiasing.
- SkIRect clippedIR;
- if (isInverse) {
- // If the path is an inverse fill, it's going to fill the entire
- // clip, and we care whether the entire clip exceeds our limits.
- clippedIR = origClip.getBounds();
- } else {
- if (!clippedIR.intersect(ir, origClip.getBounds())) {
- return;
- }
- }
- if (rect_overflows_short_shift(clippedIR, SHIFT)) {
- SkScan::FillPath(path, origClip, blitter);
- return;
- }
-
- // Our antialiasing can't handle a clip larger than 32767, so we restrict
- // the clip to that limit here. (the runs[] uses int16_t for its index).
- //
- // A more general solution (one that could also eliminate the need to
- // disable aa based on ir bounds (see overflows_short_shift) would be
- // to tile the clip/target...
- SkRegion tmpClipStorage;
- const SkRegion* clipRgn = &origClip;
- {
- static const int32_t kMaxClipCoord = 32767;
- const SkIRect& bounds = origClip.getBounds();
- if (bounds.fRight > kMaxClipCoord || bounds.fBottom > kMaxClipCoord) {
- SkIRect limit = { 0, 0, kMaxClipCoord, kMaxClipCoord };
- tmpClipStorage.op(origClip, limit, SkRegion::kIntersect_Op);
- clipRgn = &tmpClipStorage;
- }
- }
- // for here down, use clipRgn, not origClip
-
- SkScanClipper clipper(blitter, clipRgn, ir);
- const SkIRect* clipRect = clipper.getClipRect();
-
- if (clipper.getBlitter() == nullptr) { // clipped out
- if (isInverse) {
- blitter->blitRegion(*clipRgn);
- }
- return;
- }
-
- SkASSERT(clipper.getClipRect() == nullptr ||
- *clipper.getClipRect() == clipRgn->getBounds());
-
- // now use the (possibly wrapped) blitter
- blitter = clipper.getBlitter();
-
- if (isInverse) {
- sk_blit_above(blitter, ir, *clipRgn);
- }
-
- SkASSERT(SkIntToScalar(ir.fTop) <= path.getBounds().fTop);
-
- fillPathFunc(path, blitter, isInverse, ir, clipRgn->getBounds(), clipRect == nullptr, forceRLE);
-
- if (isInverse) {
- sk_blit_below(blitter, ir, *clipRgn);
- }
-}
-
#endif
diff --git a/src/core/SkScan_AAAPath.cpp b/src/core/SkScan_AAAPath.cpp
index d62b151..f9445d2 100644
--- a/src/core/SkScan_AAAPath.cpp
+++ b/src/core/SkScan_AAAPath.cpp
@@ -1672,48 +1672,45 @@
///////////////////////////////////////////////////////////////////////////////
-void SkScan::AAAFillPath(const SkPath& path, const SkRegion& origClip, SkBlitter* blitter,
- bool forceRLE) {
- FillPathFunc fillPathFunc = [](const SkPath& path, SkBlitter* blitter, bool isInverse,
- const SkIRect& ir, const SkIRect& clipBounds, bool containedInClip, bool forceRLE){
- // The mask blitter (where we store intermediate alpha values directly in a mask, and then
- // call the real blitter once in the end to blit the whole mask) is faster than the RLE
- // blitter when the blit region is small enough (i.e., canHandleRect(ir)).
- // When isInverse is true, the blit region is no longer ir so we won't use the mask blitter.
- // The caller may also use the forceRLE flag to force not using the mask blitter.
- // Also, when the path is a simple rect, preparing a mask and blitting it might have too
- // much overhead. Hence we'll use blitFatAntiRect to avoid the mask and its overhead.
- if (MaskAdditiveBlitter::canHandleRect(ir) && !isInverse && !forceRLE) {
+void SkScan::AAAFillPath(const SkPath& path, SkBlitter* blitter, const SkIRect& ir,
+ const SkIRect& clipBounds, bool containedInClip, bool forceRLE) {
+ bool isInverse = path.isInverseFillType();
+
+ // The mask blitter (where we store intermediate alpha values directly in a mask, and then call
+ // the real blitter once in the end to blit the whole mask) is faster than the RLE blitter when
+ // the blit region is small enough (i.e., canHandleRect(ir)). When isInverse is true, the blit
+ // region is no longer the rectangle ir so we won't use the mask blitter. The caller may also
+ // use the forceRLE flag to force not using the mask blitter. Also, when the path is a simple
+ // rect, preparing a mask and blitting it might have too much overhead. Hence we'll use
+ // blitFatAntiRect to avoid the mask and its overhead.
+ if (MaskAdditiveBlitter::canHandleRect(ir) && !isInverse && !forceRLE) {
#ifdef SK_SUPPORT_LEGACY_SMALLRECT_AA
+ MaskAdditiveBlitter additiveBlitter(blitter, ir, clipBounds, isInverse);
+ aaa_fill_path(path, clipBounds, &additiveBlitter, ir.fTop, ir.fBottom,
+ containedInClip, true, forceRLE);
+#else
+ // blitFatAntiRect is slower than the normal AAA flow without MaskAdditiveBlitter.
+ // Hence only tryBlitFatAntiRect when MaskAdditiveBlitter would have been used.
+ if (!TryBlitFatAntiRect(blitter, path, clipBounds)) {
MaskAdditiveBlitter additiveBlitter(blitter, ir, clipBounds, isInverse);
aaa_fill_path(path, clipBounds, &additiveBlitter, ir.fTop, ir.fBottom,
containedInClip, true, forceRLE);
-#else
- // blitFatAntiRect is slower than the normal AAA flow without MaskAdditiveBlitter.
- // Hence only tryBlitFatAntiRect when MaskAdditiveBlitter would have been used.
- if (!TryBlitFatAntiRect(blitter, path, clipBounds)) {
- MaskAdditiveBlitter additiveBlitter(blitter, ir, clipBounds, isInverse);
- aaa_fill_path(path, clipBounds, &additiveBlitter, ir.fTop, ir.fBottom,
- containedInClip, true, forceRLE);
- }
-#endif
- } else if (!isInverse && path.isConvex()) {
- // If the filling area is convex (i.e., path.isConvex && !isInverse), our simpler
- // aaa_walk_convex_edges won't generate alphas above 255. Hence we don't need
- // SafeRLEAdditiveBlitter (which is slow due to clamping). The basic RLE blitter
- // RunBasedAdditiveBlitter would suffice.
- RunBasedAdditiveBlitter additiveBlitter(blitter, ir, clipBounds, isInverse);
- aaa_fill_path(path, clipBounds, &additiveBlitter, ir.fTop, ir.fBottom,
- containedInClip, false, forceRLE);
- } else {
- // If the filling area might not be convex, the more involved aaa_walk_edges would
- // be called and we have to clamp the alpha downto 255. The SafeRLEAdditiveBlitter
- // does that at a cost of performance.
- SafeRLEAdditiveBlitter additiveBlitter(blitter, ir, clipBounds, isInverse);
- aaa_fill_path(path, clipBounds, &additiveBlitter, ir.fTop, ir.fBottom,
- containedInClip, false, forceRLE);
}
- };
-
- do_fill_path(path, origClip, blitter, forceRLE, 2, std::move(fillPathFunc));
+#endif
+ } else if (!isInverse && path.isConvex()) {
+ // If the filling area is convex (i.e., path.isConvex && !isInverse), our simpler
+ // aaa_walk_convex_edges won't generate alphas above 255. Hence we don't need
+ // SafeRLEAdditiveBlitter (which is slow due to clamping). The basic RLE blitter
+ // RunBasedAdditiveBlitter would suffice.
+ RunBasedAdditiveBlitter additiveBlitter(blitter, ir, clipBounds, isInverse);
+ aaa_fill_path(path, clipBounds, &additiveBlitter, ir.fTop, ir.fBottom,
+ containedInClip, false, forceRLE);
+ } else {
+ // If the filling area might not be convex, the more involved aaa_walk_edges would
+ // be called and we have to clamp the alpha downto 255. The SafeRLEAdditiveBlitter
+ // does that at a cost of performance.
+ SafeRLEAdditiveBlitter additiveBlitter(blitter, ir, clipBounds, isInverse);
+ aaa_fill_path(path, clipBounds, &additiveBlitter, ir.fTop, ir.fBottom,
+ containedInClip, false, forceRLE);
+ }
}
diff --git a/src/core/SkScan_AntiPath.cpp b/src/core/SkScan_AntiPath.cpp
index ba42a72..1178c9c 100644
--- a/src/core/SkScan_AntiPath.cpp
+++ b/src/core/SkScan_AntiPath.cpp
@@ -613,33 +613,155 @@
return path.countPoints() < SkTMax(bounds.width(), bounds.height()) / 2 - 10;
}
+void SkScan::SAAFillPath(const SkPath& path, SkBlitter* blitter, const SkIRect& ir,
+ const SkIRect& clipBounds, bool containedInClip, bool forceRLE) {
+ bool isInverse = path.isInverseFillType();
+
+ // MaskSuperBlitter can't handle drawing outside of ir, so we can't use it
+ // if we're an inverse filltype
+ if (!isInverse && MaskSuperBlitter::CanHandleRect(ir) && !forceRLE) {
+ MaskSuperBlitter superBlit(blitter, ir, clipBounds, isInverse);
+ SkASSERT(SkIntToScalar(ir.fTop) <= path.getBounds().fTop);
+ sk_fill_path(path, clipBounds, &superBlit, ir.fTop, ir.fBottom, SHIFT, containedInClip);
+ } else {
+ SuperBlitter superBlit(blitter, ir, clipBounds, isInverse);
+ sk_fill_path(path, clipBounds, &superBlit, ir.fTop, ir.fBottom, SHIFT, containedInClip);
+ }
+}
+
+static bool fitsInsideLimit(const SkRect& r, SkScalar max) {
+ const SkScalar min = -max;
+ return r.fLeft > min && r.fTop > min &&
+ r.fRight < max && r.fBottom < max;
+}
+
+static int overflows_short_shift(int value, int shift) {
+ const int s = 16 + shift;
+ return (SkLeftShift(value, s) >> s) - value;
+}
+
+/**
+ Would any of the coordinates of this rectangle not fit in a short,
+ when left-shifted by shift?
+*/
+static int rect_overflows_short_shift(SkIRect rect, int shift) {
+ SkASSERT(!overflows_short_shift(8191, shift));
+ SkASSERT(overflows_short_shift(8192, shift));
+ SkASSERT(!overflows_short_shift(32767, 0));
+ SkASSERT(overflows_short_shift(32768, 0));
+
+ // Since we expect these to succeed, we bit-or together
+ // for a tiny extra bit of speed.
+ return overflows_short_shift(rect.fLeft, shift) |
+ overflows_short_shift(rect.fRight, shift) |
+ overflows_short_shift(rect.fTop, shift) |
+ overflows_short_shift(rect.fBottom, shift);
+}
+
+static bool safeRoundOut(const SkRect& src, SkIRect* dst, int32_t maxInt) {
+ const SkScalar maxScalar = SkIntToScalar(maxInt);
+
+ if (fitsInsideLimit(src, maxScalar)) {
+ src.roundOut(dst);
+ return true;
+ }
+ return false;
+}
+
void SkScan::AntiFillPath(const SkPath& path, const SkRegion& origClip,
SkBlitter* blitter, bool forceRLE) {
- if (ShouldUseDAA(path)) {
- SkScan::DAAFillPath(path, origClip, blitter, forceRLE);
- return;
- } else if (ShouldUseAAA(path)) {
- // Do not use AAA if path is too complicated:
- // there won't be any speedup or significant visual improvement.
- SkScan::AAAFillPath(path, origClip, blitter, forceRLE);
+ if (origClip.isEmpty()) {
return;
}
- FillPathFunc fillPathFunc = [](const SkPath& path, SkBlitter* blitter, bool isInverse,
- const SkIRect& ir, const SkIRect& clipBounds, bool containedInClip, bool forceRLE){
- // MaskSuperBlitter can't handle drawing outside of ir, so we can't use it
- // if we're an inverse filltype
- if (!isInverse && MaskSuperBlitter::CanHandleRect(ir) && !forceRLE) {
- MaskSuperBlitter superBlit(blitter, ir, clipBounds, isInverse);
- SkASSERT(SkIntToScalar(ir.fTop) <= path.getBounds().fTop);
- sk_fill_path(path, clipBounds, &superBlit, ir.fTop, ir.fBottom, SHIFT, containedInClip);
- } else {
- SuperBlitter superBlit(blitter, ir, clipBounds, isInverse);
- sk_fill_path(path, clipBounds, &superBlit, ir.fTop, ir.fBottom, SHIFT, containedInClip);
- }
- };
+ const bool isInverse = path.isInverseFillType();
+ SkIRect ir;
- do_fill_path(path, origClip, blitter, forceRLE, SHIFT, std::move(fillPathFunc));
+ if (!safeRoundOut(path.getBounds(), &ir, SK_MaxS32 >> SHIFT)) {
+ // Bounds can't fit in SkIRect; we'll return without drawing
+ return;
+ }
+ if (ir.isEmpty()) {
+ if (isInverse) {
+ blitter->blitRegion(origClip);
+ }
+ return;
+ }
+
+ // If the intersection of the path bounds and the clip bounds
+ // will overflow 32767 when << by SHIFT, we can't supersample,
+ // so draw without antialiasing.
+ SkIRect clippedIR;
+ if (isInverse) {
+ // If the path is an inverse fill, it's going to fill the entire
+ // clip, and we care whether the entire clip exceeds our limits.
+ clippedIR = origClip.getBounds();
+ } else {
+ if (!clippedIR.intersect(ir, origClip.getBounds())) {
+ return;
+ }
+ }
+ if (rect_overflows_short_shift(clippedIR, SHIFT)) {
+ SkScan::FillPath(path, origClip, blitter);
+ return;
+ }
+
+ // Our antialiasing can't handle a clip larger than 32767, so we restrict
+ // the clip to that limit here. (the runs[] uses int16_t for its index).
+ //
+ // A more general solution (one that could also eliminate the need to
+ // disable aa based on ir bounds (see overflows_short_shift) would be
+ // to tile the clip/target...
+ SkRegion tmpClipStorage;
+ const SkRegion* clipRgn = &origClip;
+ {
+ static const int32_t kMaxClipCoord = 32767;
+ const SkIRect& bounds = origClip.getBounds();
+ if (bounds.fRight > kMaxClipCoord || bounds.fBottom > kMaxClipCoord) {
+ SkIRect limit = { 0, 0, kMaxClipCoord, kMaxClipCoord };
+ tmpClipStorage.op(origClip, limit, SkRegion::kIntersect_Op);
+ clipRgn = &tmpClipStorage;
+ }
+ }
+ // for here down, use clipRgn, not origClip
+
+ SkScanClipper clipper(blitter, clipRgn, ir);
+
+ if (clipper.getBlitter() == nullptr) { // clipped out
+ if (isInverse) {
+ blitter->blitRegion(*clipRgn);
+ }
+ return;
+ }
+
+ SkASSERT(clipper.getClipRect() == nullptr ||
+ *clipper.getClipRect() == clipRgn->getBounds());
+
+ // now use the (possibly wrapped) blitter
+ blitter = clipper.getBlitter();
+
+ if (isInverse) {
+ sk_blit_above(blitter, ir, *clipRgn);
+ }
+
+ SkASSERT(SkIntToScalar(ir.fTop) <= path.getBounds().fTop);
+
+ if (ShouldUseDAA(path)) {
+ SkScan::DAAFillPath(path, blitter, ir, clipRgn->getBounds(),
+ clipper.getClipRect() == nullptr, forceRLE);
+ } else if (ShouldUseAAA(path)) {
+ // Do not use AAA if path is too complicated:
+ // there won't be any speedup or significant visual improvement.
+ SkScan::AAAFillPath(path, blitter, ir, clipRgn->getBounds(),
+ clipper.getClipRect() == nullptr, forceRLE);
+ } else {
+ SkScan::SAAFillPath(path, blitter, ir, clipRgn->getBounds(),
+ clipper.getClipRect() == nullptr, forceRLE);
+ }
+
+ if (isInverse) {
+ sk_blit_below(blitter, ir, *clipRgn);
+ }
}
///////////////////////////////////////////////////////////////////////////////
diff --git a/src/core/SkScan_DAAPath.cpp b/src/core/SkScan_DAAPath.cpp
index e213e34..cd1717f 100644
--- a/src/core/SkScan_DAAPath.cpp
+++ b/src/core/SkScan_DAAPath.cpp
@@ -315,48 +315,42 @@
}
}
-void SkScan::DAAFillPath(const SkPath& path, const SkRegion& origClip, SkBlitter* blitter,
- bool forceRLE) {
+// For threaded backend with out-of-order init-once, we probably have to take care of the
+// blitRegion, sk_blit_above, sk_blit_below in SkScan::AntiFillPath to maintain the draw order. If
+// we do that, be caureful that blitRect may throw exception if the rect is empty.
+void SkScan::DAAFillPath(const SkPath& path, SkBlitter* blitter, const SkIRect& ir,
+ const SkIRect& clipBounds, bool containedInClip, bool forceRLE) {
+ bool isEvenOdd = path.getFillType() & 1;
+ bool isConvex = path.isConvex();
+ bool isInverse = path.isInverseFillType();
+ bool skipRect = isConvex && !isInverse;
- FillPathFunc fillPathFunc = [](const SkPath& path, SkBlitter* blitter, bool isInverse,
- const SkIRect& ir, const SkIRect& clipBounds, bool containedInClip, bool forceRLE){
- bool isEvenOdd = path.getFillType() & 1;
- bool isConvex = path.isConvex();
- bool skipRect = isConvex && !isInverse;
+ SkIRect clippedIR = ir;
+ clippedIR.intersect(clipBounds);
- SkIRect clippedIR = ir;
- clippedIR.intersect(clipBounds);
-
- // The overhead of even constructing SkCoverageDeltaList/Mask is too big.
- // So TryBlitFatAntiRect and return if it's successful.
- if (!isInverse && TryBlitFatAntiRect(blitter, path, clipBounds)) {
- return;
- }
+ // The overhead of even constructing SkCoverageDeltaList/Mask is too big.
+ // So TryBlitFatAntiRect and return if it's successful.
+ if (!isInverse && TryBlitFatAntiRect(blitter, path, clipBounds)) {
+ return;
+ }
#ifdef GOOGLE3
- constexpr int STACK_SIZE = 12 << 10; // 12K stack size alloc; Google3 has 16K limit.
+ constexpr int STACK_SIZE = 12 << 10; // 12K stack size alloc; Google3 has 16K limit.
#else
- constexpr int STACK_SIZE = 64 << 10; // 64k stack size to avoid heap allocation
+ constexpr int STACK_SIZE = 64 << 10; // 64k stack size to avoid heap allocation
#endif
- SkSTArenaAlloc<STACK_SIZE> alloc; // avoid heap allocation with SkSTArenaAlloc
+ SkSTArenaAlloc<STACK_SIZE> alloc; // avoid heap allocation with SkSTArenaAlloc
- // Only blitter->blitXXX need to be done in order in the threaded backend.
- // Everything before can be done out of order in the threaded backend.
- if (!forceRLE && !isInverse && SkCoverageDeltaMask::Suitable(clippedIR)) {
- SkCoverageDeltaMask deltaMask(&alloc, clippedIR);
- gen_alpha_deltas(path, clipBounds, deltaMask, blitter, skipRect, containedInClip);
- deltaMask.convertCoverageToAlpha(isEvenOdd, isInverse, isConvex);
- blitter->blitMask(deltaMask.prepareSkMask(), clippedIR);
- } else {
- SkCoverageDeltaList deltaList(&alloc, clippedIR.fTop, clippedIR.fBottom, forceRLE);
- gen_alpha_deltas(path, clipBounds, deltaList, blitter, skipRect, containedInClip);
- blitter->blitCoverageDeltas(&deltaList, clipBounds, isEvenOdd, isInverse, isConvex);
- }
- };
-
- // For threaded backend with out-of-order init-once (and therefore out-of-order do_fill_path),
- // we probably have to take care of the blitRegion, sk_blit_above, sk_blit_below in do_fill_path
- // to maintain the draw order. If we do that, be caureful that blitRect may throw exception is
- // the rect is empty.
- do_fill_path(path, origClip, blitter, forceRLE, 2, std::move(fillPathFunc));
+ // Only blitter->blitXXX needs to be done in order in the threaded backend.
+ // Everything before can be done out of order in the threaded backend.
+ if (!forceRLE && !isInverse && SkCoverageDeltaMask::Suitable(clippedIR)) {
+ SkCoverageDeltaMask deltaMask(&alloc, clippedIR);
+ gen_alpha_deltas(path, clipBounds, deltaMask, blitter, skipRect, containedInClip);
+ deltaMask.convertCoverageToAlpha(isEvenOdd, isInverse, isConvex);
+ blitter->blitMask(deltaMask.prepareSkMask(), clippedIR);
+ } else {
+ SkCoverageDeltaList deltaList(&alloc, clippedIR.fTop, clippedIR.fBottom, forceRLE);
+ gen_alpha_deltas(path, clipBounds, deltaList, blitter, skipRect, containedInClip);
+ blitter->blitCoverageDeltas(&deltaList, clipBounds, isEvenOdd, isInverse, isConvex);
+ }
}
diff --git a/src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp b/src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp
index c6e5a0e..b4eb89a 100644
--- a/src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp
+++ b/src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp
@@ -104,6 +104,7 @@
, fProcessors(std::move(args.fPaint))
, fTailDraw(&fHeadDraw)
, fOwningRTPendingOps(nullptr) {
+ SkDEBUGCODE(++fCCPR->fPendingDrawOpsCount);
SkDEBUGCODE(fBaseInstance = -1);
SkDEBUGCODE(fDebugInstanceCount = 1;)
SkDEBUGCODE(fDebugSkippedInstances = 0;)
@@ -139,20 +140,34 @@
this->setBounds(devBounds, GrOp::HasAABloat::kYes, GrOp::IsZeroArea::kNo);
}
+GrCoverageCountingPathRenderer::DrawPathsOp::~DrawPathsOp() {
+ if (fOwningRTPendingOps) {
+ // Remove CCPR's dangling pointer to this Op before deleting it.
+ SkASSERT(!fCCPR->fFlushing);
+ fOwningRTPendingOps->fOpList.remove(this);
+ }
+ SkDEBUGCODE(--fCCPR->fPendingDrawOpsCount);
+}
+
GrDrawOp::RequiresDstTexture DrawPathsOp::finalize(const GrCaps& caps, const GrAppliedClip* clip,
GrPixelConfigIsClamped dstIsClamped) {
- SingleDraw& onlyDraw = this->getOnlyPathDraw();
+ SkASSERT(!fCCPR->fFlushing);
+ // There should only be one single path draw in this Op right now.
+ SkASSERT(1 == fDebugInstanceCount);
+ SkASSERT(&fHeadDraw == fTailDraw);
GrProcessorSet::Analysis analysis = fProcessors.finalize(
- onlyDraw.fColor, GrProcessorAnalysisCoverage::kSingleChannel, clip, false, caps,
- dstIsClamped, &onlyDraw.fColor);
+ fHeadDraw.fColor, GrProcessorAnalysisCoverage::kSingleChannel, clip, false, caps,
+ dstIsClamped, &fHeadDraw.fColor);
return analysis.requiresDstTexture() ? RequiresDstTexture::kYes : RequiresDstTexture::kNo;
}
bool DrawPathsOp::onCombineIfPossible(GrOp* op, const GrCaps& caps) {
DrawPathsOp* that = op->cast<DrawPathsOp>();
SkASSERT(fCCPR == that->fCCPR);
+ SkASSERT(!fCCPR->fFlushing);
SkASSERT(fOwningRTPendingOps);
SkASSERT(fDebugInstanceCount);
+ SkASSERT(!that->fOwningRTPendingOps || that->fOwningRTPendingOps == fOwningRTPendingOps);
SkASSERT(that->fDebugInstanceCount);
if (this->getFillType() != that->getFillType() ||
@@ -161,20 +176,8 @@
return false;
}
- if (RTPendingOps* owningRTPendingOps = that->fOwningRTPendingOps) {
- SkASSERT(owningRTPendingOps == fOwningRTPendingOps);
- owningRTPendingOps->fOpList.remove(that);
- } else {
- // The Op is being combined immediately after creation, before a call to wasRecorded. In
- // this case wasRecorded will not be called. So we count its path here instead.
- const SingleDraw& onlyDraw = that->getOnlyPathDraw();
- ++fOwningRTPendingOps->fNumTotalPaths;
- fOwningRTPendingOps->fNumSkPoints += onlyDraw.fPath.countPoints();
- fOwningRTPendingOps->fNumSkVerbs += onlyDraw.fPath.countVerbs();
- }
-
fTailDraw->fNext = &fOwningRTPendingOps->fDrawsAllocator.push_back(that->fHeadDraw);
- fTailDraw = that->fTailDraw == &that->fHeadDraw ? fTailDraw->fNext : that->fTailDraw;
+ fTailDraw = (that->fTailDraw == &that->fHeadDraw) ? fTailDraw->fNext : that->fTailDraw;
this->joinBounds(*that);
@@ -184,12 +187,9 @@
}
void DrawPathsOp::wasRecorded(GrRenderTargetOpList* opList) {
+ SkASSERT(!fCCPR->fFlushing);
SkASSERT(!fOwningRTPendingOps);
- const SingleDraw& onlyDraw = this->getOnlyPathDraw();
fOwningRTPendingOps = &fCCPR->fRTPendingOpsMap[opList->uniqueID()];
- ++fOwningRTPendingOps->fNumTotalPaths;
- fOwningRTPendingOps->fNumSkPoints += onlyDraw.fPath.countPoints();
- fOwningRTPendingOps->fNumSkVerbs += onlyDraw.fPath.countVerbs();
fOwningRTPendingOps->fOpList.addToTail(this);
}
@@ -224,22 +224,29 @@
fPerFlushResourcesAreValid = false;
- SkTInternalLList<DrawPathsOp> flushingOps;
+ // Gather the Ops that are being flushed.
int maxTotalPaths = 0, numSkPoints = 0, numSkVerbs = 0;
-
+ SkTInternalLList<DrawPathsOp> flushingOps;
for (int i = 0; i < numOpListIDs; ++i) {
auto it = fRTPendingOpsMap.find(opListIDs[i]);
- if (fRTPendingOpsMap.end() != it) {
- RTPendingOps& rtPendingOps = it->second;
- SkASSERT(!rtPendingOps.fOpList.isEmpty());
- flushingOps.concat(std::move(rtPendingOps.fOpList));
- maxTotalPaths += rtPendingOps.fNumTotalPaths;
- numSkPoints += rtPendingOps.fNumSkPoints;
- numSkVerbs += rtPendingOps.fNumSkVerbs;
+ if (fRTPendingOpsMap.end() == it) {
+ continue;
}
+ SkTInternalLList<DrawPathsOp>::Iter iter;
+ SkTInternalLList<DrawPathsOp>& rtFlushingOps = it->second.fOpList;
+ iter.init(rtFlushingOps, SkTInternalLList<DrawPathsOp>::Iter::kHead_IterStart);
+ while (DrawPathsOp* flushingOp = iter.get()) {
+ for (const auto* draw = &flushingOp->fHeadDraw; draw; draw = draw->fNext) {
+ ++maxTotalPaths;
+ numSkPoints += draw->fPath.countPoints();
+ numSkVerbs += draw->fPath.countVerbs();
+ }
+ flushingOp->fOwningRTPendingOps = nullptr; // Owner is about to change to 'flushingOps'.
+ iter.next();
+ }
+ flushingOps.concat(std::move(rtFlushingOps));
}
- SkASSERT(flushingOps.isEmpty() == !maxTotalPaths);
if (flushingOps.isEmpty()) {
return; // Nothing to draw.
}
@@ -376,6 +383,8 @@
return; // Setup failed.
}
+ SkASSERT(fBaseInstance >= 0); // Make sure setupPerFlushResources has set us up.
+
GrPipeline::InitArgs initArgs;
initArgs.fFlags = fSRGBFlags;
initArgs.fProxy = flushState->drawOpArgs().fProxy;
diff --git a/src/gpu/ccpr/GrCoverageCountingPathRenderer.h b/src/gpu/ccpr/GrCoverageCountingPathRenderer.h
index 7d613f9..a6295a5 100644
--- a/src/gpu/ccpr/GrCoverageCountingPathRenderer.h
+++ b/src/gpu/ccpr/GrCoverageCountingPathRenderer.h
@@ -35,6 +35,12 @@
static sk_sp<GrCoverageCountingPathRenderer> CreateIfSupported(const GrCaps&,
bool drawCachablePaths);
+ ~GrCoverageCountingPathRenderer() override {
+ // Ensure nothing exists that could have a dangling pointer back into this class.
+ SkASSERT(fRTPendingOpsMap.empty());
+ SkASSERT(0 == fPendingDrawOpsCount);
+ }
+
// GrPathRenderer overrides.
StencilSupport onGetStencilSupport(const GrShape&) const override {
return GrPathRenderer::kNoSupport_StencilSupport;
@@ -55,6 +61,7 @@
SK_DECLARE_INTERNAL_LLIST_INTERFACE(DrawPathsOp);
DrawPathsOp(GrCoverageCountingPathRenderer*, const DrawPathArgs&, GrColor);
+ ~DrawPathsOp() override;
const char* name() const override { return "GrCoverageCountingPathRenderer::DrawPathsOp"; }
@@ -87,12 +94,6 @@
SingleDraw* fNext = nullptr;
};
- SingleDraw& getOnlyPathDraw() {
- SkASSERT(&fHeadDraw == fTailDraw);
- SkASSERT(1 == fDebugInstanceCount);
- return fHeadDraw;
- }
-
struct AtlasBatch {
const GrCCPRAtlas* fAtlas;
int fEndInstanceIdx;
@@ -130,14 +131,12 @@
struct RTPendingOps {
SkTInternalLList<DrawPathsOp> fOpList;
- int fNumTotalPaths = 0;
- int fNumSkPoints = 0;
- int fNumSkVerbs = 0;
GrSTAllocator<256, DrawPathsOp::SingleDraw> fDrawsAllocator;
};
// Map from render target ID to the individual render target's pending path ops.
std::map<uint32_t, RTPendingOps> fRTPendingOpsMap;
+ SkDEBUGCODE(int fPendingDrawOpsCount = 0;)
sk_sp<GrBuffer> fPerFlushIndexBuffer;
sk_sp<GrBuffer> fPerFlushVertexBuffer;
diff --git a/src/gpu/ops/GrAtlasTextOp.cpp b/src/gpu/ops/GrAtlasTextOp.cpp
index 2383510..9e98466 100644
--- a/src/gpu/ops/GrAtlasTextOp.cpp
+++ b/src/gpu/ops/GrAtlasTextOp.cpp
@@ -76,12 +76,12 @@
return analysis.requiresDstTexture() ? RequiresDstTexture::kYes : RequiresDstTexture::kNo;
}
-static void clip_quads(const SkIRect& clipRect,
- unsigned char* currVertex, unsigned char* blobVertices,
+static void clip_quads(const SkIRect& clipRect, char* currVertex, const char* blobVertices,
size_t vertexStride, int glyphCount) {
for (int i = 0; i < glyphCount; ++i) {
- SkPoint* blobPositionLT = reinterpret_cast<SkPoint*>(blobVertices);
- SkPoint* blobPositionRB = reinterpret_cast<SkPoint*>(blobVertices + 3*vertexStride);
+ const SkPoint* blobPositionLT = reinterpret_cast<const SkPoint*>(blobVertices);
+ const SkPoint* blobPositionRB =
+ reinterpret_cast<const SkPoint*>(blobVertices + 3 * vertexStride);
// positions for bitmap glyphs are pixel boundary aligned
SkIRect positionRect = SkIRect::MakeLTRB(SkScalarFloorToInt(blobPositionLT->fX),
@@ -95,11 +95,11 @@
// Pull out some more data that we'll need.
// In the LCD case the color will be garbage, but we'll overwrite it with the texcoords
// and it avoids a lot of conditionals.
- SkColor color = *reinterpret_cast<SkColor*>(blobVertices + sizeof(SkPoint));
+ auto color = *reinterpret_cast<const SkColor*>(blobVertices + sizeof(SkPoint));
size_t coordOffset = vertexStride - 2*sizeof(uint16_t);
- uint16_t* blobCoordsLT = reinterpret_cast<uint16_t*>(blobVertices + coordOffset);
- uint16_t* blobCoordsRB = reinterpret_cast<uint16_t*>(blobVertices + 3*vertexStride +
- coordOffset);
+ auto* blobCoordsLT = reinterpret_cast<const uint16_t*>(blobVertices + coordOffset);
+ auto* blobCoordsRB = reinterpret_cast<const uint16_t*>(blobVertices + 3 * vertexStride +
+ coordOffset);
// Pull out the texel coordinates and texture index bits
uint16_t coordsRectL = blobCoordsLT[0] >> 1;
uint16_t coordsRectT = blobCoordsLT[1] >> 1;
@@ -224,32 +224,34 @@
return;
}
- unsigned char* currVertex = reinterpret_cast<unsigned char*>(vertices);
+ char* currVertex = reinterpret_cast<char*>(vertices);
- GrBlobRegenHelper helper(this, target, &flushInfo);
SkAutoGlyphCache glyphCache;
// each of these is a SubRun
for (int i = 0; i < fGeoCount; i++) {
const Geometry& args = fGeoData[i];
Blob* blob = args.fBlob;
- size_t byteCount;
- void* blobVertices;
- int subRunGlyphCount;
- blob->regenInOp(target->deferredUploadTarget(), fFontCache, &helper, args.fRun,
- args.fSubRun, &glyphCache, vertexStride, args.fViewMatrix, args.fX, args.fY,
- args.fColor, &blobVertices, &byteCount, &subRunGlyphCount);
-
- // now copy all vertices
- if (args.fClipRect.isEmpty()) {
- memcpy(currVertex, blobVertices, byteCount);
- } else {
- clip_quads(args.fClipRect, currVertex, reinterpret_cast<unsigned char*>(blobVertices),
- vertexStride, subRunGlyphCount);
- }
-
- currVertex += byteCount;
+ GrAtlasTextBlob::VertexRegenerator regenerator(
+ blob, args.fRun, args.fSubRun, args.fViewMatrix, args.fX, args.fY, args.fColor,
+ target->deferredUploadTarget(), fFontCache, &glyphCache, vertexStride);
+ GrAtlasTextBlob::VertexRegenerator::Result result;
+ do {
+ result = regenerator.regenerate();
+ // Copy regenerated vertices from the blob to our vertex buffer.
+ size_t vertexBytes = result.fGlyphsRegenerated * kVerticesPerGlyph * vertexStride;
+ if (args.fClipRect.isEmpty()) {
+ memcpy(currVertex, result.fFirstVertex, vertexBytes);
+ } else {
+ clip_quads(args.fClipRect, currVertex, result.fFirstVertex, vertexStride,
+ result.fGlyphsRegenerated);
+ }
+ flushInfo.fGlyphsToFlush += result.fGlyphsRegenerated;
+ if (!result.fFinished) {
+ this->flush(target, &flushInfo);
+ }
+ currVertex += vertexBytes;
+ } while (!result.fFinished);
}
-
this->flush(target, &flushInfo);
}
@@ -406,4 +408,3 @@
}
}
-void GrBlobRegenHelper::flush() { fOp->flush(fTarget, fFlushInfo); }
diff --git a/src/gpu/ops/GrAtlasTextOp.h b/src/gpu/ops/GrAtlasTextOp.h
index 1814676..240b98b 100644
--- a/src/gpu/ops/GrAtlasTextOp.h
+++ b/src/gpu/ops/GrAtlasTextOp.h
@@ -206,29 +206,7 @@
SkColor fLuminanceColor;
bool fUseGammaCorrectDistanceTable;
- friend class GrBlobRegenHelper; // Needs to trigger flushes
-
typedef GrMeshDrawOp INHERITED;
};
-/*
- * A simple helper class to abstract the interface GrAtlasTextBlob needs to regenerate itself.
- * It'd be nicer if this was nested, but we need to forward declare it in GrAtlasTextBlob.h
- */
-class GrBlobRegenHelper {
-public:
- GrBlobRegenHelper(const GrAtlasTextOp* op, GrMeshDrawOp::Target* target,
- GrAtlasTextOp::FlushInfo* flushInfo)
- : fOp(op), fTarget(target), fFlushInfo(flushInfo) {}
-
- void flush();
-
- void incGlyphCount(int glyphCount = 1) { fFlushInfo->fGlyphsToFlush += glyphCount; }
-
-private:
- const GrAtlasTextOp* fOp;
- GrMeshDrawOp::Target* fTarget;
- GrAtlasTextOp::FlushInfo* fFlushInfo;
-};
-
#endif
diff --git a/src/gpu/text/GrAtlasTextBlob.cpp b/src/gpu/text/GrAtlasTextBlob.cpp
index 17f81a2..0b25a34 100644
--- a/src/gpu/text/GrAtlasTextBlob.cpp
+++ b/src/gpu/text/GrAtlasTextBlob.cpp
@@ -34,8 +34,7 @@
cacheBlob->fSize = size;
// setup offsets for vertices / glyphs
- cacheBlob->fVertices = sizeof(GrAtlasTextBlob) +
- reinterpret_cast<unsigned char*>(cacheBlob.get());
+ cacheBlob->fVertices = sizeof(GrAtlasTextBlob) + reinterpret_cast<char*>(cacheBlob.get());
cacheBlob->fGlyphs = reinterpret_cast<GrGlyph**>(cacheBlob->fVertices + verticesCount);
cacheBlob->fRuns = reinterpret_cast<GrAtlasTextBlob::Run*>(cacheBlob->fGlyphs + glyphCount);
diff --git a/src/gpu/text/GrAtlasTextBlob.h b/src/gpu/text/GrAtlasTextBlob.h
index 495e72a..403cbbf 100644
--- a/src/gpu/text/GrAtlasTextBlob.h
+++ b/src/gpu/text/GrAtlasTextBlob.h
@@ -21,7 +21,6 @@
#include "SkSurfaceProps.h"
#include "SkTInternalLList.h"
-class GrBlobRegenHelper;
struct GrDistanceFieldAdjustTable;
class GrMemoryPool;
class SkDrawFilter;
@@ -50,6 +49,8 @@
public:
SK_DECLARE_INTERNAL_LLIST_INTERFACE(GrAtlasTextBlob);
+ class VertexRegenerator;
+
static sk_sp<GrAtlasTextBlob> Make(GrMemoryPool* pool, int glyphCount, int runCount);
struct Key {
@@ -250,16 +251,6 @@
this->setupViewMatrix(viewMatrix, x, y);
}
- /**
- * Consecutive calls to regenInOp often use the same SkGlyphCache. If the same instance of
- * SkAutoGlyphCache is passed to multiple calls of regenInOp then it can save the cost of
- * multiple detach/attach operations of SkGlyphCache.
- */
- void regenInOp(GrDeferredUploadTarget*, GrAtlasGlyphCache* fontCache, GrBlobRegenHelper* helper,
- int run, int subRun, SkAutoGlyphCache*, size_t vertexStride,
- const SkMatrix& viewMatrix, SkScalar x, SkScalar y, GrColor color,
- void** vertices, size_t* byteCount, int* glyphCount);
-
const Key& key() const { return fKey; }
~GrAtlasTextBlob() {
@@ -492,11 +483,6 @@
bool fDrawAsPaths;
}; // Run
- template <bool regenPos, bool regenCol, bool regenTexCoords, bool regenGlyphs>
- void regenInOp(GrDeferredUploadTarget*, GrAtlasGlyphCache* fontCache, GrBlobRegenHelper* helper,
- Run* run, Run::SubRunInfo* info, SkAutoGlyphCache*, int glyphCount,
- size_t vertexStride, GrColor color, SkScalar transX, SkScalar transY) const;
-
inline std::unique_ptr<GrAtlasTextOp> makeOp(
const Run::SubRunInfo& info, int glyphCount, uint16_t run, uint16_t subRun,
const SkMatrix& viewMatrix, SkScalar x, SkScalar y, const SkIRect& clipRect,
@@ -530,7 +516,7 @@
};
// all glyph / vertex offsets are into these pools.
- unsigned char* fVertices;
+ char* fVertices;
GrGlyph** fGlyphs;
Run* fRuns;
GrMemoryPool* fPool;
@@ -554,4 +540,65 @@
uint8_t fTextType;
};
+/**
+ * Used to produce vertices for a subrun of a blob. The vertices are cached in the blob itself.
+ * This is invoked each time a sub run is drawn. It regenerates the vertex data as required either
+ * because of changes to the atlas or because of different draw parameters (e.g. color change). In
+ * rare cases the draw may have to interrupted and flushed in the middle of the sub run in order to
+ * free up atlas space. Thus, this generator is stateful and should be invoked in a loop until the
+ * entire sub run has been completed.
+ */
+class GrAtlasTextBlob::VertexRegenerator {
+public:
+ /**
+ * Consecutive VertexRegenerators often use the same SkGlyphCache. If the same instance of
+ * SkAutoGlyphCache is reused then it can save the cost of multiple detach/attach operations of
+ * SkGlyphCache.
+ */
+ VertexRegenerator(GrAtlasTextBlob* blob, int runIdx, int subRunIdx, const SkMatrix& viewMatrix,
+ SkScalar x, SkScalar y, GrColor color, GrDeferredUploadTarget*,
+ GrAtlasGlyphCache*, SkAutoGlyphCache*, size_t vertexStride);
+
+ struct Result {
+ /**
+ * Was regenerate() able to draw all the glyphs from the sub run? If not flush all glyph
+ * draws and call regenerate() again.
+ */
+ bool fFinished = true;
+
+ /**
+ * How many glyphs were regenerated. Will be equal to the sub run's glyph count if
+ * fType is kFinished.
+ */
+ int fGlyphsRegenerated = 0;
+
+ /**
+ * Pointer where the caller finds the first regenerated vertex.
+ */
+ const char* fFirstVertex;
+ };
+
+ Result regenerate();
+
+private:
+ template <bool regenPos, bool regenCol, bool regenTexCoords, bool regenGlyphs>
+ Result doRegen();
+
+ const SkMatrix& fViewMatrix;
+ GrAtlasTextBlob* fBlob;
+ GrDeferredUploadTarget* fUploadTarget;
+ GrAtlasGlyphCache* fGlyphCache;
+ SkAutoGlyphCache* fLazyCache;
+ Run* fRun;
+ Run::SubRunInfo* fSubRun;
+ size_t fVertexStride;
+ GrColor fColor;
+ SkScalar fTransX;
+ SkScalar fTransY;
+
+ uint32_t fRegenFlags = 0;
+ int fCurrGlyph = 0;
+ bool fBrokenRun = false;
+};
+
#endif
diff --git a/src/gpu/text/GrAtlasTextBlobVertexRegenerator.cpp b/src/gpu/text/GrAtlasTextBlobVertexRegenerator.cpp
new file mode 100644
index 0000000..6b242ad
--- /dev/null
+++ b/src/gpu/text/GrAtlasTextBlobVertexRegenerator.cpp
@@ -0,0 +1,317 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrAtlasTextBlob.h"
+#include "GrTextUtils.h"
+#include "SkDistanceFieldGen.h"
+#include "SkGlyphCache.h"
+#include "ops/GrAtlasTextOp.h"
+
+using Regenerator = GrAtlasTextBlob::VertexRegenerator;
+
+enum RegenMask {
+ kNoRegen = 0x0,
+ kRegenPos = 0x1,
+ kRegenCol = 0x2,
+ kRegenTex = 0x4,
+ kRegenGlyph = 0x8 | kRegenTex, // we have to regenerate the texture coords when we regen glyphs
+
+ // combinations
+ kRegenPosCol = kRegenPos | kRegenCol,
+ kRegenPosTex = kRegenPos | kRegenTex,
+ kRegenPosTexGlyph = kRegenPos | kRegenGlyph,
+ kRegenPosColTex = kRegenPos | kRegenCol | kRegenTex,
+ kRegenPosColTexGlyph = kRegenPos | kRegenCol | kRegenGlyph,
+ kRegenColTex = kRegenCol | kRegenTex,
+ kRegenColTexGlyph = kRegenCol | kRegenGlyph,
+};
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// A large template to handle regenerating the vertices of a textblob with as few branches as
+// possible
+template <bool regenPos, bool regenCol, bool regenTexCoords>
+inline void regen_vertices(char* vertex, const GrGlyph* glyph, size_t vertexStride,
+ bool useDistanceFields, SkScalar transX, SkScalar transY,
+ GrColor color) {
+ uint16_t u0, v0, u1, v1;
+ if (regenTexCoords) {
+ SkASSERT(glyph);
+ int width = glyph->fBounds.width();
+ int height = glyph->fBounds.height();
+
+ if (useDistanceFields) {
+ u0 = glyph->fAtlasLocation.fX + SK_DistanceFieldInset;
+ v0 = glyph->fAtlasLocation.fY + SK_DistanceFieldInset;
+ u1 = u0 + width - 2 * SK_DistanceFieldInset;
+ v1 = v0 + height - 2 * SK_DistanceFieldInset;
+ } else {
+ u0 = glyph->fAtlasLocation.fX;
+ v0 = glyph->fAtlasLocation.fY;
+ u1 = u0 + width;
+ v1 = v0 + height;
+ }
+ // We pack the 2bit page index in the low bit of the u and v texture coords
+ uint32_t pageIndex = glyph->pageIndex();
+ SkASSERT(pageIndex < 4);
+ uint16_t uBit = (pageIndex >> 1) & 0x1;
+ uint16_t vBit = pageIndex & 0x1;
+ u0 <<= 1;
+ u0 |= uBit;
+ v0 <<= 1;
+ v0 |= vBit;
+ u1 <<= 1;
+ u1 |= uBit;
+ v1 <<= 1;
+ v1 |= vBit;
+ }
+
+ // This is a bit wonky, but sometimes we have LCD text, in which case we won't have color
+ // vertices, hence vertexStride - sizeof(SkIPoint16)
+ intptr_t colorOffset = sizeof(SkPoint);
+ intptr_t texCoordOffset = vertexStride - sizeof(SkIPoint16);
+
+ // V0
+ if (regenPos) {
+ SkPoint* point = reinterpret_cast<SkPoint*>(vertex);
+ point->fX += transX;
+ point->fY += transY;
+ }
+
+ if (regenCol) {
+ SkColor* vcolor = reinterpret_cast<SkColor*>(vertex + colorOffset);
+ *vcolor = color;
+ }
+
+ if (regenTexCoords) {
+ uint16_t* textureCoords = reinterpret_cast<uint16_t*>(vertex + texCoordOffset);
+ textureCoords[0] = u0;
+ textureCoords[1] = v0;
+ }
+ vertex += vertexStride;
+
+ // V1
+ if (regenPos) {
+ SkPoint* point = reinterpret_cast<SkPoint*>(vertex);
+ point->fX += transX;
+ point->fY += transY;
+ }
+
+ if (regenCol) {
+ SkColor* vcolor = reinterpret_cast<SkColor*>(vertex + colorOffset);
+ *vcolor = color;
+ }
+
+ if (regenTexCoords) {
+ uint16_t* textureCoords = reinterpret_cast<uint16_t*>(vertex + texCoordOffset);
+ textureCoords[0] = u0;
+ textureCoords[1] = v1;
+ }
+ vertex += vertexStride;
+
+ // V2
+ if (regenPos) {
+ SkPoint* point = reinterpret_cast<SkPoint*>(vertex);
+ point->fX += transX;
+ point->fY += transY;
+ }
+
+ if (regenCol) {
+ SkColor* vcolor = reinterpret_cast<SkColor*>(vertex + colorOffset);
+ *vcolor = color;
+ }
+
+ if (regenTexCoords) {
+ uint16_t* textureCoords = reinterpret_cast<uint16_t*>(vertex + texCoordOffset);
+ textureCoords[0] = u1;
+ textureCoords[1] = v0;
+ }
+ vertex += vertexStride;
+
+ // V3
+ if (regenPos) {
+ SkPoint* point = reinterpret_cast<SkPoint*>(vertex);
+ point->fX += transX;
+ point->fY += transY;
+ }
+
+ if (regenCol) {
+ SkColor* vcolor = reinterpret_cast<SkColor*>(vertex + colorOffset);
+ *vcolor = color;
+ }
+
+ if (regenTexCoords) {
+ uint16_t* textureCoords = reinterpret_cast<uint16_t*>(vertex + texCoordOffset);
+ textureCoords[0] = u1;
+ textureCoords[1] = v1;
+ }
+}
+
+Regenerator::VertexRegenerator(GrAtlasTextBlob* blob, int runIdx, int subRunIdx,
+ const SkMatrix& viewMatrix, SkScalar x, SkScalar y, GrColor color,
+ GrDeferredUploadTarget* uploadTarget, GrAtlasGlyphCache* glyphCache,
+ SkAutoGlyphCache* lazyCache, size_t vertexStride)
+ : fViewMatrix(viewMatrix)
+ , fBlob(blob)
+ , fUploadTarget(uploadTarget)
+ , fGlyphCache(glyphCache)
+ , fLazyCache(lazyCache)
+ , fRun(&blob->fRuns[runIdx])
+ , fSubRun(&blob->fRuns[runIdx].fSubRunInfo[subRunIdx])
+ , fVertexStride(vertexStride)
+ , fColor(color) {
+ // Compute translation if any
+ fSubRun->computeTranslation(fViewMatrix, x, y, &fTransX, &fTransY);
+
+ // Because the GrAtlasGlyphCache may evict the strike a blob depends on using for
+ // generating its texture coords, we have to track whether or not the strike has
+ // been abandoned. If it hasn't been abandoned, then we can use the GrGlyph*s as is
+ // otherwise we have to get the new strike, and use that to get the correct glyphs.
+ // Because we do not have the packed ids, and thus can't look up our glyphs in the
+ // new strike, we instead keep our ref to the old strike and use the packed ids from
+ // it. These ids will still be valid as long as we hold the ref. When we are done
+ // updating our cache of the GrGlyph*s, we drop our ref on the old strike
+ if (fSubRun->strike()->isAbandoned()) {
+ fRegenFlags |= kRegenGlyph;
+ fRegenFlags |= kRegenTex;
+ }
+ if (kARGB_GrMaskFormat != fSubRun->maskFormat() && fSubRun->color() != color) {
+ fRegenFlags |= kRegenCol;
+ }
+ if (0.f != fTransX || 0.f != fTransY) {
+ fRegenFlags |= kRegenPos;
+ }
+}
+
+template <bool regenPos, bool regenCol, bool regenTexCoords, bool regenGlyphs>
+Regenerator::Result Regenerator::doRegen() {
+ static_assert(!regenGlyphs || regenTexCoords, "must regenTexCoords along regenGlyphs");
+ GrAtlasTextStrike* strike = nullptr;
+ if (regenTexCoords) {
+ fSubRun->resetBulkUseToken();
+
+ const SkDescriptor* desc = (fRun->fOverrideDescriptor && !fSubRun->drawAsDistanceFields())
+ ? fRun->fOverrideDescriptor->getDesc()
+ : fRun->fDescriptor.getDesc();
+
+ if (!*fLazyCache || (*fLazyCache)->getDescriptor() != *desc) {
+ SkScalerContextEffects effects;
+ effects.fPathEffect = fRun->fPathEffect.get();
+ effects.fRasterizer = fRun->fRasterizer.get();
+ effects.fMaskFilter = fRun->fMaskFilter.get();
+ fLazyCache->reset(SkGlyphCache::DetachCache(fRun->fTypeface.get(), effects, desc));
+ }
+
+ if (regenGlyphs) {
+ strike = fGlyphCache->getStrike(fLazyCache->get());
+ } else {
+ strike = fSubRun->strike();
+ }
+ }
+
+ Result result;
+ char* currVertex = fBlob->fVertices + fSubRun->vertexStartIndex() +
+ fCurrGlyph * kVerticesPerGlyph * fVertexStride;
+ result.fFirstVertex = currVertex;
+
+ for (int glyphIdx = fCurrGlyph; glyphIdx < (int)fSubRun->glyphCount(); glyphIdx++) {
+ GrGlyph* glyph = nullptr;
+ if (regenTexCoords) {
+ size_t glyphOffset = glyphIdx + fSubRun->glyphStartIndex();
+
+ if (regenGlyphs) {
+ // Get the id from the old glyph, and use the new strike to lookup
+ // the glyph.
+ GrGlyph::PackedID id = fBlob->fGlyphs[glyphOffset]->fPackedID;
+ fBlob->fGlyphs[glyphOffset] =
+ strike->getGlyph(id, fSubRun->maskFormat(), fLazyCache->get());
+ SkASSERT(id == fBlob->fGlyphs[glyphOffset]->fPackedID);
+ }
+ glyph = fBlob->fGlyphs[glyphOffset];
+ SkASSERT(glyph && glyph->fMaskFormat == fSubRun->maskFormat());
+
+ if (!fGlyphCache->hasGlyph(glyph) &&
+ !strike->addGlyphToAtlas(fUploadTarget, glyph, fLazyCache->get(),
+ fSubRun->maskFormat())) {
+ fBrokenRun = glyphIdx > 0;
+ result.fFinished = false;
+ return result;
+ }
+ fGlyphCache->addGlyphToBulkAndSetUseToken(fSubRun->bulkUseToken(), glyph,
+ fUploadTarget->nextDrawToken());
+ }
+
+ regen_vertices<regenPos, regenCol, regenTexCoords>(currVertex, glyph, fVertexStride,
+ fSubRun->drawAsDistanceFields(), fTransX,
+ fTransY, fColor);
+ currVertex += fVertexStride * GrAtlasTextOp::kVerticesPerGlyph;
+ ++result.fGlyphsRegenerated;
+ ++fCurrGlyph;
+ }
+
+ // We may have changed the color so update it here
+ fSubRun->setColor(fColor);
+ if (regenTexCoords) {
+ if (regenGlyphs) {
+ fSubRun->setStrike(strike);
+ }
+ fSubRun->setAtlasGeneration(fBrokenRun
+ ? GrDrawOpAtlas::kInvalidAtlasGeneration
+ : fGlyphCache->atlasGeneration(fSubRun->maskFormat()));
+ }
+ return result;
+}
+
+Regenerator::Result Regenerator::regenerate() {
+ uint64_t currentAtlasGen = fGlyphCache->atlasGeneration(fSubRun->maskFormat());
+ // If regenerate() is called multiple times then the atlas gen may have changed. So we check
+ // this each time.
+ if (fSubRun->atlasGeneration() != currentAtlasGen) {
+ fRegenFlags |= kRegenTex;
+ }
+
+ switch (static_cast<RegenMask>(fRegenFlags)) {
+ case kRegenPos:
+ return this->doRegen<true, false, false, false>();
+ case kRegenCol:
+ return this->doRegen<false, true, false, false>();
+ case kRegenTex:
+ return this->doRegen<false, false, true, false>();
+ case kRegenGlyph:
+ return this->doRegen<false, false, true, true>();
+
+ // combinations
+ case kRegenPosCol:
+ return this->doRegen<true, true, false, false>();
+ case kRegenPosTex:
+ return this->doRegen<true, false, true, false>();
+ case kRegenPosTexGlyph:
+ return this->doRegen<true, false, true, true>();
+ case kRegenPosColTex:
+ return this->doRegen<true, true, true, false>();
+ case kRegenPosColTexGlyph:
+ return this->doRegen<true, true, true, true>();
+ case kRegenColTex:
+ return this->doRegen<false, true, true, false>();
+ case kRegenColTexGlyph:
+ return this->doRegen<false, true, true, true>();
+ case kNoRegen: {
+ Result result;
+ result.fGlyphsRegenerated = fSubRun->glyphCount() - fCurrGlyph;
+ result.fFirstVertex = fBlob->fVertices + fSubRun->vertexStartIndex() +
+ fCurrGlyph * kVerticesPerGlyph * fVertexStride;
+ fCurrGlyph = fSubRun->glyphCount();
+
+ // set use tokens for all of the glyphs in our subrun. This is only valid if we
+ // have a valid atlas generation
+ fGlyphCache->setUseTokenBulk(*fSubRun->bulkUseToken(), fUploadTarget->nextDrawToken(),
+ fSubRun->maskFormat());
+ return result;
+ }
+ }
+ SK_ABORT("Should not get here");
+ return Result();
+}
diff --git a/src/gpu/text/GrAtlasTextBlob_regenInOp.cpp b/src/gpu/text/GrAtlasTextBlob_regenInOp.cpp
deleted file mode 100644
index e081158..0000000
--- a/src/gpu/text/GrAtlasTextBlob_regenInOp.cpp
+++ /dev/null
@@ -1,324 +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.
- */
-
-#include "GrAtlasTextBlob.h"
-
-#include "GrOpFlushState.h"
-#include "GrTextUtils.h"
-
-#include "SkDistanceFieldGen.h"
-#include "SkGlyphCache.h"
-
-#include "ops/GrAtlasTextOp.h"
-
-////////////////////////////////////////////////////////////////////////////////////////////////////
-// A large template to handle regenerating the vertices of a textblob with as few branches as
-// possible
-template <bool regenPos, bool regenCol, bool regenTexCoords>
-inline void regen_vertices(intptr_t vertex, const GrGlyph* glyph, size_t vertexStride,
- bool useDistanceFields, SkScalar transX, SkScalar transY,
- GrColor color) {
- uint16_t u0, v0, u1, v1;
- if (regenTexCoords) {
- SkASSERT(glyph);
- int width = glyph->fBounds.width();
- int height = glyph->fBounds.height();
-
- if (useDistanceFields) {
- u0 = glyph->fAtlasLocation.fX + SK_DistanceFieldInset;
- v0 = glyph->fAtlasLocation.fY + SK_DistanceFieldInset;
- u1 = u0 + width - 2 * SK_DistanceFieldInset;
- v1 = v0 + height - 2 * SK_DistanceFieldInset;
- } else {
- u0 = glyph->fAtlasLocation.fX;
- v0 = glyph->fAtlasLocation.fY;
- u1 = u0 + width;
- v1 = v0 + height;
- }
- // We pack the 2bit page index in the low bit of the u and v texture coords
- uint32_t pageIndex = glyph->pageIndex();
- SkASSERT(pageIndex < 4);
- uint16_t uBit = (pageIndex >> 1) & 0x1;
- uint16_t vBit = pageIndex & 0x1;
- u0 <<= 1;
- u0 |= uBit;
- v0 <<= 1;
- v0 |= vBit;
- u1 <<= 1;
- u1 |= uBit;
- v1 <<= 1;
- v1 |= vBit;
- }
-
- // This is a bit wonky, but sometimes we have LCD text, in which case we won't have color
- // vertices, hence vertexStride - sizeof(SkIPoint16)
- intptr_t colorOffset = sizeof(SkPoint);
- intptr_t texCoordOffset = vertexStride - sizeof(SkIPoint16);
-
- // V0
- if (regenPos) {
- SkPoint* point = reinterpret_cast<SkPoint*>(vertex);
- point->fX += transX;
- point->fY += transY;
- }
-
- if (regenCol) {
- SkColor* vcolor = reinterpret_cast<SkColor*>(vertex + colorOffset);
- *vcolor = color;
- }
-
- if (regenTexCoords) {
- uint16_t* textureCoords = reinterpret_cast<uint16_t*>(vertex + texCoordOffset);
- textureCoords[0] = u0;
- textureCoords[1] = v0;
- }
- vertex += vertexStride;
-
- // V1
- if (regenPos) {
- SkPoint* point = reinterpret_cast<SkPoint*>(vertex);
- point->fX += transX;
- point->fY += transY;
- }
-
- if (regenCol) {
- SkColor* vcolor = reinterpret_cast<SkColor*>(vertex + colorOffset);
- *vcolor = color;
- }
-
- if (regenTexCoords) {
- uint16_t* textureCoords = reinterpret_cast<uint16_t*>(vertex + texCoordOffset);
- textureCoords[0] = u0;
- textureCoords[1] = v1;
- }
- vertex += vertexStride;
-
- // V2
- if (regenPos) {
- SkPoint* point = reinterpret_cast<SkPoint*>(vertex);
- point->fX += transX;
- point->fY += transY;
- }
-
- if (regenCol) {
- SkColor* vcolor = reinterpret_cast<SkColor*>(vertex + colorOffset);
- *vcolor = color;
- }
-
- if (regenTexCoords) {
- uint16_t* textureCoords = reinterpret_cast<uint16_t*>(vertex + texCoordOffset);
- textureCoords[0] = u1;
- textureCoords[1] = v0;
- }
- vertex += vertexStride;
-
- // V3
- if (regenPos) {
- SkPoint* point = reinterpret_cast<SkPoint*>(vertex);
- point->fX += transX;
- point->fY += transY;
- }
-
- if (regenCol) {
- SkColor* vcolor = reinterpret_cast<SkColor*>(vertex + colorOffset);
- *vcolor = color;
- }
-
- if (regenTexCoords) {
- uint16_t* textureCoords = reinterpret_cast<uint16_t*>(vertex + texCoordOffset);
- textureCoords[0] = u1;
- textureCoords[1] = v1;
- }
-}
-
-template <bool regenPos, bool regenCol, bool regenTexCoords, bool regenGlyphs>
-void GrAtlasTextBlob::regenInOp(GrDeferredUploadTarget* target, GrAtlasGlyphCache* fontCache,
- GrBlobRegenHelper* helper, Run* run, Run::SubRunInfo* info,
- SkAutoGlyphCache* lazyCache, int glyphCount, size_t vertexStride,
- GrColor color, SkScalar transX, SkScalar transY) const {
- SkASSERT(lazyCache);
- static_assert(!regenGlyphs || regenTexCoords, "must regenTexCoords along regenGlyphs");
- GrAtlasTextStrike* strike = nullptr;
- if (regenTexCoords) {
- info->resetBulkUseToken();
-
- const SkDescriptor* desc = (run->fOverrideDescriptor && !info->drawAsDistanceFields())
- ? run->fOverrideDescriptor->getDesc()
- : run->fDescriptor.getDesc();
-
- if (!*lazyCache || (*lazyCache)->getDescriptor() != *desc) {
- SkScalerContextEffects effects;
- effects.fPathEffect = run->fPathEffect.get();
- effects.fRasterizer = run->fRasterizer.get();
- effects.fMaskFilter = run->fMaskFilter.get();
- lazyCache->reset(SkGlyphCache::DetachCache(run->fTypeface.get(), effects, desc));
- }
-
- if (regenGlyphs) {
- strike = fontCache->getStrike(lazyCache->get());
- } else {
- strike = info->strike();
- }
- }
-
- bool brokenRun = false;
- intptr_t vertex = reinterpret_cast<intptr_t>(fVertices);
- vertex += info->vertexStartIndex();
- for (int glyphIdx = 0; glyphIdx < glyphCount; glyphIdx++) {
- GrGlyph* glyph = nullptr;
- if (regenTexCoords) {
- size_t glyphOffset = glyphIdx + info->glyphStartIndex();
-
- if (regenGlyphs) {
- // Get the id from the old glyph, and use the new strike to lookup
- // the glyph.
- GrGlyph::PackedID id = fGlyphs[glyphOffset]->fPackedID;
- fGlyphs[glyphOffset] = strike->getGlyph(id, info->maskFormat(), lazyCache->get());
- SkASSERT(id == fGlyphs[glyphOffset]->fPackedID);
- }
- glyph = fGlyphs[glyphOffset];
- SkASSERT(glyph && glyph->fMaskFormat == info->maskFormat());
-
- if (!fontCache->hasGlyph(glyph) &&
- !strike->addGlyphToAtlas(target, glyph, lazyCache->get(), info->maskFormat())) {
- helper->flush();
- brokenRun = glyphIdx > 0;
-
- SkDEBUGCODE(bool success =) strike->addGlyphToAtlas(target,
- glyph,
- lazyCache->get(),
- info->maskFormat());
- SkASSERT(success);
- }
- fontCache->addGlyphToBulkAndSetUseToken(info->bulkUseToken(), glyph,
- target->nextDrawToken());
- }
-
- regen_vertices<regenPos, regenCol, regenTexCoords>(vertex, glyph, vertexStride,
- info->drawAsDistanceFields(),
- transX, transY, color);
- vertex += vertexStride * GrAtlasTextOp::kVerticesPerGlyph;
- helper->incGlyphCount();
- }
-
- // We may have changed the color so update it here
- info->setColor(color);
- if (regenTexCoords) {
- if (regenGlyphs) {
- info->setStrike(strike);
- }
- info->setAtlasGeneration(brokenRun ? GrDrawOpAtlas::kInvalidAtlasGeneration
- : fontCache->atlasGeneration(info->maskFormat()));
- }
-}
-
-enum RegenMask {
- kNoRegen = 0x0,
- kRegenPos = 0x1,
- kRegenCol = 0x2,
- kRegenTex = 0x4,
- kRegenGlyph = 0x8 | kRegenTex, // we have to regenerate the texture coords when we regen glyphs
-
- // combinations
- kRegenPosCol = kRegenPos | kRegenCol,
- kRegenPosTex = kRegenPos | kRegenTex,
- kRegenPosTexGlyph = kRegenPos | kRegenGlyph,
- kRegenPosColTex = kRegenPos | kRegenCol | kRegenTex,
- kRegenPosColTexGlyph = kRegenPos | kRegenCol | kRegenGlyph,
- kRegenColTex = kRegenCol | kRegenTex,
- kRegenColTexGlyph = kRegenCol | kRegenGlyph,
-};
-
-#define REGEN_ARGS target, fontCache, helper, &run, &info, lazyCache, \
- *glyphCount, vertexStride, color, transX, transY
-
-void GrAtlasTextBlob::regenInOp(GrDeferredUploadTarget* target, GrAtlasGlyphCache* fontCache,
- GrBlobRegenHelper* helper, int runIndex, int subRunIndex,
- SkAutoGlyphCache* lazyCache, size_t vertexStride,
- const SkMatrix& viewMatrix, SkScalar x, SkScalar y, GrColor color,
- void** vertices, size_t* byteCount, int* glyphCount) {
- Run& run = fRuns[runIndex];
- Run::SubRunInfo& info = run.fSubRunInfo[subRunIndex];
-
- uint64_t currentAtlasGen = fontCache->atlasGeneration(info.maskFormat());
-
- // Compute translation if any
- SkScalar transX, transY;
- info.computeTranslation(viewMatrix, x, y, &transX, &transY);
-
- // Because the GrAtlasGlyphCache may evict the strike a blob depends on using for
- // generating its texture coords, we have to track whether or not the strike has
- // been abandoned. If it hasn't been abandoned, then we can use the GrGlyph*s as is
- // otherwise we have to get the new strike, and use that to get the correct glyphs.
- // Because we do not have the packed ids, and thus can't look up our glyphs in the
- // new strike, we instead keep our ref to the old strike and use the packed ids from
- // it. These ids will still be valid as long as we hold the ref. When we are done
- // updating our cache of the GrGlyph*s, we drop our ref on the old strike
- bool regenerateGlyphs = info.strike()->isAbandoned();
- bool regenerateTextureCoords = info.atlasGeneration() != currentAtlasGen ||
- regenerateGlyphs;
- bool regenerateColors = kARGB_GrMaskFormat != info.maskFormat() &&
- info.color() != color;
- bool regeneratePositions = transX != 0.f || transY != 0.f;
- *glyphCount = info.glyphCount();
-
- uint32_t regenMaskBits = kNoRegen;
- regenMaskBits |= regeneratePositions ? kRegenPos : 0;
- regenMaskBits |= regenerateColors ? kRegenCol : 0;
- regenMaskBits |= regenerateTextureCoords ? kRegenTex : 0;
- regenMaskBits |= regenerateGlyphs ? kRegenGlyph : 0;
- RegenMask regenMask = (RegenMask)regenMaskBits;
-
- switch (regenMask) {
- case kRegenPos:
- this->regenInOp<true, false, false, false>(REGEN_ARGS);
- break;
- case kRegenCol:
- this->regenInOp<false, true, false, false>(REGEN_ARGS);
- break;
- case kRegenTex:
- this->regenInOp<false, false, true, false>(REGEN_ARGS);
- break;
- case kRegenGlyph:
- this->regenInOp<false, false, true, true>(REGEN_ARGS);
- break;
-
- // combinations
- case kRegenPosCol:
- this->regenInOp<true, true, false, false>(REGEN_ARGS);
- break;
- case kRegenPosTex:
- this->regenInOp<true, false, true, false>(REGEN_ARGS);
- break;
- case kRegenPosTexGlyph:
- this->regenInOp<true, false, true, true>(REGEN_ARGS);
- break;
- case kRegenPosColTex:
- this->regenInOp<true, true, true, false>(REGEN_ARGS);
- break;
- case kRegenPosColTexGlyph:
- this->regenInOp<true, true, true, true>(REGEN_ARGS);
- break;
- case kRegenColTex:
- this->regenInOp<false, true, true, false>(REGEN_ARGS);
- break;
- case kRegenColTexGlyph:
- this->regenInOp<false, true, true, true>(REGEN_ARGS);
- break;
- case kNoRegen:
- helper->incGlyphCount(*glyphCount);
-
- // set use tokens for all of the glyphs in our subrun. This is only valid if we
- // have a valid atlas generation
- fontCache->setUseTokenBulk(*info.bulkUseToken(), target->nextDrawToken(),
- info.maskFormat());
- break;
- }
-
- *byteCount = info.byteCount();
- *vertices = fVertices + info.vertexStartIndex();
-}
diff --git a/tests/GrCCPRTest.cpp b/tests/GrCCPRTest.cpp
index f56298b..b8e3db9 100644
--- a/tests/GrCCPRTest.cpp
+++ b/tests/GrCCPRTest.cpp
@@ -154,6 +154,28 @@
};
DEF_CCPR_TEST(GrCCPRTest_cleanup)
+class GrCCPRTest_unregisterCulledOps : public CCPRTest {
+ void onRun(skiatest::Reporter* reporter, CCPRPathDrawer& ccpr) override {
+ REPORTER_ASSERT(reporter, SkPathPriv::TestingOnly_unique(fPath));
+
+ // Ensure Ops get unregistered from CCPR when culled early.
+ ccpr.drawPath(fPath);
+ REPORTER_ASSERT(reporter, !SkPathPriv::TestingOnly_unique(fPath));
+ ccpr.clear(); // Clear should delete the CCPR Op.
+ REPORTER_ASSERT(reporter, SkPathPriv::TestingOnly_unique(fPath));
+ ccpr.flush(); // Should not crash (DrawPathsOp should have unregistered itself).
+
+ // Ensure Op unregisters work when we delete the context without flushing.
+ ccpr.drawPath(fPath);
+ REPORTER_ASSERT(reporter, !SkPathPriv::TestingOnly_unique(fPath));
+ ccpr.clear(); // Clear should delete the CCPR DrawPathsOp.
+ REPORTER_ASSERT(reporter, SkPathPriv::TestingOnly_unique(fPath));
+ ccpr.abandonGrContext();
+ fMockContext.reset(); // Should not crash (DrawPathsOp should have unregistered itself).
+ }
+};
+DEF_CCPR_TEST(GrCCPRTest_unregisterCulledOps)
+
class CCPRRenderingTest {
public:
void run(skiatest::Reporter* reporter, GrContext* ctx) const {
diff --git a/tools/sk_tool_utils.cpp b/tools/sk_tool_utils.cpp
index 0a93db4..56c1f48 100644
--- a/tools/sk_tool_utils.cpp
+++ b/tools/sk_tool_utils.cpp
@@ -263,6 +263,21 @@
add_to_text_blob_w_len(builder, text, strlen(text), origPaint, x, y);
}
+SkPath make_star(const SkRect& bounds, int numPts, int step) {
+ SkPath path;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.moveTo(0,-1);
+ for (int i = 1; i < numPts; ++i) {
+ int idx = i*step;
+ SkScalar theta = idx * 2*SK_ScalarPI/numPts + SK_ScalarPI/2;
+ SkScalar x = SkScalarCos(theta);
+ SkScalar y = -SkScalarSin(theta);
+ path.lineTo(x, y);
+ }
+ path.transform(SkMatrix::MakeRectToRect(path.getBounds(), bounds, SkMatrix::kFill_ScaleToFit));
+ return path;
+}
+
#if !defined(__clang__) && defined(_MSC_VER)
// MSVC takes ~2 minutes to compile this function with optimization.
// We don't really care to wait that long for this function.
diff --git a/tools/sk_tool_utils.h b/tools/sk_tool_utils.h
index b1bde77..2931243 100644
--- a/tools/sk_tool_utils.h
+++ b/tools/sk_tool_utils.h
@@ -135,6 +135,18 @@
void add_to_text_blob(SkTextBlobBuilder* builder, const char* text,
const SkPaint& origPaint, SkScalar x, SkScalar y);
+ // Constructs a star by walking a 'numPts'-sided regular polygon with even/odd fill:
+ //
+ // moveTo(pts[0]);
+ // lineTo(pts[step % numPts]);
+ // ...
+ // lineTo(pts[(step * (N - 1)) % numPts]);
+ //
+ // numPts=5, step=2 will produce a classic five-point star.
+ //
+ // numPts and step must be co-prime.
+ SkPath make_star(const SkRect& bounds, int numPts = 5, int step = 2);
+
void make_big_path(SkPath& path);
// Return a blurred version of 'src'. This doesn't use a separable filter