//
// Copyright 2014 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//

#include "util/shader_utils.h"

#include <array>
#include <cstring>
#include <fstream>
#include <iostream>
#include <vector>

#include "common/span.h"
#include "common/utilities.h"
#include "util/test_utils.h"

namespace
{
GLuint CompileProgramInternal(const char *vsSource,
                              const char *tcsSource,
                              const char *tesSource,
                              const char *gsSource,
                              const char *fsSource,
                              const std::function<void(GLuint)> &preLinkCallback)
{
    GLuint program = glCreateProgram();

    GLuint vs = CompileShader(GL_VERTEX_SHADER, vsSource);
    GLuint fs = CompileShader(GL_FRAGMENT_SHADER, fsSource);

    if (vs == 0 || fs == 0)
    {
        glDeleteShader(fs);
        glDeleteShader(vs);
        glDeleteProgram(program);
        return 0;
    }

    glAttachShader(program, vs);
    glDeleteShader(vs);

    glAttachShader(program, fs);
    glDeleteShader(fs);

    GLuint tcs = 0;
    GLuint tes = 0;
    GLuint gs  = 0;

    if (strlen(tcsSource) > 0)
    {
        tcs = CompileShader(GL_TESS_CONTROL_SHADER_EXT, tcsSource);
        if (tcs == 0)
        {
            glDeleteShader(vs);
            glDeleteShader(fs);
            glDeleteProgram(program);
            return 0;
        }

        glAttachShader(program, tcs);
        glDeleteShader(tcs);
    }

    if (strlen(tesSource) > 0)
    {
        tes = CompileShader(GL_TESS_EVALUATION_SHADER_EXT, tesSource);
        if (tes == 0)
        {
            glDeleteShader(vs);
            glDeleteShader(fs);
            glDeleteShader(tcs);
            glDeleteProgram(program);
            return 0;
        }

        glAttachShader(program, tes);
        glDeleteShader(tes);
    }

    if (strlen(gsSource) > 0)
    {
        gs = CompileShader(GL_GEOMETRY_SHADER_EXT, gsSource);
        if (gs == 0)
        {
            glDeleteShader(vs);
            glDeleteShader(fs);
            glDeleteShader(tcs);
            glDeleteShader(tes);
            glDeleteProgram(program);
            return 0;
        }

        glAttachShader(program, gs);
        glDeleteShader(gs);
    }

    if (preLinkCallback)
    {
        preLinkCallback(program);
    }

    glLinkProgram(program);

    return CheckLinkStatusAndReturnProgram(program, true);
}

const void *gCallbackChainUserParam;

void KHRONOS_APIENTRY DebugMessageCallback(GLenum source,
                                           GLenum type,
                                           GLuint id,
                                           GLenum severity,
                                           GLsizei length,
                                           const GLchar *message,
                                           const void *userParam)
{
    std::string sourceText   = gl::GetDebugMessageSourceString(source);
    std::string typeText     = gl::GetDebugMessageTypeString(type);
    std::string severityText = gl::GetDebugMessageSeverityString(severity);
    std::cerr << sourceText << ", " << typeText << ", " << severityText << ": " << message << "\n";

    GLDEBUGPROC callbackChain = reinterpret_cast<GLDEBUGPROC>(const_cast<void *>(userParam));
    if (callbackChain)
    {
        callbackChain(source, type, id, severity, length, message, gCallbackChainUserParam);
    }
}

void GetPerfCounterValue(const CounterNameToIndexMap &counterIndexMap,
                         std::vector<angle::PerfMonitorTriplet> &triplets,
                         const char *name,
                         GLuint64 *counterOut)
{
    auto iter = counterIndexMap.find(name);
    ASSERT(iter != counterIndexMap.end());
    GLuint counterIndex = iter->second;

    for (const angle::PerfMonitorTriplet &triplet : triplets)
    {
        ASSERT(triplet.group == 0);
        if (triplet.counter == counterIndex)
        {
            *counterOut = triplet.value;
            return;
        }
    }

    // Additional logs for b/382094011
    std::cerr << "GetPerfCounterValue missing counter: " << name << "; index: " << counterIndex
              << "; triplets: " << std::endl;
    for (const angle::PerfMonitorTriplet &triplet : triplets)
    {
        std::cerr << triplet.counter << " " << triplet.value << std::endl;
    }

    UNREACHABLE();
}
}  // namespace

GLuint CompileShader(GLenum type, const char *source)
{
    GLuint shader = glCreateShader(type);

    const char *sourceArray[1] = {source};
    glShaderSource(shader, 1, sourceArray, nullptr);
    glCompileShader(shader);

    GLint compileResult;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &compileResult);

    if (compileResult == 0)
    {
        GLint infoLogLength;
        glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLogLength);

        // Info log length includes the null terminator, so 1 means that the info log is an empty
        // string.
        if (infoLogLength > 1)
        {
            std::vector<GLchar> infoLog(infoLogLength);
            glGetShaderInfoLog(shader, static_cast<GLsizei>(infoLog.size()), nullptr, &infoLog[0]);
            std::cerr << "shader compilation failed: " << &infoLog[0];
        }
        else
        {
            std::cerr << "shader compilation failed. <Empty log message>";
        }

        std::cerr << std::endl;

        glDeleteShader(shader);
        shader = 0;
    }

    return shader;
}

GLuint CompileShaderFromFile(GLenum type, const std::string &sourcePath)
{
    std::string source;
    if (!angle::ReadEntireFileToString(sourcePath.c_str(), &source))
    {
        std::cerr << "Error reading shader file: " << sourcePath << "\n";
        return 0;
    }

    return CompileShader(type, source.c_str());
}

GLuint CheckLinkStatusAndReturnProgram(GLuint program, bool outputErrorMessages)
{
    if (glGetError() != GL_NO_ERROR)
        return 0;

    GLint linkStatus;
    glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
    if (linkStatus == 0)
    {
        if (outputErrorMessages)
        {
            GLint infoLogLength;
            glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLogLength);

            // Info log length includes the null terminator, so 1 means that the info log is an
            // empty string.
            if (infoLogLength > 1)
            {
                std::vector<GLchar> infoLog(infoLogLength);
                glGetProgramInfoLog(program, static_cast<GLsizei>(infoLog.size()), nullptr,
                                    &infoLog[0]);

                std::cerr << "program link failed: " << &infoLog[0];
            }
            else
            {
                std::cerr << "program link failed. <Empty log message>";
            }
        }

        glDeleteProgram(program);
        return 0;
    }

    return program;
}

GLuint GetProgramShader(GLuint program, GLint requestedType)
{
    GLsizei count                           = 0;
    std::array<GLuint, 16u> attachedShaders = {};
    glGetAttachedShaders(program, attachedShaders.size(), &count, attachedShaders.data());

    for (GLuint shader : angle::Span(attachedShaders).first(count))
    {
        GLint type = 0;
        glGetShaderiv(shader, GL_SHADER_TYPE, &type);
        if (type == requestedType)
        {
            return shader;
        }
    }

    return 0;
}

GLuint CompileProgramWithTransformFeedback(
    const char *vsSource,
    const char *fsSource,
    const std::vector<std::string> &transformFeedbackVaryings,
    GLenum bufferMode)
{
    auto preLink = [&](GLuint program) {
        if (transformFeedbackVaryings.size() > 0)
        {
            std::vector<const char *> constCharTFVaryings;

            for (const std::string &transformFeedbackVarying : transformFeedbackVaryings)
            {
                constCharTFVaryings.push_back(transformFeedbackVarying.c_str());
            }

            glTransformFeedbackVaryings(program,
                                        static_cast<GLsizei>(transformFeedbackVaryings.size()),
                                        &constCharTFVaryings[0], bufferMode);
        }
    };

    return CompileProgramInternal(vsSource, "", "", "", fsSource, preLink);
}

GLuint CompileProgram(const char *vsSource, const char *fsSource)
{
    return CompileProgramInternal(vsSource, "", "", "", fsSource, nullptr);
}

GLuint CompileProgram(const char *vsSource,
                      const char *fsSource,
                      const std::function<void(GLuint)> &preLinkCallback)
{
    return CompileProgramInternal(vsSource, "", "", "", fsSource, preLinkCallback);
}

GLuint CompileProgramWithGS(const char *vsSource, const char *gsSource, const char *fsSource)
{
    return CompileProgramInternal(vsSource, "", "", gsSource, fsSource, nullptr);
}

GLuint CompileProgramWithTESS(const char *vsSource,
                              const char *tcsSource,
                              const char *tesSource,
                              const char *fsSource)
{
    return CompileProgramInternal(vsSource, tcsSource, tesSource, "", fsSource, nullptr);
}

GLuint CompileProgramFromFiles(const std::string &vsPath, const std::string &fsPath)
{
    std::string vsSource;
    if (!angle::ReadEntireFileToString(vsPath.c_str(), &vsSource))
    {
        std::cerr << "Error reading shader: " << vsPath << "\n";
        return 0;
    }

    std::string fsSource;
    if (!angle::ReadEntireFileToString(fsPath.c_str(), &fsSource))
    {
        std::cerr << "Error reading shader: " << fsPath << "\n";
        return 0;
    }

    return CompileProgram(vsSource.c_str(), fsSource.c_str());
}

GLuint CompileComputeProgram(const char *csSource, bool outputErrorMessages)
{
    GLuint program = glCreateProgram();

    GLuint cs = CompileShader(GL_COMPUTE_SHADER, csSource);
    if (cs == 0)
    {
        glDeleteProgram(program);
        return 0;
    }

    glAttachShader(program, cs);

    glLinkProgram(program);

    return CheckLinkStatusAndReturnProgram(program, outputErrorMessages);
}

GLuint LoadBinaryProgramOES(const std::vector<uint8_t> &binary, GLenum binaryFormat)
{
    GLuint program = glCreateProgram();
    glProgramBinaryOES(program, binaryFormat, binary.data(), static_cast<GLint>(binary.size()));
    return CheckLinkStatusAndReturnProgram(program, true);
}

GLuint LoadBinaryProgramES3(const std::vector<uint8_t> &binary, GLenum binaryFormat)
{
    GLuint program = glCreateProgram();
    glProgramBinary(program, binaryFormat, binary.data(), static_cast<GLint>(binary.size()));
    return CheckLinkStatusAndReturnProgram(program, true);
}

bool LinkAttachedProgram(GLuint program)
{
    glLinkProgram(program);
    return (CheckLinkStatusAndReturnProgram(program, true) != 0);
}

void EnableDebugCallback(GLDEBUGPROC callbackChain, const void *userParam)
{
    gCallbackChainUserParam = userParam;

    glEnable(GL_DEBUG_OUTPUT);
    glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
    // Enable medium and high priority messages.
    glDebugMessageControlKHR(GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_HIGH, 0, nullptr,
                             GL_TRUE);
    glDebugMessageControlKHR(GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_MEDIUM, 0, nullptr,
                             GL_TRUE);
    // Disable low and notification priority messages.
    glDebugMessageControlKHR(GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_LOW, 0, nullptr,
                             GL_FALSE);
    glDebugMessageControlKHR(GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_NOTIFICATION, 0, nullptr,
                             GL_FALSE);
    // Disable performance messages to reduce spam.
    glDebugMessageControlKHR(GL_DONT_CARE, GL_DEBUG_TYPE_PERFORMANCE, GL_DONT_CARE, 0, nullptr,
                             GL_FALSE);
    glDebugMessageCallbackKHR(DebugMessageCallback, reinterpret_cast<const void *>(callbackChain));
}

CounterNameToIndexMap BuildCounterNameToIndexMap()
{
    GLint numCounters = 0;
    glGetPerfMonitorCountersAMD(0, &numCounters, nullptr, 0, nullptr);
    if (glGetError() != GL_NO_ERROR)
    {
        std::cerr << "glGetPerfMonitorCountersAMD failed (count)" << std::endl;
        return {};
    }

    std::vector<GLuint> counterIndexes(numCounters, 0);
    glGetPerfMonitorCountersAMD(0, nullptr, nullptr, numCounters, counterIndexes.data());
    if (glGetError() != GL_NO_ERROR)
    {
        std::cerr << "glGetPerfMonitorCountersAMD failed (data)" << std::endl;
        return {};
    }

    CounterNameToIndexMap indexMap;

    for (GLuint counterIndex : counterIndexes)
    {
        static constexpr size_t kBufSize = 1000;
        char buffer[kBufSize]            = {};
        glGetPerfMonitorCounterStringAMD(0, counterIndex, kBufSize, nullptr, buffer);
        if (glGetError() != GL_NO_ERROR)
        {
            std::cerr << "glGetPerfMonitorCounterStringAMD failed" << std::endl;
            return {};
        }

        indexMap[buffer] = counterIndex;
    }

    return indexMap;
}

std::vector<angle::PerfMonitorTriplet> GetPerfMonitorTriplets()
{
    GLuint resultSize = 0;
    glGetPerfMonitorCounterDataAMD(0, GL_PERFMON_RESULT_SIZE_AMD, sizeof(GLuint), &resultSize,
                                   nullptr);
    if (glGetError() != GL_NO_ERROR || resultSize == 0)
    {
        std::cerr << "glGetPerfMonitorCounterDataAMD failed (count)" << std::endl;
        return {};
    }

    std::vector<angle::PerfMonitorTriplet> perfResults(resultSize /
                                                       sizeof(angle::PerfMonitorTriplet));
    GLint bytesWritten = 0;
    glGetPerfMonitorCounterDataAMD(
        0, GL_PERFMON_RESULT_AMD, static_cast<GLsizei>(perfResults.size() * sizeof(perfResults[0])),
        &perfResults.data()->group, &bytesWritten);

    if (glGetError() != GL_NO_ERROR)
    {
        std::cerr << "glGetPerfMonitorCounterDataAMD failed (data)" << std::endl;
        return {};
    }
    ASSERT(static_cast<GLuint>(bytesWritten) == resultSize);

    return perfResults;
}

angle::VulkanPerfCounters GetPerfCounters(const CounterNameToIndexMap &indexMap)
{
    std::vector<angle::PerfMonitorTriplet> perfResults = GetPerfMonitorTriplets();

    angle::VulkanPerfCounters counters;

#define ANGLE_UNPACK_PERF_COUNTER(COUNTER) \
    GetPerfCounterValue(indexMap, perfResults, #COUNTER, &counters.COUNTER);

    ANGLE_VK_PERF_COUNTERS_X(ANGLE_UNPACK_PERF_COUNTER)

#undef ANGLE_UNPACK_PERF_COUNTER

    return counters;
}

CounterNameToValueMap BuildCounterNameToValueMap()
{
    CounterNameToIndexMap indexMap                     = BuildCounterNameToIndexMap();
    std::vector<angle::PerfMonitorTriplet> perfResults = GetPerfMonitorTriplets();

    CounterNameToValueMap valueMap;

    for (const auto &iter : indexMap)
    {
        const std::string &name = iter.first;
        GLuint index            = iter.second;

        valueMap[name] = perfResults[index].value;
    }

    return valueMap;
}

namespace angle
{

namespace essl1_shaders
{

const char *PositionAttrib()
{
    return "a_position";
}
const char *ColorUniform()
{
    return "u_color";
}

const char *Texture2DUniform()
{
    return "u_tex2D";
}

namespace vs
{

// A shader that sets gl_Position to zero.
const char *Zero()
{
    return R"(void main()
{
    gl_Position = vec4(0);
})";
}

// A shader that sets gl_Position to attribute a_position.
const char *Simple()
{
    return R"(precision highp float;
attribute vec4 a_position;

void main()
{
    gl_Position = a_position;
})";
}

// A shader that sets gl_Position to attribute a_position, and sets gl_PointSize to 1.
const char *SimpleForPoints()
{
    return R"(precision highp float;
attribute vec4 a_position;

void main()
{
    gl_Position = a_position;
    gl_PointSize = 1.0;
})";
}

// A shader that simply passes through attribute a_position, setting it to gl_Position and varying
// v_position.
const char *Passthrough()
{
    return R"(precision highp float;
attribute vec4 a_position;
varying vec4 v_position;

void main()
{
    gl_Position = a_position;
    v_position = a_position;
})";
}

// A shader that simply passes through attribute a_position, setting it to gl_Position and varying
// texcoord.
const char *Texture2D()
{
    return R"(precision highp float;
attribute vec4 a_position;
varying vec2 v_texCoord;

void main()
{
    gl_Position = a_position;
    v_texCoord = a_position.xy * 0.5 + vec2(0.5);
})";
}

}  // namespace vs

namespace fs
{

// A shader that renders a simple checker pattern of red and green. X axis and y axis separate the
// different colors. Needs varying v_position.
const char *Checkered()
{
    return R"(precision highp float;
varying vec4 v_position;

void main()
{
    bool isLeft = v_position.x < 0.0;
    bool isTop = v_position.y < 0.0;
    if (isLeft)
    {
        if (isTop)
        {
            gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
        }
        else
        {
            gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
        }
    }
    else
    {
        if (isTop)
        {
            gl_FragColor = vec4(0.0, 0.0, 1.0, 1.0);
        }
        else
        {
            gl_FragColor = vec4(1.0, 1.0, 0.0, 1.0);
        }
    }
})";
}

// A shader that fills with color taken from uniform named "color".
const char *UniformColor()
{
    return R"(uniform mediump vec4 u_color;
void main(void)
{
    gl_FragColor = u_color;
})";
}

// A shader that fills with 100% opaque red.
const char *Red()
{
    return R"(precision mediump float;

void main()
{
    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
})";
}

// A shader that fills with 100% opaque green.
const char *Green()
{
    return R"(precision mediump float;

void main()
{
    gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
})";
}

// A shader that fills with 100% opaque blue.
const char *Blue()
{
    return R"(precision mediump float;

void main()
{
    gl_FragColor = vec4(0.0, 0.0, 1.0, 1.0);
})";
}

// A shader that samples the texture.
const char *Texture2D()
{
    return R"(precision mediump float;
uniform sampler2D u_tex2D;
varying vec2 v_texCoord;

void main()
{
    gl_FragColor = texture2D(u_tex2D, v_texCoord);
})";
}

}  // namespace fs
}  // namespace essl1_shaders

namespace essl3_shaders
{

const char *PositionAttrib()
{
    return "a_position";
}
const char *Texture2DUniform()
{
    return "u_tex2D";
}
const char *LodUniform()
{
    return "u_lod";
}

namespace vs
{

// A shader that sets gl_Position to zero.
const char *Zero()
{
    return R"(#version 300 es
void main()
{
    gl_Position = vec4(0);
})";
}

// A shader that sets gl_Position to attribute a_position.
const char *Simple()
{
    return R"(#version 300 es
in vec4 a_position;
void main()
{
    gl_Position = a_position;
})";
}

// A shader that sets gl_Position to attribute a_position, and sets gl_PointSize to 1.
const char *SimpleForPoints()
{
    return R"(#version 300 es
in vec4 a_position;
void main()
{
    gl_Position = a_position;
    gl_PointSize = 1.0;
})";
}

// A shader that simply passes through attribute a_position, setting it to gl_Position and varying
// v_position.
const char *Passthrough()
{
    return R"(#version 300 es
in vec4 a_position;
out vec4 v_position;
void main()
{
    gl_Position = a_position;
    v_position = a_position;
})";
}

// A shader that simply passes through attribute a_position, setting it to gl_Position and varying
// texcoord.
const char *Texture2DLod()
{
    return R"(#version 300 es
in vec4 a_position;
out vec2 v_texCoord;

void main()
{
    gl_Position = vec4(a_position.xy, 0.0, 1.0);
    v_texCoord = a_position.xy * 0.5 + vec2(0.5);
})";
}

const char *Texture2DArray()
{
    return R"(#version 300 es
out vec2 v_texCoord;
in vec4 a_position;
void main()
{
    gl_Position = vec4(a_position.xy, 0.0, 1.0);
    v_texCoord = (a_position.xy * 0.5) + 0.5;
})";
}

}  // namespace vs

namespace fs
{

// A shader that fills with 100% opaque red.
const char *Red()
{
    return R"(#version 300 es
precision highp float;
out vec4 my_FragColor;
void main()
{
    my_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
})";
}

// A shader that fills with 100% opaque green.
const char *Green()
{
    return R"(#version 300 es
precision highp float;
out vec4 my_FragColor;
void main()
{
    my_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
})";
}

// A shader that fills with 100% opaque blue.
const char *Blue()
{
    return R"(#version 300 es
precision highp float;
out vec4 my_FragColor;
void main()
{
    my_FragColor = vec4(0.0, 0.0, 1.0, 1.0);
})";
}

// A shader that samples the texture at a given lod.
const char *Texture2DLod()
{
    return R"(#version 300 es
precision mediump float;
uniform sampler2D u_tex2D;
uniform float u_lod;
in vec2 v_texCoord;
out vec4 my_FragColor;

void main()
{
    my_FragColor = textureLod(u_tex2D, v_texCoord, u_lod);
})";
}

const char *Texture2DArray()
{
    return R"(#version 300 es
precision highp float;
uniform highp sampler2DArray tex2DArray;
uniform int slice;
in vec2 v_texCoord;
out vec4 fragColor;
void main()
{
    fragColor = texture(tex2DArray, vec3(v_texCoord, float(slice)));
})";
}

}  // namespace fs
}  // namespace essl3_shaders

namespace essl31_shaders
{

const char *PositionAttrib()
{
    return "a_position";
}

namespace vs
{

// A shader that sets gl_Position to zero.
const char *Zero()
{
    return R"(#version 310 es
void main()
{
    gl_Position = vec4(0);
})";
}

// A shader that sets gl_Position to attribute a_position.
const char *Simple()
{
    return R"(#version 310 es
in vec4 a_position;
void main()
{
    gl_Position = a_position;
})";
}

// A shader that simply passes through attribute a_position, setting it to gl_Position and varying
// v_position.
const char *Passthrough()
{
    return R"(#version 310 es
in vec4 a_position;
out vec4 v_position;
void main()
{
    gl_Position = a_position;
    v_position = a_position;
})";
}

}  // namespace vs

namespace fs
{

// A shader that fills with 100% opaque red.
const char *Red()
{
    return R"(#version 310 es
precision highp float;
out vec4 my_FragColor;
void main()
{
    my_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
})";
}

// A shader that fills with 100% opaque green.
const char *Green()
{
    return R"(#version 310 es
precision highp float;
out vec4 my_FragColor;
void main()
{
    my_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
})";
}

// A shader that renders a simple gradient of red to green. Needs varying v_position.
const char *RedGreenGradient()
{
    return R"(#version 310 es
precision highp float;
in vec4 v_position;
out vec4 my_FragColor;

void main()
{
    my_FragColor = vec4(v_position.xy * 0.5 + vec2(0.5), 0.0, 1.0);
})";
}

}  // namespace fs
}  // namespace essl31_shaders
}  // namespace angle
