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