[svgcanvas] Refactor clipping

Instead of emitting one fully-resolved clip per primitive, build a
clip hierarchy based on SkClipStack genIDs and SVG groups.

Bug: skia:9344
Change-Id: Ic26c7cd2e268a4cc2f251757f0e9e22475fb9994
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/236346
Commit-Queue: Florin Malita <fmalita@chromium.org>
Reviewed-by: Mike Reed <reed@google.com>
This commit is contained in:
Florin Malita 2019-09-04 10:09:13 -04:00 committed by Skia Commit-Bot
parent 3e710b0231
commit 8bb6f61d6f
2 changed files with 104 additions and 71 deletions

View File

@ -30,6 +30,7 @@
#include "src/core/SkClipStack.h"
#include "src/core/SkDraw.h"
#include "src/core/SkFontPriv.h"
#include "src/core/SkMakeUnique.h"
#include "src/core/SkUtils.h"
#include "src/shaders/SkShaderBase.h"
#include "src/xml/SkXMLWriter.h"
@ -136,7 +137,6 @@ struct Resources {
: fPaintServer(svg_color(paint.getColor())) {}
SkString fPaintServer;
SkString fClip;
SkString fColorFilter;
};
@ -195,7 +195,6 @@ class SkSVGDevice::ResourceBucket : ::SkNoncopyable {
public:
ResourceBucket()
: fGradientCount(0)
, fClipCount(0)
, fPathCount(0)
, fImageCount(0)
, fPatternCount(0)
@ -205,10 +204,6 @@ public:
return SkStringPrintf("gradient_%d", fGradientCount++);
}
SkString addClip() {
return SkStringPrintf("clip_%d", fClipCount++);
}
SkString addPath() {
return SkStringPrintf("path_%d", fPathCount++);
}
@ -225,7 +220,6 @@ public:
private:
uint32_t fGradientCount;
uint32_t fClipCount;
uint32_t fPathCount;
uint32_t fImageCount;
uint32_t fPatternCount;
@ -251,20 +245,14 @@ public:
AutoElement(const char name[], const std::unique_ptr<SkXMLWriter>& writer)
: AutoElement(name, writer.get()) {}
AutoElement(const char name[], const std::unique_ptr<SkXMLWriter>& writer,
AutoElement(const char name[], SkSVGDevice* svgdev,
ResourceBucket* bucket, const MxCp& mc, const SkPaint& paint)
: fWriter(writer.get())
: fWriter(svgdev->fWriter.get())
, fResourceBucket(bucket) {
svgdev->syncClipStack(*mc.fClipStack);
Resources res = this->addResources(mc, paint);
if (!res.fClip.isEmpty()) {
// The clip is in device space. Apply it via a <g> wrapper to avoid local transform
// interference.
fClipGroup.reset(new AutoElement("g", fWriter));
fClipGroup->addAttribute("clip-path",res.fClip);
}
fWriter->startElement(name);
this->addPaint(paint, res);
@ -304,7 +292,6 @@ public:
private:
Resources addResources(const MxCp&, const SkPaint& paint);
void addClipResources(const MxCp&, Resources* resources);
void addShaderResources(const SkPaint& paint, Resources* resources);
void addGradientShaderResources(const SkShader* shader, const SkPaint& paint,
Resources* resources);
@ -321,7 +308,6 @@ private:
SkXMLWriter* fWriter;
ResourceBucket* fResourceBucket;
std::unique_ptr<AutoElement> fClipGroup;
};
void SkSVGDevice::AutoElement::addPaint(const SkPaint& paint, const Resources& resources) {
@ -379,20 +365,10 @@ void SkSVGDevice::AutoElement::addPaint(const SkPaint& paint, const Resources& r
Resources SkSVGDevice::AutoElement::addResources(const MxCp& mc, const SkPaint& paint) {
Resources resources(paint);
// FIXME: this is a weak heuristic and we end up with LOTS of redundant clips.
bool hasClip = !mc.fClipStack->isWideOpen();
bool hasShader = SkToBool(paint.getShader());
if (hasClip || hasShader) {
if (paint.getShader()) {
AutoElement defs("defs", fWriter);
if (hasClip) {
this->addClipResources(mc, &resources);
}
if (hasShader) {
this->addShaderResources(paint, &resources);
}
this->addShaderResources(paint, &resources);
}
if (const SkColorFilter* cf = paint.getColorFilter()) {
@ -402,6 +378,7 @@ Resources SkSVGDevice::AutoElement::addResources(const MxCp& mc, const SkPaint&
this->addColorFilterResources(*cf, &resources);
}
}
return resources;
}
@ -563,36 +540,6 @@ void SkSVGDevice::AutoElement::addShaderResources(const SkPaint& paint, Resource
// TODO: other shader types?
}
void SkSVGDevice::AutoElement::addClipResources(const MxCp& mc, Resources* resources) {
SkASSERT(!mc.fClipStack->isWideOpen());
SkPath clipPath;
(void) mc.fClipStack->asPath(&clipPath);
SkString clipID = fResourceBucket->addClip();
{
// clipPath is in device space, but since we're only pushing transform attributes
// to the leaf nodes, so are all our elements => SVG userSpaceOnUse == device space.
AutoElement clipPathElement("clipPath", fWriter);
clipPathElement.addAttribute("id", clipID);
SkRect clipRect = SkRect::MakeEmpty();
if (clipPath.isEmpty() || clipPath.isRect(&clipRect)) {
AutoElement rectElement("rect", fWriter);
rectElement.addRectAttributes(clipRect);
} else {
AutoElement pathElement("path", fWriter);
pathElement.addPathAttributes(clipPath);
if (clipPath.getFillType() == SkPath::kEvenOdd_FillType) {
pathElement.addAttribute("clip-rule", "evenodd"); // Default: nonzero
}
}
}
resources->fClip.printf("url(#%s)", clipID.c_str());
}
SkString SkSVGDevice::AutoElement::addLinearGradientDef(const SkShader::GradientInfo& info,
const SkShader* shader) {
SkASSERT(fResourceBucket);
@ -724,10 +671,87 @@ SkSVGDevice::SkSVGDevice(const SkISize& size, std::unique_ptr<SkXMLWriter> write
fRootElement->addAttribute("height", size.height());
}
SkSVGDevice::~SkSVGDevice() = default;
SkSVGDevice::~SkSVGDevice() {
// Pop order is important.
while (!fClipStack.empty()) {
fClipStack.pop_back();
}
}
void SkSVGDevice::syncClipStack(const SkClipStack& cs) {
SkClipStack::B2TIter iter(cs);
const SkClipStack::Element* elem;
size_t rec_idx = 0;
// First, find/preserve the common bottom.
while ((elem = iter.next()) && (rec_idx < fClipStack.size())) {
if (fClipStack[SkToInt(rec_idx)].fGenID != elem->getGenID()) {
break;
}
rec_idx++;
}
// Discard out-of-date stack top.
while (fClipStack.size() > rec_idx) {
fClipStack.pop_back();
}
auto define_clip = [this](const SkClipStack::Element* e) {
const auto cid = SkStringPrintf("cl_%x", e->getGenID());
AutoElement clip_path("clipPath", fWriter);
clip_path.addAttribute("id", cid);
// TODO: handle non-intersect clips.
switch (e->getDeviceSpaceType()) {
case SkClipStack::Element::DeviceSpaceType::kEmpty: {
// TODO: can we skip this?
AutoElement rect("rect", fWriter);
} break;
case SkClipStack::Element::DeviceSpaceType::kRect: {
AutoElement rect("rect", fWriter);
rect.addRectAttributes(e->getDeviceSpaceRect());
} break;
case SkClipStack::Element::DeviceSpaceType::kRRect: {
// TODO: complex rrect handling?
const auto& rr = e->getDeviceSpaceRRect();
const auto radii = rr.getSimpleRadii();
AutoElement rrect("rect", fWriter);
rrect.addRectAttributes(rr.rect());
rrect.addAttribute("rx", radii.x());
rrect.addAttribute("ry", radii.y());
} break;
case SkClipStack::Element::DeviceSpaceType::kPath: {
const auto& p = e->getDeviceSpacePath();
AutoElement path("path", fWriter);
path.addPathAttributes(p);
if (p.getFillType() == SkPath::kEvenOdd_FillType) {
path.addAttribute("clip-rule", "evenodd");
}
} break;
}
return cid;
};
// Rebuild the top.
while (elem) {
const auto cid = define_clip(elem);
auto clip_grp = skstd::make_unique<AutoElement>("g", fWriter);
clip_grp->addAttribute("clip-path", SkStringPrintf("url(#%s)", cid.c_str()));
fClipStack.push_back({ std::move(clip_grp), elem->getGenID() });
elem = iter.next();
}
}
void SkSVGDevice::drawPaint(const SkPaint& paint) {
AutoElement rect("rect", fWriter, fResourceBucket.get(), MxCp(this), paint);
AutoElement rect("rect", this, fResourceBucket.get(), MxCp(this), paint);
rect.addRectAttributes(SkRect::MakeWH(SkIntToScalar(this->width()),
SkIntToScalar(this->height())));
}
@ -773,7 +797,7 @@ void SkSVGDevice::drawPoints(SkCanvas::PointMode mode, size_t count,
path.rewind();
path.moveTo(pts[i]);
path.lineTo(pts[i+1]);
AutoElement elem("path", fWriter, fResourceBucket.get(), MxCp(this), paint);
AutoElement elem("path", this, fResourceBucket.get(), MxCp(this), paint);
elem.addPathAttributes(path);
}
break;
@ -781,7 +805,7 @@ void SkSVGDevice::drawPoints(SkCanvas::PointMode mode, size_t count,
if (count > 1) {
path.addPoly(pts, SkToInt(count), false);
path.moveTo(pts[0]);
AutoElement elem("path", fWriter, fResourceBucket.get(), MxCp(this), paint);
AutoElement elem("path", this, fResourceBucket.get(), MxCp(this), paint);
elem.addPathAttributes(path);
}
break;
@ -791,11 +815,11 @@ void SkSVGDevice::drawPoints(SkCanvas::PointMode mode, size_t count,
void SkSVGDevice::drawRect(const SkRect& r, const SkPaint& paint) {
std::unique_ptr<AutoElement> svg;
if (RequiresViewportReset(paint)) {
svg.reset(new AutoElement("svg", fWriter, fResourceBucket.get(), MxCp(this), paint));
svg.reset(new AutoElement("svg", this, fResourceBucket.get(), MxCp(this), paint));
svg->addRectAttributes(r);
}
AutoElement rect("rect", fWriter, fResourceBucket.get(), MxCp(this), paint);
AutoElement rect("rect", this, fResourceBucket.get(), MxCp(this), paint);
if (svg) {
rect.addAttribute("x", 0);
@ -808,7 +832,7 @@ void SkSVGDevice::drawRect(const SkRect& r, const SkPaint& paint) {
}
void SkSVGDevice::drawOval(const SkRect& oval, const SkPaint& paint) {
AutoElement ellipse("ellipse", fWriter, fResourceBucket.get(), MxCp(this), paint);
AutoElement ellipse("ellipse", this, fResourceBucket.get(), MxCp(this), paint);
ellipse.addAttribute("cx", oval.centerX());
ellipse.addAttribute("cy", oval.centerY());
ellipse.addAttribute("rx", oval.width() / 2);
@ -819,12 +843,12 @@ void SkSVGDevice::drawRRect(const SkRRect& rr, const SkPaint& paint) {
SkPath path;
path.addRRect(rr);
AutoElement elem("path", fWriter, fResourceBucket.get(), MxCp(this), paint);
AutoElement elem("path", this, fResourceBucket.get(), MxCp(this), paint);
elem.addPathAttributes(path);
}
void SkSVGDevice::drawPath(const SkPath& path, const SkPaint& paint, bool pathIsMutable) {
AutoElement elem("path", fWriter, fResourceBucket.get(), MxCp(this), paint);
AutoElement elem("path", this, fResourceBucket.get(), MxCp(this), paint);
elem.addPathAttributes(path);
// TODO: inverse fill types?
@ -864,7 +888,7 @@ void SkSVGDevice::drawBitmapCommon(const MxCp& mc, const SkBitmap& bm, const SkP
}
{
AutoElement imageUse("use", fWriter, fResourceBucket.get(), mc, paint);
AutoElement imageUse("use", this, fResourceBucket.get(), mc, paint);
imageUse.addAttribute("xlink:href", SkStringPrintf("#%s", imageID.c_str()));
}
}
@ -994,7 +1018,7 @@ void SkSVGDevice::drawGlyphRunAsPath(const SkGlyphRun& glyphRun, const SkPoint&
void SkSVGDevice::drawGlyphRunAsText(const SkGlyphRun& glyphRun, const SkPoint& origin,
const SkPaint& runPaint) {
AutoElement elem("text", fWriter, fResourceBucket.get(), MxCp(this), runPaint);
AutoElement elem("text", this, fResourceBucket.get(), MxCp(this), runPaint);
elem.addTextAttributes(glyphRun.font());
SVGTextBuilder builder(origin, glyphRun);

View File

@ -8,6 +8,7 @@
#ifndef SkSVGDevice_DEFINED
#define SkSVGDevice_DEFINED
#include "include/private/SkTArray.h"
#include "include/private/SkTemplates.h"
#include "src/core/SkClipStackDevice.h"
@ -52,6 +53,8 @@ private:
struct MxCp;
void drawBitmapCommon(const MxCp&, const SkBitmap& bm, const SkPaint& paint);
void syncClipStack(const SkClipStack&);
class AutoElement;
class ResourceBucket;
@ -59,7 +62,13 @@ private:
const std::unique_ptr<ResourceBucket> fResourceBucket;
const uint32_t fFlags;
struct ClipRec {
std::unique_ptr<AutoElement> fClipPathElem;
uint32_t fGenID;
};
std::unique_ptr<AutoElement> fRootElement;
SkTArray<ClipRec> fClipStack;
typedef SkClipStackDevice INHERITED;
};