Flutter Engine
The Flutter Engine
OpChainTest.cpp
Go to the documentation of this file.
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
11#include "include/core/SkSize.h"
16#include "include/gpu/GrTypes.h"
19#include "src/base/SkRandom.h"
20#include "src/gpu/AtlasTypes.h"
22#include "src/gpu/Swizzle.h"
35#include "tests/Test.h"
36
37#include <algorithm>
38#include <array>
39#include <cassert>
40#include <cstddef>
41#include <cstdint>
42#include <iterator>
43#include <utility>
44#include <vector>
45
46class GrAppliedClip;
48class GrDstProxyView;
50class SkArenaAlloc;
51enum class GrXferBarrierFlags;
52struct GrContextOptions;
53
54// We create Ops that write a value into a range of a buffer. We create ranges from
55// kNumOpPositions starting positions x kRanges canonical ranges. We repeat each range kNumRepeats
56// times (with a different value written by each of the repeats).
57namespace {
58struct Range {
59 unsigned fOffset;
60 unsigned fLength;
61};
62
63static constexpr int kNumOpPositions = 4;
64static constexpr Range kRanges[] = {{0, 4,}, {1, 2}};
65static constexpr int kNumRanges = (int)std::size(kRanges);
66static constexpr int kNumRepeats = 2;
67static constexpr int kNumOps = kNumRepeats * kNumOpPositions * kNumRanges;
68
69static constexpr uint64_t fact(int n) {
70 assert(n > 0);
71 return n > 1 ? n * fact(n - 1) : 1;
72}
73
74// How wide should our result buffer be to hold values written by the ranges of the ops.
75static constexpr unsigned result_width() {
76 unsigned maxLength = 0;
77 for (size_t i = 0; i < kNumRanges; ++i) {
78 maxLength = maxLength > kRanges[i].fLength ? maxLength : kRanges[i].fLength;
79 }
80 return kNumOpPositions + maxLength - 1;
81}
82
83// Number of possible allowable binary chainings among the kNumOps ops.
84static constexpr int kNumCombinableValues = fact(kNumOps) / fact(kNumOps - 2);
85using Combinable = std::array<GrOp::CombineResult, kNumCombinableValues>;
86
87/**
88 * The index in Combinable for the result for combining op 'b' into op 'a', i.e. the result of
89 * op[a]->combineIfPossible(op[b]).
90 */
91int64_t combinable_index(int a, int b) {
92 SkASSERT(b != a);
93 // Each index gets kNumOps - 1 contiguous bools
94 int64_t aOffset = a * (kNumOps - 1);
95 // Within a's range we have one value each other op, but not one for a itself.
96 int64_t bIdxInA = b < a ? b : b - 1;
97 return aOffset + bIdxInA;
98}
99
100/**
101 * Creates a legal set of combinability results for the ops. The likelihood that any two ops
102 * in a group can merge is randomly chosen.
103 */
104static void init_combinable(int numGroups, Combinable* combinable, SkRandom* random) {
105 SkScalar mergeProbability = random->nextUScalar1();
106 std::fill_n(combinable->begin(), kNumCombinableValues, GrOp::CombineResult::kCannotCombine);
107 SkTDArray<int> groups[kNumOps];
108 for (int i = 0; i < kNumOps; ++i) {
109 auto& group = groups[random->nextULessThan(numGroups)];
110 for (int g = 0; g < group.size(); ++g) {
111 int j = group[g];
112 if (random->nextUScalar1() < mergeProbability) {
113 (*combinable)[combinable_index(i, j)] = GrOp::CombineResult::kMerged;
114 } else {
115 (*combinable)[combinable_index(i, j)] = GrOp::CombineResult::kMayChain;
116 }
117 if (random->nextUScalar1() < mergeProbability) {
118 (*combinable)[combinable_index(j, i)] = GrOp::CombineResult::kMerged;
119 } else {
120 (*combinable)[combinable_index(j, i)] = GrOp::CombineResult::kMayChain;
121 }
122 }
123 group.push_back(i);
124 }
125}
126
127/**
128 * A simple test op. It has an integer position, p. When it executes it writes p into an array
129 * of ints at index p and p+1. It takes a bitfield that indicates allowed pair-wise chainings.
130 */
131class TestOp : public GrOp {
132public:
134
135 static GrOp::Owner Make(GrRecordingContext* context, int value, const Range& range,
136 int result[], const Combinable* combinable) {
137 return GrOp::Make<TestOp>(context, value, range, result, combinable);
138 }
139
140 const char* name() const override { return "TestOp"; }
141
142 void writeResult(int result[]) const {
143 for (const auto& op : ChainRange<TestOp>(this)) {
144 for (const auto& vr : op.fValueRanges) {
145 for (unsigned i = 0; i < vr.fRange.fLength; ++i) {
146 result[vr.fRange.fOffset + i] = vr.fValue;
147 }
148 }
149 }
150 }
151
152private:
153 friend class ::GrOp; // for ctor
154
155 TestOp(int value, const Range& range, int result[], const Combinable* combinable)
156 : INHERITED(ClassID()), fResult(result), fCombinable(combinable) {
157 fValueRanges.push_back({value, range});
158 this->setBounds(SkRect::MakeXYWH(range.fOffset, 0, range.fOffset + range.fLength, 1),
159 HasAABloat::kNo, IsHairline::kNo);
160 }
161
162 void onPrePrepare(GrRecordingContext*,
163 const GrSurfaceProxyView& writeView,
165 const GrDstProxyView&,
166 GrXferBarrierFlags renderPassXferBarriers,
167 GrLoadOp colorLoadOp) override {}
168
169 void onPrepare(GrOpFlushState*) override {}
170
171 void onExecute(GrOpFlushState*, const SkRect& chainBounds) override {
172 for (auto& op : ChainRange<TestOp>(this)) {
173 op.writeResult(fResult);
174 }
175 }
176
177 CombineResult onCombineIfPossible(GrOp* t, SkArenaAlloc* arenas, const GrCaps&) override {
178 // This op doesn't use the arenas, but make sure the OpsTask is sending it
179 SkASSERT(arenas);
180 (void) arenas;
181 auto that = t->cast<TestOp>();
182 int v0 = fValueRanges[0].fValue;
183 int v1 = that->fValueRanges[0].fValue;
184 auto result = (*fCombinable)[combinable_index(v0, v1)];
186 std::move(that->fValueRanges.begin(), that->fValueRanges.end(),
187 std::back_inserter(fValueRanges));
188 }
189 return result;
190 }
191
192 struct ValueRange {
193 int fValue;
194 Range fRange;
195 };
196 std::vector<ValueRange> fValueRanges;
197 int* fResult;
198 const Combinable* fCombinable;
199
200 using INHERITED = GrOp;
201};
202} // namespace
203
204/**
205 * Tests adding kNumOps to an op list with all possible allowed chaining configurations. Tests
206 * adding the ops in all possible orders and verifies that the chained executions don't violate
207 * painter's order.
208 */
211 SkASSERT(dContext);
212 const GrCaps* caps = dContext->priv().caps();
213 static constexpr SkISize kDims = {kNumOps + 1, 1};
214
217
218 static const GrSurfaceOrigin kOrigin = kTopLeft_GrSurfaceOrigin;
219 auto proxy = dContext->priv().proxyProvider()->createProxy(format,
220 kDims,
222 1,
227 /*label=*/"OpChainTest",
229 SkASSERT(proxy);
230 proxy->instantiate(dContext->priv().resourceProvider());
231
233
234 int result[result_width()];
235 int validResult[result_width()];
236
237 int permutation[kNumOps];
238 for (int i = 0; i < kNumOps; ++i) {
239 permutation[i] = i;
240 }
241 // Op order permutations.
242 static constexpr int kNumPermutations = 100;
243 // For a given number of chainability groups, this is the number of random combinability reuslts
244 // we will test.
245 static constexpr int kNumCombinabilitiesPerGrouping = 20;
246 SkRandom random;
247 bool repeat = false;
248 Combinable combinable;
249 GrDrawingManager* drawingMgr = dContext->priv().drawingManager();
250 sk_sp<GrArenas> arenas = sk_make_sp<GrArenas>();
251 for (int p = 0; p < kNumPermutations; ++p) {
252 for (int i = 0; i < kNumOps - 2 && !repeat; ++i) {
253 // The current implementation of nextULessThan() is biased. :(
254 unsigned j = i + random.nextULessThan(kNumOps - i);
255 std::swap(permutation[i], permutation[j]);
256 }
257 // g is the number of chainable groups that we partition the ops into.
258 for (int g = 1; g < kNumOps; ++g) {
259 for (int c = 0; c < kNumCombinabilitiesPerGrouping; ++c) {
260 init_combinable(g, &combinable, &random);
261 skgpu::TokenTracker tracker;
262 GrOpFlushState flushState(dContext->priv().getGpu(),
263 dContext->priv().resourceProvider(),
264 &tracker);
265 skgpu::ganesh::OpsTask opsTask(drawingMgr,
266 GrSurfaceProxyView(proxy, kOrigin, writeSwizzle),
267 dContext->priv().auditTrail(),
268 arenas);
269 // This assumes the particular values of kRanges.
270 std::fill_n(result, result_width(), -1);
271 std::fill_n(validResult, result_width(), -1);
272 for (int i = 0; i < kNumOps; ++i) {
273 int value = permutation[i];
274 // factor out the repeats and then use the canonical starting position and range
275 // to determine an actual range.
276 int j = value % (kNumRanges * kNumOpPositions);
277 int pos = j % kNumOpPositions;
278 Range range = kRanges[j / kNumOpPositions];
279 range.fOffset += pos;
280 auto op = TestOp::Make(dContext.get(), value, range, result, &combinable);
281 TestOp* testOp = (TestOp*)op.get();
282 testOp->writeResult(validResult);
283 opsTask.addOp(drawingMgr, std::move(op),
285 *caps);
286 }
287 opsTask.makeClosed(dContext.get());
288 opsTask.prepare(&flushState);
289 opsTask.execute(&flushState);
290 opsTask.endFlush(drawingMgr);
291 opsTask.disown(drawingMgr);
292#if 0 // Useful to repeat a random configuration that fails the test while debugger attached.
293 if (!std::equal(result, result + result_width(), validResult)) {
294 repeat = true;
295 }
296#endif
297 (void)repeat;
298 REPORTER_ASSERT(reporter, std::equal(result, result + result_width(), validResult));
299 }
300 }
301 }
302}
reporter
Definition: FontMgrTest.cpp:39
#define DEFINE_OP_CLASS_ID
Definition: GrOp.h:64
GrLoadOp
Definition: GrTypesPriv.h:155
GrSurfaceOrigin
Definition: GrTypes.h:147
@ kTopLeft_GrSurfaceOrigin
Definition: GrTypes.h:148
GrXferBarrierFlags
SkPoint pos
static bool equal(const SkBitmap &a, const SkBitmap &b)
Definition: ImageTest.cpp:1395
DEF_GANESH_TEST(OpChainTest, reporter,, CtsEnforcement::kApiLevel_T)
#define SkASSERT(cond)
Definition: SkAssert.h:116
static unsigned repeat(SkFixed fx, int max)
#define INHERITED(method,...)
Definition: SkRecorder.cpp:128
void swap(sk_sp< T > &a, sk_sp< T > &b)
Definition: SkRefCnt.h:341
LoopControlFlowInfo fResult
#define REPORTER_ASSERT(r, cond,...)
Definition: Test.h:286
const GrCaps * caps() const
Definition: GrCaps.h:57
virtual skgpu::Swizzle getWriteSwizzle(const GrBackendFormat &, GrColorType) const =0
GrBackendFormat getDefaultBackendFormat(GrColorType, GrRenderable) const
Definition: GrCaps.cpp:400
GrResourceProvider * resourceProvider()
static sk_sp< GrDirectContext > MakeMock(const GrMockOptions *, const GrContextOptions &)
GrDirectContextPriv priv()
Definition: GrOp.h:70
std::unique_ptr< GrOp > Owner
Definition: GrOp.h:72
const T & cast() const
Definition: GrOp.h:148
sk_sp< GrTextureProxy > createProxy(const GrBackendFormat &, SkISize dimensions, GrRenderable, int renderTargetSampleCnt, skgpu::Mipmapped, SkBackingFit, skgpu::Budgeted, GrProtected, std::string_view label, GrInternalSurfaceFlags=GrInternalSurfaceFlags::kNone, UseAllocator useAllocator=UseAllocator::kYes)
GrDrawingManager * drawingManager()
GrProxyProvider * proxyProvider()
void prepare(GrOpFlushState *flushState)
bool execute(GrOpFlushState *flushState)
Definition: GrRenderTask.h:42
virtual void disown(GrDrawingManager *)
SkScalar nextUScalar1()
Definition: SkRandom.h:101
uint32_t nextULessThan(uint32_t count)
Definition: SkRandom.h:93
T * get() const
Definition: SkRefCnt.h:303
void endFlush(GrDrawingManager *) override
Definition: OpsTask.cpp:460
void addOp(GrDrawingManager *, GrOp::Owner, GrTextureResolveManager, const GrCaps &)
Definition: OpsTask.cpp:417
float SkScalar
Definition: extension.cpp:12
static bool b
struct MyStruct a[10]
uint8_t value
GAsyncResult * result
uint32_t uint32_t * format
SK_API sk_sp< SkDocument > Make(SkWStream *dst, const SkSerialProcs *=nullptr, std::function< void(const SkPicture *)> onEndPage=nullptr)
DEF_SWITCHES_START aot vmservice shared library name
Definition: switches.h:32
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core running concurrent GC tasks on threads can cause them to contend with the UI thread which could potentially lead to jank This option turns off all concurrent GC activities domain network JSON encoded network policy per domain This overrides the DisallowInsecureConnections switch Embedder can specify whether to allow or disallow insecure connections at a domain level old gen heap size
Definition: switches.h:259
Definition: SkSize.h:16
static constexpr SkRect MakeXYWH(float x, float y, float w, float h)
Definition: SkRect.h:659