/*
 * Copyright 2021 Google LLC
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */
#ifndef AtlasRenderTask_DEFINED
#define AtlasRenderTask_DEFINED

#include "include/core/SkPath.h"
#include "include/core/SkRefCnt.h"
#include "include/private/base/SkAssert.h"
#include "include/private/base/SkNoncopyable.h"
#include "src/base/SkBlockAllocator.h"
#include "src/base/SkTBlockList.h"
#include "src/core/SkColorData.h"
#include "src/gpu/ganesh/GrCaps.h"
#include "src/gpu/ganesh/GrDynamicAtlas.h"
#include "src/gpu/ganesh/GrSurfaceProxyView.h"
#include "src/gpu/ganesh/GrTexture.h"
#include "src/gpu/ganesh/ops/GrOp.h"
#include "src/gpu/ganesh/ops/OpsTask.h"
#include "src/gpu/ganesh/tessellate/PathTessellator.h"

#include <limits>
#include <memory>
#include <utility>

class GrArenas;
class GrOnFlushResourceProvider;
class GrOpFlushState;
class GrRecordingContext;
class GrTextureProxy;
class SkMatrix;
struct GrUserStencilSettings;
struct SkIPoint16;
struct SkIPoint;
struct SkIRect;
struct SkRect;

namespace skgpu::ganesh {

// Represents a GrRenderTask that draws paths into an atlas. This task gets added the DAG and left
// open, lays out its atlas while future tasks call addPath(), and finally adds its internal draw
// ops during onMakeClosed().
//
// The atlas texture does not get instantiated automatically. It is the creator's responsibility to
// call instantiate() at flush time.
class AtlasRenderTask final : public OpsTask {
public:
    AtlasRenderTask(GrRecordingContext*,
                    sk_sp<GrArenas>,
                    std::unique_ptr<GrDynamicAtlas>);

    const GrTextureProxy* atlasProxy() const { return fDynamicAtlas->textureProxy(); }
    GrSurfaceProxyView readView(const GrCaps& caps) const { return fDynamicAtlas->readView(caps); }

    // Allocates a rectangle for, and stages the given path to be rendered into the atlas. Returns
    // false if there was not room in the atlas. On success, writes out the location of the path's
    // upper-left corner to 'locationInAtlas'.
    bool addPath(const SkMatrix&, const SkPath&, SkIPoint pathDevTopLeft, int widthInAtlas,
                 int heightInAtlas, bool transposedInAtlas, SkIPoint16* locationInAtlas);

    // Must be called at flush time. The texture proxy is instantiated with 'backingTexture', if
    // provided. See GrDynamicAtlas.
    [[nodiscard]] bool instantiate(GrOnFlushResourceProvider* onFlushRP,
                                   sk_sp<GrTexture> backingTexture = nullptr) {
        SkASSERT(this->isClosed());
        return fDynamicAtlas->instantiate(onFlushRP, std::move(backingTexture));
    }

private:
    // Adds internal ops to render the atlas before deferring to OpsTask::onMakeClosed.
    ExpectedOutcome onMakeClosed(GrRecordingContext*, SkIRect* targetUpdateBounds) override;

    void stencilAtlasRect(GrRecordingContext*, const SkRect&, const SkPMColor4f&,
                          const GrUserStencilSettings*);
    void addAtlasDrawOp(GrOp::Owner, const GrCaps&);

    // Executes the OpsTask and resolves msaa if needed.
    bool onExecute(GrOpFlushState* flushState) override;

    const std::unique_ptr<GrDynamicAtlas> fDynamicAtlas;

    // Allocate enough inline entries for 16 atlas path draws, then spill to the heap.
    using PathDrawList = PathTessellator::PathDrawList;
    using PathDrawAllocator = SkTBlockList<PathDrawList, 16>;
    PathDrawAllocator fPathDrawAllocator{64, SkBlockAllocator::GrowthPolicy::kFibonacci};

    class AtlasPathList : SkNoncopyable {
    public:
        void add(PathDrawAllocator* alloc, const SkMatrix& pathMatrix, const SkPath& path) {
            SkASSERT(this->canAdd(path));
            fPathDrawList = &alloc->emplace_back(pathMatrix, path, SK_PMColor4fTRANSPARENT,
                                                 fPathDrawList);
            if (path.isInverseFillType()) {
                // The atlas never has inverse paths. The inversion happens later.
                fPathDrawList->fPath.toggleInverseFillType();
            }
            fTotalCombinedPathVerbCnt += path.countVerbs();
            ++fPathCount;
        }

        bool canAdd(const SkPath& path) const {
            // Return true so long as we won't overflow the total verb count, with a bit of head
            // room so that later-on allocations are unlikely to overflow (they still need to guard
            // themselves, but we'd rather just make new Ops).
            static constexpr int kMaxVerbLimit = std::numeric_limits<int>::max() >> 4;
            return kMaxVerbLimit - fTotalCombinedPathVerbCnt >= path.countVerbs();
        }

        const PathDrawList* pathDrawList() const { return fPathDrawList; }
        int totalCombinedPathVerbCnt() const { return fTotalCombinedPathVerbCnt; }
        int pathCount() const { return fPathCount; }

    private:
        PathDrawList* fPathDrawList = nullptr;
        int fTotalCombinedPathVerbCnt = 0;
        int fPathCount = 0;
    };

    AtlasPathList fWindingPathList;
    AtlasPathList fEvenOddPathList;
};

}  // namespace skgpu::ganesh

#endif // AtlasRenderTask_DEFINED
