/*
 * 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 GrDeferredProxyUploader_DEFINED
#define GrDeferredProxyUploader_DEFINED

#include "SkAutoPixmapStorage.h"
#include "SkMakeUnique.h"
#include "SkRefCnt.h"
#include "SkSemaphore.h"

#include "GrOpFlushState.h"
#include "GrTextureProxyPriv.h"

/**
 * GrDeferredProxyUploader assists with threaded generation of textures. Currently used by both
 * software clip masks, and the software path renderer. The calling code typically needs to store
 * some additional data (T) for use on the worker thread. GrTDeferredProxyUploader allows storing
 * such data. The common flow is:
 *
 * 1) A GrTDeferredProxyUploader is created, with some payload (eg an SkPath to draw).
 *    The uploader is owned by the proxy that it's going to populate.
 * 2) A task is created with a pointer to the uploader. A worker thread executes that task, using
 *    the payload data to allocate and fill in the fPixels pixmap.
 * 3) The worker thread calls signalAndFreeData(), which notifies the main thread that the pixmap
 *    is ready, and then deletes the payload data (which is no longer needed).
 * 4) In parallel to 2-3, on the main thread... Some op is created that refers to the proxy. When
 *    that op is added to an op list, the op list retains a pointer to the "deferred" proxies.
 * 5) At flush time, the op list ensures that the deferred proxies are instantiated, then calls
 *    scheduleUpload on those proxies, which calls scheduleUpload on the uploader (below).
 * 6) scheduleUpload defers the upload even further, by adding an ASAPUpload to the flush.
 * 7) When the ASAP upload happens, we wait to make sure that the pixels are marked ready
 *    (from step #3 on the worker thread). Then we perform the actual upload to the texture.
 *    Finally, we call resetDeferredUploader, which deletes the uploader object, causing fPixels
 *    to be freed.
 */
class GrDeferredProxyUploader : public SkNoncopyable {
public:
    GrDeferredProxyUploader() : fScheduledUpload(false), fWaited(false) {}

    virtual ~GrDeferredProxyUploader() {
        // In normal usage (i.e., through GrTDeferredProxyUploader) this will be redundant
        this->wait();
    }

    void scheduleUpload(GrOpFlushState* flushState, GrTextureProxy* proxy) {
        if (fScheduledUpload) {
            // Multiple references to the owning proxy may have caused us to already execute
            return;
        }

        auto uploadMask = [this, proxy](GrDeferredTextureUploadWritePixelsFn& writePixelsFn) {
            this->wait();
            GrColorType pixelColorType = SkColorTypeToGrColorType(this->fPixels.info().colorType());
            // If the worker thread was unable to allocate pixels, this check will fail, and we'll
            // end up drawing with an uninitialized mask texture, but at least we won't crash.
            if (this->fPixels.addr()) {
                writePixelsFn(proxy, 0, 0, this->fPixels.width(), this->fPixels.height(),
                              pixelColorType, this->fPixels.addr(), this->fPixels.rowBytes());
            }
            // Upload has finished, so tell the proxy to release this GrDeferredProxyUploader
            proxy->texPriv().resetDeferredUploader();
        };
        flushState->addASAPUpload(std::move(uploadMask));
        fScheduledUpload = true;
    }

    void signalAndFreeData() {
        this->freeData();
        fPixelsReady.signal();
    }

    SkAutoPixmapStorage* getPixels() { return &fPixels; }

protected:
    void wait() {
        if (!fWaited) {
            fPixelsReady.wait();
            fWaited = true;
        }
    }

private:
    virtual void freeData() {}

    SkAutoPixmapStorage fPixels;
    SkSemaphore fPixelsReady;
    bool fScheduledUpload;
    bool fWaited;
};

template <typename T>
class GrTDeferredProxyUploader : public GrDeferredProxyUploader {
public:
    template <typename... Args>
    GrTDeferredProxyUploader(Args&&... args)
        : fData(skstd::make_unique<T>(std::forward<Args>(args)...)) {
    }

    ~GrTDeferredProxyUploader() override {
        // We need to wait here, so that we don't free fData before the worker thread is done
        // with it. (This happens if the proxy is deleted early due to a full clear or failure
        // of an op list to instantiate).
        this->wait();
    }

    T& data() { return *fData; }

private:
    void freeData() override {
        fData.reset();
    }

    std::unique_ptr<T> fData;
};

#endif
