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