/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 3.1 Module
 * -------------------------------------------------
 *
 * Copyright 2014 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 Uniform block tests.
 *//*--------------------------------------------------------------------*/

#include "es31fUniformBlockTests.hpp"
#include "glsUniformBlockCase.hpp"
#include "glsRandomUniformBlockCase.hpp"
#include "tcuCommandLine.hpp"
#include "deRandom.hpp"
#include "deStringUtil.hpp"

using std::string;
using std::vector;

namespace deqp
{
namespace gles31
{
namespace Functional
{
namespace
{

using gls::RandomUniformBlockCase;
using gls::UniformBlockCase;
using namespace gls::ub;

void createRandomCaseGroup(tcu::TestCaseGroup *parentGroup, Context &context, const char *groupName,
                           const char *description, UniformBlockCase::BufferMode bufferMode, uint32_t features,
                           int numCases, uint32_t baseSeed)
{
    tcu::TestCaseGroup *group = new tcu::TestCaseGroup(context.getTestContext(), groupName, description);
    parentGroup->addChild(group);

    baseSeed += (uint32_t)context.getTestContext().getCommandLine().getBaseSeed();

    for (int ndx = 0; ndx < numCases; ndx++)
        group->addChild(new RandomUniformBlockCase(context.getTestContext(), context.getRenderContext(),
                                                   glu::GLSL_VERSION_310_ES, de::toString(ndx).c_str(), "", bufferMode,
                                                   features, (uint32_t)ndx + baseSeed));
}

class BlockBasicTypeCase : public UniformBlockCase
{
public:
    BlockBasicTypeCase(Context &context, const char *name, const char *description, const VarType &type,
                       uint32_t layoutFlags, int numInstances)
        : UniformBlockCase(context.getTestContext(), context.getRenderContext(), name, description,
                           glu::GLSL_VERSION_310_ES, BUFFERMODE_PER_BLOCK)
    {
        UniformBlock &block = m_interface.allocBlock("Block");
        block.addUniform(Uniform("var", type, 0));
        block.setFlags(layoutFlags);

        if (numInstances > 0)
        {
            block.setArraySize(numInstances);
            block.setInstanceName("block");
        }
    }
};

static void createBlockBasicTypeCases(tcu::TestCaseGroup *group, Context &context, const char *name,
                                      const VarType &type, uint32_t layoutFlags, int numInstances = 0)
{
    group->addChild(new BlockBasicTypeCase(context, (string(name) + "_vertex").c_str(), "", type,
                                           layoutFlags | DECLARE_VERTEX, numInstances));
    group->addChild(new BlockBasicTypeCase(context, (string(name) + "_fragment").c_str(), "", type,
                                           layoutFlags | DECLARE_FRAGMENT, numInstances));

    if (!(layoutFlags & LAYOUT_PACKED))
        group->addChild(new BlockBasicTypeCase(context, (string(name) + "_both").c_str(), "", type,
                                               layoutFlags | DECLARE_VERTEX | DECLARE_FRAGMENT, numInstances));
}

class Block2LevelStructArrayCase : public UniformBlockCase
{
public:
    Block2LevelStructArrayCase(Context &context, const char *name, const char *description, uint32_t layoutFlags,
                               BufferMode bufferMode, int numInstances)
        : UniformBlockCase(context.getTestContext(), context.getRenderContext(), name, description,
                           glu::GLSL_VERSION_310_ES, bufferMode)
        , m_layoutFlags(layoutFlags)
        , m_numInstances(numInstances)
    {
    }

    void init(void)
    {
        StructType &typeS = m_interface.allocStruct("S");
        typeS.addMember("a", VarType(glu::TYPE_UINT_VEC3, PRECISION_HIGH), UNUSED_BOTH);
        typeS.addMember("b", VarType(VarType(glu::TYPE_FLOAT_MAT2, PRECISION_MEDIUM), 4));
        typeS.addMember("c", VarType(glu::TYPE_UINT, PRECISION_LOW));

        UniformBlock &block = m_interface.allocBlock("Block");
        block.addUniform(Uniform("u", VarType(glu::TYPE_INT, PRECISION_MEDIUM)));
        block.addUniform(Uniform("s", VarType(VarType(VarType(&typeS), 3), 2)));
        block.addUniform(Uniform("v", VarType(glu::TYPE_FLOAT_VEC2, PRECISION_MEDIUM)));
        block.setFlags(m_layoutFlags);

        if (m_numInstances > 0)
        {
            block.setInstanceName("block");
            block.setArraySize(m_numInstances);
        }
    }

private:
    uint32_t m_layoutFlags;
    int m_numInstances;
};

} // namespace

UniformBlockTests::UniformBlockTests(Context &context) : TestCaseGroup(context, "ubo", "Uniform Block tests")
{
}

UniformBlockTests::~UniformBlockTests(void)
{
}

void UniformBlockTests::init(void)
{
    static const glu::DataType basicTypes[] = {
        glu::TYPE_FLOAT,        glu::TYPE_FLOAT_VEC2,   glu::TYPE_FLOAT_VEC3,   glu::TYPE_FLOAT_VEC4,
        glu::TYPE_INT,          glu::TYPE_INT_VEC2,     glu::TYPE_INT_VEC3,     glu::TYPE_INT_VEC4,
        glu::TYPE_UINT,         glu::TYPE_UINT_VEC2,    glu::TYPE_UINT_VEC3,    glu::TYPE_UINT_VEC4,
        glu::TYPE_BOOL,         glu::TYPE_BOOL_VEC2,    glu::TYPE_BOOL_VEC3,    glu::TYPE_BOOL_VEC4,
        glu::TYPE_FLOAT_MAT2,   glu::TYPE_FLOAT_MAT3,   glu::TYPE_FLOAT_MAT4,   glu::TYPE_FLOAT_MAT2X3,
        glu::TYPE_FLOAT_MAT2X4, glu::TYPE_FLOAT_MAT3X2, glu::TYPE_FLOAT_MAT3X4, glu::TYPE_FLOAT_MAT4X2,
        glu::TYPE_FLOAT_MAT4X3};

    static const struct
    {
        const char *name;
        uint32_t flags;
    } layoutFlags[] = {{"shared", LAYOUT_SHARED}, {"packed", LAYOUT_PACKED}, {"std140", LAYOUT_STD140}};

    static const struct
    {
        const char *name;
        uint32_t flags;
    } matrixFlags[] = {{"row_major", LAYOUT_ROW_MAJOR}, {"column_major", LAYOUT_COLUMN_MAJOR}};

    static const struct
    {
        const char *name;
        UniformBlockCase::BufferMode mode;
    } bufferModes[] = {{"per_block_buffer", UniformBlockCase::BUFFERMODE_PER_BLOCK},
                       {"single_buffer", UniformBlockCase::BUFFERMODE_SINGLE}};

    // ubo.2_level_array
    {
        tcu::TestCaseGroup *nestedArrayGroup =
            new tcu::TestCaseGroup(m_testCtx, "2_level_array", "2-level basic array variable in single buffer");
        addChild(nestedArrayGroup);

        for (int layoutFlagNdx = 0; layoutFlagNdx < DE_LENGTH_OF_ARRAY(layoutFlags); layoutFlagNdx++)
        {
            tcu::TestCaseGroup *layoutGroup = new tcu::TestCaseGroup(m_testCtx, layoutFlags[layoutFlagNdx].name, "");
            nestedArrayGroup->addChild(layoutGroup);

            for (int basicTypeNdx = 0; basicTypeNdx < DE_LENGTH_OF_ARRAY(basicTypes); basicTypeNdx++)
            {
                const glu::DataType type = basicTypes[basicTypeNdx];
                const char *typeName     = glu::getDataTypeName(type);
                const int childSize      = 4;
                const int parentSize     = 3;
                const VarType childType(VarType(type, glu::isDataTypeBoolOrBVec(type) ? 0 : PRECISION_HIGH), childSize);
                const VarType parentType(childType, parentSize);

                createBlockBasicTypeCases(layoutGroup, m_context, typeName, parentType,
                                          layoutFlags[layoutFlagNdx].flags);

                if (glu::isDataTypeMatrix(type))
                {
                    for (int matFlagNdx = 0; matFlagNdx < DE_LENGTH_OF_ARRAY(matrixFlags); matFlagNdx++)
                        createBlockBasicTypeCases(
                            layoutGroup, m_context, (string(matrixFlags[matFlagNdx].name) + "_" + typeName).c_str(),
                            parentType, layoutFlags[layoutFlagNdx].flags | matrixFlags[matFlagNdx].flags);
                }
            }
        }
    }

    // ubo.3_level_array
    {
        tcu::TestCaseGroup *nestedArrayGroup =
            new tcu::TestCaseGroup(m_testCtx, "3_level_array", "3-level basic array variable in single buffer");
        addChild(nestedArrayGroup);

        for (int layoutFlagNdx = 0; layoutFlagNdx < DE_LENGTH_OF_ARRAY(layoutFlags); layoutFlagNdx++)
        {
            tcu::TestCaseGroup *layoutGroup = new tcu::TestCaseGroup(m_testCtx, layoutFlags[layoutFlagNdx].name, "");
            nestedArrayGroup->addChild(layoutGroup);

            for (int basicTypeNdx = 0; basicTypeNdx < DE_LENGTH_OF_ARRAY(basicTypes); basicTypeNdx++)
            {
                const glu::DataType type = basicTypes[basicTypeNdx];
                const char *typeName     = glu::getDataTypeName(type);
                const int childSize0     = 2;
                const int childSize1     = 4;
                const int parentSize     = 3;
                const VarType childType0(VarType(type, glu::isDataTypeBoolOrBVec(type) ? 0 : PRECISION_HIGH),
                                         childSize0);
                const VarType childType1(childType0, childSize1);
                const VarType parentType(childType1, parentSize);

                createBlockBasicTypeCases(layoutGroup, m_context, typeName, parentType,
                                          layoutFlags[layoutFlagNdx].flags);

                if (glu::isDataTypeMatrix(type))
                {
                    for (int matFlagNdx = 0; matFlagNdx < DE_LENGTH_OF_ARRAY(matrixFlags); matFlagNdx++)
                        createBlockBasicTypeCases(
                            layoutGroup, m_context, (string(matrixFlags[matFlagNdx].name) + "_" + typeName).c_str(),
                            parentType, layoutFlags[layoutFlagNdx].flags | matrixFlags[matFlagNdx].flags);
                }
            }
        }
    }

    // ubo.2_level_struct_array
    {
        tcu::TestCaseGroup *structArrayArrayGroup =
            new tcu::TestCaseGroup(m_testCtx, "2_level_struct_array", "Struct array in one uniform block");
        addChild(structArrayArrayGroup);

        for (int modeNdx = 0; modeNdx < DE_LENGTH_OF_ARRAY(bufferModes); modeNdx++)
        {
            tcu::TestCaseGroup *modeGroup = new tcu::TestCaseGroup(m_testCtx, bufferModes[modeNdx].name, "");
            structArrayArrayGroup->addChild(modeGroup);

            for (int layoutFlagNdx = 0; layoutFlagNdx < DE_LENGTH_OF_ARRAY(layoutFlags); layoutFlagNdx++)
            {
                for (int isArray = 0; isArray < 2; isArray++)
                {
                    std::string baseName = layoutFlags[layoutFlagNdx].name;
                    uint32_t baseFlags   = layoutFlags[layoutFlagNdx].flags;

                    if (bufferModes[modeNdx].mode == UniformBlockCase::BUFFERMODE_SINGLE && isArray == 0)
                        continue; // Doesn't make sense to add this variant.

                    if (isArray)
                        baseName += "_instance_array";

                    modeGroup->addChild(new Block2LevelStructArrayCase(m_context, (baseName + "_vertex").c_str(), "",
                                                                       baseFlags | DECLARE_VERTEX,
                                                                       bufferModes[modeNdx].mode, isArray ? 3 : 0));
                    modeGroup->addChild(new Block2LevelStructArrayCase(m_context, (baseName + "_fragment").c_str(), "",
                                                                       baseFlags | DECLARE_FRAGMENT,
                                                                       bufferModes[modeNdx].mode, isArray ? 3 : 0));

                    if (!(baseFlags & LAYOUT_PACKED))
                        modeGroup->addChild(new Block2LevelStructArrayCase(
                            m_context, (baseName + "_both").c_str(), "", baseFlags | DECLARE_VERTEX | DECLARE_FRAGMENT,
                            bufferModes[modeNdx].mode, isArray ? 3 : 0));
                }
            }
        }
    }

    // ubo.random
    {
        const uint32_t allShaders    = FEATURE_VERTEX_BLOCKS | FEATURE_FRAGMENT_BLOCKS | FEATURE_SHARED_BLOCKS;
        const uint32_t allLayouts    = FEATURE_PACKED_LAYOUT | FEATURE_SHARED_LAYOUT | FEATURE_STD140_LAYOUT;
        const uint32_t allBasicTypes = FEATURE_VECTORS | FEATURE_MATRICES;
        const uint32_t unused        = FEATURE_UNUSED_MEMBERS | FEATURE_UNUSED_UNIFORMS;
        const uint32_t matFlags      = FEATURE_MATRIX_LAYOUT;
        const uint32_t basicTypeArrays =
            allShaders | allLayouts | unused | allBasicTypes | matFlags | FEATURE_ARRAYS | FEATURE_ARRAYS_OF_ARRAYS;
        const uint32_t allFeatures = ~0u;

        tcu::TestCaseGroup *randomGroup = new tcu::TestCaseGroup(m_testCtx, "random", "Random Uniform Block cases");
        addChild(randomGroup);

        createRandomCaseGroup(randomGroup, m_context, "basic_type_arrays", "Arrays, per-block buffers",
                              UniformBlockCase::BUFFERMODE_PER_BLOCK, basicTypeArrays, 25, 1150);
        createRandomCaseGroup(randomGroup, m_context, "all_per_block_buffers", "All random features, per-block buffers",
                              UniformBlockCase::BUFFERMODE_PER_BLOCK, allFeatures, 50, 11200);
        createRandomCaseGroup(randomGroup, m_context, "all_shared_buffer", "All random features, shared buffer",
                              UniformBlockCase::BUFFERMODE_SINGLE, allFeatures, 50, 11250);
    }
}

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