Update MakeChildFP to allow processor hierarchies to be created.
Previously, MakeChildFP avoided infinite recursion by rejecting any FP
that took inputs. MakeChildFP now generates random inputs up to a
user-supplied tree depth.
The ProcessorOptimizationValidationTest test has been updated to
test up to a tree depth of 3. The ProcessorCloneTest has been left at
a tree depth of 1 due to a bug that only appears on Galaxy S20/Mali G77.
The Mali bug doesn't appear to be related to FP cloning, but probably
deserves further analysis. (It appears that on this device, these
processors hooked together in sequence render a tiny bit differently
each time: DitherEffect -> RectBlurEffect -> ImprovedPerlinNoise. By
visual inspection it looks like the dither varies on each draw.)
Change-Id: Ib8f619eb7a8a9c9254080303504c20065ff35453
Bug: skia:10384, skia:10595
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/308556
Reviewed-by: Brian Osman <brianosman@google.com>
Commit-Queue: John Stiles <johnstiles@google.com>
Auto-Submit: John Stiles <johnstiles@google.com>
diff --git a/src/gpu/GrProcessorUnitTest.cpp b/src/gpu/GrProcessorUnitTest.cpp
index 0f47ba5..013b57a 100644
--- a/src/gpu/GrProcessorUnitTest.cpp
+++ b/src/gpu/GrProcessorUnitTest.cpp
@@ -12,19 +12,24 @@
#include "include/gpu/GrRecordingContext.h"
#include "src/gpu/GrFragmentProcessor.h"
#include "src/gpu/GrRecordingContextPriv.h"
+#include "src/gpu/effects/generated/GrConstColorProcessor.h"
#if GR_TEST_UTILS
class GrGeometryProcessor;
GrProcessorTestData::GrProcessorTestData(SkRandom* random, GrRecordingContext* context,
- int numViews, const ViewInfo views[])
- : GrProcessorTestData(random, context, numViews, views, /*inputFP=*/nullptr) {}
+ int maxTreeDepth, int numViews, const ViewInfo views[])
+ : GrProcessorTestData(random, context, maxTreeDepth, numViews, views,
+ /*inputFP=*/nullptr) {}
GrProcessorTestData::GrProcessorTestData(SkRandom* random, GrRecordingContext* context,
- int numViews, const ViewInfo views[],
+ int maxTreeDepth, int numViews, const ViewInfo views[],
std::unique_ptr<GrFragmentProcessor> inputFP)
- : fRandom(random), fContext(context), fInputFP(std::move(inputFP)) {
+ : fRandom(random)
+ , fMaxTreeDepth(maxTreeDepth)
+ , fContext(context)
+ , fInputFP(std::move(inputFP)) {
fViews.reset(views, numViews);
fArena = std::make_unique<SkArenaAlloc>(1000);
}
@@ -35,7 +40,15 @@
const GrCaps* GrProcessorTestData::caps() { return fContext->priv().caps(); }
-std::unique_ptr<GrFragmentProcessor> GrProcessorTestData::inputFP() { return std::move(fInputFP); }
+std::unique_ptr<GrFragmentProcessor> GrProcessorTestData::inputFP() {
+ if (fCurrentTreeDepth == 0) {
+ // At the top level of the tree, provide the input FP from the test data.
+ return fInputFP ? fInputFP->clone() : nullptr;
+ } else {
+ // At deeper levels of recursion, synthesize a random input.
+ return GrProcessorUnitTest::MakeChildFP(this);
+ }
+}
GrProcessorTestData::ViewInfo GrProcessorTestData::randomView() {
SkASSERT(!fViews.empty());
@@ -164,13 +177,34 @@
std::unique_ptr<GrFragmentProcessor> GrProcessorUnitTest::MakeChildFP(GrProcessorTestData* data) {
std::unique_ptr<GrFragmentProcessor> fp;
- do {
- fp = GrFragmentProcessorTestFactory::Make(data);
- SkASSERT(fp);
- } while (fp->numNonNullChildProcessors() != 0);
+
+ ++data->fCurrentTreeDepth;
+ if (data->fCurrentTreeDepth > data->fMaxTreeDepth) {
+ // We've gone too deep, but we can't necessarily return null without risking an assertion.
+ // Instead, return a known-simple zero-child FP. This limits the recursion, and the
+ // generated FP will be rejected by the numNonNullChildProcessors check below.
+ fp = GrConstColorProcessor::Make(SK_PMColor4fTRANSPARENT);
+ } else {
+ for (;;) {
+ fp = GrFragmentProcessorTestFactory::Make(data);
+ SkASSERT(fp);
+ // If our tree has already reached its max depth, we must reject FPs that have children.
+ if (data->fCurrentTreeDepth < data->fMaxTreeDepth ||
+ fp->numNonNullChildProcessors() == 0) {
+ break;
+ }
+ }
+ }
+
+ --data->fCurrentTreeDepth;
return fp;
}
+std::unique_ptr<GrFragmentProcessor> GrProcessorUnitTest::MakeOptionalChildFP(
+ GrProcessorTestData* data) {
+ return data->fRandom->nextBool() ? MakeChildFP(data) : nullptr;
+}
+
template class GrProcessorTestFactory<GrGeometryProcessor*>;
template class GrProcessorTestFactory<std::unique_ptr<GrFragmentProcessor>>;
diff --git a/src/gpu/GrProcessorUnitTest.h b/src/gpu/GrProcessorUnitTest.h
index 5225c1f..7c2cbde 100644
--- a/src/gpu/GrProcessorUnitTest.h
+++ b/src/gpu/GrProcessorUnitTest.h
@@ -37,25 +37,27 @@
};
/** This allows parent FPs to implement a test create with known leaf children in order to avoid
-creating an unbounded FP tree which may overflow various shader limits. */
+ * creating an unbounded FP tree which may overflow various shader limits.
+ * MakeOptionalChildFP is the same as MakeChildFP, but can return null.
+ */
std::unique_ptr<GrFragmentProcessor> MakeChildFP(GrProcessorTestData*);
+std::unique_ptr<GrFragmentProcessor> MakeOptionalChildFP(GrProcessorTestData*);
} // namespace GrProcessorUnitTest
-/*
- * GrProcessorTestData is an argument struct to TestCreate functions
- * fTextures are valid textures that can optionally be used to construct
- * TextureSampler. The first texture has a RGBA8 format and the second has Alpha8 format for the
- * specific backend API. TestCreate functions are also free to create additional textures using
- * the GrContext.
+/** GrProcessorTestData is an argument struct to TestCreate functions
+ * fTextures are valid textures that can optionally be used to construct
+ * TextureSampler. The first texture has a RGBA8 format and the second has Alpha8 format for the
+ * specific backend API. TestCreate functions are also free to create additional textures using
+ * the GrContext.
*/
class GrProcessorTestData {
public:
using ViewInfo = std::tuple<GrSurfaceProxyView, GrColorType, SkAlphaType>;
- GrProcessorTestData(SkRandom* random, GrRecordingContext* context,
+ GrProcessorTestData(SkRandom* random, GrRecordingContext* context, int maxTreeDepth,
int numViews, const ViewInfo views[]);
- GrProcessorTestData(SkRandom* random, GrRecordingContext* context,
+ GrProcessorTestData(SkRandom* random, GrRecordingContext* context, int maxTreeDepth,
int numViews, const ViewInfo views[],
std::unique_ptr<GrFragmentProcessor> inputFP);
GrProcessorTestData(const GrProcessorTestData&) = delete;
@@ -71,6 +73,8 @@
ViewInfo randomAlphaOnlyView();
SkRandom* fRandom;
+ int fCurrentTreeDepth = 0;
+ int fMaxTreeDepth = 1;
private:
GrRecordingContext* fContext;
diff --git a/src/gpu/effects/GrBlendFragmentProcessor.cpp b/src/gpu/effects/GrBlendFragmentProcessor.cpp
index 83d701a..e1a7dea 100644
--- a/src/gpu/effects/GrBlendFragmentProcessor.cpp
+++ b/src/gpu/effects/GrBlendFragmentProcessor.cpp
@@ -239,19 +239,22 @@
#if GR_TEST_UTILS
std::unique_ptr<GrFragmentProcessor> BlendFragmentProcessor::TestCreate(GrProcessorTestData* d) {
- // Create two random frag procs.
- std::unique_ptr<GrFragmentProcessor> fpA(GrProcessorUnitTest::MakeChildFP(d));
- std::unique_ptr<GrFragmentProcessor> fpB(GrProcessorUnitTest::MakeChildFP(d));
+ // Create one or two random fragment processors.
+ std::unique_ptr<GrFragmentProcessor> src(GrProcessorUnitTest::MakeOptionalChildFP(d));
+ std::unique_ptr<GrFragmentProcessor> dst(GrProcessorUnitTest::MakeChildFP(d));
+ if (d->fRandom->nextBool()) {
+ std::swap(src, dst);
+ }
SkBlendMode mode;
BlendBehavior behavior;
do {
mode = static_cast<SkBlendMode>(d->fRandom->nextRangeU(0, (int)SkBlendMode::kLastMode));
behavior = static_cast<BlendBehavior>(
- d->fRandom->nextRangeU(0, (int)BlendBehavior::kLastBlendBehavior));
+ d->fRandom->nextRangeU(0, (int)BlendBehavior::kLastBlendBehavior));
} while (SkBlendMode::kClear == mode || SkBlendMode::kSrc == mode || SkBlendMode::kDst == mode);
return std::unique_ptr<GrFragmentProcessor>(
- new BlendFragmentProcessor(std::move(fpA), std::move(fpB), mode, behavior));
+ new BlendFragmentProcessor(std::move(src), std::move(dst), mode, behavior));
}
#endif
diff --git a/tests/ProcessorTest.cpp b/tests/ProcessorTest.cpp
index 210cd00..8408a13 100644
--- a/tests/ProcessorTest.cpp
+++ b/tests/ProcessorTest.cpp
@@ -332,19 +332,21 @@
fRandomSeed = random.nextU();
}
- std::unique_ptr<GrFragmentProcessor> make(int type,
+ std::unique_ptr<GrFragmentProcessor> make(int type, int randomTreeDepth,
std::unique_ptr<GrFragmentProcessor> inputFP) {
// This will generate the exact same randomized FP (of each requested type) each time
// it's called. Call `reroll` to get a different FP.
SkRandom random{fRandomSeed};
- GrProcessorTestData testData{&random, fContext, SK_ARRAY_COUNT(fTestViews), fTestViews,
+ GrProcessorTestData testData{&random, fContext, randomTreeDepth,
+ SK_ARRAY_COUNT(fTestViews), fTestViews,
std::move(inputFP)};
return GrFragmentProcessorTestFactory::MakeIdx(type, &testData);
}
- std::unique_ptr<GrFragmentProcessor> make(int type, GrSurfaceProxyView view,
+ std::unique_ptr<GrFragmentProcessor> make(int type, int randomTreeDepth,
+ GrSurfaceProxyView view,
SkAlphaType alpha = kPremul_SkAlphaType) {
- return make(type, GrTextureEffect::Make(view, alpha));
+ return make(type, randomTreeDepth, GrTextureEffect::Make(view, alpha));
}
private:
@@ -590,7 +592,8 @@
for (int trial = 0;; ++trial) {
// Create a randomly-configured FP.
fpGenerator.reroll();
- std::unique_ptr<GrFragmentProcessor> fp = fpGenerator.make(i, inputTexture1);
+ std::unique_ptr<GrFragmentProcessor> fp =
+ fpGenerator.make(i, /*randomTreeDepth=*/3, inputTexture1);
// If we have iterated enough times and seen a sufficient number of successes on each
// optimization bit that can be returned, stop running trials.
@@ -622,8 +625,12 @@
// Create and render two identical versions of this FP, but using different input
// textures, to check coverage optimization. We don't need to do this step for
// constant-output or preserving-opacity tests.
- render_fp(context, rtc.get(), fpGenerator.make(i, inputTexture2), readData2.data());
- render_fp(context, rtc.get(), fpGenerator.make(i, inputTexture3), readData3.data());
+ render_fp(context, rtc.get(),
+ fpGenerator.make(i, /*randomTreeDepth=*/3, inputTexture2),
+ readData2.data());
+ render_fp(context, rtc.get(),
+ fpGenerator.make(i, /*randomTreeDepth=*/3, inputTexture3),
+ readData3.data());
++optimizedForCoverageAsAlpha;
}
@@ -637,7 +644,8 @@
// Draw base frame last so that rtc holds the original FP behavior if we need to dump
// the image to the log.
- render_fp(context, rtc.get(), fpGenerator.make(i, inputTexture1), readData1.data());
+ render_fp(context, rtc.get(), fpGenerator.make(i, /*randomTreeDepth=*/3, inputTexture1),
+ readData1.data());
// This test has a history of being flaky on a number of devices. If an FP is logically
// violating the optimizations, it's reasonable to expect it to violate requirements on
@@ -928,8 +936,10 @@
static constexpr int kTimesToInvokeFactory = 10;
for (int j = 0; j < kTimesToInvokeFactory; ++j) {
fpGenerator.reroll();
- std::unique_ptr<GrFragmentProcessor> fp = fpGenerator.make(i, /*inputFP=*/nullptr);
- std::unique_ptr<GrFragmentProcessor> regen = fpGenerator.make(i, /*inputFP=*/nullptr);
+ std::unique_ptr<GrFragmentProcessor> fp =
+ fpGenerator.make(i, /*randomTreeDepth=*/1, /*inputFP=*/nullptr);
+ std::unique_ptr<GrFragmentProcessor> regen =
+ fpGenerator.make(i, /*randomTreeDepth=*/1, /*inputFP=*/nullptr);
std::unique_ptr<GrFragmentProcessor> clone = fp->clone();
if (!clone) {
ERRORF(reporter, "Clone of processor %s failed.", fp->name());
diff --git a/tests/ProgramsTest.cpp b/tests/ProgramsTest.cpp
index 3aa343e..35184af 100644
--- a/tests/ProgramsTest.cpp
+++ b/tests/ProgramsTest.cpp
@@ -296,7 +296,7 @@
}
GrPaint paint;
- GrProcessorTestData ptd(&random, direct, 2, views);
+ GrProcessorTestData ptd(&random, direct, /*maxTreeDepth=*/1, SK_ARRAY_COUNT(views), views);
set_random_color_coverage_stages(&paint, &ptd, maxStages, maxLevels);
set_random_xpf(&paint, &ptd);
GrDrawRandomOp(&random, renderTargetContext.get(), std::move(paint));
@@ -318,7 +318,8 @@
for (int i = 0; i < fpFactoryCnt; ++i) {
// Since FP factories internally randomize, call each 10 times.
for (int j = 0; j < 10; ++j) {
- GrProcessorTestData ptd(&random, direct, 2, views);
+ GrProcessorTestData ptd(&random, direct, /*maxTreeDepth=*/1, SK_ARRAY_COUNT(views),
+ views);
GrPaint paint;
paint.setXPFactory(GrPorterDuffXPFactory::Get(SkBlendMode::kSrc));