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>
This commit is contained in:
John Stiles 2020-08-10 13:12:41 -04:00 committed by Skia Commit-Bot
parent 46a324a16c
commit 87d0a2fa53
5 changed files with 87 additions and 35 deletions

View File

@ -12,19 +12,24 @@
#include "include/gpu/GrRecordingContext.h" #include "include/gpu/GrRecordingContext.h"
#include "src/gpu/GrFragmentProcessor.h" #include "src/gpu/GrFragmentProcessor.h"
#include "src/gpu/GrRecordingContextPriv.h" #include "src/gpu/GrRecordingContextPriv.h"
#include "src/gpu/effects/generated/GrConstColorProcessor.h"
#if GR_TEST_UTILS #if GR_TEST_UTILS
class GrGeometryProcessor; class GrGeometryProcessor;
GrProcessorTestData::GrProcessorTestData(SkRandom* random, GrRecordingContext* context, GrProcessorTestData::GrProcessorTestData(SkRandom* random, GrRecordingContext* context,
int numViews, const ViewInfo views[]) int maxTreeDepth, int numViews, const ViewInfo views[])
: GrProcessorTestData(random, context, numViews, views, /*inputFP=*/nullptr) {} : GrProcessorTestData(random, context, maxTreeDepth, numViews, views,
/*inputFP=*/nullptr) {}
GrProcessorTestData::GrProcessorTestData(SkRandom* random, GrRecordingContext* context, GrProcessorTestData::GrProcessorTestData(SkRandom* random, GrRecordingContext* context,
int numViews, const ViewInfo views[], int maxTreeDepth, int numViews, const ViewInfo views[],
std::unique_ptr<GrFragmentProcessor> inputFP) 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); fViews.reset(views, numViews);
fArena = std::make_unique<SkArenaAlloc>(1000); fArena = std::make_unique<SkArenaAlloc>(1000);
} }
@ -35,7 +40,15 @@ GrProxyProvider* GrProcessorTestData::proxyProvider() { return fContext->priv().
const GrCaps* GrProcessorTestData::caps() { return fContext->priv().caps(); } 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() { GrProcessorTestData::ViewInfo GrProcessorTestData::randomView() {
SkASSERT(!fViews.empty()); SkASSERT(!fViews.empty());
@ -164,13 +177,34 @@ void GrXPFactoryTestFactory::VerifyFactoryCount() {
std::unique_ptr<GrFragmentProcessor> GrProcessorUnitTest::MakeChildFP(GrProcessorTestData* data) { std::unique_ptr<GrFragmentProcessor> GrProcessorUnitTest::MakeChildFP(GrProcessorTestData* data) {
std::unique_ptr<GrFragmentProcessor> fp; std::unique_ptr<GrFragmentProcessor> fp;
do {
fp = GrFragmentProcessorTestFactory::Make(data); ++data->fCurrentTreeDepth;
SkASSERT(fp); if (data->fCurrentTreeDepth > data->fMaxTreeDepth) {
} while (fp->numNonNullChildProcessors() != 0); // 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; return fp;
} }
std::unique_ptr<GrFragmentProcessor> GrProcessorUnitTest::MakeOptionalChildFP(
GrProcessorTestData* data) {
return data->fRandom->nextBool() ? MakeChildFP(data) : nullptr;
}
template class GrProcessorTestFactory<GrGeometryProcessor*>; template class GrProcessorTestFactory<GrGeometryProcessor*>;
template class GrProcessorTestFactory<std::unique_ptr<GrFragmentProcessor>>; template class GrProcessorTestFactory<std::unique_ptr<GrFragmentProcessor>>;

View File

@ -37,25 +37,27 @@ enum {
}; };
/** This allows parent FPs to implement a test create with known leaf children in order to avoid /** 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> MakeChildFP(GrProcessorTestData*);
std::unique_ptr<GrFragmentProcessor> MakeOptionalChildFP(GrProcessorTestData*);
} // namespace GrProcessorUnitTest } // namespace GrProcessorUnitTest
/* /** GrProcessorTestData is an argument struct to TestCreate functions
* GrProcessorTestData is an argument struct to TestCreate functions * fTextures are valid textures that can optionally be used to construct
* 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
* 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
* specific backend API. TestCreate functions are also free to create additional textures using * the GrContext.
* the GrContext.
*/ */
class GrProcessorTestData { class GrProcessorTestData {
public: public:
using ViewInfo = std::tuple<GrSurfaceProxyView, GrColorType, SkAlphaType>; using ViewInfo = std::tuple<GrSurfaceProxyView, GrColorType, SkAlphaType>;
GrProcessorTestData(SkRandom* random, GrRecordingContext* context, GrProcessorTestData(SkRandom* random, GrRecordingContext* context, int maxTreeDepth,
int numViews, const ViewInfo views[]); int numViews, const ViewInfo views[]);
GrProcessorTestData(SkRandom* random, GrRecordingContext* context, GrProcessorTestData(SkRandom* random, GrRecordingContext* context, int maxTreeDepth,
int numViews, const ViewInfo views[], int numViews, const ViewInfo views[],
std::unique_ptr<GrFragmentProcessor> inputFP); std::unique_ptr<GrFragmentProcessor> inputFP);
GrProcessorTestData(const GrProcessorTestData&) = delete; GrProcessorTestData(const GrProcessorTestData&) = delete;
@ -71,6 +73,8 @@ public:
ViewInfo randomAlphaOnlyView(); ViewInfo randomAlphaOnlyView();
SkRandom* fRandom; SkRandom* fRandom;
int fCurrentTreeDepth = 0;
int fMaxTreeDepth = 1;
private: private:
GrRecordingContext* fContext; GrRecordingContext* fContext;

View File

@ -239,19 +239,22 @@ GR_DEFINE_FRAGMENT_PROCESSOR_TEST(BlendFragmentProcessor);
#if GR_TEST_UTILS #if GR_TEST_UTILS
std::unique_ptr<GrFragmentProcessor> BlendFragmentProcessor::TestCreate(GrProcessorTestData* d) { std::unique_ptr<GrFragmentProcessor> BlendFragmentProcessor::TestCreate(GrProcessorTestData* d) {
// Create two random frag procs. // Create one or two random fragment processors.
std::unique_ptr<GrFragmentProcessor> fpA(GrProcessorUnitTest::MakeChildFP(d)); std::unique_ptr<GrFragmentProcessor> src(GrProcessorUnitTest::MakeOptionalChildFP(d));
std::unique_ptr<GrFragmentProcessor> fpB(GrProcessorUnitTest::MakeChildFP(d)); std::unique_ptr<GrFragmentProcessor> dst(GrProcessorUnitTest::MakeChildFP(d));
if (d->fRandom->nextBool()) {
std::swap(src, dst);
}
SkBlendMode mode; SkBlendMode mode;
BlendBehavior behavior; BlendBehavior behavior;
do { do {
mode = static_cast<SkBlendMode>(d->fRandom->nextRangeU(0, (int)SkBlendMode::kLastMode)); mode = static_cast<SkBlendMode>(d->fRandom->nextRangeU(0, (int)SkBlendMode::kLastMode));
behavior = static_cast<BlendBehavior>( 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); } while (SkBlendMode::kClear == mode || SkBlendMode::kSrc == mode || SkBlendMode::kDst == mode);
return std::unique_ptr<GrFragmentProcessor>( 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 #endif

View File

@ -332,19 +332,21 @@ class TestFPGenerator {
fRandomSeed = random.nextU(); fRandomSeed = random.nextU();
} }
std::unique_ptr<GrFragmentProcessor> make(int type, std::unique_ptr<GrFragmentProcessor> make(int type, int randomTreeDepth,
std::unique_ptr<GrFragmentProcessor> inputFP) { std::unique_ptr<GrFragmentProcessor> inputFP) {
// This will generate the exact same randomized FP (of each requested type) each time // This will generate the exact same randomized FP (of each requested type) each time
// it's called. Call `reroll` to get a different FP. // it's called. Call `reroll` to get a different FP.
SkRandom random{fRandomSeed}; 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)}; std::move(inputFP)};
return GrFragmentProcessorTestFactory::MakeIdx(type, &testData); 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) { SkAlphaType alpha = kPremul_SkAlphaType) {
return make(type, GrTextureEffect::Make(view, alpha)); return make(type, randomTreeDepth, GrTextureEffect::Make(view, alpha));
} }
private: private:
@ -590,7 +592,8 @@ DEF_GPUTEST_FOR_GL_RENDERING_CONTEXTS(ProcessorOptimizationValidationTest, repor
for (int trial = 0;; ++trial) { for (int trial = 0;; ++trial) {
// Create a randomly-configured FP. // Create a randomly-configured FP.
fpGenerator.reroll(); 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 // If we have iterated enough times and seen a sufficient number of successes on each
// optimization bit that can be returned, stop running trials. // optimization bit that can be returned, stop running trials.
@ -622,8 +625,12 @@ DEF_GPUTEST_FOR_GL_RENDERING_CONTEXTS(ProcessorOptimizationValidationTest, repor
// Create and render two identical versions of this FP, but using different input // 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 // textures, to check coverage optimization. We don't need to do this step for
// constant-output or preserving-opacity tests. // constant-output or preserving-opacity tests.
render_fp(context, rtc.get(), fpGenerator.make(i, inputTexture2), readData2.data()); render_fp(context, rtc.get(),
render_fp(context, rtc.get(), fpGenerator.make(i, inputTexture3), readData3.data()); fpGenerator.make(i, /*randomTreeDepth=*/3, inputTexture2),
readData2.data());
render_fp(context, rtc.get(),
fpGenerator.make(i, /*randomTreeDepth=*/3, inputTexture3),
readData3.data());
++optimizedForCoverageAsAlpha; ++optimizedForCoverageAsAlpha;
} }
@ -637,7 +644,8 @@ DEF_GPUTEST_FOR_GL_RENDERING_CONTEXTS(ProcessorOptimizationValidationTest, repor
// Draw base frame last so that rtc holds the original FP behavior if we need to dump // Draw base frame last so that rtc holds the original FP behavior if we need to dump
// the image to the log. // 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 // 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 // violating the optimizations, it's reasonable to expect it to violate requirements on
@ -928,8 +936,10 @@ DEF_GPUTEST_FOR_GL_RENDERING_CONTEXTS(ProcessorCloneTest, reporter, ctxInfo) {
static constexpr int kTimesToInvokeFactory = 10; static constexpr int kTimesToInvokeFactory = 10;
for (int j = 0; j < kTimesToInvokeFactory; ++j) { for (int j = 0; j < kTimesToInvokeFactory; ++j) {
fpGenerator.reroll(); fpGenerator.reroll();
std::unique_ptr<GrFragmentProcessor> fp = fpGenerator.make(i, /*inputFP=*/nullptr); std::unique_ptr<GrFragmentProcessor> fp =
std::unique_ptr<GrFragmentProcessor> regen = fpGenerator.make(i, /*inputFP=*/nullptr); 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(); std::unique_ptr<GrFragmentProcessor> clone = fp->clone();
if (!clone) { if (!clone) {
ERRORF(reporter, "Clone of processor %s failed.", fp->name()); ERRORF(reporter, "Clone of processor %s failed.", fp->name());

View File

@ -296,7 +296,7 @@ bool GrDrawingManager::ProgramUnitTest(GrDirectContext* direct, int maxStages, i
} }
GrPaint paint; 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_color_coverage_stages(&paint, &ptd, maxStages, maxLevels);
set_random_xpf(&paint, &ptd); set_random_xpf(&paint, &ptd);
GrDrawRandomOp(&random, renderTargetContext.get(), std::move(paint)); GrDrawRandomOp(&random, renderTargetContext.get(), std::move(paint));
@ -318,7 +318,8 @@ bool GrDrawingManager::ProgramUnitTest(GrDirectContext* direct, int maxStages, i
for (int i = 0; i < fpFactoryCnt; ++i) { for (int i = 0; i < fpFactoryCnt; ++i) {
// Since FP factories internally randomize, call each 10 times. // Since FP factories internally randomize, call each 10 times.
for (int j = 0; j < 10; ++j) { 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; GrPaint paint;
paint.setXPFactory(GrPorterDuffXPFactory::Get(SkBlendMode::kSrc)); paint.setXPFactory(GrPorterDuffXPFactory::Get(SkBlendMode::kSrc));