-- glslfx version 0.1

//
// Copyright 2016 Pixar
//
// Licensed under the Apache License, Version 2.0 (the "Apache License")
// with the following modification; you may not use this file except in
// compliance with the Apache License and the following modification to it:
// Section 6. Trademarks. is deleted and replaced with:
//
// 6. Trademarks. This License does not grant permission to use the trade
//    names, trademarks, service marks, or product names of the Licensor
//    and its affiliates, except as required to comply with Section 4(c) of
//    the License and to reproduce the content of the NOTICE file.
//
// You may obtain a copy of the Apache License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the Apache License with the above modification is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the Apache License for the specific
// language governing permissions and limitations under the Apache License.
//

--- This is what an import might look like.
--- #import $TOOLS/hdSt/shaders/mesh.glslfx

#import $TOOLS/hdSt/shaders/instancing.glslfx
#import $TOOLS/hdSt/shaders/meshNormal.glslfx
#import $TOOLS/hdSt/shaders/meshWire.glslfx
#import $TOOLS/hdSt/shaders/terminals.glslfx
#import $TOOLS/hdSt/shaders/edgeId.glslfx
#import $TOOLS/hdSt/shaders/pointId.glslfx
#import $TOOLS/hdSt/shaders/visibility.glslfx

--- --------------------------------------------------------------------------
-- glsl Mesh.Vertex

out VertexData
{
    vec4 Peye;
    vec3 Neye;
} outData;

out vec4 tesPatchCoord;
out vec2 tesTessCoord;
out vec4 gsPatchCoord;
out vec2 gsTessCoord;

// Fwd declare methods defined in pointId.glslfx, that are used below.
int GetPointId();
float GetPointRasterSize(int);
void ProcessPointId(int);

void main(void)
{
    ProcessPrimvars();

    MAT4 transform    = ApplyInstanceTransform(HdGet_transform());
    vec4 point        = vec4(HdGet_points().xyz, 1);
    outData.Peye = vec4(GetWorldToViewMatrix() * transform * point);

    outData.Neye = GetNormal(vec3(0), 0); // normalized

    // Fill out redundantly, since at this point we don't know
    // the following stages include tessellation or geometry shader.
    // we rely on that compiler (linker) optimizes out these redundant
    // assignment and attribute plumbing.
    tesPatchCoord = gsPatchCoord = vec4(0);
    tesTessCoord = gsTessCoord = vec2(0);

    int pointId = GetPointId();
    gl_PointSize = GetPointRasterSize(pointId);
    ProcessPointId(pointId);

    gl_Position = vec4(GetProjectionMatrix() * outData.Peye);
    ApplyClipPlanes(outData.Peye);
}

--- --------------------------------------------------------------------------
-- glsl Mesh.TessControl.BSplineQuad

layout(vertices = 16) out;

patch out vec4 tessOuterLo, tessOuterHi;

in VertexData
{
    vec4 Peye;
    vec3 Neye;
} inpt[gl_MaxPatchVertices];

out VertexDataTess
{
    OsdPerPatchVertexBezier v;
} outpt[16];

void main(void)
{
    vec3 cv[16];
    for (int i = 0; i < 16; ++i) {
        cv[i] = inpt[i].Peye.xyz;
    }

    ivec3 patchParam = GetPatchParam();

    OsdComputePerPatchVertexBSpline(patchParam, gl_InvocationID, cv,
                                    outpt[gl_InvocationID].v);

    // Wait for all basis conversion to be finished
    barrier();

    if (gl_InvocationID == 0) {
        vec4 tessLevelOuter = vec4(0);
        vec2 tessLevelInner = vec2(0);

        // Gather bezier control points to compute limit surface tess levels
        OsdPerPatchVertexBezier cpBezier[16];
        for (int i = 0; i < 16; ++i) {
            cpBezier[i] = outpt[i].v;
        }
        OsdGetTessLevelsAdaptiveLimitPoints(cpBezier, patchParam,
                                            tessLevelOuter, tessLevelInner,
                                            tessOuterLo, tessOuterHi);

        gl_TessLevelOuter[0] = tessLevelOuter[0];
        gl_TessLevelOuter[1] = tessLevelOuter[1];
        gl_TessLevelOuter[2] = tessLevelOuter[2];
        gl_TessLevelOuter[3] = tessLevelOuter[3];

        gl_TessLevelInner[0] = tessLevelInner[0];
        gl_TessLevelInner[1] = tessLevelInner[1];
    }

    ProcessPrimvars();
}

--- --------------------------------------------------------------------------
-- glsl Mesh.TessEval.BezierQuad

layout(quads) in;

patch in vec4 tessOuterLo, tessOuterHi;

in VertexDataTess {
    OsdPerPatchVertexBezier v;
} inpt[gl_MaxPatchVertices];

out VertexData
{
    vec4 Peye;
    vec3 Neye;
} outData;

// XXX: due to NVIDIA shader compiler bug (filed as 1687344)
// we can't put patchCoord into interface block.
out vec4 tesPatchCoord;
out vec2 tesTessCoord;

void main(void)
{
    OsdPerPatchVertexBezier cv[16];
    for (int i = 0; i < 16; ++i) {
        cv[i] = inpt[i].v;
    }
    vec2 UV = OsdGetTessParameterization(gl_TessCoord.xy,
                                         tessOuterLo,
                                         tessOuterHi);

    vec3 P = vec3(0), dPu = vec3(0), dPv = vec3(0);
    vec3 N = vec3(0), dNu = vec3(0), dNv = vec3(0);

    ivec3 patchParam = inpt[0].v.patchParam;
    OsdEvalPatchBezier(patchParam, UV, cv, P, dPu, dPv, N, dNu, dNv);

    outData.Peye = vec4(P, 1);
    outData.Neye = N; // normalized

    tesPatchCoord = OsdInterpolatePatchCoord(UV, patchParam);
    tesTessCoord = UV;

    // Bilinear basis
    vec4 basis = vec4(
        (1.0-UV.x) * (1.0-UV.y), UV.x * (1.0-UV.y),
        (1.0-UV.x) * UV.y, UV.x * UV.y );

    ProcessPrimvars(basis, 5, 6, 9, 10);
}

--- --------------------------------------------------------------------------
-- glsl Mesh.TessControl.BoxSplineTriangle

layout(vertices = 15) out;

patch out vec4 tessOuterLo, tessOuterHi;

in VertexData
{
    vec4 Peye;
    vec3 Neye;
} inpt[gl_MaxPatchVertices];

out VertexDataTess
{
    OsdPerPatchVertexBezier v;
} outpt[16];

void main(void)
{
    vec3 cv[12];
    for (int i = 0; i < 12; ++i) {
        cv[i] = inpt[i].Peye.xyz;
    }

    ivec3 patchParam = GetPatchParam();

    OsdComputePerPatchVertexBoxSplineTriangle(patchParam, gl_InvocationID, cv,
                                              outpt[gl_InvocationID].v);

    // Wait for all basis conversion to be finished
    barrier();

    if (gl_InvocationID == 0) {
        vec4 tessLevelOuter = vec4(0);
        vec2 tessLevelInner = vec2(0);

        // Gather bezier control points to compute limit surface tess levels
        vec3 cpBezier[15];
        for (int i = 0; i < 15; ++i) {
            cpBezier[i] = outpt[i].v.P;
        }
        OsdEvalPatchBezierTriangleTessLevels(cpBezier, patchParam,
                                            tessLevelOuter, tessLevelInner,
                                            tessOuterLo, tessOuterHi);

        gl_TessLevelOuter[0] = tessLevelOuter[0];
        gl_TessLevelOuter[1] = tessLevelOuter[1];
        gl_TessLevelOuter[2] = tessLevelOuter[2];

        gl_TessLevelInner[0] = tessLevelInner[0];
    }

    ProcessPrimvars();
}

--- --------------------------------------------------------------------------
-- glsl Mesh.TessEval.BezierTriangle

layout(triangles) in;

patch in vec4 tessOuterLo, tessOuterHi;

in VertexDataTess {
    OsdPerPatchVertexBezier v;
} inpt[gl_MaxPatchVertices];

out VertexData
{
    vec4 Peye;
    vec3 Neye;
} outData;

// XXX: due to NVIDIA shader compiler bug (filed as 1687344)
// we can't put patchCoord into interface block.
out vec4 tesPatchCoord;
out vec2 tesTessCoord;

void main(void)
{
    OsdPerPatchVertexBezier cv[15];
    for (int i = 0; i < 15; ++i) {
        cv[i] = inpt[i].v;
    }
    vec2 UV = OsdGetTessParameterizationTriangle(gl_TessCoord.xyz,
                                                 tessOuterLo,
                                                 tessOuterHi);

    vec3 P = vec3(0), dPu = vec3(0), dPv = vec3(0);
    vec3 N = vec3(0), dNu = vec3(0), dNv = vec3(0);

    ivec3 patchParam = inpt[0].v.patchParam;
    OsdEvalPatchBezierTriangle(patchParam, UV, cv, P, dPu, dPv, N, dNu, dNv);

    outData.Peye = vec4(P, 1);
    outData.Neye = N; // normalized

    tesPatchCoord = OsdInterpolatePatchCoordTriangle(UV, patchParam);
    tesTessCoord = vec2(0); // used only for hiding internal edges w/ quad tess

    // Barycentric basis
    vec4 basis = vec4(
        (1.0f-UV.x-UV.y), UV.x, UV.y, 0.0f);

    ProcessPrimvars(basis, 4, 5, 8, 0);
}

--- --------------------------------------------------------------------------
-- glsl Mesh.Geometry.TriangleTess

layout(triangles) in;
layout(triangle_strip, max_vertices = 3) out;

in VertexData
{
    vec4 Peye;
    vec3 Neye;
} inData[3];

in vec4 tesPatchCoord[3];
in vec2 tesTessCoord[3];

out VertexData
{
    vec4 Peye;
    vec3 Neye;
} outData;

out vec4 gsPatchCoord;
out vec2 gsTessCoord;


vec4 GetPatchCoord(int index)
{
    return tesPatchCoord[index];
}

void emit(int index, vec4 Peye, vec3 Neye,
          vec4 triPeye0, vec4 triPeye1, vec4 triPeye2)
{
    outData.Peye = Peye;
    outData.Neye = Neye;
    gsPatchCoord = GetPatchCoord(index);
    gsTessCoord = vec2(0);
    gl_Position = vec4(GetProjectionMatrix() * outData.Peye);
    ApplyClipPlanes(outData.Peye);

    int inEdgeFlag = 0;
    vec2 dt01 = abs(tesTessCoord[0] - tesTessCoord[1]);
    vec2 dt12 = abs(tesTessCoord[1] - tesTessCoord[2]);
    vec2 dt20 = abs(tesTessCoord[2] - tesTessCoord[0]);
    if (min(dt01.x, dt01.y) > 0.03) inEdgeFlag |= 2;
    if (min(dt12.x, dt12.y) > 0.03) inEdgeFlag |= 4;
    if (min(dt20.x, dt20.y) > 0.03) inEdgeFlag |= 1;

    ProcessTriEdgeDistance(Peye, triPeye0, triPeye1, triPeye2, inEdgeFlag);

    ProcessEdgeId(index, gsPatchCoord.xy);

    ProcessPrimvars(index);

    EmitVertex();
}

void main(void)
{
    gl_PrimitiveID = gl_PrimitiveIDIn;

    bool isFlipped = IsFlipped(); // consider handedness AND negative-scale

    vec3 Neye0 = isFlipped ? -inData[0].Neye : inData[0].Neye;
    Neye0 = GetNormal(Neye0, 0);
    Neye0 = GetTriGeometryNormal(Neye0, inData[0].Peye, inData[1].Peye,
        inData[2].Peye, isFlipped);
    vec3 Neye1 = isFlipped ? -inData[1].Neye : inData[1].Neye;
    Neye1 = GetNormal(Neye1, 0);
    Neye1 = GetTriGeometryNormal(Neye1, inData[0].Peye, inData[1].Peye,
        inData[2].Peye, isFlipped);
    vec3 Neye2 = isFlipped ? -inData[2].Neye : inData[2].Neye;
    Neye2 = GetNormal(Neye2, 0);
    Neye2 = GetTriGeometryNormal(Neye2, inData[0].Peye, inData[1].Peye,
        inData[2].Peye, isFlipped);

    vec4 Peye0 = DisplacementTerminal(
        0, inData[0].Peye, Neye0, GetPatchCoord(0));
    vec4 Peye1 = DisplacementTerminal(
        1, inData[1].Peye, Neye1, GetPatchCoord(1));
    vec4 Peye2 = DisplacementTerminal(
        2, inData[2].Peye, Neye2, GetPatchCoord(2));

    // triangle 0: vertices (0,1,2)
    emit(0, Peye0, Neye0, Peye0, Peye1, Peye2);
    emit(1, Peye1, Neye1, Peye0, Peye1, Peye2);
    emit(2, Peye2, Neye2, Peye0, Peye1, Peye2);

    EndPrimitive();
}

--- --------------------------------------------------------------------------
-- glsl Mesh.Geometry.Triangle

layout(triangles) in;
layout(triangle_strip, max_vertices = 3) out;

in VertexData
{
    vec4 Peye;
    vec3 Neye;
} inData[3];

out VertexData
{
    vec4 Peye;
    vec3 Neye;
} outData;

out vec4 gsPatchCoord;
out vec2 gsTessCoord;

// The OpenSubdiv implementation does not yet provide a utility
// method to interpolate barycentric patch coordinates so we provide
// our own implementation modeled after OsdInterpolatePatchCoord().
// We need to interpolate only two of the three barycentric coordinates.
vec4 InterpolatePatchCoordBarycentric(vec2 localUV, ivec3 patchParam)
{
    ivec4 perPrimPatchCoord = OsdGetPatchCoord(patchParam);
    int faceId = perPrimPatchCoord.w;
    int faceLevel = perPrimPatchCoord.z;
    vec2 faceUV = vec2(perPrimPatchCoord.x, perPrimPatchCoord.y);

    localUV = (faceUV + localUV) / faceLevel;
    if ((faceUV.x + faceUV.y) >= faceLevel) {
        localUV = vec2(1.0f) - localUV;
    }

    // add 0.5 to integer values for more robust interpolation
    return vec4(localUV.x, localUV.y, faceLevel+0.5f, faceId+0.5f);
}

vec4 GetPatchCoord(int index)
{
    vec2 uv[3];
    uv[0] = vec2(0, 0); // (0, 0, 1);
    uv[1] = vec2(1, 0); // (1, 0, 0);
    uv[2] = vec2(0, 1); // (0, 1, 0);

    ivec3 patchParam = GetPatchParam();
    return InterpolatePatchCoordBarycentric(uv[index], patchParam);
}

void emit(int index, vec4 Peye, vec3 Neye,
          vec4 triPeye0, vec4 triPeye1, vec4 triPeye2)
{
    outData.Peye = Peye;
    outData.Neye = Neye;

    gsPatchCoord = GetPatchCoord(index);
    gsTessCoord = vec2(0);

    gl_Position = vec4(GetProjectionMatrix() * outData.Peye);
    ApplyClipPlanes(outData.Peye);

    int inEdgeFlag = 0;
    ProcessTriEdgeDistance(Peye, triPeye0, triPeye1, triPeye2, inEdgeFlag);

    ProcessEdgeId(index, gsPatchCoord.xy);

    ProcessPrimvars(index);

    EmitVertex();
}

void main(void)
{
    gl_PrimitiveID = gl_PrimitiveIDIn;

    bool isFlipped = IsFlipped(); // consider handedness AND negative-scale

    vec3 Neye0 = GetNormal(inData[0].Neye, 0);
    Neye0 = GetTriGeometryNormal(Neye0, inData[0].Peye, inData[1].Peye,
        inData[2].Peye, isFlipped);
    vec3 Neye1 = GetNormal(inData[1].Neye, 1);
    Neye1 = GetTriGeometryNormal(Neye1, inData[0].Peye, inData[1].Peye,
        inData[2].Peye, isFlipped);
    vec3 Neye2 = GetNormal(inData[2].Neye, 2);
    Neye2 = GetTriGeometryNormal(Neye2, inData[0].Peye, inData[1].Peye,
        inData[2].Peye, isFlipped);

    vec4 Peye0 = DisplacementTerminal(
        0, inData[0].Peye, Neye0, GetPatchCoord(0));
    vec4 Peye1 = DisplacementTerminal(
        1, inData[1].Peye, Neye1, GetPatchCoord(1));
    vec4 Peye2 = DisplacementTerminal(
        2, inData[2].Peye, Neye2, GetPatchCoord(2));

    // triangle 0: vertices (0,1,2)
    emit(0, Peye0, Neye0, Peye0, Peye1, Peye2);
    emit(1, Peye1, Neye1, Peye0, Peye1, Peye2);
    emit(2, Peye2, Neye2, Peye0, Peye1, Peye2);

    EndPrimitive();
}

--- --------------------------------------------------------------------------
-- glsl Mesh.Geometry.Quad

layout(lines_adjacency) in;
layout(triangle_strip, max_vertices = 6) out;

in VertexData
{
    vec4 Peye;
    vec3 Neye;
} inData[4];

out VertexData
{
    vec4 Peye;
    vec3 Neye;
} outData;

out vec4 gsPatchCoord;
out vec2 gsTessCoord;

vec4 GetPatchCoord(int index)
{
    vec2 uv[4];
    uv[0] = vec2(0, 0);
    uv[1] = vec2(1, 0);
    uv[2] = vec2(1, 1);
    uv[3] = vec2(0, 1);

    ivec3 patchParam = GetPatchParam();
    return OsdInterpolatePatchCoord(uv[index], patchParam);
}

void emit(int triCounter, int index, vec4 Peye, vec3 Neye,
          vec4 triPeye0, vec4 triPeye1, vec4 triPeye2)
{
    outData.Peye = Peye;
    outData.Neye = Neye;

    gsPatchCoord = GetPatchCoord(index);
    gsTessCoord = vec2(0);

    gl_Position = vec4(GetProjectionMatrix() * outData.Peye);
    ApplyClipPlanes(outData.Peye);

    ProcessQuadEdgeDistance(Peye, triPeye0, triPeye1, triPeye2, triCounter);

    ProcessEdgeId(index, gsPatchCoord.xy);

    ProcessPrimvars(index);

    EmitVertex();
}

void main(void)
{
    gl_PrimitiveID = gl_PrimitiveIDIn;

    bool isFlipped = IsFlipped(); // consider handedness AND negative-scale

    vec3 Neye0 = GetNormal(inData[0].Neye, 0);
    Neye0 = GetQuadGeometryNormal(Neye0, inData[0].Peye, inData[1].Peye,
        inData[2].Peye, inData[3].Peye, isFlipped);
    vec3 Neye1 = GetNormal(inData[1].Neye, 1);
    Neye1 = GetQuadGeometryNormal(Neye1, inData[0].Peye, inData[1].Peye,
        inData[2].Peye, inData[3].Peye, isFlipped);
    vec3 Neye2 = GetNormal(inData[2].Neye, 2);
    Neye2 = GetQuadGeometryNormal(Neye2, inData[0].Peye, inData[1].Peye,
        inData[2].Peye, inData[3].Peye, isFlipped);
    vec3 Neye3 = GetNormal(inData[3].Neye, 3);
    Neye3 = GetQuadGeometryNormal(Neye3, inData[0].Peye, inData[1].Peye,
        inData[2].Peye, inData[3].Peye, isFlipped);

    vec4 Peye0 = DisplacementTerminal(
        0, inData[0].Peye, Neye0, GetPatchCoord(0));
    vec4 Peye1 = DisplacementTerminal(
        1, inData[1].Peye, Neye1, GetPatchCoord(1));
    vec4 Peye2 = DisplacementTerminal(
        2, inData[2].Peye, Neye2, GetPatchCoord(2));
    vec4 Peye3 = DisplacementTerminal(
        3, inData[3].Peye, Neye3, GetPatchCoord(3));

    // Generate triangles (3,0,2) and (2,0,1)
    //  0---3
    //  |.  |
    //  | . |
    //  |  .|
    //  1---2
    // The indices post-quadrangulation/subdivision follow the convention:
    // 0   -> original (hull) vertex
    // 1,3 -> edge vertices
    // 2   -> center vertex
    // 
    // By having index 2 in both the triangles, we ensure the pre-quadrangulated
    // face's normal (at the center) is part of the rasterizer interpolation,
    // which matters when we use smooth/limit normals. 
    // In the case of flat normals, we use the vertex positions, so it doesn't
    // matter.

    // triangle 0: vertices (3,0,2)
    emit(0, 3, Peye3, Neye3, Peye3, Peye0, Peye2);
    emit(0, 0, Peye0, Neye0, Peye3, Peye0, Peye2);
    emit(0, 2, Peye2, Neye2, Peye3, Peye0, Peye2);
    EndPrimitive();

    // triangle 1: vertices (2,0,1)
    emit(1, 2, Peye2, Neye2, Peye2, Peye0, Peye1);
    emit(1, 0, Peye0, Neye0, Peye2, Peye0, Peye1);
    emit(1, 1, Peye1, Neye1, Peye2, Peye0, Peye1);
    EndPrimitive();
}

--- --------------------------------------------------------------------------
-- glsl Mesh.Fragment

in VertexData
{
    vec4 Peye;
    vec3 Neye;
} inData;

in vec4 gsPatchCoord;
in vec2 gsTessCoord;

#ifndef HD_HAS_ptexFaceOffset
#define HD_HAS_ptexFaceOffset
int HdGet_ptexFaceOffset()
{
    return 0;
}
#endif

vec4 GetPatchCoord(int localIndex)
{
    return vec4(gsPatchCoord.xyz,
                gsPatchCoord.w + HdGet_ptexFaceOffset());
}

void main(void)
{
    bool isFlipped = IsFlipped();

    if (ShouldCullFaceAccordingToShading(gl_FrontFacing, isFlipped)) {
        discard;
    }

    if (ShouldDiscardFragmentAccordingToTopology()) {
        discard;
    }

    vec4 color = vec4(0.5, 0.5, 0.5, 1);
#ifdef HD_HAS_displayColor
    color.rgb = HdGet_displayColor().rgb;
#endif
#ifdef HD_HAS_displayOpacity
    color.a = HdGet_displayOpacity().r;
#endif

    vec3 Peye = inData.Peye.xyz / inData.Peye.w;

    vec3 Neye = inData.Neye;
    // Normalize Neye after rasterizer interpolation.
    if (length(Neye) > 0.0) {
        Neye = normalize(Neye);
    }
    // Give the shader key a chance to override the normal.
    Neye = GetNormal(Neye, 0);
    // Orient the normal for shading.
    Neye = GetShadingNormal(Neye, isFlipped);

    vec4 patchCoord = GetPatchCoord();
    color = ShadingTerminal(vec4(Peye, 1), Neye, color, patchCoord);

    color = ApplyEdgeColor(color, patchCoord);

    if (ShouldDiscardByAlpha(color)) {
        discard;
    }

    RenderOutput(vec4(Peye, 1), Neye, color, patchCoord);
}
