//
// Copyright 2021 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.
//
// glslang_wrapper:
//   A wrapper to compile GLSL strings to SPIR-V blobs.  glslang here refers to the Khronos
//   compiler.
//
//   This file is separated as glslang's header contains conflicting macro definitions with ANGLE's.
//

#include "common/spirv/spirv_types.h"
#include "compiler/translator/glslang_wrapper.h"

// glslang has issues with some specific warnings.
ANGLE_DISABLE_SHADOWING_WARNING
ANGLE_DISABLE_SUGGEST_OVERRIDE_WARNINGS

// glslang's version of ShaderLang.h, not to be confused with ANGLE's.
#include <glslang/Public/ShaderLang.h>

// Other glslang includes.
#include <SPIRV/GlslangToSpv.h>
#include <StandAlone/ResourceLimits.h>

// SPIR-V tools include for disassembly
#include <spirv-tools/libspirv.hpp>

// Enable this for debug logging of pre-transform SPIR-V:
#if !defined(ANGLE_DEBUG_SPIRV_GENERATION)
#    define ANGLE_DEBUG_SPIRV_GENERATION 0
#endif  // !defined(ANGLE_DEBUG_SPIRV_GENERATION)

ANGLE_REENABLE_SUGGEST_OVERRIDE_WARNINGS
ANGLE_REENABLE_SHADOWING_WARNING

namespace sh
{
namespace
{
// Run at startup to warm up glslang's internals to avoid hitches on first shader compile.
void Warmup()
{
    EShMessages messages = static_cast<EShMessages>(EShMsgSpvRules | EShMsgVulkanRules);

    const TBuiltInResource builtInResources(glslang::DefaultTBuiltInResource);
    glslang::TShader warmUpShader(EShLangVertex);

    const char *kShaderString = R"(#version 450 core
        void main(){}
    )";
    const int kShaderLength   = static_cast<int>(strlen(kShaderString));

    warmUpShader.setStringsWithLengths(&kShaderString, &kShaderLength, 1);
    warmUpShader.setEntryPoint("main");

    bool result = warmUpShader.parse(&builtInResources, 450, ECoreProfile, false, false, messages);
    ASSERT(result);
}

// Generate glslang resources from ANGLE translator resources.
void GetBuiltInResources(const ShBuiltInResources &resources, TBuiltInResource *outResources)
{
    outResources->maxDrawBuffers                   = resources.MaxDrawBuffers;
    outResources->maxAtomicCounterBindings         = resources.MaxAtomicCounterBindings;
    outResources->maxAtomicCounterBufferSize       = resources.MaxAtomicCounterBufferSize;
    outResources->maxCombinedAtomicCounterBuffers  = resources.MaxCombinedAtomicCounterBuffers;
    outResources->maxCombinedAtomicCounters        = resources.MaxCombinedAtomicCounters;
    outResources->maxCombinedImageUniforms         = resources.MaxCombinedImageUniforms;
    outResources->maxCombinedTextureImageUnits     = resources.MaxCombinedTextureImageUnits;
    outResources->maxCombinedShaderOutputResources = resources.MaxCombinedShaderOutputResources;
    outResources->maxComputeWorkGroupCountX        = resources.MaxComputeWorkGroupCount[0];
    outResources->maxComputeWorkGroupCountY        = resources.MaxComputeWorkGroupCount[1];
    outResources->maxComputeWorkGroupCountZ        = resources.MaxComputeWorkGroupCount[2];
    outResources->maxComputeWorkGroupSizeX         = resources.MaxComputeWorkGroupSize[0];
    outResources->maxComputeWorkGroupSizeY         = resources.MaxComputeWorkGroupSize[1];
    outResources->maxComputeWorkGroupSizeZ         = resources.MaxComputeWorkGroupSize[2];
    outResources->minProgramTexelOffset            = resources.MinProgramTexelOffset;
    outResources->maxFragmentUniformVectors        = resources.MaxFragmentUniformVectors;
    outResources->maxGeometryInputComponents       = resources.MaxGeometryInputComponents;
    outResources->maxGeometryOutputComponents      = resources.MaxGeometryOutputComponents;
    outResources->maxGeometryOutputVertices        = resources.MaxGeometryOutputVertices;
    outResources->maxGeometryTotalOutputComponents = resources.MaxGeometryTotalOutputComponents;
    outResources->maxPatchVertices                 = resources.MaxPatchVertices;
    outResources->maxProgramTexelOffset            = resources.MaxProgramTexelOffset;
    outResources->maxVaryingVectors                = resources.MaxVaryingVectors;
    outResources->maxVertexUniformVectors          = resources.MaxVertexUniformVectors;
    outResources->maxClipDistances                 = resources.MaxClipDistances;
    outResources->maxSamples                       = resources.MaxSamples;
    outResources->maxCullDistances                 = resources.MaxCullDistances;
    outResources->maxCombinedClipAndCullDistances  = resources.MaxCombinedClipAndCullDistances;
}
}  // anonymous namespace

void GlslangInitialize()
{
    int result = ShInitialize();
    ASSERT(result != 0);
    Warmup();
}

void GlslangFinalize()
{
    int result = ShFinalize();
    ASSERT(result != 0);
}

ANGLE_NO_DISCARD bool GlslangCompileToSpirv(const ShBuiltInResources &resources,
                                            sh::GLenum shaderType,
                                            const std::string &shaderSource,
                                            angle::spirv::Blob *spirvBlobOut)
{
    TBuiltInResource builtInResources(glslang::DefaultTBuiltInResource);
    GetBuiltInResources(resources, &builtInResources);

    EShLanguage language = EShLangVertex;

    switch (shaderType)
    {
        case GL_VERTEX_SHADER:
            language = EShLangVertex;
            break;
        case GL_TESS_CONTROL_SHADER:
            language = EShLangTessControl;
            break;
        case GL_TESS_EVALUATION_SHADER_EXT:
            language = EShLangTessEvaluation;
            break;
        case GL_GEOMETRY_SHADER:
            language = EShLangGeometry;
            break;
        case GL_FRAGMENT_SHADER:
            language = EShLangFragment;
            break;
        case GL_COMPUTE_SHADER:
            language = EShLangCompute;
            break;
        default:
            UNREACHABLE();
    }

    glslang::TShader shader(language);
    glslang::TProgram program;

    // Enable SPIR-V and Vulkan rules when parsing GLSL
    constexpr EShMessages messages = static_cast<EShMessages>(EShMsgSpvRules | EShMsgVulkanRules);

    const char *shaderString = shaderSource.c_str();
    int shaderLength         = static_cast<int>(shaderSource.size());

    shader.setStringsWithLengths(&shaderString, &shaderLength, 1);
    shader.setEntryPoint("main");

#if ANGLE_DEBUG_SPIRV_GENERATION
    fprintf(stderr, "%s\n", shaderString);
#endif  // ANGLE_DEBUG_SPIRV_GENERATION

    bool result = shader.parse(&builtInResources, 450, ECoreProfile, false, false, messages);
    if (!result)
    {
        WARN() << "Internal error parsing Vulkan shader corresponding to " << shaderType << ":\n"
               << shader.getInfoLog() << "\n"
               << shader.getInfoDebugLog() << "\n";
        return false;
    }

    program.addShader(&shader);

    bool linkResult = program.link(messages);
    if (!linkResult)
    {
        WARN() << "Internal error linking Vulkan shader:\n" << program.getInfoLog() << "\n";
    }

    glslang::TIntermediate *intermediate = program.getIntermediate(language);
    glslang::GlslangToSpv(*intermediate, *spirvBlobOut);

#if ANGLE_DEBUG_SPIRV_GENERATION
    spvtools::SpirvTools spirvTools(SPV_ENV_VULKAN_1_1);
    std::string readableSpirv;
    spirvTools.Disassemble(*spirvBlobOut, &readableSpirv, 0);
    fprintf(stderr, "%s\n", readableSpirv.c_str());
#endif  // ANGLE_DEBUG_SPIRV_GENERATION

    return true;
}
}  // namespace sh
