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

#ifndef AtlasPathRenderer_DEFINED
#define AtlasPathRenderer_DEFINED

#include "include/core/SkRefCnt.h"
#include "include/private/base/SkTArray.h"
#include "src/core/SkChecksum.h"
#include "src/core/SkIPoint16.h"
#include "src/core/SkTHash.h"
#include "src/gpu/ganesh/GrFragmentProcessor.h"
#include "src/gpu/ganesh/GrOnFlushResourceProvider.h"
#include "src/gpu/ganesh/PathRenderer.h"
#include "src/gpu/ganesh/ops/AtlasRenderTask.h"

#include <cstdint>
#include <cstring>
#include <functional>
#include <memory>

class GrDirectContext;
class GrOp;
class GrRecordingContext;
class GrStyledShape;
class GrSurfaceProxy;
class SkMatrix;
class SkPath;
enum class GrAAType : unsigned int;
struct SkIRect;
struct SkRect;

namespace skgpu::ganesh {

class SurfaceDrawContext;

// Draws paths by first rendering their coverage mask into an offscreen atlas.
class AtlasPathRenderer final : public PathRenderer, public GrOnFlushCallbackObject {
public:
    static bool IsSupported(GrRecordingContext*);

    // Returns a GrAtlasPathRenderer if it is supported, otherwise null.
    static sk_sp<AtlasPathRenderer> Make(GrRecordingContext* rContext);

    const char* name() const override { return "GrAtlasPathRenderer"; }

    // Returns a fragment processor that modulates inputFP by the given deviceSpacePath's coverage,
    // implemented using an internal atlas.
    //
    // Returns 'inputFP' wrapped in GrFPFailure() if the path was too large, or if the current atlas
    // is full and already used by either opBeingClipped or inputFP. (Currently, "too large" means
    // larger than fMaxAtlasSize in either dimension, more than 256^2 total pixels, or more than
    // 128^2 total pixels if the surfaceDrawContext supports MSAA or DMSAA.)
    //
    // Also returns GrFPFailure() if the view matrix has perspective.
    GrFPResult makeAtlasClipEffect(const skgpu::ganesh::SurfaceDrawContext*,
                                   const GrOp* opBeingClipped,
                                   std::unique_ptr<GrFragmentProcessor> inputFP,
                                   const SkIRect& drawBounds,
                                   const SkMatrix&,
                                   const SkPath&);

private:
    // The atlas is not compatible with DDL. We can only use it on direct contexts.
    AtlasPathRenderer(GrDirectContext*);

    StencilSupport onGetStencilSupport(const GrStyledShape&) const override {
        return kNoSupport_StencilSupport;
    }

    CanDrawPath onCanDrawPath(const CanDrawPathArgs&) const override;

    bool onDrawPath(const DrawPathArgs&) override;

    // Returns true if the given device-space path bounds are small enough to fit in an atlas and to
    // benefit from atlasing. (Currently, "small enough" means no larger than fMaxAtlasSize in
    // either dimension, no more than 256^2 total pixels, or no more than 128^2 total pixels if the
    // fallbackAAType is kMSAA.)
    bool pathFitsInAtlas(const SkRect& pathDevBounds, GrAAType fallbackAAType) const;

    // Returns true if the draw being set up already uses the given atlasProxy.
    using DrawRefsAtlasCallback = std::function<bool(const GrSurfaceProxy* atlasProxy)>;

    // Adds the filled path to an atlas.
    //
    // pathFitsInAtlas() and is_visible() both must have returned true before making this call.
    //
    // Fails and returns false if the current atlas is full and already in use according to
    // DrawRefsAtlasCallback.
    bool addPathToAtlas(GrRecordingContext*,
                        const SkMatrix&,
                        const SkPath&,
                        const SkRect& pathDevBounds,
                        SkIRect* devIBounds,
                        SkIPoint16* locationInAtlas,
                        bool* transposedInAtlas,
                        const DrawRefsAtlasCallback&);

    // Instantiates texture(s) for all atlases we've created since the last flush. Atlases that are
    // the same size will be instantiated with the same backing texture.
    bool preFlush(GrOnFlushResourceProvider*) override;

    float fAtlasMaxSize = 0;
    float fAtlasMaxPathWidth = 0;
    int fAtlasInitialSize = 0;

    // A collection of all atlases we've created and used since the last flush. We instantiate these
    // at flush time during preFlush().
    skia_private::STArray<4, sk_sp<AtlasRenderTask>> fAtlasRenderTasks;

    // This simple cache remembers the locations of cacheable path masks in the most recent atlas.
    // Its main motivation is for clip paths.
    struct AtlasPathKey {
        void set(const SkMatrix&, const SkPath&);
        bool operator==(const AtlasPathKey& k) const {
            static_assert(sizeof(*this) == sizeof(uint32_t) * 8);
            return !memcmp(this, &k, sizeof(*this));
        }
        uint32_t fPathGenID;
        float fAffineMatrix[6];
        uint32_t fFillRule;

        using Hash = SkForceDirectHash<AtlasPathKey>;
    };

    skia_private::THashMap<AtlasPathKey, SkIPoint16, AtlasPathKey::Hash> fAtlasPathCache;
};

}  // namespace skgpu::ganesh

#endif // GrAtlasPathRenderer_DEFINED
