• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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