Home | History | Annotate | Download | only in core
      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 
     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 
     52         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 
     69         DrawElement() {}
     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)) {}
     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 
     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 
     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 
    110         SkDraw getDraw() const { return fDS.getDraw(); }
    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 
    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.
    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>
    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 
    189     void setEmptyDrawFn() {
    190         fElement->setDrawFn([](SkArenaAlloc* threadAlloc, const SkThreadedBMPDevice::DrawState& ds,
    191                                const SkIRect& tileBounds){});
    192     }
    193 };
    194 
    195 #endif // SkThreadedBMPDevice_DEFINED
    196