Home | History | Annotate | Download | only in tests
      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 "SkTypes.h"
      9 #include "Test.h"
     10 
     11 #if SK_SUPPORT_GPU
     12 
     13 #include "GrContext.h"
     14 #include "GrGeometryProcessor.h"
     15 #include "GrGpuCommandBuffer.h"
     16 #include "GrOpFlushState.h"
     17 #include "GrRenderTargetContext.h"
     18 #include "GrRenderTargetContextPriv.h"
     19 #include "GrResourceProvider.h"
     20 #include "GrResourceKey.h"
     21 #include "SkMakeUnique.h"
     22 #include "glsl/GrGLSLVertexShaderBuilder.h"
     23 #include "glsl/GrGLSLFragmentShaderBuilder.h"
     24 #include "glsl/GrGLSLGeometryProcessor.h"
     25 #include "glsl/GrGLSLVarying.h"
     26 #include <array>
     27 #include <vector>
     28 
     29 
     30 GR_DECLARE_STATIC_UNIQUE_KEY(gIndexBufferKey);
     31 
     32 static constexpr int kBoxSize = 2;
     33 static constexpr int kBoxCountY = 8;
     34 static constexpr int kBoxCountX = 8;
     35 static constexpr int kBoxCount = kBoxCountY * kBoxCountX;
     36 
     37 static constexpr int kImageWidth = kBoxCountY * kBoxSize;
     38 static constexpr int kImageHeight = kBoxCountX * kBoxSize;
     39 
     40 static constexpr int kIndexPatternRepeatCount = 3;
     41 constexpr uint16_t kIndexPattern[6] = {0, 1, 2, 1, 2, 3};
     42 
     43 
     44 class DrawMeshHelper {
     45 public:
     46     DrawMeshHelper(GrOpFlushState* state) : fState(state) {}
     47 
     48     sk_sp<const GrBuffer> getIndexBuffer();
     49 
     50     template<typename T> sk_sp<const GrBuffer> makeVertexBuffer(const SkTArray<T>& data) {
     51         return this->makeVertexBuffer(data.begin(), data.count());
     52     }
     53     template<typename T> sk_sp<const GrBuffer> makeVertexBuffer(const std::vector<T>& data) {
     54         return this->makeVertexBuffer(data.data(), data.size());
     55     }
     56     template<typename T> sk_sp<const GrBuffer> makeVertexBuffer(const T* data, int count);
     57 
     58     void drawMesh(const GrMesh& mesh);
     59 
     60 private:
     61     GrOpFlushState* fState;
     62 };
     63 
     64 struct Box {
     65     float fX, fY;
     66     GrColor fColor;
     67 };
     68 
     69 ////////////////////////////////////////////////////////////////////////////////////////////////////
     70 
     71 /**
     72  * This is a GPU-backend specific test. It tries to test all possible usecases of GrMesh. The test
     73  * works by drawing checkerboards of colored boxes, reading back the pixels, and comparing with
     74  * expected results. The boxes are drawn on integer boundaries and the (opaque) colors are chosen
     75  * from the set (r,g,b) = (0,255)^3, so the GPU renderings ought to produce exact matches.
     76  */
     77 
     78 static void run_test(const char* testName, skiatest::Reporter*, const sk_sp<GrRenderTargetContext>&,
     79                      const SkBitmap& gold, std::function<void(DrawMeshHelper*)> testFn);
     80 
     81 DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrMeshTest, reporter, ctxInfo) {
     82     GrContext* const context = ctxInfo.grContext();
     83 
     84     sk_sp<GrRenderTargetContext> rtc(
     85         context->makeDeferredRenderTargetContext(SkBackingFit::kExact, kImageWidth, kImageHeight,
     86                                                  kRGBA_8888_GrPixelConfig, nullptr));
     87     if (!rtc) {
     88         ERRORF(reporter, "could not create render target context.");
     89         return;
     90     }
     91 
     92     SkTArray<Box> boxes;
     93     SkTArray<std::array<Box, 4>> vertexData;
     94     SkBitmap gold;
     95 
     96     // ---- setup ----------
     97 
     98     SkPaint paint;
     99     paint.setBlendMode(SkBlendMode::kSrc);
    100     gold.allocN32Pixels(kImageWidth, kImageHeight);
    101 
    102     SkCanvas goldCanvas(gold);
    103 
    104     for (int y = 0; y < kBoxCountY; ++y) {
    105         for (int x = 0; x < kBoxCountX; ++x) {
    106             int c = y + x;
    107             int rgb[3] = {-(c & 1) & 0xff, -((c >> 1) & 1) & 0xff, -((c >> 2) & 1) & 0xff};
    108 
    109             const Box box = boxes.push_back() = {
    110                 float(x * kBoxSize),
    111                 float(y * kBoxSize),
    112                 GrColorPackRGBA(rgb[0], rgb[1], rgb[2], 255)
    113             };
    114 
    115             std::array<Box, 4>& boxVertices = vertexData.push_back();
    116             for (int i = 0; i < 4; ++i) {
    117                 boxVertices[i] = {
    118                     box.fX + (i/2) * kBoxSize,
    119                     box.fY + (i%2) * kBoxSize,
    120                     box.fColor
    121                 };
    122             }
    123 
    124             paint.setARGB(255, rgb[0], rgb[1], rgb[2]);
    125             goldCanvas.drawRect(SkRect::MakeXYWH(box.fX, box.fY, kBoxSize, kBoxSize), paint);
    126         }
    127     }
    128 
    129     goldCanvas.flush();
    130 
    131     // ---- tests ----------
    132 
    133 #define VALIDATE(buff) \
    134     if (!buff) { \
    135         ERRORF(reporter, #buff " is null."); \
    136         return; \
    137     }
    138 
    139     run_test("setNonIndexedNonInstanced", reporter, rtc, gold, [&](DrawMeshHelper* helper) {
    140         SkTArray<Box> expandedVertexData;
    141         for (int i = 0; i < kBoxCount; ++i) {
    142             for (int j = 0; j < 6; ++j) {
    143                 expandedVertexData.push_back(vertexData[i][kIndexPattern[j]]);
    144             }
    145         }
    146 
    147         // Draw boxes one line at a time to exercise base vertex.
    148         auto vbuff = helper->makeVertexBuffer(expandedVertexData);
    149         VALIDATE(vbuff);
    150         for (int y = 0; y < kBoxCountY; ++y) {
    151             GrMesh mesh(GrPrimitiveType::kTriangles);
    152             mesh.setNonIndexedNonInstanced(kBoxCountX * 6);
    153             mesh.setVertexData(vbuff.get(), y * kBoxCountX * 6);
    154             helper->drawMesh(mesh);
    155         }
    156     });
    157 
    158     run_test("setIndexed", reporter, rtc, gold, [&](DrawMeshHelper* helper) {
    159         auto ibuff = helper->getIndexBuffer();
    160         VALIDATE(ibuff);
    161         auto vbuff = helper->makeVertexBuffer(vertexData);
    162         VALIDATE(vbuff);
    163         int baseRepetition = 0;
    164         int i = 0;
    165 
    166         // Start at various repetitions within the patterned index buffer to exercise base index.
    167         while (i < kBoxCount) {
    168             GR_STATIC_ASSERT(kIndexPatternRepeatCount >= 3);
    169             int repetitionCount = SkTMin(3 - baseRepetition, kBoxCount - i);
    170 
    171             GrMesh mesh(GrPrimitiveType::kTriangles);
    172             mesh.setIndexed(ibuff.get(), repetitionCount * 6, baseRepetition * 6,
    173                             baseRepetition * 4, (baseRepetition + repetitionCount) * 4 - 1);
    174             mesh.setVertexData(vbuff.get(), (i - baseRepetition) * 4);
    175             helper->drawMesh(mesh);
    176 
    177             baseRepetition = (baseRepetition + 1) % 3;
    178             i += repetitionCount;
    179         }
    180     });
    181 
    182     run_test("setIndexedPatterned", reporter, rtc, gold, [&](DrawMeshHelper* helper) {
    183         auto ibuff = helper->getIndexBuffer();
    184         VALIDATE(ibuff);
    185         auto vbuff = helper->makeVertexBuffer(vertexData);
    186         VALIDATE(vbuff);
    187 
    188         // Draw boxes one line at a time to exercise base vertex. setIndexedPatterned does not
    189         // support a base index.
    190         for (int y = 0; y < kBoxCountY; ++y) {
    191             GrMesh mesh(GrPrimitiveType::kTriangles);
    192             mesh.setIndexedPatterned(ibuff.get(), 6, 4, kBoxCountX, kIndexPatternRepeatCount);
    193             mesh.setVertexData(vbuff.get(), y * kBoxCountX * 4);
    194             helper->drawMesh(mesh);
    195         }
    196     });
    197 
    198     for (bool indexed : {false, true}) {
    199         if (!context->caps()->instanceAttribSupport()) {
    200             break;
    201         }
    202 
    203         run_test(indexed ? "setIndexedInstanced" : "setInstanced",
    204                  reporter, rtc, gold, [&](DrawMeshHelper* helper) {
    205             auto idxbuff = indexed ? helper->getIndexBuffer() : nullptr;
    206             auto instbuff = helper->makeVertexBuffer(boxes);
    207             VALIDATE(instbuff);
    208             auto vbuff = helper->makeVertexBuffer(std::vector<float>{0,0, 0,1, 1,0, 1,1});
    209             VALIDATE(vbuff);
    210             auto vbuff2 = helper->makeVertexBuffer( // for testing base vertex.
    211                               std::vector<float>{-1,-1, -1,-1, 0,0, 0,1, 1,0, 1,1});
    212             VALIDATE(vbuff2);
    213 
    214             // Draw boxes one line at a time to exercise base instance, base vertex, and null vertex
    215             // buffer. setIndexedInstanced intentionally does not support a base index.
    216             for (int y = 0; y < kBoxCountY; ++y) {
    217                 GrMesh mesh(indexed ? GrPrimitiveType::kTriangles
    218                                     : GrPrimitiveType::kTriangleStrip);
    219                 if (indexed) {
    220                     VALIDATE(idxbuff);
    221                     mesh.setIndexedInstanced(idxbuff.get(), 6,
    222                                              instbuff.get(), kBoxCountX, y * kBoxCountX);
    223                 } else {
    224                     mesh.setInstanced(instbuff.get(), kBoxCountX, y * kBoxCountX, 4);
    225                 }
    226                 switch (y % 3) {
    227                     case 0:
    228                         if (context->caps()->shaderCaps()->vertexIDSupport()) {
    229                             if (y % 2) {
    230                                 // We don't need this call because it's the initial state of GrMesh.
    231                                 mesh.setVertexData(nullptr);
    232                             }
    233                             break;
    234                         }
    235                         // Fallthru.
    236                     case 1:
    237                         mesh.setVertexData(vbuff.get());
    238                         break;
    239                     case 2:
    240                         mesh.setVertexData(vbuff2.get(), 2);
    241                         break;
    242                 }
    243                 helper->drawMesh(mesh);
    244             }
    245         });
    246     }
    247 }
    248 
    249 ////////////////////////////////////////////////////////////////////////////////////////////////////
    250 
    251 class GrMeshTestOp : public GrDrawOp {
    252 public:
    253     DEFINE_OP_CLASS_ID
    254 
    255     GrMeshTestOp(std::function<void(DrawMeshHelper*)> testFn)
    256         : INHERITED(ClassID())
    257         , fTestFn(testFn) {
    258         this->setBounds(SkRect::MakeIWH(kImageWidth, kImageHeight),
    259                         HasAABloat::kNo, IsZeroArea::kNo);
    260     }
    261 
    262 private:
    263     const char* name() const override { return "GrMeshTestOp"; }
    264     FixedFunctionFlags fixedFunctionFlags() const override { return FixedFunctionFlags::kNone; }
    265     RequiresDstTexture finalize(const GrCaps&, const GrAppliedClip*) override {
    266         return RequiresDstTexture::kNo;
    267     }
    268     bool onCombineIfPossible(GrOp* other, const GrCaps& caps) override { return false; }
    269     void onPrepare(GrOpFlushState*) override {}
    270     void onExecute(GrOpFlushState* state) override {
    271         DrawMeshHelper helper(state);
    272         fTestFn(&helper);
    273     }
    274 
    275     std::function<void(DrawMeshHelper*)> fTestFn;
    276 
    277     typedef GrDrawOp INHERITED;
    278 };
    279 
    280 class GrMeshTestProcessor : public GrGeometryProcessor {
    281 public:
    282     GrMeshTestProcessor(bool instanced, bool hasVertexBuffer)
    283         : fInstanceLocation(nullptr)
    284         , fVertex(nullptr)
    285         , fColor(nullptr) {
    286         if (instanced) {
    287             fInstanceLocation = &this->addInstanceAttrib("location", kVec2f_GrVertexAttribType);
    288             if (hasVertexBuffer) {
    289                 fVertex = &this->addVertexAttrib("vertex", kVec2f_GrVertexAttribType);
    290             }
    291             fColor = &this->addInstanceAttrib("color", kVec4ub_GrVertexAttribType);
    292         } else {
    293             fVertex = &this->addVertexAttrib("vertex", kVec2f_GrVertexAttribType);
    294             fColor = &this->addVertexAttrib("color", kVec4ub_GrVertexAttribType);
    295         }
    296         this->initClassID<GrMeshTestProcessor>();
    297     }
    298 
    299     const char* name() const override { return "GrMeshTest Processor"; }
    300 
    301     void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const final {
    302         b->add32(SkToBool(fInstanceLocation));
    303         b->add32(SkToBool(fVertex));
    304     }
    305 
    306     GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const final;
    307 
    308 protected:
    309     const Attribute* fInstanceLocation;
    310     const Attribute* fVertex;
    311     const Attribute* fColor;
    312 
    313     friend class GLSLMeshTestProcessor;
    314     typedef GrGeometryProcessor INHERITED;
    315 };
    316 
    317 class GLSLMeshTestProcessor : public GrGLSLGeometryProcessor {
    318     void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor&,
    319                  FPCoordTransformIter&& transformIter) final {}
    320 
    321     void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) final {
    322         const GrMeshTestProcessor& mp = args.fGP.cast<GrMeshTestProcessor>();
    323 
    324         GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
    325         varyingHandler->emitAttributes(mp);
    326         varyingHandler->addPassThroughAttribute(mp.fColor, args.fOutputColor);
    327 
    328         GrGLSLVertexBuilder* v = args.fVertBuilder;
    329         if (!mp.fInstanceLocation) {
    330             v->codeAppendf("vec2 vertex = %s;", mp.fVertex->fName);
    331         } else {
    332             if (mp.fVertex) {
    333                 v->codeAppendf("vec2 offset = %s;", mp.fVertex->fName);
    334             } else {
    335                 v->codeAppend ("vec2 offset = vec2(sk_VertexID / 2, sk_VertexID % 2);");
    336             }
    337             v->codeAppendf("vec2 vertex = %s + offset * %i;",
    338                            mp.fInstanceLocation->fName, kBoxSize);
    339         }
    340         gpArgs->fPositionVar.set(kVec2f_GrSLType, "vertex");
    341 
    342         GrGLSLPPFragmentBuilder* f = args.fFragBuilder;
    343         f->codeAppendf("%s = vec4(1);", args.fOutputCoverage);
    344     }
    345 };
    346 
    347 GrGLSLPrimitiveProcessor* GrMeshTestProcessor::createGLSLInstance(const GrShaderCaps&) const {
    348     return new GLSLMeshTestProcessor;
    349 }
    350 
    351 ////////////////////////////////////////////////////////////////////////////////////////////////////
    352 
    353 template<typename T>
    354 sk_sp<const GrBuffer> DrawMeshHelper::makeVertexBuffer(const T* data, int count) {
    355     return sk_sp<const GrBuffer>(
    356         fState->resourceProvider()->createBuffer(
    357             count * sizeof(T), kVertex_GrBufferType, kDynamic_GrAccessPattern,
    358             GrResourceProvider::kNoPendingIO_Flag |
    359             GrResourceProvider::kRequireGpuMemory_Flag, data));
    360 }
    361 
    362 sk_sp<const GrBuffer> DrawMeshHelper::getIndexBuffer() {
    363     GR_DEFINE_STATIC_UNIQUE_KEY(gIndexBufferKey);
    364     return sk_sp<const GrBuffer>(
    365         fState->resourceProvider()->findOrCreatePatternedIndexBuffer(
    366             kIndexPattern, 6, kIndexPatternRepeatCount, 4, gIndexBufferKey));
    367 }
    368 
    369 void DrawMeshHelper::drawMesh(const GrMesh& mesh) {
    370     GrRenderTarget* rt = fState->drawOpArgs().fRenderTarget;
    371     GrPipeline pipeline(rt, GrPipeline::ScissorState::kDisabled, SkBlendMode::kSrc);
    372     GrMeshTestProcessor mtp(mesh.isInstanced(), mesh.hasVertexData());
    373     fState->commandBuffer()->draw(pipeline, mtp, &mesh, nullptr, 1,
    374                                   SkRect::MakeIWH(kImageWidth, kImageHeight));
    375 }
    376 
    377 static void run_test(const char* testName, skiatest::Reporter* reporter,
    378                      const sk_sp<GrRenderTargetContext>& rtc, const SkBitmap& gold,
    379                      std::function<void(DrawMeshHelper*)> testFn) {
    380     const int w = gold.width(), h = gold.height(), rowBytes = gold.rowBytes();
    381     const uint32_t* goldPx = reinterpret_cast<const uint32_t*>(gold.getPixels());
    382     if (h != rtc->height() || w != rtc->width()) {
    383         ERRORF(reporter, "[%s] expectation and rtc not compatible (?).", testName);
    384         return;
    385     }
    386     if (sizeof(uint32_t) * kImageWidth != gold.rowBytes()) {
    387         ERRORF(reporter, "unexpected row bytes in gold image.", testName);
    388         return;
    389     }
    390 
    391     SkAutoSTMalloc<kImageHeight * kImageWidth, uint32_t> resultPx(h * rowBytes);
    392     rtc->clear(nullptr, 0xbaaaaaad, true);
    393     rtc->priv().testingOnly_addDrawOp(skstd::make_unique<GrMeshTestOp>(testFn));
    394     rtc->readPixels(gold.info(), resultPx, rowBytes, 0, 0, 0);
    395     for (int y = 0; y < h; ++y) {
    396         for (int x = 0; x < w; ++x) {
    397             uint32_t expected = goldPx[y * kImageWidth + x];
    398             uint32_t actual = resultPx[y * kImageWidth + x];
    399             if (expected != actual) {
    400                 ERRORF(reporter, "[%s] pixel (%i,%i): got 0x%x expected 0x%x",
    401                        testName, x, y, actual, expected);
    402                 return;
    403             }
    404         }
    405     }
    406 }
    407 
    408 #endif
    409