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

#include "src/core/SkIPoint16.h"
#include "src/gpu/ganesh/GrGeometryProcessor.h"
#include "src/gpu/ganesh/GrSurfaceProxyView.h"
#include "src/gpu/ganesh/glsl/GrGLSLUniformHandler.h"

namespace skgpu::ganesh {

// This class encapsulates all the necessary steps for an instanced GrGeometryProcessor to clip
// against a path mask from an atlas.
class AtlasInstancedHelper {
public:
    enum class ShaderFlags {
        kNone = 0,
        kInvertCoverage = 1 << 0,
        kCheckBounds = 1 << 1
    };

    GR_DECL_BITFIELD_CLASS_OPS_FRIENDS(ShaderFlags);

    constexpr static int kNumShaderFlags = 2;

    AtlasInstancedHelper(GrSurfaceProxyView atlasView, ShaderFlags shaderFlags)
            : fAtlasProxy(atlasView.detachProxy())
            , fAtlasSwizzle(atlasView.swizzle())
            , fShaderFlags(shaderFlags) {
        // Bottom left origin is not supported.
        SkASSERT(atlasView.origin() == kTopLeft_GrSurfaceOrigin);
    }

    GrSurfaceProxy* proxy() const { return fAtlasProxy.get(); }
    const skgpu::Swizzle& atlasSwizzle() const { return fAtlasSwizzle; }

    // Returns whether the two helpers can be batched together in a single draw.
    bool isCompatible(const AtlasInstancedHelper& helper) {
        // TODO: We may want to consider two helpers compatible if they only differ in the
        // kCheckBounds flag -- we can always promote one to checking its bounds.
        SkASSERT(fAtlasProxy != helper.fAtlasProxy || fAtlasSwizzle == helper.fAtlasSwizzle);
        return fAtlasProxy == helper.fAtlasProxy && fShaderFlags == helper.fShaderFlags;
    }

    // Adds bits to the shader key that uniquely identify this specific helper's shader code.
    void getKeyBits(KeyBuilder* b) const;

    // Appends the instanced input attribs to the back of the array that we will need in order to
    // locate our path in the atlas.
    void appendInstanceAttribs(
            skia_private::TArray<GrGeometryProcessor::Attribute>* instanceAttribs) const;

    struct Instance {
        Instance(SkIPoint16 locationInAtlas, const SkIRect& pathDevIBounds, bool transposedInAtlas)
                : fLocationInAtlas(locationInAtlas)
                , fPathDevIBounds(pathDevIBounds)
                , fTransposedInAtlas(transposedInAtlas) {
            SkASSERT(fLocationInAtlas.x() >= 0);
            SkASSERT(fLocationInAtlas.y() >= 0);
        }
        SkIPoint16 fLocationInAtlas;
        SkIRect fPathDevIBounds;
        bool fTransposedInAtlas;
    };

    // Writes out the given instance data, formatted for the specific attribs that we added during
    // appendInstanceAttribs().
    void writeInstanceData(VertexWriter* instanceWriter, const Instance*) const;

    // Injects vertex code, fragment code, varyings, and uniforms to ultimately multiply
    // "args.fOutputCoverage" in the fragment shader by the atlas coverage.
    //
    // The caller is responsible to store "atlasAdjustUniformHandle" and pass it to
    // setUniformData().
    void injectShaderCode(const GrGeometryProcessor::ProgramImpl::EmitArgs&,
                          const GrShaderVar& devCoord,
                          GrGLSLUniformHandler::UniformHandle* atlasAdjustUniformHandle) const;

    // The atlas clip requires one uniform value -- "atlasAdjustUniform". The caller should have
    // stored this handle after its call to injectShaderCode(). This method sets its value prior to
    // drawing.
    void setUniformData(const GrGLSLProgramDataManager&,
                        const GrGLSLUniformHandler::UniformHandle& atlasAdjustUniformHandle) const;

private:
    const sk_sp<GrSurfaceProxy> fAtlasProxy;
    const skgpu::Swizzle fAtlasSwizzle;
    const ShaderFlags fShaderFlags;
};

GR_MAKE_BITFIELD_CLASS_OPS(AtlasInstancedHelper::ShaderFlags)

}  // namespace skgpu::ganesh

#endif // AtlasInstancedHelper_DEFINED
