/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 3.1 Module
 * -------------------------------------------------
 *
 * Copyright 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Negative Shader Storage Tests
 *//*--------------------------------------------------------------------*/

#include "es31fNegativeShaderStorageTests.hpp"

#include "gluShaderProgram.hpp"
#include "glwDefs.hpp"
#include "glwEnums.hpp"

namespace deqp
{
namespace gles31
{
namespace Functional
{
namespace NegativeTestShared
{
namespace
{

void verifyProgram(NegativeTestContext &ctx, glu::ProgramSources sources)
{
    tcu::TestLog &log = ctx.getLog();
    const glu::ShaderProgram program(ctx.getRenderContext(), sources);
    bool testFailed = false;

    log << program;

    testFailed = program.getProgramInfo().linkOk;

    if (testFailed)
    {
        const char *const message("Program was not expected to link.");
        log << tcu::TestLog::Message << message << tcu::TestLog::EndMessage;
        ctx.fail(message);
    }
}

const char *getShaderExtensionDeclaration(glw::GLenum glShaderType)
{
    switch (glShaderType)
    {
    case GL_TESS_CONTROL_SHADER:
    case GL_TESS_EVALUATION_SHADER:
        return "#extension GL_EXT_tessellation_shader : require\n";
    case GL_GEOMETRY_SHADER:
        return "#extension GL_EXT_geometry_shader : require\n";
    default:
        return "";
    }
}

glu::ShaderType getGLUShaderType(glw::GLenum glShaderType)
{
    switch (glShaderType)
    {
    case GL_VERTEX_SHADER:
        return glu::SHADERTYPE_VERTEX;
    case GL_FRAGMENT_SHADER:
        return glu::SHADERTYPE_FRAGMENT;
    case GL_TESS_CONTROL_SHADER:
        return glu::SHADERTYPE_TESSELLATION_CONTROL;
    case GL_TESS_EVALUATION_SHADER:
        return glu::SHADERTYPE_TESSELLATION_EVALUATION;
    case GL_GEOMETRY_SHADER:
        return glu::SHADERTYPE_GEOMETRY;
    case GL_COMPUTE_SHADER:
        return glu::SHADERTYPE_COMPUTE;
    default:
        DE_FATAL("Unknown shader type");
        return glu::SHADERTYPE_LAST;
    }
}

glw::GLenum getMaxSSBlockSizeEnum(glw::GLenum glShaderType)
{
    switch (glShaderType)
    {
    case GL_VERTEX_SHADER:
        return GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS;
    case GL_FRAGMENT_SHADER:
        return GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS;
    case GL_TESS_CONTROL_SHADER:
        return GL_MAX_TESS_CONTROL_SHADER_STORAGE_BLOCKS;
    case GL_TESS_EVALUATION_SHADER:
        return GL_MAX_TESS_EVALUATION_SHADER_STORAGE_BLOCKS;
    case GL_GEOMETRY_SHADER:
        return GL_MAX_GEOMETRY_SHADER_STORAGE_BLOCKS;
    case GL_COMPUTE_SHADER:
        return GL_MAX_COMPUTE_SHADER_STORAGE_BLOCKS;
    default:
        DE_FATAL("Unknown shader type");
        return -1;
    }
}

int getMaxSSBlockSize(NegativeTestContext &ctx, glw::GLenum glShaderType)
{
    int maxSSBlocks = 0;
    ctx.glGetIntegerv(getMaxSSBlockSizeEnum(glShaderType), &maxSSBlocks);

    return maxSSBlocks;
}

std::string genBlockSource(NegativeTestContext &ctx, int64_t numSSBlocks, glw::GLenum shaderType)
{
    const bool isES32              = contextSupports(ctx.getRenderContext().getType(), glu::ApiType::es(3, 2));
    const glu::GLSLVersion version = isES32 ? glu::GLSL_VERSION_320_ES : glu::GLSL_VERSION_310_ES;
    std::ostringstream source;

    source << glu::getGLSLVersionDeclaration(version) << "\n"
           << ((isES32) ? "" : getShaderExtensionDeclaration(shaderType));

    switch (shaderType)
    {
    case GL_VERTEX_SHADER:
    case GL_FRAGMENT_SHADER:
        break;

    case GL_COMPUTE_SHADER:
        source << "layout (local_size_x = 1) in;\n";
        break;

    case GL_GEOMETRY_SHADER:
        source << "layout(points) in;\n"
               << "layout(line_strip, max_vertices = 3) out;\n";
        break;

    case GL_TESS_CONTROL_SHADER:
        source << "layout(vertices = 10) out;\n";
        break;

    case GL_TESS_EVALUATION_SHADER:
        source << "layout(triangles) in;\n";
        break;

    default:
        DE_FATAL("Unknown shader type");
        break;
    }

    source << "\n"
           << "layout(std430, binding = 0) buffer Block {\n"
           << "    int value;\n"
           << "} sb_in[" << numSSBlocks << "];\n"
           << "void main(void) { sb_in[0].value = 1; }\n";

    return source.str();
}

std::string genCommonSource(NegativeTestContext &ctx, glw::GLenum shaderType)
{
    const bool isES32              = contextSupports(ctx.getRenderContext().getType(), glu::ApiType::es(3, 2));
    const glu::GLSLVersion version = isES32 ? glu::GLSL_VERSION_320_ES : glu::GLSL_VERSION_310_ES;
    std::ostringstream source;

    source << glu::getGLSLVersionDeclaration(version) << "\n"
           << ((isES32) ? "" : getShaderExtensionDeclaration(shaderType));

    switch (shaderType)
    {
    case GL_TESS_CONTROL_SHADER:
        source << "layout(vertices = 3) out;\n"
               << "void main() {}\n";
        break;

    case GL_TESS_EVALUATION_SHADER:
        source << "layout(triangles, equal_spacing, cw) in;\n"
               << "void main() {}\n";
        break;

    default:
        source << "void main() {}\n";
        break;
    }

    return source.str();
}

int genMaxSSBlocksSource(NegativeTestContext &ctx, glw::GLenum glShaderType, glu::ProgramSources &sources)
{
    int maxSSBlocks             = getMaxSSBlockSize(ctx, glShaderType);
    const std::string shaderSrc = genBlockSource(ctx, (maxSSBlocks), glShaderType);

    sources.sources[getGLUShaderType(glShaderType)].push_back(shaderSrc);

    return maxSSBlocks;
}

void block_number_limits(NegativeTestContext &ctx)
{
    const glw::GLenum glShaderTypes[] = {
        GL_VERTEX_SHADER,          GL_FRAGMENT_SHADER, GL_TESS_CONTROL_SHADER,
        GL_TESS_EVALUATION_SHADER, GL_GEOMETRY_SHADER, GL_COMPUTE_SHADER,
    };

    const std::string vertSource        = genCommonSource(ctx, GL_VERTEX_SHADER);
    const std::string fragSource        = genCommonSource(ctx, GL_FRAGMENT_SHADER);
    const std::string tessControlSource = genCommonSource(ctx, GL_TESS_CONTROL_SHADER);
    const std::string tessEvalSource    = genCommonSource(ctx, GL_TESS_EVALUATION_SHADER);

    for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(glShaderTypes); ndx++)
    {
        ctx.beginSection("maxShaderStorageBlocks: Exceed limits");

        if (!ctx.isShaderSupported(static_cast<glu::ShaderType>(getGLUShaderType(glShaderTypes[ndx]))))
        {
            ctx.endSection();
            continue;
        }

        int maxSSBlocks    = getMaxSSBlockSize(ctx, glShaderTypes[ndx]);
        std::string source = genBlockSource(ctx, maxSSBlocks + 1, glShaderTypes[ndx]);

        glu::ProgramSources sources;

        if (maxSSBlocks == 0)
        {
            ctx.endSection();
            continue;
        }

        switch (glShaderTypes[ndx])
        {
        case GL_VERTEX_SHADER:
            sources << glu::VertexSource(source) << glu::FragmentSource(fragSource);
            break;

        case GL_FRAGMENT_SHADER:
            sources << glu::VertexSource(vertSource) << glu::FragmentSource(source);
            break;

        case GL_TESS_CONTROL_SHADER:
            sources << glu::VertexSource(vertSource) << glu::FragmentSource(fragSource)
                    << glu::TessellationControlSource(source) << glu::TessellationEvaluationSource(tessEvalSource);
            break;

        case GL_TESS_EVALUATION_SHADER:
            sources << glu::VertexSource(vertSource) << glu::FragmentSource(fragSource)
                    << glu::TessellationControlSource(tessControlSource) << glu::TessellationEvaluationSource(source);
            break;

        case GL_GEOMETRY_SHADER:
            sources << glu::VertexSource(vertSource) << glu::FragmentSource(fragSource) << glu::GeometrySource(source);
            break;

        case GL_COMPUTE_SHADER:
            sources << glu::ComputeSource(source);
            break;

        default:
            DE_FATAL("Unknown shader type");
            break;
        }

        verifyProgram(ctx, sources);
        ctx.endSection();
    }
}

void max_combined_block_number_limit(NegativeTestContext &ctx)
{
    ctx.beginSection("maxCombinedShaderStorageBlocks: Exceed limits");

    glu::ProgramSources sources;

    int combinedSSBlocks    = 0;
    int maxCombinedSSBlocks = 0;

    combinedSSBlocks += genMaxSSBlocksSource(ctx, GL_VERTEX_SHADER, sources);
    combinedSSBlocks += genMaxSSBlocksSource(ctx, GL_FRAGMENT_SHADER, sources);

    if ((ctx.isShaderSupported(glu::SHADERTYPE_TESSELLATION_CONTROL)) &&
        (ctx.isShaderSupported(glu::SHADERTYPE_TESSELLATION_EVALUATION)))
    {
        combinedSSBlocks += genMaxSSBlocksSource(ctx, GL_TESS_CONTROL_SHADER, sources);
        combinedSSBlocks += genMaxSSBlocksSource(ctx, GL_TESS_EVALUATION_SHADER, sources);
    }

    if (ctx.isShaderSupported(glu::SHADERTYPE_GEOMETRY))
        combinedSSBlocks += genMaxSSBlocksSource(ctx, GL_GEOMETRY_SHADER, sources);

    ctx.glGetIntegerv(GL_MAX_COMBINED_SHADER_STORAGE_BLOCKS, &maxCombinedSSBlocks);

    ctx.getLog() << tcu::TestLog::Message << "GL_MAX_COMBINED_SHADER_STORAGE_BLOCKS: " << maxCombinedSSBlocks
                 << tcu::TestLog::EndMessage;
    ctx.getLog() << tcu::TestLog::Message << "Combined shader storage blocks: " << combinedSSBlocks
                 << tcu::TestLog::EndMessage;

    if (combinedSSBlocks > maxCombinedSSBlocks)
        verifyProgram(ctx, sources);
    else
        ctx.getLog() << tcu::TestLog::Message
                     << "Test skipped: Combined shader storage blocks < GL_MAX_COMBINED_SHADER_STORAGE_BLOCKS: "
                     << tcu::TestLog::EndMessage;

    ctx.endSection();
}

} // namespace

std::vector<FunctionContainer> getNegativeShaderStorageTestFunctions()
{
    const FunctionContainer funcs[] = {
        {block_number_limits, "block_number_limits", "Invalid shader linkage"},
        {max_combined_block_number_limit, "max_combined_block_number_limit", "Invalid shader linkage"},
    };

    return std::vector<FunctionContainer>(DE_ARRAY_BEGIN(funcs), DE_ARRAY_END(funcs));
}

} // namespace NegativeTestShared
} // namespace Functional
} // namespace gles31
} // namespace deqp
