1 /*
2 * Copyright 2017 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 "tests/Test.h"
9
10 #include <array>
11 #include <vector>
12 #include "include/core/SkBitmap.h"
13 #include "include/gpu/GrContext.h"
14 #include "include/private/GrResourceKey.h"
15 #include "src/gpu/GrCaps.h"
16 #include "src/gpu/GrContextPriv.h"
17 #include "src/gpu/GrGeometryProcessor.h"
18 #include "src/gpu/GrImageInfo.h"
19 #include "src/gpu/GrMemoryPool.h"
20 #include "src/gpu/GrOpFlushState.h"
21 #include "src/gpu/GrOpsRenderPass.h"
22 #include "src/gpu/GrProgramInfo.h"
23 #include "src/gpu/GrRenderTargetContext.h"
24 #include "src/gpu/GrRenderTargetContextPriv.h"
25 #include "src/gpu/GrResourceProvider.h"
26 #include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
27 #include "src/gpu/glsl/GrGLSLGeometryProcessor.h"
28 #include "src/gpu/glsl/GrGLSLVarying.h"
29 #include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
30 #include "src/gpu/ops/GrSimpleMeshDrawOpHelper.h"
31
32 GR_DECLARE_STATIC_UNIQUE_KEY(gIndexBufferKey);
33
34 static constexpr int kBoxSize = 2;
35 static constexpr int kBoxCountY = 8;
36 static constexpr int kBoxCountX = 8;
37 static constexpr int kBoxCount = kBoxCountY * kBoxCountX;
38
39 static constexpr int kImageWidth = kBoxCountY * kBoxSize;
40 static constexpr int kImageHeight = kBoxCountX * kBoxSize;
41
42 static constexpr int kIndexPatternRepeatCount = 3;
43 constexpr uint16_t kIndexPattern[6] = {0, 1, 2, 1, 2, 3};
44
45
46 class DrawMeshHelper {
47 public:
DrawMeshHelper(GrOpFlushState * state)48 DrawMeshHelper(GrOpFlushState* state) : fState(state) {}
49
50 sk_sp<const GrBuffer> getIndexBuffer();
51
makeVertexBuffer(const SkTArray<T> & data)52 template<typename T> sk_sp<const GrBuffer> makeVertexBuffer(const SkTArray<T>& data) {
53 return this->makeVertexBuffer(data.begin(), data.count());
54 }
makeVertexBuffer(const std::vector<T> & data)55 template<typename T> sk_sp<const GrBuffer> makeVertexBuffer(const std::vector<T>& data) {
56 return this->makeVertexBuffer(data.data(), data.size());
57 }
58 template<typename T> sk_sp<const GrBuffer> makeVertexBuffer(const T* data, int count);
59
60 sk_sp<const GrBuffer> fVertBuffer;
61 sk_sp<const GrBuffer> fVertBuffer2;
62 sk_sp<const GrBuffer> fIndexBuffer;
63 sk_sp<const GrBuffer> fInstBuffer;
64
65 void drawMesh(const GrMesh& mesh, GrPrimitiveType);
66
67 private:
68 GrOpFlushState* fState;
69 };
70
71 struct Box {
72 float fX, fY;
73 GrColor fColor;
74 };
75
76 ////////////////////////////////////////////////////////////////////////////////////////////////////
77
78 /**
79 * This is a GPU-backend specific test. It tries to test all possible usecases of GrMesh. The test
80 * works by drawing checkerboards of colored boxes, reading back the pixels, and comparing with
81 * expected results. The boxes are drawn on integer boundaries and the (opaque) colors are chosen
82 * from the set (r,g,b) = (0,255)^3, so the GPU renderings ought to produce exact matches.
83 */
84
85 static void run_test(GrContext* context, const char* testName, skiatest::Reporter*,
86 const std::unique_ptr<GrRenderTargetContext>&, const SkBitmap& gold,
87 std::function<void(DrawMeshHelper*)> prepareFn,
88 std::function<void(DrawMeshHelper*)> executeFn);
89
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrMeshTest,reporter,ctxInfo)90 DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrMeshTest, reporter, ctxInfo) {
91 GrContext* context = ctxInfo.grContext();
92
93 auto rtc = GrRenderTargetContext::Make(
94 context, GrColorType::kRGBA_8888, nullptr, SkBackingFit::kExact,
95 {kImageWidth, kImageHeight});
96 if (!rtc) {
97 ERRORF(reporter, "could not create render target context.");
98 return;
99 }
100
101 SkTArray<Box> boxes;
102 SkTArray<std::array<Box, 4>> vertexData;
103 SkBitmap gold;
104
105 // ---- setup ----------
106
107 SkPaint paint;
108 paint.setBlendMode(SkBlendMode::kSrc);
109 gold.allocN32Pixels(kImageWidth, kImageHeight);
110
111 SkCanvas goldCanvas(gold);
112
113 for (int y = 0; y < kBoxCountY; ++y) {
114 for (int x = 0; x < kBoxCountX; ++x) {
115 int c = y + x;
116 int rgb[3] = {-(c & 1) & 0xff, -((c >> 1) & 1) & 0xff, -((c >> 2) & 1) & 0xff};
117
118 const Box box = boxes.push_back() = {
119 float(x * kBoxSize),
120 float(y * kBoxSize),
121 GrColorPackRGBA(rgb[0], rgb[1], rgb[2], 255)
122 };
123
124 std::array<Box, 4>& boxVertices = vertexData.push_back();
125 for (int i = 0; i < 4; ++i) {
126 boxVertices[i] = {
127 box.fX + (i / 2) * kBoxSize,
128 box.fY + (i % 2) * kBoxSize,
129 box.fColor
130 };
131 }
132
133 paint.setARGB(255, rgb[0], rgb[1], rgb[2]);
134 goldCanvas.drawRect(SkRect::MakeXYWH(box.fX, box.fY, kBoxSize, kBoxSize), paint);
135 }
136 }
137
138 // ---- tests ----------
139
140 #define VALIDATE(buff) \
141 do { \
142 if (!buff) { \
143 ERRORF(reporter, #buff " is null."); \
144 return; \
145 } \
146 } while (0)
147
148 run_test(context, "setNonIndexedNonInstanced", reporter, rtc, gold,
149 [&](DrawMeshHelper* helper) {
150 SkTArray<Box> expandedVertexData;
151 for (int i = 0; i < kBoxCount; ++i) {
152 for (int j = 0; j < 6; ++j) {
153 expandedVertexData.push_back(vertexData[i][kIndexPattern[j]]);
154 }
155 }
156
157 // Draw boxes one line at a time to exercise base vertex.
158 helper->fVertBuffer = helper->makeVertexBuffer(expandedVertexData);
159 VALIDATE(helper->fVertBuffer);
160 },
161 [&](DrawMeshHelper* helper) {
162 for (int y = 0; y < kBoxCountY; ++y) {
163 GrMesh mesh;
164 mesh.setNonIndexedNonInstanced(kBoxCountX * 6);
165 mesh.setVertexData(helper->fVertBuffer, y * kBoxCountX * 6);
166 helper->drawMesh(mesh, GrPrimitiveType::kTriangles);
167 }
168 });
169
170 run_test(context, "setIndexed", reporter, rtc, gold,
171 [&](DrawMeshHelper* helper) {
172 helper->fIndexBuffer = helper->getIndexBuffer();
173 VALIDATE(helper->fIndexBuffer);
174 helper->fVertBuffer = helper->makeVertexBuffer(vertexData);
175 VALIDATE(helper->fVertBuffer);
176 },
177 [&](DrawMeshHelper* helper) {
178 int baseRepetition = 0;
179 int i = 0;
180 // Start at various repetitions within the patterned index buffer to exercise base
181 // index.
182 while (i < kBoxCount) {
183 static_assert(kIndexPatternRepeatCount >= 3);
184 int repetitionCount = std::min(3 - baseRepetition, kBoxCount - i);
185
186 GrMesh mesh;
187 mesh.setIndexed(helper->fIndexBuffer, repetitionCount * 6, baseRepetition * 6,
188 baseRepetition * 4, (baseRepetition + repetitionCount) * 4 - 1,
189 GrPrimitiveRestart::kNo);
190 mesh.setVertexData(helper->fVertBuffer, (i - baseRepetition) * 4);
191 helper->drawMesh(mesh, GrPrimitiveType::kTriangles);
192
193 baseRepetition = (baseRepetition + 1) % 3;
194 i += repetitionCount;
195 }
196 });
197
198 run_test(context, "setIndexedPatterned", reporter, rtc, gold,
199 [&](DrawMeshHelper* helper) {
200 helper->fIndexBuffer = helper->getIndexBuffer();
201 VALIDATE(helper->fIndexBuffer);
202 helper->fVertBuffer = helper->makeVertexBuffer(vertexData);
203 VALIDATE(helper->fVertBuffer);
204 },
205 [&](DrawMeshHelper* helper) {
206 // Draw boxes one line at a time to exercise base vertex. setIndexedPatterned does
207 // not support a base index.
208 for (int y = 0; y < kBoxCountY; ++y) {
209 GrMesh mesh;
210 mesh.setIndexedPatterned(helper->fIndexBuffer, 6, 4, kBoxCountX,
211 kIndexPatternRepeatCount);
212 mesh.setVertexData(helper->fVertBuffer, y * kBoxCountX * 4);
213 helper->drawMesh(mesh, GrPrimitiveType::kTriangles);
214 }
215 });
216
217 for (bool indexed : {false, true}) {
218 if (!context->priv().caps()->instanceAttribSupport()) {
219 break;
220 }
221
222 run_test(context, indexed ? "setIndexedInstanced" : "setInstanced",
223 reporter, rtc, gold,
224 [&](DrawMeshHelper* helper) {
225 helper->fIndexBuffer = indexed ? helper->getIndexBuffer() : nullptr;
226 helper->fInstBuffer = helper->makeVertexBuffer(boxes);
227 VALIDATE(helper->fInstBuffer);
228 helper->fVertBuffer =
229 helper->makeVertexBuffer(std::vector<float>{0,0, 0,1, 1,0, 1,1});
230 VALIDATE(helper->fVertBuffer);
231 helper->fVertBuffer2 = helper->makeVertexBuffer( // for testing base vertex.
232 std::vector<float>{-1,-1, -1,-1, 0,0, 0,1, 1,0, 1,1});
233 VALIDATE(helper->fVertBuffer2);
234 },
235 [&](DrawMeshHelper* helper) {
236 // Draw boxes one line at a time to exercise base instance, base vertex, and
237 // null vertex buffer. setIndexedInstanced intentionally does not support a
238 // base index.
239 for (int y = 0; y < kBoxCountY; ++y) {
240
241 GrPrimitiveType primitiveType = indexed ? GrPrimitiveType::kTriangles
242 : GrPrimitiveType::kTriangleStrip;
243 GrMesh mesh;
244 if (indexed) {
245 VALIDATE(helper->fIndexBuffer);
246 mesh.setIndexedInstanced(helper->fIndexBuffer, 6, helper->fInstBuffer,
247 kBoxCountX, y * kBoxCountX,
248 GrPrimitiveRestart::kNo);
249 } else {
250 mesh.setInstanced(helper->fInstBuffer, kBoxCountX, y * kBoxCountX, 4);
251 }
252 switch (y % 3) {
253 case 0:
254 if (context->priv().caps()->shaderCaps()->vertexIDSupport()) {
255 if (y % 2) {
256 // We don't need this call because it's the initial state
257 // of GrMesh.
258 mesh.setVertexData(nullptr);
259 }
260 break;
261 }
262 // Fallthru.
263 case 1:
264 mesh.setVertexData(helper->fVertBuffer);
265 break;
266 case 2:
267 mesh.setVertexData(helper->fVertBuffer2, 2);
268 break;
269 }
270 helper->drawMesh(mesh, primitiveType);
271 }
272 });
273 }
274 }
275
276 ////////////////////////////////////////////////////////////////////////////////////////////////////
277
278 class GrMeshTestOp : public GrDrawOp {
279 public:
280 DEFINE_OP_CLASS_ID
281
Make(GrContext * context,std::function<void (DrawMeshHelper *)> prepareFn,std::function<void (DrawMeshHelper *)> executeFn)282 static std::unique_ptr<GrDrawOp> Make(GrContext* context,
283 std::function<void(DrawMeshHelper*)> prepareFn,
284 std::function<void(DrawMeshHelper*)> executeFn) {
285 GrOpMemoryPool* pool = context->priv().opMemoryPool();
286
287 return pool->allocate<GrMeshTestOp>(prepareFn, executeFn);
288 }
289
290 private:
291 friend class GrOpMemoryPool; // for ctor
292
GrMeshTestOp(std::function<void (DrawMeshHelper *)> prepareFn,std::function<void (DrawMeshHelper *)> executeFn)293 GrMeshTestOp(std::function<void(DrawMeshHelper*)> prepareFn,
294 std::function<void(DrawMeshHelper*)> executeFn)
295 : INHERITED(ClassID())
296 , fPrepareFn(prepareFn)
297 , fExecuteFn(executeFn){
298 this->setBounds(SkRect::MakeIWH(kImageWidth, kImageHeight),
299 HasAABloat::kNo, IsHairline::kNo);
300 }
301
name() const302 const char* name() const override { return "GrMeshTestOp"; }
fixedFunctionFlags() const303 FixedFunctionFlags fixedFunctionFlags() const override { return FixedFunctionFlags::kNone; }
finalize(const GrCaps &,const GrAppliedClip *,bool hasMixedSampledCoverage,GrClampType)304 GrProcessorSet::Analysis finalize(const GrCaps&, const GrAppliedClip*,
305 bool hasMixedSampledCoverage, GrClampType) override {
306 return GrProcessorSet::EmptySetAnalysis();
307 }
onPrepare(GrOpFlushState * state)308 void onPrepare(GrOpFlushState* state) override {
309 fHelper.reset(new DrawMeshHelper(state));
310 fPrepareFn(fHelper.get());
311 }
onExecute(GrOpFlushState * state,const SkRect & chainBounds)312 void onExecute(GrOpFlushState* state, const SkRect& chainBounds) override {
313 fExecuteFn(fHelper.get());
314 }
315
316 std::unique_ptr<DrawMeshHelper> fHelper;
317 std::function<void(DrawMeshHelper*)> fPrepareFn;
318 std::function<void(DrawMeshHelper*)> fExecuteFn;
319
320 typedef GrDrawOp INHERITED;
321 };
322
323 class GrMeshTestProcessor : public GrGeometryProcessor {
324 public:
Make(SkArenaAlloc * arena,bool instanced,bool hasVertexBuffer)325 static GrGeometryProcessor* Make(SkArenaAlloc* arena, bool instanced, bool hasVertexBuffer) {
326 return arena->make<GrMeshTestProcessor>(instanced, hasVertexBuffer);
327 }
328
name() const329 const char* name() const override { return "GrMeshTestProcessor"; }
330
inColor() const331 const Attribute& inColor() const {
332 return fVertexColor.isInitialized() ? fVertexColor : fInstanceColor;
333 }
334
getGLSLProcessorKey(const GrShaderCaps &,GrProcessorKeyBuilder * b) const335 void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const final {
336 b->add32(fInstanceLocation.isInitialized());
337 b->add32(fVertexPosition.isInitialized());
338 }
339
340 GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const final;
341
342 private:
343 friend class GLSLMeshTestProcessor;
344 friend class ::SkArenaAlloc; // for access to ctor
345
GrMeshTestProcessor(bool instanced,bool hasVertexBuffer)346 GrMeshTestProcessor(bool instanced, bool hasVertexBuffer)
347 : INHERITED(kGrMeshTestProcessor_ClassID) {
348 if (instanced) {
349 fInstanceLocation = {"location", kFloat2_GrVertexAttribType, kHalf2_GrSLType};
350 fInstanceColor = {"color", kUByte4_norm_GrVertexAttribType, kHalf4_GrSLType};
351 this->setInstanceAttributes(&fInstanceLocation, 2);
352 if (hasVertexBuffer) {
353 fVertexPosition = {"vertex", kFloat2_GrVertexAttribType, kHalf2_GrSLType};
354 this->setVertexAttributes(&fVertexPosition, 1);
355 }
356 } else {
357 fVertexPosition = {"vertex", kFloat2_GrVertexAttribType, kHalf2_GrSLType};
358 fVertexColor = {"color", kUByte4_norm_GrVertexAttribType, kHalf4_GrSLType};
359 this->setVertexAttributes(&fVertexPosition, 2);
360 }
361 }
362
363 Attribute fVertexPosition;
364 Attribute fVertexColor;
365
366 Attribute fInstanceLocation;
367 Attribute fInstanceColor;
368
369 typedef GrGeometryProcessor INHERITED;
370 };
371
372 class GLSLMeshTestProcessor : public GrGLSLGeometryProcessor {
setData(const GrGLSLProgramDataManager & pdman,const GrPrimitiveProcessor &,const CoordTransformRange & transformIter)373 void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor&,
374 const CoordTransformRange& transformIter) final {}
375
onEmitCode(EmitArgs & args,GrGPArgs * gpArgs)376 void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) final {
377 const GrMeshTestProcessor& mp = args.fGP.cast<GrMeshTestProcessor>();
378
379 GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
380 varyingHandler->emitAttributes(mp);
381 varyingHandler->addPassThroughAttribute(mp.inColor(), args.fOutputColor);
382
383 GrGLSLVertexBuilder* v = args.fVertBuilder;
384 if (!mp.fInstanceLocation.isInitialized()) {
385 v->codeAppendf("float2 vertex = %s;", mp.fVertexPosition.name());
386 } else {
387 if (mp.fVertexPosition.isInitialized()) {
388 v->codeAppendf("float2 offset = %s;", mp.fVertexPosition.name());
389 } else {
390 v->codeAppend ("float2 offset = float2(sk_VertexID / 2, sk_VertexID % 2);");
391 }
392 v->codeAppendf("float2 vertex = %s + offset * %i;", mp.fInstanceLocation.name(),
393 kBoxSize);
394 }
395 gpArgs->fPositionVar.set(kFloat2_GrSLType, "vertex");
396
397 GrGLSLFPFragmentBuilder* f = args.fFragBuilder;
398 f->codeAppendf("%s = half4(1);", args.fOutputCoverage);
399 }
400 };
401
createGLSLInstance(const GrShaderCaps &) const402 GrGLSLPrimitiveProcessor* GrMeshTestProcessor::createGLSLInstance(const GrShaderCaps&) const {
403 return new GLSLMeshTestProcessor;
404 }
405
406 ////////////////////////////////////////////////////////////////////////////////////////////////////
407
408 template<typename T>
makeVertexBuffer(const T * data,int count)409 sk_sp<const GrBuffer> DrawMeshHelper::makeVertexBuffer(const T* data, int count) {
410 return sk_sp<const GrBuffer>(fState->resourceProvider()->createBuffer(
411 count * sizeof(T), GrGpuBufferType::kVertex, kDynamic_GrAccessPattern, data));
412 }
413
getIndexBuffer()414 sk_sp<const GrBuffer> DrawMeshHelper::getIndexBuffer() {
415 GR_DEFINE_STATIC_UNIQUE_KEY(gIndexBufferKey);
416 return fState->resourceProvider()->findOrCreatePatternedIndexBuffer(
417 kIndexPattern, 6, kIndexPatternRepeatCount, 4, gIndexBufferKey);
418 }
419
drawMesh(const GrMesh & mesh,GrPrimitiveType primitiveType)420 void DrawMeshHelper::drawMesh(const GrMesh& mesh, GrPrimitiveType primitiveType) {
421 GrProcessorSet processorSet(SkBlendMode::kSrc);
422
423 // TODO: add a GrProcessorSet testing helper to make this easier
424 SkPMColor4f overrideColor;
425 processorSet.finalize(GrProcessorAnalysisColor(),
426 GrProcessorAnalysisCoverage::kNone,
427 fState->appliedClip(),
428 nullptr,
429 false,
430 fState->caps(),
431 GrClampType::kAuto,
432 &overrideColor);
433
434 auto pipeline = GrSimpleMeshDrawOpHelper::CreatePipeline(fState,
435 std::move(processorSet),
436 GrPipeline::InputFlags::kNone);
437
438 GrGeometryProcessor* mtp = GrMeshTestProcessor::Make(
439 fState->allocator(), mesh.isInstanced(), SkToBool(mesh.vertexBuffer()));
440
441 GrProgramInfo programInfo(fState->proxy()->numSamples(),
442 fState->proxy()->numStencilSamples(),
443 fState->proxy()->backendFormat(),
444 fState->view()->origin(),
445 pipeline,
446 mtp,
447 nullptr, nullptr, 0, primitiveType);
448
449 fState->opsRenderPass()->bindPipeline(programInfo, SkRect::MakeIWH(kImageWidth, kImageHeight));
450 fState->opsRenderPass()->drawMeshes(programInfo, &mesh, 1);
451 }
452
run_test(GrContext * context,const char * testName,skiatest::Reporter * reporter,const std::unique_ptr<GrRenderTargetContext> & rtc,const SkBitmap & gold,std::function<void (DrawMeshHelper *)> prepareFn,std::function<void (DrawMeshHelper *)> executeFn)453 static void run_test(GrContext* context, const char* testName, skiatest::Reporter* reporter,
454 const std::unique_ptr<GrRenderTargetContext>& rtc, const SkBitmap& gold,
455 std::function<void(DrawMeshHelper*)> prepareFn,
456 std::function<void(DrawMeshHelper*)> executeFn) {
457 const int w = gold.width(), h = gold.height(), rowBytes = gold.rowBytes();
458 const uint32_t* goldPx = reinterpret_cast<const uint32_t*>(gold.getPixels());
459 if (h != rtc->height() || w != rtc->width()) {
460 ERRORF(reporter, "[%s] expectation and rtc not compatible (?).", testName);
461 return;
462 }
463 if (sizeof(uint32_t) * kImageWidth != gold.rowBytes()) {
464 ERRORF(reporter, "unexpected row bytes in gold image.", testName);
465 return;
466 }
467
468 SkAutoSTMalloc<kImageHeight * kImageWidth, uint32_t> resultPx(h * rowBytes);
469 rtc->clear(nullptr, SkPMColor4f::FromBytes_RGBA(0xbaaaaaad),
470 GrRenderTargetContext::CanClearFullscreen::kYes);
471 rtc->priv().testingOnly_addDrawOp(GrMeshTestOp::Make(context, prepareFn, executeFn));
472 rtc->readPixels(gold.info(), resultPx, rowBytes, {0, 0});
473 for (int y = 0; y < h; ++y) {
474 for (int x = 0; x < w; ++x) {
475 uint32_t expected = goldPx[y * kImageWidth + x];
476 uint32_t actual = resultPx[y * kImageWidth + x];
477 if (expected != actual) {
478 ERRORF(reporter, "[%s] pixel (%i,%i): got 0x%x expected 0x%x",
479 testName, x, y, actual, expected);
480 return;
481 }
482 }
483 }
484 }
485