/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 3.1 Module
 * -------------------------------------------------
 *
 * Copyright 2015 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 Atomic Counter Tests
 *//*--------------------------------------------------------------------*/

#include "es31fNegativeAtomicCounterTests.hpp"

#include "deUniquePtr.hpp"

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

#include "tcuTestLog.hpp"

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

enum TestCase
{
    TESTCASE_LAYOUT_LARGE_BINDING = 0,
    TESTCASE_LAYOUT_MEDIUMP_PRECISION,
    TESTCASE_LAYOUT_LOWP_PRECISION,
    TESTCASE_LAYOUT_BINDING_OFFSET_OVERLAP,
    TESTCASE_LAYOUT_BINDING_OMITTED,
    TESTCASE_STRUCT,
    TESTCASE_BODY_WRITE,
    TESTCASE_BODY_DECLARE,

    TESTCASE_LAST
};

static const glu::ShaderType s_shaders[] = {glu::SHADERTYPE_VERTEX,
                                            glu::SHADERTYPE_FRAGMENT,
                                            glu::SHADERTYPE_GEOMETRY,
                                            glu::SHADERTYPE_TESSELLATION_CONTROL,
                                            glu::SHADERTYPE_TESSELLATION_EVALUATION,
                                            glu::SHADERTYPE_COMPUTE};

std::string genShaderSource(NegativeTestContext &ctx, TestCase test, glu::ShaderType type)
{
    DE_ASSERT(test < TESTCASE_LAST && type < glu::SHADERTYPE_LAST);

    glw::GLint maxBuffers = -1;
    std::ostringstream shader;

    ctx.glGetIntegerv(GL_MAX_ATOMIC_COUNTER_BUFFER_BINDINGS, &maxBuffers);

    shader << getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES) << "\n";

    switch (type)
    {
    case glu::SHADERTYPE_GEOMETRY:
        shader << "#extension GL_EXT_geometry_shader : enable\n";
        shader << "layout(max_vertices = 3) out;\n";
        break;

    case glu::SHADERTYPE_TESSELLATION_CONTROL:
    case glu::SHADERTYPE_TESSELLATION_EVALUATION:
        shader << "#extension GL_EXT_tessellation_shader : enable\n";
        break;

    default:
        break;
    }

    switch (test)
    {
    case TESTCASE_LAYOUT_LARGE_BINDING:
        shader << "layout (binding = " << maxBuffers << ", offset = 0) uniform atomic_uint counter0;\n";
        break;

    case TESTCASE_LAYOUT_MEDIUMP_PRECISION:
        shader << "layout (binding = 1, offset = 0) " << glu::getPrecisionName(glu::PRECISION_MEDIUMP)
               << " uniform atomic_uint counter0;\n";
        break;

    case TESTCASE_LAYOUT_LOWP_PRECISION:
        shader << "layout (binding = 1, offset = 0) " << glu::getPrecisionName(glu::PRECISION_LOWP)
               << " uniform atomic_uint counter0;\n";
        break;

    case TESTCASE_LAYOUT_BINDING_OFFSET_OVERLAP:
        shader << "layout (binding = 1, offset = 0) uniform atomic_uint counter0;\n"
               << "layout (binding = 1, offset = 2) uniform atomic_uint counter1;\n";
        break;

    case TESTCASE_LAYOUT_BINDING_OMITTED:
        shader << "layout (offset = 0) uniform atomic_uint counter0;\n";
        break;

    case TESTCASE_STRUCT:
        shader << "struct\n"
               << "{\n"
               << "  int a;\n"
               << "  atomic_uint counter;\n"
               << "} S;\n";
        break;

    case TESTCASE_BODY_WRITE:
        shader << "layout (binding = 1) uniform atomic_uint counter;\n";
        break;

    default:
        break;
    }

    shader << "void main (void)\n"
           << "{\n";

    switch (test)
    {
    case TESTCASE_BODY_WRITE:
        shader << "counter = 1;\n";
        break;

    case TESTCASE_BODY_DECLARE:
        shader << "atomic_uint counter;\n";
        break;

    default:
        break;
    }

    shader << "}\n";

    return shader.str();
}

void iterateShaders(NegativeTestContext &ctx, TestCase testCase)
{
    tcu::TestLog &log = ctx.getLog();
    for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(s_shaders); ndx++)
    {
        if (ctx.isShaderSupported(s_shaders[ndx]))
        {
            ctx.beginSection(std::string("Verify shader: ") + glu::getShaderTypeName(s_shaders[ndx]));
            const glu::ShaderProgram program(ctx.getRenderContext(),
                                             glu::ProgramSources() << glu::ShaderSource(
                                                 s_shaders[ndx], genShaderSource(ctx, testCase, s_shaders[ndx])));
            if (program.getShaderInfo(s_shaders[ndx]).compileOk)
            {
                log << program;
                log << tcu::TestLog::Message << "Expected program to fail, but compilation passed."
                    << tcu::TestLog::EndMessage;
                ctx.fail("Shader was not expected to compile.");
            }
            ctx.endSection();
        }
    }
}

void atomic_max_counter_bindings(NegativeTestContext &ctx)
{
    ctx.beginSection("It is a compile-time error to bind an atomic counter with a binding value greater than or equal "
                     "to gl_MaxAtomicCounterBindings.");
    iterateShaders(ctx, TESTCASE_LAYOUT_LARGE_BINDING);
    ctx.endSection();
}

void atomic_precision(NegativeTestContext &ctx)
{
    ctx.beginSection("It is an error to declare an atomic type with a lowp or mediump precision.");
    iterateShaders(ctx, TESTCASE_LAYOUT_MEDIUMP_PRECISION);
    iterateShaders(ctx, TESTCASE_LAYOUT_LOWP_PRECISION);
    ctx.endSection();
}

void atomic_binding_offset_overlap(NegativeTestContext &ctx)
{
    ctx.beginSection("Atomic counters may not have overlapping offsets in the same binding.");
    iterateShaders(ctx, TESTCASE_LAYOUT_BINDING_OFFSET_OVERLAP);
    ctx.endSection();
}

void atomic_binding_omitted(NegativeTestContext &ctx)
{
    ctx.beginSection("Atomic counters must specify a binding point");
    iterateShaders(ctx, TESTCASE_LAYOUT_BINDING_OMITTED);
    ctx.endSection();
}

void atomic_struct(NegativeTestContext &ctx)
{
    ctx.beginSection("Structures may not have an atomic_uint variable.");
    iterateShaders(ctx, TESTCASE_STRUCT);
    ctx.endSection();
}

void atomic_body_write(NegativeTestContext &ctx)
{
    ctx.beginSection("An atomic_uint variable cannot be directly written to.");
    iterateShaders(ctx, TESTCASE_BODY_WRITE);
    ctx.endSection();
}

void atomic_body_declare(NegativeTestContext &ctx)
{
    ctx.beginSection("An atomic_uint variable cannot be declared in local scope");
    iterateShaders(ctx, TESTCASE_BODY_DECLARE);
    ctx.endSection();
}

} // namespace

std::vector<FunctionContainer> getNegativeAtomicCounterTestFunctions()
{
    const FunctionContainer funcs[] = {
        {atomic_max_counter_bindings, "atomic_max_counter_bindings", "Invalid atomic counter buffer binding."},
        {atomic_precision, "atomic_precision", "Invalid precision qualifier."},
        {atomic_binding_offset_overlap, "atomic_binding_offset_overlap", "Invalid offset."},
        {atomic_binding_omitted, "atomic_binding_omitted", "Binding not specified."},
        {atomic_struct, "atomic_struct", "Invalid atomic_uint usage in struct."},
        {atomic_body_write, "atomic_body_write", "Invalid write access to atomic_uint."},
        {atomic_body_declare, "atomic_body_declare", "Invalid precision qualifier."},
    };

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

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