| /* |
| * 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 "include/core/SkRect.h" |
| #include "include/core/SkRefCnt.h" |
| #include "include/core/SkScalar.h" |
| #include "include/core/SkSize.h" |
| #include "include/core/SkTypes.h" |
| #include "include/gpu/GpuTypes.h" |
| #include "include/gpu/GrBackendSurface.h" |
| #include "include/gpu/GrDirectContext.h" |
| #include "include/gpu/GrTypes.h" |
| #include "include/private/base/SkTDArray.h" |
| #include "include/private/gpu/ganesh/GrTypesPriv.h" |
| #include "include/utils/SkRandom.h" |
| #include "src/gpu/AtlasTypes.h" |
| #include "src/gpu/SkBackingFit.h" |
| #include "src/gpu/Swizzle.h" |
| #include "src/gpu/ganesh/GrCaps.h" |
| #include "src/gpu/ganesh/GrDirectContextPriv.h" |
| #include "src/gpu/ganesh/GrOpFlushState.h" |
| #include "src/gpu/ganesh/GrProxyProvider.h" |
| #include "src/gpu/ganesh/GrRenderTargetProxy.h" |
| #include "src/gpu/ganesh/GrSurfaceProxy.h" |
| #include "src/gpu/ganesh/GrSurfaceProxyView.h" |
| #include "src/gpu/ganesh/GrTextureProxy.h" |
| #include "src/gpu/ganesh/GrTextureResolveManager.h" |
| #include "src/gpu/ganesh/ops/GrOp.h" |
| #include "src/gpu/ganesh/ops/OpsTask.h" |
| #include "tests/CtsEnforcement.h" |
| #include "tests/Test.h" |
| |
| #include <algorithm> |
| #include <array> |
| #include <cassert> |
| #include <cstddef> |
| #include <cstdint> |
| #include <iterator> |
| #include <utility> |
| #include <vector> |
| |
| class GrAppliedClip; |
| class GrDrawingManager; |
| class GrDstProxyView; |
| class GrRecordingContext; |
| class SkArenaAlloc; |
| enum class GrXferBarrierFlags; |
| struct GrContextOptions; |
| |
| // 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)std::size(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>; |
| |
| /** |
| * The index in Combinable for the result for combining op 'b' into op 'a', i.e. the result of |
| * op[a]->combineIfPossible(op[b]). |
| */ |
| int64_t combinable_index(int a, int b) { |
| SkASSERT(b != a); |
| // Each index gets kNumOps - 1 contiguous bools |
| int64_t 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 aOffset + bIdxInA; |
| } |
| |
| /** |
| * Creates a legal set of combinability results for the ops. The likelihood that any two ops |
| * in a group can merge is randomly chosen. |
| */ |
| static void init_combinable(int numGroups, Combinable* combinable, SkRandom* random) { |
| SkScalar mergeProbability = random->nextUScalar1(); |
| std::fill_n(combinable->begin(), kNumCombinableValues, GrOp::CombineResult::kCannotCombine); |
| SkTDArray<int> groups[kNumOps]; |
| for (int i = 0; i < kNumOps; ++i) { |
| auto& group = groups[random->nextULessThan(numGroups)]; |
| for (int g = 0; g < group.size(); ++g) { |
| int j = group[g]; |
| if (random->nextUScalar1() < mergeProbability) { |
| (*combinable)[combinable_index(i, j)] = GrOp::CombineResult::kMerged; |
| } else { |
| (*combinable)[combinable_index(i, j)] = GrOp::CombineResult::kMayChain; |
| } |
| if (random->nextUScalar1() < mergeProbability) { |
| (*combinable)[combinable_index(j, i)] = GrOp::CombineResult::kMerged; |
| } else { |
| (*combinable)[combinable_index(j, i)] = GrOp::CombineResult::kMayChain; |
| } |
| } |
| group.push_back(i); |
| } |
| } |
| |
| /** |
| * 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 GrOp::Owner Make(GrRecordingContext* context, int value, const Range& range, |
| int result[], const Combinable* combinable) { |
| return GrOp::Make<TestOp>(context, 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 ::GrOp; // 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, IsHairline::kNo); |
| } |
| |
| void onPrePrepare(GrRecordingContext*, |
| const GrSurfaceProxyView& writeView, |
| GrAppliedClip*, |
| const GrDstProxyView&, |
| GrXferBarrierFlags renderPassXferBarriers, |
| GrLoadOp colorLoadOp) override {} |
| |
| void onPrepare(GrOpFlushState*) override {} |
| |
| void onExecute(GrOpFlushState*, const SkRect& chainBounds) override { |
| for (auto& op : ChainRange<TestOp>(this)) { |
| op.writeResult(fResult); |
| } |
| } |
| |
| CombineResult onCombineIfPossible(GrOp* t, SkArenaAlloc* arenas, const GrCaps&) override { |
| // This op doesn't use the arenas, but make sure the OpsTask is sending it |
| SkASSERT(arenas); |
| (void) arenas; |
| auto that = t->cast<TestOp>(); |
| int v0 = fValueRanges[0].fValue; |
| int v1 = that->fValueRanges[0].fValue; |
| auto result = (*fCombinable)[combinable_index(v0, v1)]; |
| 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; |
| |
| using INHERITED = GrOp; |
| }; |
| } // 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_GANESH_TEST(OpChainTest, reporter, /*ctxInfo*/, CtsEnforcement::kApiLevel_T) { |
| sk_sp<GrDirectContext> dContext = GrDirectContext::MakeMock(nullptr); |
| SkASSERT(dContext); |
| const GrCaps* caps = dContext->priv().caps(); |
| static constexpr SkISize kDims = {kNumOps + 1, 1}; |
| |
| const GrBackendFormat format = caps->getDefaultBackendFormat(GrColorType::kRGBA_8888, |
| GrRenderable::kYes); |
| |
| static const GrSurfaceOrigin kOrigin = kTopLeft_GrSurfaceOrigin; |
| auto proxy = dContext->priv().proxyProvider()->createProxy(format, |
| kDims, |
| GrRenderable::kYes, |
| 1, |
| GrMipmapped::kNo, |
| SkBackingFit::kExact, |
| skgpu::Budgeted::kNo, |
| GrProtected::kNo, |
| /*label=*/"OpChainTest", |
| GrInternalSurfaceFlags::kNone); |
| SkASSERT(proxy); |
| proxy->instantiate(dContext->priv().resourceProvider()); |
| |
| skgpu::Swizzle writeSwizzle = caps->getWriteSwizzle(format, GrColorType::kRGBA_8888); |
| |
| int result[result_width()]; |
| int validResult[result_width()]; |
| |
| int permutation[kNumOps]; |
| for (int i = 0; i < kNumOps; ++i) { |
| permutation[i] = i; |
| } |
| // Op order permutations. |
| static constexpr int kNumPermutations = 100; |
| // For a given number of chainability groups, this is the number of random combinability reuslts |
| // we will test. |
| static constexpr int kNumCombinabilitiesPerGrouping = 20; |
| SkRandom random; |
| bool repeat = false; |
| Combinable combinable; |
| GrDrawingManager* drawingMgr = dContext->priv().drawingManager(); |
| sk_sp<GrArenas> arenas = sk_make_sp<GrArenas>(); |
| 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]); |
| } |
| // g is the number of chainable groups that we partition the ops into. |
| for (int g = 1; g < kNumOps; ++g) { |
| for (int c = 0; c < kNumCombinabilitiesPerGrouping; ++c) { |
| init_combinable(g, &combinable, &random); |
| skgpu::TokenTracker tracker; |
| GrOpFlushState flushState(dContext->priv().getGpu(), |
| dContext->priv().resourceProvider(), |
| &tracker); |
| skgpu::v1::OpsTask opsTask(drawingMgr, |
| GrSurfaceProxyView(proxy, kOrigin, writeSwizzle), |
| dContext->priv().auditTrail(), |
| arenas); |
| // 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(dContext.get(), value, range, result, &combinable); |
| TestOp* testOp = (TestOp*)op.get(); |
| testOp->writeResult(validResult); |
| opsTask.addOp(drawingMgr, std::move(op), |
| GrTextureResolveManager(dContext->priv().drawingManager()), |
| *caps); |
| } |
| opsTask.makeClosed(dContext.get()); |
| opsTask.prepare(&flushState); |
| opsTask.execute(&flushState); |
| opsTask.endFlush(drawingMgr); |
| opsTask.disown(drawingMgr); |
| #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)); |
| } |
| } |
| } |
| } |