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

#include "src/gpu/ccpr/GrSampleMaskProcessor.h"

#include "src/gpu/GrMesh.h"
#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"

class GrSampleMaskProcessor::Impl : public GrGLSLGeometryProcessor {
public:
    Impl(std::unique_ptr<Shader> shader) : fShader(std::move(shader)) {}

private:
    void setData(const GrGLSLProgramDataManager&, const GrPrimitiveProcessor&,
                 const CoordTransformRange&) override {}

    void onEmitCode(EmitArgs&, GrGPArgs*) override;

    const std::unique_ptr<Shader> fShader;
};

void GrSampleMaskProcessor::Impl::onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) {
    const GrSampleMaskProcessor& proc = args.fGP.cast<GrSampleMaskProcessor>();
    GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
    GrGLSLVertexBuilder* v = args.fVertBuilder;
    int numInputPoints = proc.numInputPoints();
    int inputWidth = (4 == numInputPoints || proc.hasInputWeight()) ? 4 : 3;

    varyingHandler->emitAttributes(proc);
    SkASSERT(!*args.fFPCoordTransformHandler);

    if (PrimitiveType::kTriangles == proc.fPrimitiveType) {
        SkASSERT(!proc.hasInstanceAttributes());  // Triangles are drawn with vertex arrays.
        gpArgs->fPositionVar = proc.fInputAttribs.front().asShaderVar();
    } else {
        SkASSERT(!proc.hasVertexAttributes());  // Curves are drawn with instanced rendering.

        // Shaders expect a global "bloat" variable when calculating gradients.
        v->defineConstant("half", "bloat", ".5");

        const char* swizzle = (4 == numInputPoints || proc.hasInputWeight()) ? "xyzw" : "xyz";
        v->codeAppendf("float%ix2 pts = transpose(float2x%i(X.%s, Y.%s));",
                       inputWidth, inputWidth, swizzle, swizzle);

        const char* hullPts = "pts";
        fShader->emitSetupCode(v, "pts", &hullPts);
        v->codeAppendf("float2 vertexpos = %s[sk_VertexID ^ (sk_VertexID >> 1)];", hullPts);
        gpArgs->fPositionVar.set(kFloat2_GrSLType, "vertexpos");

        fShader->emitVaryings(varyingHandler, GrGLSLVarying::Scope::kVertToFrag,
                              &AccessCodeString(v), "vertexpos", nullptr, nullptr, nullptr);
    }

    // Fragment shader.
    fShader->emitSampleMaskCode(args.fFragBuilder);
}

void GrSampleMaskProcessor::reset(PrimitiveType primitiveType, GrResourceProvider* rp) {
    fPrimitiveType = primitiveType;  // This will affect the return values for numInputPoints, etc.
    SkASSERT(PrimitiveType::kWeightedTriangles != fPrimitiveType);

    this->resetCustomFeatures();
    fInputAttribs.reset();

    switch (fPrimitiveType) {
        case PrimitiveType::kTriangles:
        case PrimitiveType::kWeightedTriangles:
            fInputAttribs.emplace_back("point", kFloat2_GrVertexAttribType, kFloat2_GrSLType);
            this->setVertexAttributes(fInputAttribs.begin(), 1);
            this->setInstanceAttributes(nullptr, 0);
            break;
        case PrimitiveType::kQuadratics:
        case PrimitiveType::kCubics:
        case PrimitiveType::kConics: {
            auto instanceAttribType = (PrimitiveType::kQuadratics == fPrimitiveType)
                    ? kFloat3_GrVertexAttribType : kFloat4_GrVertexAttribType;
            auto shaderVarType = (PrimitiveType::kQuadratics == fPrimitiveType)
                    ? kFloat3_GrSLType : kFloat4_GrSLType;
            fInputAttribs.emplace_back("X", instanceAttribType, shaderVarType);
            fInputAttribs.emplace_back("Y", instanceAttribType, shaderVarType);
            this->setVertexAttributes(nullptr, 0);
            this->setInstanceAttributes(fInputAttribs.begin(), fInputAttribs.count());
            this->setWillUseCustomFeature(CustomFeatures::kSampleLocations);
            break;
        }
    }
}

void GrSampleMaskProcessor::appendMesh(sk_sp<const GrGpuBuffer> instanceBuffer, int instanceCount,
                                       int baseInstance, SkTArray<GrMesh>* out) const {
    SkASSERT(PrimitiveType::kWeightedTriangles != fPrimitiveType);

    switch (fPrimitiveType) {
        case PrimitiveType::kTriangles:
        case PrimitiveType::kWeightedTriangles: {
            GrMesh& mesh = out->emplace_back(GrPrimitiveType::kTriangles);
            mesh.setNonIndexedNonInstanced(instanceCount * 3);
            mesh.setVertexData(std::move(instanceBuffer), baseInstance * 3);
            break;
        }
        case PrimitiveType::kQuadratics:
        case PrimitiveType::kCubics:
        case PrimitiveType::kConics: {
            GrMesh& mesh = out->emplace_back(GrPrimitiveType::kTriangleStrip);
            mesh.setInstanced(std::move(instanceBuffer), instanceCount, baseInstance, 4);
            break;
        }
    }
}

GrPrimitiveType GrSampleMaskProcessor::primType() const {
    SkASSERT(PrimitiveType::kWeightedTriangles != fPrimitiveType);

    switch (fPrimitiveType) {
        case PrimitiveType::kTriangles:
        case PrimitiveType::kWeightedTriangles:
            return GrPrimitiveType::kTriangles;
        case PrimitiveType::kQuadratics:
        case PrimitiveType::kCubics:
        case PrimitiveType::kConics:
            return GrPrimitiveType::kTriangleStrip;
        default:
            return GrPrimitiveType::kTriangleStrip;
    }
}

GrGLSLPrimitiveProcessor* GrSampleMaskProcessor::onCreateGLSLInstance(
        std::unique_ptr<Shader> shader) const {
    return new Impl(std::move(shader));
}
