C++程序  |  196行  |  7.33 KB

/*
 * Copyright 2017 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#ifndef SkThreadedBMPDevice_DEFINED
#define SkThreadedBMPDevice_DEFINED

#include "SkBitmapDevice.h"
#include "SkDraw.h"
#include "SkTaskGroup2D.h"

class SkThreadedBMPDevice : public SkBitmapDevice {
public:
    // When threads = 0, we make fThreadCnt = tiles. Otherwise fThreadCnt = threads.
    // When executor = nullptr, we manages the thread pool. Otherwise, the caller manages it.
    SkThreadedBMPDevice(const SkBitmap& bitmap, int tiles, int threads = 0,
                        SkExecutor* executor = nullptr);

    ~SkThreadedBMPDevice() override { fQueue.finish(); }

protected:
    void drawPaint(const SkPaint& paint) override;
    void drawPoints(SkCanvas::PointMode mode, size_t count,
                            const SkPoint[], const SkPaint& paint) override;
    void drawRect(const SkRect& r, const SkPaint& paint) override;
    void drawRRect(const SkRRect& rr, const SkPaint& paint) override;

    void drawPath(const SkPath&, const SkPaint&, const SkMatrix* prePathMatrix,
                  bool pathIsMutable) override;
    void drawBitmap(const SkBitmap&, SkScalar x, SkScalar y, const SkPaint&) override;
    void drawSprite(const SkBitmap&, int x, int y, const SkPaint&) override;

    void drawText(const void* text, size_t len, SkScalar x, SkScalar y,
                  const SkPaint&) override;
    void drawPosText(const void* text, size_t len, const SkScalar pos[],
                     int scalarsPerPos, const SkPoint& offset, const SkPaint& paint) override;
    void drawVertices(const SkVertices*, SkBlendMode, const SkPaint&) override;
    void drawDevice(SkBaseDevice*, int x, int y, const SkPaint&) override;

    void flush() override;

private:
    // We store DrawState inside DrawElement because inifFn and drawFn both want to use it
    struct DrawState {
        SkPixmap fDst;
        SkMatrix fMatrix;
        SkRasterClip fRC;

        DrawState() {}
        explicit DrawState(SkThreadedBMPDevice* dev);

        SkDraw getDraw() const;
    };

    class TileDraw : public SkDraw {
        public: TileDraw(const DrawState& ds, const SkIRect& tileBounds);
        private: SkRasterClip fTileRC;
    };

    class DrawElement {
    public:
        using InitFn = std::function<void(SkArenaAlloc* threadAlloc, DrawElement* element)>;
        using DrawFn = std::function<void(SkArenaAlloc* threadAlloc, const DrawState& ds,
                                          const SkIRect& tileBounds)>;

        DrawElement() {}
        DrawElement(SkThreadedBMPDevice* device, DrawFn&& drawFn, const SkRect& rawDrawBounds)
                : fInitialized(true)
                , fDrawFn(std::move(drawFn))
                , fDS(device)
                , fDrawBounds(device->transformDrawBounds(rawDrawBounds)) {}
        DrawElement(SkThreadedBMPDevice* device, InitFn&& initFn, const SkRect& rawDrawBounds)
                : fInitialized(false)
                , fNeedInit(true)
                , fInitFn(std::move(initFn))
                , fDS(device)
                , fDrawBounds(device->transformDrawBounds(rawDrawBounds)) {}

        SK_ALWAYS_INLINE bool tryInitOnce(SkArenaAlloc* alloc) {
            bool t = true;
            // If there are multiple threads reaching this point simutaneously,
            // compare_exchange_strong ensures that only one thread can enter the if condition and
            // do the initialization.
            if (!fInitialized && fNeedInit && fNeedInit.compare_exchange_strong(t, false)) {
#ifdef SK_DEBUG
                fDrawFn = 0; // Invalidate fDrawFn
#endif
                fInitFn(alloc, this);
                fInitialized = true;
                SkASSERT(fDrawFn != 0); // Ensure that fInitFn does populate fDrawFn
                return true;
            }
            return false;
        }

        SK_ALWAYS_INLINE bool tryDraw(const SkIRect& tileBounds, SkArenaAlloc* alloc) {
            if (!SkIRect::Intersects(tileBounds, fDrawBounds)) {
                return true;
            }
            if (fInitialized) {
                fDrawFn(alloc, fDS, tileBounds);
                return true;
            }
            return false;
        }

        SkDraw getDraw() const { return fDS.getDraw(); }
        void setDrawFn(DrawFn&& fn) { fDrawFn = std::move(fn); }

    private:
        std::atomic<bool>   fInitialized;
        std::atomic<bool>   fNeedInit;
        InitFn              fInitFn;
        DrawFn              fDrawFn;
        DrawState           fDS;
        SkIRect             fDrawBounds;
    };

    class DrawQueue : public SkWorkKernel2D {
    public:
        static constexpr int MAX_QUEUE_SIZE = 100000;

        DrawQueue(SkThreadedBMPDevice* device) : fDevice(device) {}
        void reset();

        // For ~SkThreadedBMPDevice() to shutdown tasks, we use this instead of reset because reset
        // will start new tasks.
        void finish() { fTasks->finish(); }

        // Push a draw command into the queue. If Fn is DrawFn, we're pushing an element without
        // the need of initialization. If Fn is InitFn, we're pushing an element with init-once
        // and the InitFn will generate the DrawFn during initialization.
        template<typename Fn>
        SK_ALWAYS_INLINE void push(const SkRect& rawDrawBounds, Fn&& fn) {
            if (fSize == MAX_QUEUE_SIZE) {
                this->reset();
            }
            SkASSERT(fSize < MAX_QUEUE_SIZE);
            new (&fElements[fSize++]) DrawElement(fDevice, std::move(fn), rawDrawBounds);
            fTasks->addColumn();
        }

        // SkWorkKernel2D
        bool initColumn(int column, int thread) override;
        bool work2D(int row, int column, int thread) override;

    private:
        SkThreadedBMPDevice*                fDevice;
        std::unique_ptr<SkTaskGroup2D>      fTasks;
        SkTArray<SkSTArenaAlloc<8 << 10>>   fThreadAllocs; // 8k stack size
        DrawElement                         fElements[MAX_QUEUE_SIZE];
        int                                 fSize;
    };

    SkIRect transformDrawBounds(const SkRect& drawBounds) const;

    const int fTileCnt;
    const int fThreadCnt;
    SkTArray<SkIRect> fTileBounds;

    /**
     * This can either be
     * 1. fInternalExecutor.get() which means that we're managing the thread pool's life cycle.
     * 2. provided by our caller which means that our caller is managing the threads' life cycle.
     * In the 2nd case, fInternalExecutor == nullptr.
     */
    SkExecutor* fExecutor = nullptr;
    std::unique_ptr<SkExecutor> fInternalExecutor;

    SkSTArenaAlloc<8 << 10> fAlloc; // so we can allocate memory that lives until flush

    DrawQueue fQueue;

    friend struct SkInitOnceData;   // to access DrawElement and DrawState
    friend class SkDraw;            // to access DrawState

    typedef SkBitmapDevice INHERITED;
};

// Passed to SkDraw::drawXXX to enable threaded draw with init-once. The goal is to reuse as much
// code as possible from SkDraw. (See SkDraw::drawPath and SkDraw::drawDevPath for an example.)
struct SkInitOnceData {
    SkArenaAlloc* fAlloc;
    SkThreadedBMPDevice::DrawElement* fElement;

    void setEmptyDrawFn() {
        fElement->setDrawFn([](SkArenaAlloc* threadAlloc, const SkThreadedBMPDevice::DrawState& ds,
                               const SkIRect& tileBounds){});
    }
};

#endif // SkThreadedBMPDevice_DEFINED