Make shadow tessellators fail gracefully and add unit test for this.
Change-Id: I42a9d06a18928588347a6dea2f6150518ba29aa8
Reviewed-on: https://skia-review.googlesource.com/7886
Commit-Queue: Brian Salomon <bsalomon@google.com>
Reviewed-by: Jim Van Verth <jvanverth@google.com>
diff --git a/gn/tests.gni b/gn/tests.gni
index 6a836f6..baf1910 100644
--- a/gn/tests.gni
+++ b/gn/tests.gni
@@ -191,6 +191,7 @@
"$_tests/SerializationTest.cpp",
"$_tests/ShaderOpacityTest.cpp",
"$_tests/ShaderTest.cpp",
+ "$_tests/ShadowUtilsTest.cpp",
"$_tests/SizeTest.cpp",
"$_tests/Sk4x4fTest.cpp",
"$_tests/SkBase64Test.cpp",
diff --git a/src/utils/SkShadowTessellator.cpp b/src/utils/SkShadowTessellator.cpp
index f140ceb..a0a011f 100755
--- a/src/utils/SkShadowTessellator.cpp
+++ b/src/utils/SkShadowTessellator.cpp
@@ -26,6 +26,8 @@
int vertexCount() const { return fPositions.count(); }
int indexCount() const { return fIndices.count(); }
+ bool succeeded() const { return fSucceeded; }
+
// The casts are needed to work around a, older GCC issue where the fact that the pointers are
// T* and not const T* causes calls to a deleted unique_ptr constructor.
UniqueArray<SkPoint> releasePositions() {
@@ -71,6 +73,8 @@
SkTDArray<SkPoint> fInitPoints;
// temporary buffer
SkTDArray<SkPoint> fPointBuffer;
+
+ bool fSucceeded;
};
static bool compute_normal(const SkPoint& p0, const SkPoint& p1, SkScalar radius, SkScalar dir,
@@ -107,12 +111,12 @@
SkColor umbraColor,
SkColor penumbraColor,
bool transparent)
- : fRadius(radius)
- , fUmbraColor(umbraColor)
- , fPenumbraColor(penumbraColor)
- , fTransparent(transparent)
- , fPrevUmbraIndex(-1) {
-
+ : fRadius(radius)
+ , fUmbraColor(umbraColor)
+ , fPenumbraColor(penumbraColor)
+ , fTransparent(transparent)
+ , fPrevUmbraIndex(-1)
+ , fSucceeded(false) {
// Outer ring: 3*numPts
// Middle ring: numPts
fPositions.setReserve(4 * path.countPoints());
@@ -154,6 +158,10 @@
}
}
+ if (!this->indexCount()) {
+ return;
+ }
+
SkVector normal;
if (compute_normal(fPositions[fPrevUmbraIndex], fPositions[fFirstVertex], fRadius, fDirection,
&normal)) {
@@ -198,6 +206,7 @@
*fIndices.push() = fPositions.count() - 1;
*fIndices.push() = fFirstVertex + 1;
}
+ fSucceeded = true;
}
// tesselation tolerance values, in device space pixels
@@ -388,6 +397,8 @@
return UniqueArray<uint16_t>(static_cast<const uint16_t*>(fIndices.release()));
}
+ bool succeeded() const { return fSucceeded; }
+
private:
void computeClipBounds(const SkPath& path);
void checkUmbraAndTransformCentroid(SkScalar scale, const SkVector& xlate,
@@ -439,6 +450,8 @@
SkTDArray<SkPoint> fInitPoints;
// temporary buffer
SkTDArray<SkPoint> fPointBuffer;
+
+ bool fSucceeded;
};
@@ -448,15 +461,16 @@
SkScalar radius,
SkColor umbraColor, SkColor penumbraColor,
bool transparent)
- : fRadius(radius)
- , fUmbraColor(umbraColor)
- , fPenumbraColor(penumbraColor)
- , fTransparent(transparent)
- , fValidUmbra(true)
- , fPrevUmbraIndex(-1)
- , fCurrPolyPoint(0)
- , fPrevUmbraOutside(false)
- , fFirstUmbraOutside(false) {
+ : fRadius(radius)
+ , fUmbraColor(umbraColor)
+ , fPenumbraColor(penumbraColor)
+ , fTransparent(transparent)
+ , fValidUmbra(true)
+ , fPrevUmbraIndex(-1)
+ , fCurrPolyPoint(0)
+ , fPrevUmbraOutside(false)
+ , fFirstUmbraOutside(false)
+ , fSucceeded(false) {
// TODO: calculate these better
// Penumbra ring: 3*numPts
@@ -514,6 +528,10 @@
}
}
+ if (!this->indexCount()) {
+ return;
+ }
+
SkVector normal;
if (compute_normal(fPrevPoint, fFirstPoint, fRadius, fDirection,
&normal)) {
@@ -579,6 +597,7 @@
*fIndices.push() = fFirstVertex + 1;
}
}
+ fSucceeded = true;
}
void SkSpotShadowTessellator::computeClipBounds(const SkPath& path) {
@@ -1008,6 +1027,9 @@
SkColor umbraColor, SkColor penumbraColor,
bool transparent) {
SkAmbientShadowTessellator ambientTess(path, radius, umbraColor, penumbraColor, transparent);
+ if (!ambientTess.succeeded()) {
+ return nullptr;
+ }
int vcount = ambientTess.vertexCount();
int icount = ambientTess.indexCount();
return sk_sp<SkShadowVertices>(new SkShadowVertices(ambientTess.releasePositions(),
@@ -1022,6 +1044,9 @@
bool transparent) {
SkSpotShadowTessellator spotTess(path, scale, translate, radius, umbraColor, penumbraColor,
transparent);
+ if (!spotTess.succeeded()) {
+ return nullptr;
+ }
int vcount = spotTess.vertexCount();
int icount = spotTess.indexCount();
return sk_sp<SkShadowVertices>(new SkShadowVertices(spotTess.releasePositions(),
diff --git a/src/utils/SkShadowTessellator.h b/src/utils/SkShadowTessellator.h
index 13924f2..c9abd72 100755
--- a/src/utils/SkShadowTessellator.h
+++ b/src/utils/SkShadowTessellator.h
@@ -61,7 +61,8 @@
, fPositions(std::move(positions))
, fColors(std::move(colors))
, fIndices(std::move(indices)) {
- SkASSERT(SkToBool(fIndices) == SkToBool(indexCnt));
+ SkASSERT(SkToBool(fPositions) && SkToBool(fColors) && SkToBool(vertexCnt) &&
+ SkToBool(fIndices) && SkToBool(indexCnt));
}
int fVertexCnt;
diff --git a/src/utils/SkShadowUtils.cpp b/src/utils/SkShadowUtils.cpp
index 32f1c6f..83699c5 100755
--- a/src/utils/SkShadowUtils.cpp
+++ b/src/utils/SkShadowUtils.cpp
@@ -267,6 +267,9 @@
} else {
// TODO: handle transforming the path as part of the tessellator
vertices = factory.makeVertices(path.transformedPath());
+ if (!vertices) {
+ return;
+ }
translate = &kZeroTranslate;
}
diff --git a/tests/ShadowUtilsTest.cpp b/tests/ShadowUtilsTest.cpp
new file mode 100644
index 0000000..adcb503
--- /dev/null
+++ b/tests/ShadowUtilsTest.cpp
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkCanvas.h"
+#include "SkPath.h"
+#include "SkShadowTessellator.h"
+#include "SkShadowUtils.h"
+#include "Test.h"
+
+void tessellate_shadow(skiatest::Reporter* reporter, const SkPath& path, bool expectSuccess) {
+ static constexpr SkScalar kRadius = 2.f;
+ static constexpr SkColor kUmbraColor = 0xFFFFFFFF;
+ static constexpr SkColor kPenumbraColor = 0x20202020;
+ auto verts = SkShadowVertices::MakeAmbient(path, kRadius, kUmbraColor, kPenumbraColor, true);
+ if (expectSuccess != SkToBool(verts)) {
+ ERRORF(reporter, "Expected shadow tessellation to % but it did not.",
+ expectSuccess ? "succeed" : "fail");
+ }
+ verts = SkShadowVertices::MakeAmbient(path, kRadius, kUmbraColor, kPenumbraColor, false);
+ if (expectSuccess != SkToBool(verts)) {
+ ERRORF(reporter, "Expected shadow tessellation to % but it did not.",
+ expectSuccess ? "succeed" : "fail");
+ }
+ verts = SkShadowVertices::MakeSpot(path, 1.5f, {0, 0}, kRadius, kUmbraColor, kPenumbraColor,
+ false);
+ if (expectSuccess != SkToBool(verts)) {
+ ERRORF(reporter, "Expected shadow tessellation to % but it did not.",
+ expectSuccess ? "succeed" : "fail");
+ }
+ verts = SkShadowVertices::MakeSpot(path, 1.5f, {0, 0}, kRadius, kUmbraColor, kPenumbraColor,
+ true);
+ if (expectSuccess != SkToBool(verts)) {
+ ERRORF(reporter, "Expected shadow tessellation to % but it did not.",
+ expectSuccess ? "succeed" : "fail");
+ }
+}
+
+DEF_TEST(ShadowUtils, reporter) {
+ SkCanvas canvas(100, 100);
+ // Currently SkShadowUtils doesn't really stupport cubics when compiled without SK_SUPPORT_GPU.
+ // However, this should now crash.
+ SkPath path;
+ path.cubicTo(100, 50, 20, 100, 0, 0);
+ tessellate_shadow(reporter, path, (bool)SK_SUPPORT_GPU);
+
+ // This line segment has no area and no shadow.
+ path.reset();
+ path.lineTo(10.f, 10.f);
+ tessellate_shadow(reporter, path, false);
+
+ // A series of colinear line segments
+ path.reset();
+ for (int i = 0; i < 10; ++i) {
+ path.lineTo((SkScalar)i, (SkScalar)i);
+ }
+ tessellate_shadow(reporter, path, false);
+}