| /* |
| * Copyright 2018 Google Inc. |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "GrContext.h" |
| #include "GrContextPriv.h" |
| #include "GrMemoryPool.h" |
| #include "GrOpFlushState.h" |
| #include "GrRenderTargetOpList.h" |
| #include "Test.h" |
| #include "ops/GrOp.h" |
| |
| // We create Ops that write a value into a range of a buffer. We create ranges from |
| // kNumOpPositions starting positions x kRanges canonical ranges. We repeat each range kNumRepeats |
| // times (with a different value written by each of the repeats). |
| namespace { |
| struct Range { |
| unsigned fOffset; |
| unsigned fLength; |
| }; |
| |
| static constexpr int kNumOpPositions = 4; |
| static constexpr Range kRanges[] = {{0, 4,}, {1, 2}}; |
| static constexpr int kNumRanges = (int)SK_ARRAY_COUNT(kRanges); |
| static constexpr int kNumRepeats = 2; |
| static constexpr int kNumOps = kNumRepeats * kNumOpPositions * kNumRanges; |
| |
| static constexpr uint64_t fact(int n) { |
| assert(n > 0); |
| return n > 1 ? n * fact(n - 1) : 1; |
| } |
| |
| // How wide should our result buffer be to hold values written by the ranges of the ops. |
| static constexpr unsigned result_width() { |
| unsigned maxLength = 0; |
| for (size_t i = 0; i < kNumRanges; ++i) { |
| maxLength = maxLength > kRanges[i].fLength ? maxLength : kRanges[i].fLength; |
| } |
| return kNumOpPositions + maxLength - 1; |
| } |
| |
| // Number of possible allowable binary chainings among the kNumOps ops. |
| static constexpr int kNumCombinableValues = fact(kNumOps) / fact(kNumOps - 2); |
| using Combinable = std::array<GrOp::CombineResult, kNumCombinableValues>; |
| |
| /** What should the result be for combining op with value a with op with value b. */ |
| static GrOp::CombineResult combine_result(int a, int b, const Combinable& combinable) { |
| SkASSERT(b != a); |
| // Each index gets kNumOps - 1 contiguous bools |
| int aOffset = a * (kNumOps - 1); |
| // Within a's range we have one value each other op, but not one for a itself. |
| int64_t bIdxInA = b < a ? b : b - 1; |
| return combinable[aOffset + bIdxInA]; |
| } |
| |
| /** |
| * A simple test op. It has an integer position, p. When it executes it writes p into an array |
| * of ints at index p and p+1. It takes a bitfield that indicates allowed pair-wise chainings. |
| */ |
| class TestOp : public GrOp { |
| public: |
| DEFINE_OP_CLASS_ID |
| |
| static std::unique_ptr<TestOp> Make(GrContext* context, int value, const Range& range, |
| int result[], const Combinable* combinable) { |
| GrOpMemoryPool* pool = context->contextPriv().opMemoryPool(); |
| return pool->allocate<TestOp>(value, range, result, combinable); |
| } |
| |
| const char* name() const override { return "TestOp"; } |
| |
| void writeResult(int result[]) const { |
| for (const auto& op : ChainRange<TestOp>(this)) { |
| for (const auto& vr : op.fValueRanges) { |
| for (unsigned i = 0; i < vr.fRange.fLength; ++i) { |
| result[vr.fRange.fOffset + i] = vr.fValue; |
| } |
| } |
| } |
| } |
| |
| private: |
| friend class ::GrOpMemoryPool; // for ctor |
| |
| TestOp(int value, const Range& range, int result[], const Combinable* combinable) |
| : INHERITED(ClassID()), fResult(result), fCombinable(combinable) { |
| fValueRanges.push_back({value, range}); |
| this->setBounds(SkRect::MakeXYWH(range.fOffset, 0, range.fOffset + range.fLength, 1), |
| HasAABloat::kNo, IsZeroArea::kNo); |
| } |
| |
| void onPrepare(GrOpFlushState*) override {} |
| |
| void onExecute(GrOpFlushState*) override { |
| for (auto& op : ChainRange<TestOp>(this)) { |
| op.writeResult(fResult); |
| } |
| } |
| |
| CombineResult onCombineIfPossible(GrOp* t, const GrCaps&) override { |
| auto that = t->cast<TestOp>(); |
| auto result = |
| combine_result(fValueRanges[0].fValue, that->fValueRanges[0].fValue, *fCombinable); |
| // Op chaining rules bar us from merging a chained that. GrOp asserts this. |
| if (that->isChained() && result == CombineResult::kMerged) { |
| return CombineResult::kCannotCombine; |
| } |
| if (result == GrOp::CombineResult::kMerged) { |
| std::move(that->fValueRanges.begin(), that->fValueRanges.end(), |
| std::back_inserter(fValueRanges)); |
| } |
| return result; |
| } |
| |
| struct ValueRange { |
| int fValue; |
| Range fRange; |
| }; |
| std::vector<ValueRange> fValueRanges; |
| int* fResult; |
| const Combinable* fCombinable; |
| |
| typedef GrOp INHERITED; |
| }; |
| } // namespace |
| |
| /** |
| * Tests adding kNumOps to an op list with all possible allowed chaining configurations. Tests |
| * adding the ops in all possible orders and verifies that the chained executions don't violate |
| * painter's order. |
| */ |
| DEF_GPUTEST(OpChainTest, reporter, /*ctxInfo*/) { |
| auto context = GrContext::MakeMock(nullptr); |
| SkASSERT(context); |
| GrSurfaceDesc desc; |
| desc.fConfig = kRGBA_8888_GrPixelConfig; |
| desc.fWidth = kNumOps + 1; |
| desc.fHeight = 1; |
| desc.fFlags = kRenderTarget_GrSurfaceFlag; |
| |
| auto proxy = context->contextPriv().proxyProvider()->createProxy( |
| desc, kTopLeft_GrSurfaceOrigin, GrMipMapped::kNo, SkBackingFit::kExact, SkBudgeted::kNo, |
| GrInternalSurfaceFlags::kNone); |
| SkASSERT(proxy); |
| proxy->instantiate(context->contextPriv().resourceProvider()); |
| int result[result_width()]; |
| int validResult[result_width()]; |
| |
| int permutation[kNumOps]; |
| for (int i = 0; i < kNumOps; ++i) { |
| permutation[i] = i; |
| } |
| static constexpr int kNumPermutations = 100; |
| static constexpr int kNumCombinabilities = 100; |
| SkRandom random; |
| bool repeat = false; |
| Combinable combinable; |
| for (int p = 0; p < kNumPermutations; ++p) { |
| for (int i = 0; i < kNumOps - 2 && !repeat; ++i) { |
| // The current implementation of nextULessThan() is biased. :( |
| unsigned j = i + random.nextULessThan(kNumOps - i); |
| std::swap(permutation[i], permutation[j]); |
| } |
| for (int c = 0; c < kNumCombinabilities; ++c) { |
| for (int i = 0; i < kNumCombinableValues && !repeat; ++i) { |
| combinable[i] = static_cast<GrOp::CombineResult>(random.nextULessThan(3)); |
| } |
| GrTokenTracker tracker; |
| GrOpFlushState flushState(context->contextPriv().getGpu(), |
| context->contextPriv().resourceProvider(), &tracker, nullptr, |
| nullptr); |
| GrRenderTargetOpList opList(context->contextPriv().resourceProvider(), |
| sk_ref_sp(context->contextPriv().opMemoryPool()), |
| proxy->asRenderTargetProxy(), |
| context->contextPriv().getAuditTrail()); |
| // This assumes the particular values of kRanges. |
| std::fill_n(result, result_width(), -1); |
| std::fill_n(validResult, result_width(), -1); |
| for (int i = 0; i < kNumOps; ++i) { |
| int value = permutation[i]; |
| // factor out the repeats and then use the canonical starting position and range |
| // to determine an actual range. |
| int j = value % (kNumRanges * kNumOpPositions); |
| int pos = j % kNumOpPositions; |
| Range range = kRanges[j / kNumOpPositions]; |
| range.fOffset += pos; |
| auto op = TestOp::Make(context.get(), value, range, result, &combinable); |
| op->writeResult(validResult); |
| opList.addOp(std::move(op), *context->contextPriv().caps()); |
| } |
| opList.makeClosed(*context->contextPriv().caps()); |
| opList.prepare(&flushState); |
| opList.execute(&flushState); |
| opList.endFlush(); |
| #if 0 // Useful to repeat a random configuration that fails the test while debugger attached. |
| if (!std::equal(result, result + result_width(), validResult)) { |
| repeat = true; |
| } |
| #endif |
| (void)repeat; |
| REPORTER_ASSERT(reporter, std::equal(result, result + result_width(), validResult)); |
| } |
| } |
| } |