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 #ifndef SkThreadedBMPDevice_DEFINED 9 #define SkThreadedBMPDevice_DEFINED 10 11 #include "SkBitmapDevice.h" 12 #include "SkDraw.h" 13 #include "SkTaskGroup2D.h" 14 15 class SkThreadedBMPDevice : public SkBitmapDevice { 16 public: 17 // When threads = 0, we make fThreadCnt = tiles. Otherwise fThreadCnt = threads. 18 // When executor = nullptr, we manages the thread pool. Otherwise, the caller manages it. 19 SkThreadedBMPDevice(const SkBitmap& bitmap, int tiles, int threads = 0, 20 SkExecutor* executor = nullptr); 21 ~SkThreadedBMPDevice()22 ~SkThreadedBMPDevice() override { fQueue.finish(); } 23 24 protected: 25 void drawPaint(const SkPaint& paint) override; 26 void drawPoints(SkCanvas::PointMode mode, size_t count, 27 const SkPoint[], const SkPaint& paint) override; 28 void drawRect(const SkRect& r, const SkPaint& paint) override; 29 void drawRRect(const SkRRect& rr, const SkPaint& paint) override; 30 31 void drawPath(const SkPath&, const SkPaint&, const SkMatrix* prePathMatrix, 32 bool pathIsMutable) override; 33 void drawBitmap(const SkBitmap&, SkScalar x, SkScalar y, const SkPaint&) override; 34 void drawSprite(const SkBitmap&, int x, int y, const SkPaint&) override; 35 36 void drawText(const void* text, size_t len, SkScalar x, SkScalar y, 37 const SkPaint&) override; 38 void drawPosText(const void* text, size_t len, const SkScalar pos[], 39 int scalarsPerPos, const SkPoint& offset, const SkPaint& paint) override; 40 void drawVertices(const SkVertices*, SkBlendMode, const SkPaint&) override; 41 void drawDevice(SkBaseDevice*, int x, int y, const SkPaint&) override; 42 43 void flush() override; 44 45 private: 46 // We store DrawState inside DrawElement because inifFn and drawFn both want to use it 47 struct DrawState { 48 SkPixmap fDst; 49 SkMatrix fMatrix; 50 SkRasterClip fRC; 51 DrawStateDrawState52 DrawState() {} 53 explicit DrawState(SkThreadedBMPDevice* dev); 54 55 SkDraw getDraw() const; 56 }; 57 58 class TileDraw : public SkDraw { 59 public: TileDraw(const DrawState& ds, const SkIRect& tileBounds); 60 private: SkRasterClip fTileRC; 61 }; 62 63 class DrawElement { 64 public: 65 using InitFn = std::function<void(SkArenaAlloc* threadAlloc, DrawElement* element)>; 66 using DrawFn = std::function<void(SkArenaAlloc* threadAlloc, const DrawState& ds, 67 const SkIRect& tileBounds)>; 68 DrawElement()69 DrawElement() {} DrawElement(SkThreadedBMPDevice * device,DrawFn && drawFn,const SkRect & rawDrawBounds)70 DrawElement(SkThreadedBMPDevice* device, DrawFn&& drawFn, const SkRect& rawDrawBounds) 71 : fInitialized(true) 72 , fDrawFn(std::move(drawFn)) 73 , fDS(device) 74 , fDrawBounds(device->transformDrawBounds(rawDrawBounds)) {} DrawElement(SkThreadedBMPDevice * device,InitFn && initFn,const SkRect & rawDrawBounds)75 DrawElement(SkThreadedBMPDevice* device, InitFn&& initFn, const SkRect& rawDrawBounds) 76 : fInitialized(false) 77 , fNeedInit(true) 78 , fInitFn(std::move(initFn)) 79 , fDS(device) 80 , fDrawBounds(device->transformDrawBounds(rawDrawBounds)) {} 81 tryInitOnce(SkArenaAlloc * alloc)82 SK_ALWAYS_INLINE bool tryInitOnce(SkArenaAlloc* alloc) { 83 bool t = true; 84 // If there are multiple threads reaching this point simutaneously, 85 // compare_exchange_strong ensures that only one thread can enter the if condition and 86 // do the initialization. 87 if (!fInitialized && fNeedInit && fNeedInit.compare_exchange_strong(t, false)) { 88 #ifdef SK_DEBUG 89 fDrawFn = 0; // Invalidate fDrawFn 90 #endif 91 fInitFn(alloc, this); 92 fInitialized = true; 93 SkASSERT(fDrawFn != 0); // Ensure that fInitFn does populate fDrawFn 94 return true; 95 } 96 return false; 97 } 98 tryDraw(const SkIRect & tileBounds,SkArenaAlloc * alloc)99 SK_ALWAYS_INLINE bool tryDraw(const SkIRect& tileBounds, SkArenaAlloc* alloc) { 100 if (!SkIRect::Intersects(tileBounds, fDrawBounds)) { 101 return true; 102 } 103 if (fInitialized) { 104 fDrawFn(alloc, fDS, tileBounds); 105 return true; 106 } 107 return false; 108 } 109 getDraw()110 SkDraw getDraw() const { return fDS.getDraw(); } setDrawFn(DrawFn && fn)111 void setDrawFn(DrawFn&& fn) { fDrawFn = std::move(fn); } 112 113 private: 114 std::atomic<bool> fInitialized; 115 std::atomic<bool> fNeedInit; 116 InitFn fInitFn; 117 DrawFn fDrawFn; 118 DrawState fDS; 119 SkIRect fDrawBounds; 120 }; 121 122 class DrawQueue : public SkWorkKernel2D { 123 public: 124 static constexpr int MAX_QUEUE_SIZE = 100000; 125 DrawQueue(SkThreadedBMPDevice * device)126 DrawQueue(SkThreadedBMPDevice* device) : fDevice(device) {} 127 void reset(); 128 129 // For ~SkThreadedBMPDevice() to shutdown tasks, we use this instead of reset because reset 130 // will start new tasks. finish()131 void finish() { fTasks->finish(); } 132 133 // Push a draw command into the queue. If Fn is DrawFn, we're pushing an element without 134 // the need of initialization. If Fn is InitFn, we're pushing an element with init-once 135 // and the InitFn will generate the DrawFn during initialization. 136 template<typename Fn> push(const SkRect & rawDrawBounds,Fn && fn)137 SK_ALWAYS_INLINE void push(const SkRect& rawDrawBounds, Fn&& fn) { 138 if (fSize == MAX_QUEUE_SIZE) { 139 this->reset(); 140 } 141 SkASSERT(fSize < MAX_QUEUE_SIZE); 142 new (&fElements[fSize++]) DrawElement(fDevice, std::move(fn), rawDrawBounds); 143 fTasks->addColumn(); 144 } 145 146 // SkWorkKernel2D 147 bool initColumn(int column, int thread) override; 148 bool work2D(int row, int column, int thread) override; 149 150 private: 151 SkThreadedBMPDevice* fDevice; 152 std::unique_ptr<SkTaskGroup2D> fTasks; 153 SkTArray<SkSTArenaAlloc<8 << 10>> fThreadAllocs; // 8k stack size 154 DrawElement fElements[MAX_QUEUE_SIZE]; 155 int fSize; 156 }; 157 158 SkIRect transformDrawBounds(const SkRect& drawBounds) const; 159 160 const int fTileCnt; 161 const int fThreadCnt; 162 SkTArray<SkIRect> fTileBounds; 163 164 /** 165 * This can either be 166 * 1. fInternalExecutor.get() which means that we're managing the thread pool's life cycle. 167 * 2. provided by our caller which means that our caller is managing the threads' life cycle. 168 * In the 2nd case, fInternalExecutor == nullptr. 169 */ 170 SkExecutor* fExecutor = nullptr; 171 std::unique_ptr<SkExecutor> fInternalExecutor; 172 173 SkSTArenaAlloc<8 << 10> fAlloc; // so we can allocate memory that lives until flush 174 175 DrawQueue fQueue; 176 177 friend struct SkInitOnceData; // to access DrawElement and DrawState 178 friend class SkDraw; // to access DrawState 179 180 typedef SkBitmapDevice INHERITED; 181 }; 182 183 // Passed to SkDraw::drawXXX to enable threaded draw with init-once. The goal is to reuse as much 184 // code as possible from SkDraw. (See SkDraw::drawPath and SkDraw::drawDevPath for an example.) 185 struct SkInitOnceData { 186 SkArenaAlloc* fAlloc; 187 SkThreadedBMPDevice::DrawElement* fElement; 188 setEmptyDrawFnSkInitOnceData189 void setEmptyDrawFn() { 190 fElement->setDrawFn([](SkArenaAlloc* threadAlloc, const SkThreadedBMPDevice::DrawState& ds, 191 const SkIRect& tileBounds){}); 192 } 193 }; 194 195 #endif // SkThreadedBMPDevice_DEFINED 196