1 /*
2 * Copyright 2018 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8 #include "include/gpu/GrDirectContext.h"
9 #include "src/gpu/GrDirectContextPriv.h"
10 #include "src/gpu/GrMemoryPool.h"
11 #include "src/gpu/GrOpFlushState.h"
12 #include "src/gpu/GrProxyProvider.h"
13 #include "src/gpu/GrRecordingContextPriv.h"
14 #include "src/gpu/ops/GrOp.h"
15 #include "src/gpu/ops/OpsTask.h"
16 #include "tests/Test.h"
17 #include <iterator>
18
19 // We create Ops that write a value into a range of a buffer. We create ranges from
20 // kNumOpPositions starting positions x kRanges canonical ranges. We repeat each range kNumRepeats
21 // times (with a different value written by each of the repeats).
22 namespace {
23 struct Range {
24 unsigned fOffset;
25 unsigned fLength;
26 };
27
28 static constexpr int kNumOpPositions = 4;
29 static constexpr Range kRanges[] = {{0, 4,}, {1, 2}};
30 static constexpr int kNumRanges = (int)SK_ARRAY_COUNT(kRanges);
31 static constexpr int kNumRepeats = 2;
32 static constexpr int kNumOps = kNumRepeats * kNumOpPositions * kNumRanges;
33
fact(int n)34 static constexpr uint64_t fact(int n) {
35 assert(n > 0);
36 return n > 1 ? n * fact(n - 1) : 1;
37 }
38
39 // How wide should our result buffer be to hold values written by the ranges of the ops.
result_width()40 static constexpr unsigned result_width() {
41 unsigned maxLength = 0;
42 for (size_t i = 0; i < kNumRanges; ++i) {
43 maxLength = maxLength > kRanges[i].fLength ? maxLength : kRanges[i].fLength;
44 }
45 return kNumOpPositions + maxLength - 1;
46 }
47
48 // Number of possible allowable binary chainings among the kNumOps ops.
49 static constexpr int kNumCombinableValues = fact(kNumOps) / fact(kNumOps - 2);
50 using Combinable = std::array<GrOp::CombineResult, kNumCombinableValues>;
51
52 /**
53 * The index in Combinable for the result for combining op 'b' into op 'a', i.e. the result of
54 * op[a]->combineIfPossible(op[b]).
55 */
combinable_index(int a,int b)56 int64_t combinable_index(int a, int b) {
57 SkASSERT(b != a);
58 // Each index gets kNumOps - 1 contiguous bools
59 int64_t aOffset = a * (kNumOps - 1);
60 // Within a's range we have one value each other op, but not one for a itself.
61 int64_t bIdxInA = b < a ? b : b - 1;
62 return aOffset + bIdxInA;
63 }
64
65 /**
66 * Creates a legal set of combinability results for the ops. The likelihood that any two ops
67 * in a group can merge is randomly chosen.
68 */
init_combinable(int numGroups,Combinable * combinable,SkRandom * random)69 static void init_combinable(int numGroups, Combinable* combinable, SkRandom* random) {
70 SkScalar mergeProbability = random->nextUScalar1();
71 std::fill_n(combinable->begin(), kNumCombinableValues, GrOp::CombineResult::kCannotCombine);
72 SkTDArray<int> groups[kNumOps];
73 for (int i = 0; i < kNumOps; ++i) {
74 auto& group = groups[random->nextULessThan(numGroups)];
75 for (int g = 0; g < group.count(); ++g) {
76 int j = group[g];
77 if (random->nextUScalar1() < mergeProbability) {
78 (*combinable)[combinable_index(i, j)] = GrOp::CombineResult::kMerged;
79 } else {
80 (*combinable)[combinable_index(i, j)] = GrOp::CombineResult::kMayChain;
81 }
82 if (random->nextUScalar1() < mergeProbability) {
83 (*combinable)[combinable_index(j, i)] = GrOp::CombineResult::kMerged;
84 } else {
85 (*combinable)[combinable_index(j, i)] = GrOp::CombineResult::kMayChain;
86 }
87 }
88 group.push_back(i);
89 }
90 }
91
92 /**
93 * A simple test op. It has an integer position, p. When it executes it writes p into an array
94 * of ints at index p and p+1. It takes a bitfield that indicates allowed pair-wise chainings.
95 */
96 class TestOp : public GrOp {
97 public:
98 DEFINE_OP_CLASS_ID
99
Make(GrRecordingContext * context,int value,const Range & range,int result[],const Combinable * combinable)100 static GrOp::Owner Make(GrRecordingContext* context, int value, const Range& range,
101 int result[], const Combinable* combinable) {
102 return GrOp::Make<TestOp>(context, value, range, result, combinable);
103 }
104
name() const105 const char* name() const override { return "TestOp"; }
106
writeResult(int result[]) const107 void writeResult(int result[]) const {
108 for (const auto& op : ChainRange<TestOp>(this)) {
109 for (const auto& vr : op.fValueRanges) {
110 for (unsigned i = 0; i < vr.fRange.fLength; ++i) {
111 result[vr.fRange.fOffset + i] = vr.fValue;
112 }
113 }
114 }
115 }
116
117 private:
118 friend class ::GrOp; // for ctor
119
TestOp(int value,const Range & range,int result[],const Combinable * combinable)120 TestOp(int value, const Range& range, int result[], const Combinable* combinable)
121 : INHERITED(ClassID()), fResult(result), fCombinable(combinable) {
122 fValueRanges.push_back({value, range});
123 this->setBounds(SkRect::MakeXYWH(range.fOffset, 0, range.fOffset + range.fLength, 1),
124 HasAABloat::kNo, IsHairline::kNo);
125 }
126
onPrePrepare(GrRecordingContext *,const GrSurfaceProxyView & writeView,GrAppliedClip *,const GrDstProxyView &,GrXferBarrierFlags renderPassXferBarriers,GrLoadOp colorLoadOp)127 void onPrePrepare(GrRecordingContext*,
128 const GrSurfaceProxyView& writeView,
129 GrAppliedClip*,
130 const GrDstProxyView&,
131 GrXferBarrierFlags renderPassXferBarriers,
132 GrLoadOp colorLoadOp) override {}
133
onPrepare(GrOpFlushState *)134 void onPrepare(GrOpFlushState*) override {}
135
onExecute(GrOpFlushState *,const SkRect & chainBounds)136 void onExecute(GrOpFlushState*, const SkRect& chainBounds) override {
137 for (auto& op : ChainRange<TestOp>(this)) {
138 op.writeResult(fResult);
139 }
140 }
141
onCombineIfPossible(GrOp * t,SkArenaAlloc * arenas,const GrCaps &)142 CombineResult onCombineIfPossible(GrOp* t, SkArenaAlloc* arenas, const GrCaps&) override {
143 // This op doesn't use the arenas, but make sure the OpsTask is sending it
144 SkASSERT(arenas);
145 (void) arenas;
146 auto that = t->cast<TestOp>();
147 int v0 = fValueRanges[0].fValue;
148 int v1 = that->fValueRanges[0].fValue;
149 auto result = (*fCombinable)[combinable_index(v0, v1)];
150 if (result == GrOp::CombineResult::kMerged) {
151 std::move(that->fValueRanges.begin(), that->fValueRanges.end(),
152 std::back_inserter(fValueRanges));
153 }
154 return result;
155 }
156
157 struct ValueRange {
158 int fValue;
159 Range fRange;
160 };
161 std::vector<ValueRange> fValueRanges;
162 int* fResult;
163 const Combinable* fCombinable;
164
165 using INHERITED = GrOp;
166 };
167 } // namespace
168
169 /**
170 * Tests adding kNumOps to an op list with all possible allowed chaining configurations. Tests
171 * adding the ops in all possible orders and verifies that the chained executions don't violate
172 * painter's order.
173 */
174 DEF_GPUTEST(OpChainTest, reporter, /*ctxInfo*/) {
175 sk_sp<GrDirectContext> dContext = GrDirectContext::MakeMock(nullptr);
176 SkASSERT(dContext);
177 const GrCaps* caps = dContext->priv().caps();
178 static constexpr SkISize kDims = {kNumOps + 1, 1};
179
180 const GrBackendFormat format = caps->getDefaultBackendFormat(GrColorType::kRGBA_8888,
181 GrRenderable::kYes);
182
183 static const GrSurfaceOrigin kOrigin = kTopLeft_GrSurfaceOrigin;
184 auto proxy = dContext->priv().proxyProvider()->createProxy(
185 format, kDims, GrRenderable::kYes, 1, GrMipmapped::kNo, SkBackingFit::kExact,
186 SkBudgeted::kNo, GrProtected::kNo, GrInternalSurfaceFlags::kNone);
187 SkASSERT(proxy);
188 proxy->instantiate(dContext->priv().resourceProvider());
189
190 GrSwizzle writeSwizzle = caps->getWriteSwizzle(format, GrColorType::kRGBA_8888);
191
192 int result[result_width()];
193 int validResult[result_width()];
194
195 int permutation[kNumOps];
196 for (int i = 0; i < kNumOps; ++i) {
197 permutation[i] = i;
198 }
199 // Op order permutations.
200 static constexpr int kNumPermutations = 100;
201 // For a given number of chainability groups, this is the number of random combinability reuslts
202 // we will test.
203 static constexpr int kNumCombinabilitiesPerGrouping = 20;
204 SkRandom random;
205 bool repeat = false;
206 Combinable combinable;
207 GrDrawingManager* drawingMgr = dContext->priv().drawingManager();
208 sk_sp<GrArenas> arenas = sk_make_sp<GrArenas>();
209 for (int p = 0; p < kNumPermutations; ++p) {
210 for (int i = 0; i < kNumOps - 2 && !repeat; ++i) {
211 // The current implementation of nextULessThan() is biased. :(
212 unsigned j = i + random.nextULessThan(kNumOps - i);
213 std::swap(permutation[i], permutation[j]);
214 }
215 // g is the number of chainable groups that we partition the ops into.
216 for (int g = 1; g < kNumOps; ++g) {
217 for (int c = 0; c < kNumCombinabilitiesPerGrouping; ++c) {
218 init_combinable(g, &combinable, &random);
219 GrTokenTracker tracker;
220 GrOpFlushState flushState(dContext->priv().getGpu(),
221 dContext->priv().resourceProvider(),
222 &tracker);
223 skgpu::v1::OpsTask opsTask(drawingMgr,
224 GrSurfaceProxyView(proxy, kOrigin, writeSwizzle),
225 dContext->priv().auditTrail(),
226 arenas);
227 // This assumes the particular values of kRanges.
228 std::fill_n(result, result_width(), -1);
229 std::fill_n(validResult, result_width(), -1);
230 for (int i = 0; i < kNumOps; ++i) {
231 int value = permutation[i];
232 // factor out the repeats and then use the canonical starting position and range
233 // to determine an actual range.
234 int j = value % (kNumRanges * kNumOpPositions);
235 int pos = j % kNumOpPositions;
236 Range range = kRanges[j / kNumOpPositions];
237 range.fOffset += pos;
238 auto op = TestOp::Make(dContext.get(), value, range, result, &combinable);
239 TestOp* testOp = (TestOp*)op.get();
240 testOp->writeResult(validResult);
241 opsTask.addOp(drawingMgr, std::move(op),
242 GrTextureResolveManager(dContext->priv().drawingManager()),
243 *caps);
244 }
245 opsTask.makeClosed(dContext.get());
246 opsTask.prepare(&flushState);
247 opsTask.execute(&flushState);
248 opsTask.endFlush(drawingMgr);
249 opsTask.disown(drawingMgr);
250 #if 0 // Useful to repeat a random configuration that fails the test while debugger attached.
251 if (!std::equal(result, result + result_width(), validResult)) {
252 repeat = true;
253 }
254 #endif
255 (void)repeat;
256 REPORTER_ASSERT(reporter, std::equal(result, result + result_width(), validResult));
257 }
258 }
259 }
260 }
261