1 /* 2 * Copyright 2021 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 #include "bench/Benchmark.h" 8 #include "experimental/graphite/src/geom/IntersectionTree.h" 9 #include "include/core/SkPaint.h" 10 #include "include/core/SkPath.h" 11 #include "include/utils/SkRandom.h" 12 #include "src/core/SkMathPriv.h" 13 #include "tools/ToolUtils.h" 14 #include "tools/flags/CommandLineFlags.h" 15 16 static DEFINE_string(intersectionTreeFile, "", 17 "svg or skp for the IntersectionTree bench to sniff paths from."); 18 19 namespace skgpu { 20 21 class IntersectionTreeBench : public Benchmark { 22 protected: onGetName()23 const char* onGetName() final { return fName.c_str(); } 24 isSuitableFor(Backend backend)25 bool isSuitableFor(Backend backend) override { 26 return backend == kNonRendering_Backend; 27 } 28 onDelayedSetup()29 void onDelayedSetup() final { 30 SkTArray<SkRect> rects; 31 this->gatherRects(&rects); 32 fRectCount = rects.count(); 33 fRects = fAlignedAllocator.makeArray<Rect>(fRectCount); 34 for (int i = 0; i < fRectCount; ++i) { 35 fRects[i] = rects[i]; 36 } 37 fRectBufferA = fAlignedAllocator.makeArray<Rect>(fRectCount); 38 fRectBufferB = fAlignedAllocator.makeArray<Rect>(fRectCount); 39 } 40 41 virtual void gatherRects(SkTArray<SkRect>* rects) = 0; 42 onDraw(int loops,SkCanvas *)43 void onDraw(int loops, SkCanvas*) final { 44 for (int i = 0; i < loops; ++i) { 45 this->doBench(); 46 } 47 } 48 doBench()49 void doBench() { 50 Rect* rects = fRects; 51 Rect* collided = fRectBufferA; 52 int rectCount = fRectCount; 53 fNumTrees = 0; 54 while (rectCount > 0) { 55 IntersectionTree intersectionTree; 56 int collidedCount = 0; 57 for (int i = 0; i < rectCount; ++i) { 58 if (!intersectionTree.add(rects[i])) { 59 collided[collidedCount++] = rects[i]; 60 } 61 } 62 std::swap(rects, collided); 63 if (collided == fRects) { 64 collided = fRectBufferB; 65 } 66 rectCount = collidedCount; 67 ++fNumTrees; 68 } 69 } 70 71 SkString fName; 72 SkArenaAlloc fAlignedAllocator{0}; 73 int fRectCount; 74 Rect* fRects; 75 Rect* fRectBufferA; 76 Rect* fRectBufferB; 77 int fNumTrees = 0; 78 }; 79 80 class RandomIntersectionBench : public IntersectionTreeBench { 81 public: RandomIntersectionBench(int numRandomRects)82 RandomIntersectionBench(int numRandomRects) : fNumRandomRects(numRandomRects) { 83 fName.printf("IntersectionTree_%i", numRandomRects); 84 } 85 86 private: gatherRects(SkTArray<SkRect> * rects)87 void gatherRects(SkTArray<SkRect>* rects) override { 88 SkRandom rand; 89 for (int i = 0; i < fNumRandomRects; ++i) { 90 rects->push_back(SkRect::MakeXYWH(rand.nextRangeF(0, 2000), 91 rand.nextRangeF(0, 2000), 92 rand.nextRangeF(0, 70), 93 rand.nextRangeF(0, 70))); 94 } 95 } 96 97 const int fNumRandomRects; 98 }; 99 100 class FileIntersectionBench : public IntersectionTreeBench { 101 public: FileIntersectionBench()102 FileIntersectionBench() { 103 if (FLAGS_intersectionTreeFile.isEmpty()) { 104 return; 105 } 106 const char* filename = strrchr(FLAGS_intersectionTreeFile[0], '/'); 107 if (filename) { 108 ++filename; 109 } else { 110 filename = FLAGS_intersectionTreeFile[0]; 111 } 112 fName.printf("IntersectionTree_file_%s", filename); 113 } 114 115 private: isSuitableFor(Backend backend)116 bool isSuitableFor(Backend backend) final { 117 if (FLAGS_intersectionTreeFile.isEmpty()) { 118 return false; 119 } 120 return IntersectionTreeBench::isSuitableFor(backend); 121 } 122 gatherRects(SkTArray<SkRect> * rects)123 void gatherRects(SkTArray<SkRect>* rects) override { 124 if (FLAGS_intersectionTreeFile.isEmpty()) { 125 return; 126 } 127 ToolUtils::sniff_paths(FLAGS_intersectionTreeFile[0], [&](const SkMatrix& matrix, 128 const SkPath& path, 129 const SkPaint& paint) { 130 if (paint.getStyle() == SkPaint::kStroke_Style) { 131 return; // Goes to stroker. 132 } 133 if (path.isConvex()) { 134 return; // Goes to convex renderer. 135 } 136 int numVerbs = path.countVerbs(); 137 SkRect drawBounds = matrix.mapRect(path.getBounds()); 138 float gpuFragmentWork = drawBounds.height() * drawBounds.width(); 139 float cpuTessellationWork = numVerbs * SkNextLog2(numVerbs); // N log N. 140 constexpr static float kCpuWeight = 512; 141 constexpr static float kMinNumPixelsToTriangulate = 256 * 256; 142 if (cpuTessellationWork * kCpuWeight + kMinNumPixelsToTriangulate < gpuFragmentWork) { 143 return; // Goes to inner triangulator. 144 } 145 rects->push_back(drawBounds); 146 }); 147 SkDebugf(">> Found %i stencil/cover paths in %s <<\n", 148 rects->count(), FLAGS_intersectionTreeFile[0]); 149 } 150 onPerCanvasPostDraw(SkCanvas *)151 void onPerCanvasPostDraw(SkCanvas*) override { 152 if (FLAGS_intersectionTreeFile.isEmpty()) { 153 return; 154 } 155 SkDebugf(">> Reordered %s into %i different stencil/cover draws <<\n", 156 FLAGS_intersectionTreeFile[0], fNumTrees); 157 } 158 }; 159 160 } // namespace skgpu 161 162 DEF_BENCH( return new skgpu::RandomIntersectionBench(100); ) 163 DEF_BENCH( return new skgpu::RandomIntersectionBench(500); ) 164 DEF_BENCH( return new skgpu::RandomIntersectionBench(1000); ) 165 DEF_BENCH( return new skgpu::RandomIntersectionBench(5000); ) 166 DEF_BENCH( return new skgpu::RandomIntersectionBench(10000); ) 167 DEF_BENCH( return new skgpu::FileIntersectionBench(); ) // Sniffs --intersectionTreeFile 168