/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL (ES) 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 case.
 *//*--------------------------------------------------------------------*/

#include "glsUniformBlockCase.hpp"
#include "gluRenderContext.hpp"
#include "gluShaderProgram.hpp"
#include "gluPixelTransfer.hpp"
#include "gluContextInfo.hpp"
#include "gluRenderContext.hpp"
#include "gluDrawUtil.hpp"
#include "glwFunctions.hpp"
#include "glwEnums.hpp"
#include "tcuTestLog.hpp"
#include "tcuSurface.hpp"
#include "tcuRenderTarget.hpp"
#include "deInt32.h"
#include "deMemory.h"
#include "deRandom.hpp"
#include "deString.h"
#include "deStringUtil.hpp"

#include <algorithm>
#include <map>

using std::map;
using std::string;
using std::vector;
using tcu::TestLog;

namespace deqp
{
namespace gls
{
namespace ub
{

static bool isSupportedGLSLVersion(glu::GLSLVersion version)
{
    return version >= (glslVersionIsES(version) ? glu::GLSL_VERSION_300_ES : glu::GLSL_VERSION_330);
}

struct PrecisionFlagsFmt
{
    uint32_t flags;
    PrecisionFlagsFmt(uint32_t flags_) : flags(flags_)
    {
    }
};

std::ostream &operator<<(std::ostream &str, const PrecisionFlagsFmt &fmt)
{
    // Precision.
    DE_ASSERT(dePop32(fmt.flags & (PRECISION_LOW | PRECISION_MEDIUM | PRECISION_HIGH)) <= 1);
    str << (fmt.flags & PRECISION_LOW    ? "lowp" :
            fmt.flags & PRECISION_MEDIUM ? "mediump" :
            fmt.flags & PRECISION_HIGH   ? "highp" :
                                           "");
    return str;
}

struct LayoutFlagsFmt
{
    uint32_t flags;
    LayoutFlagsFmt(uint32_t flags_) : flags(flags_)
    {
    }
};

std::ostream &operator<<(std::ostream &str, const LayoutFlagsFmt &fmt)
{
    static const struct
    {
        uint32_t bit;
        const char *token;
    } bitDesc[] = {{LAYOUT_SHARED, "shared"},
                   {LAYOUT_PACKED, "packed"},
                   {LAYOUT_STD140, "std140"},
                   {LAYOUT_ROW_MAJOR, "row_major"},
                   {LAYOUT_COLUMN_MAJOR, "column_major"}};

    uint32_t remBits = fmt.flags;
    for (int descNdx = 0; descNdx < DE_LENGTH_OF_ARRAY(bitDesc); descNdx++)
    {
        if (remBits & bitDesc[descNdx].bit)
        {
            if (remBits != fmt.flags)
                str << ", ";
            str << bitDesc[descNdx].token;
            remBits &= ~bitDesc[descNdx].bit;
        }
    }
    DE_ASSERT(remBits == 0);
    return str;
}

// VarType implementation.

VarType::VarType(void) : m_type(TYPE_LAST), m_flags(0)
{
}

VarType::VarType(const VarType &other) : m_type(TYPE_LAST), m_flags(0)
{
    *this = other;
}

VarType::VarType(glu::DataType basicType, uint32_t flags) : m_type(TYPE_BASIC), m_flags(flags)
{
    m_data.basicType = basicType;
}

VarType::VarType(const VarType &elementType, int arraySize) : m_type(TYPE_ARRAY), m_flags(0)
{
    m_data.array.size        = arraySize;
    m_data.array.elementType = new VarType(elementType);
}

VarType::VarType(const StructType *structPtr, uint32_t flags) : m_type(TYPE_STRUCT), m_flags(flags)
{
    m_data.structPtr = structPtr;
}

VarType::~VarType(void)
{
    if (m_type == TYPE_ARRAY)
        delete m_data.array.elementType;
}

VarType &VarType::operator=(const VarType &other)
{
    if (this == &other)
        return *this; // Self-assignment.

    if (m_type == TYPE_ARRAY)
        delete m_data.array.elementType;

    m_type  = other.m_type;
    m_flags = other.m_flags;
    m_data  = Data();

    if (m_type == TYPE_ARRAY)
    {
        m_data.array.elementType = new VarType(*other.m_data.array.elementType);
        m_data.array.size        = other.m_data.array.size;
    }
    else
        m_data = other.m_data;

    return *this;
}

// StructType implementation.

void StructType::addMember(const char *name, const VarType &type, uint32_t flags)
{
    m_members.push_back(StructMember(name, type, flags));
}

// Uniform implementation.

Uniform::Uniform(const char *name, const VarType &type, uint32_t flags) : m_name(name), m_type(type), m_flags(flags)
{
}

// UniformBlock implementation.

UniformBlock::UniformBlock(const char *blockName) : m_blockName(blockName), m_arraySize(0), m_flags(0)
{
}

struct BlockLayoutEntry
{
    BlockLayoutEntry(void) : size(0)
    {
    }

    std::string name;
    int size;
    std::vector<int> activeUniformIndices;
};

std::ostream &operator<<(std::ostream &stream, const BlockLayoutEntry &entry)
{
    stream << entry.name << " { name = " << entry.name << ", size = " << entry.size << ", activeUniformIndices = [";

    for (vector<int>::const_iterator i = entry.activeUniformIndices.begin(); i != entry.activeUniformIndices.end(); i++)
    {
        if (i != entry.activeUniformIndices.begin())
            stream << ", ";
        stream << *i;
    }

    stream << "] }";
    return stream;
}

struct UniformLayoutEntry
{
    UniformLayoutEntry(void)
        : type(glu::TYPE_LAST)
        , size(0)
        , blockNdx(-1)
        , offset(-1)
        , arrayStride(-1)
        , matrixStride(-1)
        , isRowMajor(false)
    {
    }

    std::string name;
    glu::DataType type;
    int size;
    int blockNdx;
    int offset;
    int arrayStride;
    int matrixStride;
    bool isRowMajor;
};

std::ostream &operator<<(std::ostream &stream, const UniformLayoutEntry &entry)
{
    stream << entry.name << " { type = " << glu::getDataTypeName(entry.type) << ", size = " << entry.size
           << ", blockNdx = " << entry.blockNdx << ", offset = " << entry.offset
           << ", arrayStride = " << entry.arrayStride << ", matrixStride = " << entry.matrixStride
           << ", isRowMajor = " << (entry.isRowMajor ? "true" : "false") << " }";
    return stream;
}

class UniformLayout
{
public:
    std::vector<BlockLayoutEntry> blocks;
    std::vector<UniformLayoutEntry> uniforms;

    int getUniformIndex(const char *name) const;
    int getBlockIndex(const char *name) const;
};

// \todo [2012-01-24 pyry] Speed up lookups using hash.

int UniformLayout::getUniformIndex(const char *name) const
{
    for (int ndx = 0; ndx < (int)uniforms.size(); ndx++)
    {
        if (uniforms[ndx].name == name)
            return ndx;
    }
    return -1;
}

int UniformLayout::getBlockIndex(const char *name) const
{
    for (int ndx = 0; ndx < (int)blocks.size(); ndx++)
    {
        if (blocks[ndx].name == name)
            return ndx;
    }
    return -1;
}

// ShaderInterface implementation.

ShaderInterface::ShaderInterface(void)
{
}

ShaderInterface::~ShaderInterface(void)
{
    for (std::vector<StructType *>::iterator i = m_structs.begin(); i != m_structs.end(); i++)
        delete *i;

    for (std::vector<UniformBlock *>::iterator i = m_uniformBlocks.begin(); i != m_uniformBlocks.end(); i++)
        delete *i;
}

StructType &ShaderInterface::allocStruct(const char *name)
{
    m_structs.reserve(m_structs.size() + 1);
    m_structs.push_back(new StructType(name));
    return *m_structs.back();
}

struct StructNameEquals
{
    std::string name;

    StructNameEquals(const char *name_) : name(name_)
    {
    }

    bool operator()(const StructType *type) const
    {
        return type->getTypeName() && name == type->getTypeName();
    }
};

const StructType *ShaderInterface::findStruct(const char *name) const
{
    std::vector<StructType *>::const_iterator pos =
        std::find_if(m_structs.begin(), m_structs.end(), StructNameEquals(name));
    return pos != m_structs.end() ? *pos : DE_NULL;
}

void ShaderInterface::getNamedStructs(std::vector<const StructType *> &structs) const
{
    for (std::vector<StructType *>::const_iterator i = m_structs.begin(); i != m_structs.end(); i++)
    {
        if ((*i)->getTypeName() != DE_NULL)
            structs.push_back(*i);
    }
}

UniformBlock &ShaderInterface::allocBlock(const char *name)
{
    m_uniformBlocks.reserve(m_uniformBlocks.size() + 1);
    m_uniformBlocks.push_back(new UniformBlock(name));
    return *m_uniformBlocks.back();
}

namespace // Utilities
{

// Layout computation.

int getDataTypeByteSize(glu::DataType type)
{
    return glu::getDataTypeScalarSize(type) * (int)sizeof(uint32_t);
}

int getDataTypeByteAlignment(glu::DataType type)
{
    switch (type)
    {
    case glu::TYPE_FLOAT:
    case glu::TYPE_INT:
    case glu::TYPE_UINT:
    case glu::TYPE_BOOL:
        return 1 * (int)sizeof(uint32_t);

    case glu::TYPE_FLOAT_VEC2:
    case glu::TYPE_INT_VEC2:
    case glu::TYPE_UINT_VEC2:
    case glu::TYPE_BOOL_VEC2:
        return 2 * (int)sizeof(uint32_t);

    case glu::TYPE_FLOAT_VEC3:
    case glu::TYPE_INT_VEC3:
    case glu::TYPE_UINT_VEC3:
    case glu::TYPE_BOOL_VEC3: // Fall-through to vec4

    case glu::TYPE_FLOAT_VEC4:
    case glu::TYPE_INT_VEC4:
    case glu::TYPE_UINT_VEC4:
    case glu::TYPE_BOOL_VEC4:
        return 4 * (int)sizeof(uint32_t);

    default:
        DE_ASSERT(false);
        return 0;
    }
}

int getDataTypeArrayStride(glu::DataType type)
{
    DE_ASSERT(!glu::isDataTypeMatrix(type));

    const int baseStride    = getDataTypeByteSize(type);
    const int vec4Alignment = (int)sizeof(uint32_t) * 4;

    DE_ASSERT(baseStride <= vec4Alignment);
    return de::max(baseStride, vec4Alignment); // Really? See rule 4.
}

int computeStd140BaseAlignment(const VarType &type)
{
    const int vec4Alignment = (int)sizeof(uint32_t) * 4;

    if (type.isBasicType())
    {
        glu::DataType basicType = type.getBasicType();

        if (glu::isDataTypeMatrix(basicType))
        {
            bool isRowMajor = !!(type.getFlags() & LAYOUT_ROW_MAJOR);
            int vecSize =
                isRowMajor ? glu::getDataTypeMatrixNumColumns(basicType) : glu::getDataTypeMatrixNumRows(basicType);

            return getDataTypeArrayStride(glu::getDataTypeFloatVec(vecSize));
        }
        else
            return getDataTypeByteAlignment(basicType);
    }
    else if (type.isArrayType())
    {
        int elemAlignment = computeStd140BaseAlignment(type.getElementType());

        // Round up to alignment of vec4
        return deRoundUp32(elemAlignment, vec4Alignment);
    }
    else
    {
        DE_ASSERT(type.isStructType());

        int maxBaseAlignment = 0;

        for (StructType::ConstIterator memberIter = type.getStruct().begin(); memberIter != type.getStruct().end();
             memberIter++)
            maxBaseAlignment = de::max(maxBaseAlignment, computeStd140BaseAlignment(memberIter->getType()));

        return deRoundUp32(maxBaseAlignment, vec4Alignment);
    }
}

inline uint32_t mergeLayoutFlags(uint32_t prevFlags, uint32_t newFlags)
{
    const uint32_t packingMask = LAYOUT_PACKED | LAYOUT_SHARED | LAYOUT_STD140;
    const uint32_t matrixMask  = LAYOUT_ROW_MAJOR | LAYOUT_COLUMN_MAJOR;

    uint32_t mergedFlags = 0;

    mergedFlags |= ((newFlags & packingMask) ? newFlags : prevFlags) & packingMask;
    mergedFlags |= ((newFlags & matrixMask) ? newFlags : prevFlags) & matrixMask;

    return mergedFlags;
}

void computeStd140Layout(UniformLayout &layout, int &curOffset, int curBlockNdx, const std::string &curPrefix,
                         const VarType &type, uint32_t layoutFlags)
{
    int baseAlignment = computeStd140BaseAlignment(type);

    curOffset = deAlign32(curOffset, baseAlignment);

    if (type.isBasicType())
    {
        glu::DataType basicType = type.getBasicType();
        UniformLayoutEntry entry;

        entry.name         = curPrefix;
        entry.type         = basicType;
        entry.size         = 1;
        entry.arrayStride  = 0;
        entry.matrixStride = 0;
        entry.blockNdx     = curBlockNdx;

        if (glu::isDataTypeMatrix(basicType))
        {
            // Array of vectors as specified in rules 5 & 7.
            bool isRowMajor =
                !!(((type.getFlags() & (LAYOUT_ROW_MAJOR | LAYOUT_COLUMN_MAJOR) ? type.getFlags() : layoutFlags) &
                    LAYOUT_ROW_MAJOR));

            int vecSize =
                isRowMajor ? glu::getDataTypeMatrixNumColumns(basicType) : glu::getDataTypeMatrixNumRows(basicType);
            int numVecs =
                isRowMajor ? glu::getDataTypeMatrixNumRows(basicType) : glu::getDataTypeMatrixNumColumns(basicType);
            int stride = getDataTypeArrayStride(glu::getDataTypeFloatVec(vecSize));

            entry.offset       = curOffset;
            entry.matrixStride = stride;
            entry.isRowMajor   = isRowMajor;

            curOffset += numVecs * stride;
        }
        else
        {
            // Scalar or vector.
            entry.offset = curOffset;

            curOffset += getDataTypeByteSize(basicType);
        }

        layout.uniforms.push_back(entry);
    }
    else if (type.isArrayType())
    {
        const VarType &elemType = type.getElementType();

        if (elemType.isBasicType() && !glu::isDataTypeMatrix(elemType.getBasicType()))
        {
            // Array of scalars or vectors.
            glu::DataType elemBasicType = elemType.getBasicType();
            UniformLayoutEntry entry;
            int stride = getDataTypeArrayStride(elemBasicType);

            entry.name         = curPrefix + "[0]"; // Array uniforms are always postfixed with [0]
            entry.type         = elemBasicType;
            entry.blockNdx     = curBlockNdx;
            entry.offset       = curOffset;
            entry.size         = type.getArraySize();
            entry.arrayStride  = stride;
            entry.matrixStride = 0;

            curOffset += stride * type.getArraySize();

            layout.uniforms.push_back(entry);
        }
        else if (elemType.isBasicType() && glu::isDataTypeMatrix(elemType.getBasicType()))
        {
            // Array of matrices.
            glu::DataType elemBasicType = elemType.getBasicType();
            bool isRowMajor             = !!(
                ((elemType.getFlags() & (LAYOUT_ROW_MAJOR | LAYOUT_COLUMN_MAJOR) ? elemType.getFlags() : layoutFlags) &
                 LAYOUT_ROW_MAJOR));
            int vecSize = isRowMajor ? glu::getDataTypeMatrixNumColumns(elemBasicType) :
                                       glu::getDataTypeMatrixNumRows(elemBasicType);
            int numVecs = isRowMajor ? glu::getDataTypeMatrixNumRows(elemBasicType) :
                                       glu::getDataTypeMatrixNumColumns(elemBasicType);
            int stride  = getDataTypeArrayStride(glu::getDataTypeFloatVec(vecSize));
            UniformLayoutEntry entry;

            entry.name         = curPrefix + "[0]"; // Array uniforms are always postfixed with [0]
            entry.type         = elemBasicType;
            entry.blockNdx     = curBlockNdx;
            entry.offset       = curOffset;
            entry.size         = type.getArraySize();
            entry.arrayStride  = stride * numVecs;
            entry.matrixStride = stride;
            entry.isRowMajor   = isRowMajor;

            curOffset += numVecs * type.getArraySize() * stride;

            layout.uniforms.push_back(entry);
        }
        else
        {
            DE_ASSERT(elemType.isStructType() || elemType.isArrayType());

            for (int elemNdx = 0; elemNdx < type.getArraySize(); elemNdx++)
                computeStd140Layout(layout, curOffset, curBlockNdx, curPrefix + "[" + de::toString(elemNdx) + "]",
                                    type.getElementType(), layoutFlags);
        }
    }
    else
    {
        DE_ASSERT(type.isStructType());

        // Override matrix packing layout flags in case the structure has them defined.
        const uint32_t matrixLayoutMask = LAYOUT_ROW_MAJOR | LAYOUT_COLUMN_MAJOR;
        if (type.getFlags() & matrixLayoutMask)
            layoutFlags = (layoutFlags & (~matrixLayoutMask)) | (type.getFlags() & matrixLayoutMask);

        for (StructType::ConstIterator memberIter = type.getStruct().begin(); memberIter != type.getStruct().end();
             memberIter++)
            computeStd140Layout(layout, curOffset, curBlockNdx, curPrefix + "." + memberIter->getName(),
                                memberIter->getType(), layoutFlags);

        curOffset = deAlign32(curOffset, baseAlignment);
    }
}

void computeStd140Layout(UniformLayout &layout, const ShaderInterface &interface)
{
    // \todo [2012-01-23 pyry] Uniforms in default block.

    int numUniformBlocks = interface.getNumUniformBlocks();

    for (int blockNdx = 0; blockNdx < numUniformBlocks; blockNdx++)
    {
        const UniformBlock &block = interface.getUniformBlock(blockNdx);
        bool hasInstanceName      = block.getInstanceName() != DE_NULL;
        std::string blockPrefix   = hasInstanceName ? (std::string(block.getBlockName()) + ".") : std::string("");
        int curOffset             = 0;
        int activeBlockNdx        = (int)layout.blocks.size();
        int firstUniformNdx       = (int)layout.uniforms.size();

        for (UniformBlock::ConstIterator uniformIter = block.begin(); uniformIter != block.end(); uniformIter++)
        {
            const Uniform &uniform = *uniformIter;
            computeStd140Layout(layout, curOffset, activeBlockNdx, blockPrefix + uniform.getName(), uniform.getType(),
                                mergeLayoutFlags(block.getFlags(), uniform.getFlags()));
        }

        int uniformIndicesEnd = (int)layout.uniforms.size();
        int blockSize         = curOffset;
        int numInstances      = block.isArray() ? block.getArraySize() : 1;

        // Create block layout entries for each instance.
        for (int instanceNdx = 0; instanceNdx < numInstances; instanceNdx++)
        {
            // Allocate entry for instance.
            layout.blocks.push_back(BlockLayoutEntry());
            BlockLayoutEntry &blockEntry = layout.blocks.back();

            blockEntry.name = block.getBlockName();
            blockEntry.size = blockSize;

            // Compute active uniform set for block.
            for (int uniformNdx = firstUniformNdx; uniformNdx < uniformIndicesEnd; uniformNdx++)
                blockEntry.activeUniformIndices.push_back(uniformNdx);

            if (block.isArray())
                blockEntry.name += "[" + de::toString(instanceNdx) + "]";
        }
    }
}

// Value generator.

void generateValue(const UniformLayoutEntry &entry, void *basePtr, de::Random &rnd)
{
    glu::DataType scalarType = glu::getDataTypeScalarType(entry.type);
    int scalarSize           = glu::getDataTypeScalarSize(entry.type);
    bool isMatrix            = glu::isDataTypeMatrix(entry.type);
    int numVecs              = isMatrix ? (entry.isRowMajor ? glu::getDataTypeMatrixNumRows(entry.type) :
                                                              glu::getDataTypeMatrixNumColumns(entry.type)) :
                                          1;
    int vecSize              = scalarSize / numVecs;
    bool isArray             = entry.size > 1;
    const int compSize       = sizeof(uint32_t);

    DE_ASSERT(scalarSize % numVecs == 0);

    for (int elemNdx = 0; elemNdx < entry.size; elemNdx++)
    {
        uint8_t *elemPtr = (uint8_t *)basePtr + entry.offset + (isArray ? elemNdx * entry.arrayStride : 0);

        for (int vecNdx = 0; vecNdx < numVecs; vecNdx++)
        {
            uint8_t *vecPtr = elemPtr + (isMatrix ? vecNdx * entry.matrixStride : 0);

            for (int compNdx = 0; compNdx < vecSize; compNdx++)
            {
                uint8_t *compPtr = vecPtr + compSize * compNdx;

                switch (scalarType)
                {
                case glu::TYPE_FLOAT:
                    *((float *)compPtr) = (float)rnd.getInt(-9, 9);
                    break;
                case glu::TYPE_INT:
                    *((int *)compPtr) = rnd.getInt(-9, 9);
                    break;
                case glu::TYPE_UINT:
                    *((uint32_t *)compPtr) = (uint32_t)rnd.getInt(0, 9);
                    break;
                // \note Random bit pattern is used for true values. Spec states that all non-zero values are
                //       interpreted as true but some implementations fail this.
                case glu::TYPE_BOOL:
                    *((uint32_t *)compPtr) = rnd.getBool() ? rnd.getUint32() | 1u : 0u;
                    break;
                default:
                    DE_ASSERT(false);
                }
            }
        }
    }
}

void generateValues(const UniformLayout &layout, const std::map<int, void *> &blockPointers, uint32_t seed)
{
    de::Random rnd(seed);
    int numBlocks = (int)layout.blocks.size();

    for (int blockNdx = 0; blockNdx < numBlocks; blockNdx++)
    {
        void *basePtr  = blockPointers.find(blockNdx)->second;
        int numEntries = (int)layout.blocks[blockNdx].activeUniformIndices.size();

        for (int entryNdx = 0; entryNdx < numEntries; entryNdx++)
        {
            const UniformLayoutEntry &entry = layout.uniforms[layout.blocks[blockNdx].activeUniformIndices[entryNdx]];
            generateValue(entry, basePtr, rnd);
        }
    }
}

// Shader generator.

const char *getCompareFuncForType(glu::DataType type)
{
    switch (type)
    {
    case glu::TYPE_FLOAT:
        return "mediump float compare_float    (highp float a, highp float b)  { return abs(a - b) < 0.05 ? 1.0 : 0.0; "
               "}\n";
    case glu::TYPE_FLOAT_VEC2:
        return "mediump float compare_vec2     (highp vec2 a, highp vec2 b)    { return compare_float(a.x, "
               "b.x)*compare_float(a.y, b.y); }\n";
    case glu::TYPE_FLOAT_VEC3:
        return "mediump float compare_vec3     (highp vec3 a, highp vec3 b)    { return compare_float(a.x, "
               "b.x)*compare_float(a.y, b.y)*compare_float(a.z, b.z); }\n";
    case glu::TYPE_FLOAT_VEC4:
        return "mediump float compare_vec4     (highp vec4 a, highp vec4 b)    { return compare_float(a.x, "
               "b.x)*compare_float(a.y, b.y)*compare_float(a.z, b.z)*compare_float(a.w, b.w); }\n";
    case glu::TYPE_FLOAT_MAT2:
        return "mediump float compare_mat2     (highp mat2 a, highp mat2 b)    { return compare_vec2(a[0], "
               "b[0])*compare_vec2(a[1], b[1]); }\n";
    case glu::TYPE_FLOAT_MAT2X3:
        return "mediump float compare_mat2x3   (highp mat2x3 a, highp mat2x3 b){ return compare_vec3(a[0], "
               "b[0])*compare_vec3(a[1], b[1]); }\n";
    case glu::TYPE_FLOAT_MAT2X4:
        return "mediump float compare_mat2x4   (highp mat2x4 a, highp mat2x4 b){ return compare_vec4(a[0], "
               "b[0])*compare_vec4(a[1], b[1]); }\n";
    case glu::TYPE_FLOAT_MAT3X2:
        return "mediump float compare_mat3x2   (highp mat3x2 a, highp mat3x2 b){ return compare_vec2(a[0], "
               "b[0])*compare_vec2(a[1], b[1])*compare_vec2(a[2], b[2]); }\n";
    case glu::TYPE_FLOAT_MAT3:
        return "mediump float compare_mat3     (highp mat3 a, highp mat3 b)    { return compare_vec3(a[0], "
               "b[0])*compare_vec3(a[1], b[1])*compare_vec3(a[2], b[2]); }\n";
    case glu::TYPE_FLOAT_MAT3X4:
        return "mediump float compare_mat3x4   (highp mat3x4 a, highp mat3x4 b){ return compare_vec4(a[0], "
               "b[0])*compare_vec4(a[1], b[1])*compare_vec4(a[2], b[2]); }\n";
    case glu::TYPE_FLOAT_MAT4X2:
        return "mediump float compare_mat4x2   (highp mat4x2 a, highp mat4x2 b){ return compare_vec2(a[0], "
               "b[0])*compare_vec2(a[1], b[1])*compare_vec2(a[2], b[2])*compare_vec2(a[3], b[3]); }\n";
    case glu::TYPE_FLOAT_MAT4X3:
        return "mediump float compare_mat4x3   (highp mat4x3 a, highp mat4x3 b){ return compare_vec3(a[0], "
               "b[0])*compare_vec3(a[1], b[1])*compare_vec3(a[2], b[2])*compare_vec3(a[3], b[3]); }\n";
    case glu::TYPE_FLOAT_MAT4:
        return "mediump float compare_mat4     (highp mat4 a, highp mat4 b)    { return compare_vec4(a[0], "
               "b[0])*compare_vec4(a[1], b[1])*compare_vec4(a[2], b[2])*compare_vec4(a[3], b[3]); }\n";
    case glu::TYPE_INT:
        return "mediump float compare_int      (highp int a, highp int b)      { return a == b ? 1.0 : 0.0; }\n";
    case glu::TYPE_INT_VEC2:
        return "mediump float compare_ivec2    (highp ivec2 a, highp ivec2 b)  { return a == b ? 1.0 : 0.0; }\n";
    case glu::TYPE_INT_VEC3:
        return "mediump float compare_ivec3    (highp ivec3 a, highp ivec3 b)  { return a == b ? 1.0 : 0.0; }\n";
    case glu::TYPE_INT_VEC4:
        return "mediump float compare_ivec4    (highp ivec4 a, highp ivec4 b)  { return a == b ? 1.0 : 0.0; }\n";
    case glu::TYPE_UINT:
        return "mediump float compare_uint     (highp uint a, highp uint b)    { return a == b ? 1.0 : 0.0; }\n";
    case glu::TYPE_UINT_VEC2:
        return "mediump float compare_uvec2    (highp uvec2 a, highp uvec2 b)  { return a == b ? 1.0 : 0.0; }\n";
    case glu::TYPE_UINT_VEC3:
        return "mediump float compare_uvec3    (highp uvec3 a, highp uvec3 b)  { return a == b ? 1.0 : 0.0; }\n";
    case glu::TYPE_UINT_VEC4:
        return "mediump float compare_uvec4    (highp uvec4 a, highp uvec4 b)  { return a == b ? 1.0 : 0.0; }\n";
    case glu::TYPE_BOOL:
        return "mediump float compare_bool     (bool a, bool b)                { return a == b ? 1.0 : 0.0; }\n";
    case glu::TYPE_BOOL_VEC2:
        return "mediump float compare_bvec2    (bvec2 a, bvec2 b)              { return a == b ? 1.0 : 0.0; }\n";
    case glu::TYPE_BOOL_VEC3:
        return "mediump float compare_bvec3    (bvec3 a, bvec3 b)              { return a == b ? 1.0 : 0.0; }\n";
    case glu::TYPE_BOOL_VEC4:
        return "mediump float compare_bvec4    (bvec4 a, bvec4 b)              { return a == b ? 1.0 : 0.0; }\n";
    default:
        DE_ASSERT(false);
        return DE_NULL;
    }
}

void getCompareDependencies(std::set<glu::DataType> &compareFuncs, glu::DataType basicType)
{
    switch (basicType)
    {
    case glu::TYPE_FLOAT_VEC2:
    case glu::TYPE_FLOAT_VEC3:
    case glu::TYPE_FLOAT_VEC4:
        compareFuncs.insert(glu::TYPE_FLOAT);
        compareFuncs.insert(basicType);
        break;

    case glu::TYPE_FLOAT_MAT2:
    case glu::TYPE_FLOAT_MAT2X3:
    case glu::TYPE_FLOAT_MAT2X4:
    case glu::TYPE_FLOAT_MAT3X2:
    case glu::TYPE_FLOAT_MAT3:
    case glu::TYPE_FLOAT_MAT3X4:
    case glu::TYPE_FLOAT_MAT4X2:
    case glu::TYPE_FLOAT_MAT4X3:
    case glu::TYPE_FLOAT_MAT4:
        compareFuncs.insert(glu::TYPE_FLOAT);
        compareFuncs.insert(glu::getDataTypeFloatVec(glu::getDataTypeMatrixNumRows(basicType)));
        compareFuncs.insert(basicType);
        break;

    default:
        compareFuncs.insert(basicType);
        break;
    }
}

void collectUniqueBasicTypes(std::set<glu::DataType> &basicTypes, const VarType &type)
{
    if (type.isStructType())
    {
        for (StructType::ConstIterator iter = type.getStruct().begin(); iter != type.getStruct().end(); ++iter)
            collectUniqueBasicTypes(basicTypes, iter->getType());
    }
    else if (type.isArrayType())
        collectUniqueBasicTypes(basicTypes, type.getElementType());
    else
    {
        DE_ASSERT(type.isBasicType());
        basicTypes.insert(type.getBasicType());
    }
}

void collectUniqueBasicTypes(std::set<glu::DataType> &basicTypes, const UniformBlock &uniformBlock)
{
    for (UniformBlock::ConstIterator iter = uniformBlock.begin(); iter != uniformBlock.end(); ++iter)
        collectUniqueBasicTypes(basicTypes, iter->getType());
}

void collectUniqueBasicTypes(std::set<glu::DataType> &basicTypes, const ShaderInterface &interface)
{
    for (int ndx = 0; ndx < interface.getNumUniformBlocks(); ++ndx)
        collectUniqueBasicTypes(basicTypes, interface.getUniformBlock(ndx));
}

void generateCompareFuncs(std::ostream &str, const ShaderInterface &interface)
{
    std::set<glu::DataType> types;
    std::set<glu::DataType> compareFuncs;

    // Collect unique basic types
    collectUniqueBasicTypes(types, interface);

    // Set of compare functions required
    for (std::set<glu::DataType>::const_iterator iter = types.begin(); iter != types.end(); ++iter)
    {
        getCompareDependencies(compareFuncs, *iter);
    }

    for (int type = 0; type < glu::TYPE_LAST; ++type)
    {
        if (compareFuncs.find(glu::DataType(type)) != compareFuncs.end())
            str << getCompareFuncForType(glu::DataType(type));
    }
}

struct Indent
{
    int level;
    Indent(int level_) : level(level_)
    {
    }
};

std::ostream &operator<<(std::ostream &str, const Indent &indent)
{
    for (int i = 0; i < indent.level; i++)
        str << "\t";
    return str;
}

void generateDeclaration(std::ostringstream &src, const VarType &type, const char *name, int indentLevel,
                         uint32_t unusedHints);
void generateDeclaration(std::ostringstream &src, const Uniform &uniform, int indentLevel);
void generateDeclaration(std::ostringstream &src, const StructType &structType, int indentLevel);

void generateLocalDeclaration(std::ostringstream &src, const StructType &structType, int indentLevel);
void generateFullDeclaration(std::ostringstream &src, const StructType &structType, int indentLevel);

void generateDeclaration(std::ostringstream &src, const StructType &structType, int indentLevel)
{
    DE_ASSERT(structType.getTypeName() != DE_NULL);
    generateFullDeclaration(src, structType, indentLevel);
    src << ";\n";
}

void generateFullDeclaration(std::ostringstream &src, const StructType &structType, int indentLevel)
{
    src << "struct";
    if (structType.getTypeName())
        src << " " << structType.getTypeName();
    src << "\n" << Indent(indentLevel) << "{\n";

    for (StructType::ConstIterator memberIter = structType.begin(); memberIter != structType.end(); memberIter++)
    {
        src << Indent(indentLevel + 1);
        generateDeclaration(src, memberIter->getType(), memberIter->getName(), indentLevel + 1,
                            memberIter->getFlags() & UNUSED_BOTH);
    }

    src << Indent(indentLevel) << "}";
}

void generateLocalDeclaration(std::ostringstream &src, const StructType &structType, int indentLevel)
{
    if (structType.getTypeName() == DE_NULL)
        generateFullDeclaration(src, structType, indentLevel);
    else
        src << structType.getTypeName();
}

void generateDeclaration(std::ostringstream &src, const VarType &type, const char *name, int indentLevel,
                         uint32_t unusedHints)
{
    uint32_t flags = type.getFlags();

    if ((flags & LAYOUT_MASK) != 0)
        src << "layout(" << LayoutFlagsFmt(flags & LAYOUT_MASK) << ") ";

    if ((flags & PRECISION_MASK) != 0)
        src << PrecisionFlagsFmt(flags & PRECISION_MASK) << " ";

    if (type.isBasicType())
        src << glu::getDataTypeName(type.getBasicType()) << " " << name;
    else if (type.isArrayType())
    {
        std::vector<int> arraySizes;
        const VarType *curType = &type;
        while (curType->isArrayType())
        {
            arraySizes.push_back(curType->getArraySize());
            curType = &curType->getElementType();
        }

        if (curType->isBasicType())
        {
            if ((curType->getFlags() & LAYOUT_MASK) != 0)
                src << "layout(" << LayoutFlagsFmt(curType->getFlags() & LAYOUT_MASK) << ") ";
            if ((curType->getFlags() & PRECISION_MASK) != 0)
                src << PrecisionFlagsFmt(curType->getFlags() & PRECISION_MASK) << " ";
            src << glu::getDataTypeName(curType->getBasicType());
        }
        else
        {
            DE_ASSERT(curType->isStructType());
            generateLocalDeclaration(src, curType->getStruct(), indentLevel + 1);
        }

        src << " " << name;

        for (std::vector<int>::const_iterator sizeIter = arraySizes.begin(); sizeIter != arraySizes.end(); sizeIter++)
            src << "[" << *sizeIter << "]";
    }
    else
    {
        generateLocalDeclaration(src, type.getStruct(), indentLevel + 1);
        src << " " << name;
    }

    src << ";";

    // Print out unused hints.
    if (unusedHints != 0)
        src << " // unused in "
            << (unusedHints == UNUSED_BOTH     ? "both shaders" :
                unusedHints == UNUSED_VERTEX   ? "vertex shader" :
                unusedHints == UNUSED_FRAGMENT ? "fragment shader" :
                                                 "???");

    src << "\n";
}

void generateDeclaration(std::ostringstream &src, const Uniform &uniform, int indentLevel)
{
    if ((uniform.getFlags() & LAYOUT_MASK) != 0)
        src << "layout(" << LayoutFlagsFmt(uniform.getFlags() & LAYOUT_MASK) << ") ";

    generateDeclaration(src, uniform.getType(), uniform.getName(), indentLevel, uniform.getFlags() & UNUSED_BOTH);
}

void generateDeclaration(std::ostringstream &src, const UniformBlock &block)
{
    if ((block.getFlags() & LAYOUT_MASK) != 0)
        src << "layout(" << LayoutFlagsFmt(block.getFlags() & LAYOUT_MASK) << ") ";

    src << "uniform " << block.getBlockName();
    src << "\n{\n";

    for (UniformBlock::ConstIterator uniformIter = block.begin(); uniformIter != block.end(); uniformIter++)
    {
        src << Indent(1);
        generateDeclaration(src, *uniformIter, 1 /* indent level */);
    }

    src << "}";

    if (block.getInstanceName() != DE_NULL)
    {
        src << " " << block.getInstanceName();
        if (block.isArray())
            src << "[" << block.getArraySize() << "]";
    }
    else
        DE_ASSERT(!block.isArray());

    src << ";\n";
}

void generateValueSrc(std::ostringstream &src, const UniformLayoutEntry &entry, const void *basePtr, int elementNdx)
{
    glu::DataType scalarType = glu::getDataTypeScalarType(entry.type);
    int scalarSize           = glu::getDataTypeScalarSize(entry.type);
    bool isArray             = entry.size > 1;
    const uint8_t *elemPtr   = (const uint8_t *)basePtr + entry.offset + (isArray ? elementNdx * entry.arrayStride : 0);
    const int compSize       = sizeof(uint32_t);

    if (scalarSize > 1)
        src << glu::getDataTypeName(entry.type) << "(";

    if (glu::isDataTypeMatrix(entry.type))
    {
        int numRows = glu::getDataTypeMatrixNumRows(entry.type);
        int numCols = glu::getDataTypeMatrixNumColumns(entry.type);

        DE_ASSERT(scalarType == glu::TYPE_FLOAT);

        // Constructed in column-wise order.
        for (int colNdx = 0; colNdx < numCols; colNdx++)
        {
            for (int rowNdx = 0; rowNdx < numRows; rowNdx++)
            {
                const uint8_t *compPtr = elemPtr + (entry.isRowMajor ? rowNdx * entry.matrixStride + colNdx * compSize :
                                                                       colNdx * entry.matrixStride + rowNdx * compSize);

                if (colNdx > 0 || rowNdx > 0)
                    src << ", ";

                src << de::floatToString(*((const float *)compPtr), 1);
            }
        }
    }
    else
    {
        for (int scalarNdx = 0; scalarNdx < scalarSize; scalarNdx++)
        {
            const uint8_t *compPtr = elemPtr + scalarNdx * compSize;

            if (scalarNdx > 0)
                src << ", ";

            switch (scalarType)
            {
            case glu::TYPE_FLOAT:
                src << de::floatToString(*((const float *)compPtr), 1);
                break;
            case glu::TYPE_INT:
                src << *((const int *)compPtr);
                break;
            case glu::TYPE_UINT:
                src << *((const uint32_t *)compPtr) << "u";
                break;
            case glu::TYPE_BOOL:
                src << (*((const uint32_t *)compPtr) != 0u ? "true" : "false");
                break;
            default:
                DE_ASSERT(false);
            }
        }
    }

    if (scalarSize > 1)
        src << ")";
}

void generateCompareSrc(std::ostringstream &src, const char *resultVar, const VarType &type, const char *srcName,
                        const char *apiName, const UniformLayout &layout, const void *basePtr, uint32_t unusedMask)
{
    if (type.isBasicType() || (type.isArrayType() && type.getElementType().isBasicType()))
    {
        // Basic type or array of basic types.
        bool isArray              = type.isArrayType();
        glu::DataType elementType = isArray ? type.getElementType().getBasicType() : type.getBasicType();
        const char *typeName      = glu::getDataTypeName(elementType);
        std::string fullApiName   = string(apiName) + (isArray ? "[0]" : ""); // Arrays are always postfixed with [0]
        int uniformNdx            = layout.getUniformIndex(fullApiName.c_str());
        const UniformLayoutEntry &entry = layout.uniforms[uniformNdx];

        if (isArray)
        {
            for (int elemNdx = 0; elemNdx < type.getArraySize(); elemNdx++)
            {
                src << "\tresult *= compare_" << typeName << "(" << srcName << "[" << elemNdx << "], ";
                generateValueSrc(src, entry, basePtr, elemNdx);
                src << ");\n";
            }
        }
        else
        {
            src << "\tresult *= compare_" << typeName << "(" << srcName << ", ";
            generateValueSrc(src, entry, basePtr, 0);
            src << ");\n";
        }
    }
    else if (type.isArrayType())
    {
        const VarType &elementType = type.getElementType();

        for (int elementNdx = 0; elementNdx < type.getArraySize(); elementNdx++)
        {
            std::string op = string("[") + de::toString(elementNdx) + "]";
            generateCompareSrc(src, resultVar, elementType, (string(srcName) + op).c_str(),
                               (string(apiName) + op).c_str(), layout, basePtr, unusedMask);
        }
    }
    else
    {
        DE_ASSERT(type.isStructType());

        for (StructType::ConstIterator memberIter = type.getStruct().begin(); memberIter != type.getStruct().end();
             memberIter++)
        {
            if (memberIter->getFlags() & unusedMask)
                continue; // Skip member.

            string op = string(".") + memberIter->getName();
            generateCompareSrc(src, resultVar, memberIter->getType(), (string(srcName) + op).c_str(),
                               (string(apiName) + op).c_str(), layout, basePtr, unusedMask);
        }
    }
}

void generateCompareSrc(std::ostringstream &src, const char *resultVar, const ShaderInterface &interface,
                        const UniformLayout &layout, const std::map<int, void *> &blockPointers, bool isVertex)
{
    uint32_t unusedMask = isVertex ? UNUSED_VERTEX : UNUSED_FRAGMENT;

    for (int blockNdx = 0; blockNdx < interface.getNumUniformBlocks(); blockNdx++)
    {
        const UniformBlock &block = interface.getUniformBlock(blockNdx);

        if ((block.getFlags() & (isVertex ? DECLARE_VERTEX : DECLARE_FRAGMENT)) == 0)
            continue; // Skip.

        bool hasInstanceName  = block.getInstanceName() != DE_NULL;
        bool isArray          = block.isArray();
        int numInstances      = isArray ? block.getArraySize() : 1;
        std::string apiPrefix = hasInstanceName ? string(block.getBlockName()) + "." : string("");

        DE_ASSERT(!isArray || hasInstanceName);

        for (int instanceNdx = 0; instanceNdx < numInstances; instanceNdx++)
        {
            std::string instancePostfix   = isArray ? string("[") + de::toString(instanceNdx) + "]" : string("");
            std::string blockInstanceName = block.getBlockName() + instancePostfix;
            std::string srcPrefix =
                hasInstanceName ? string(block.getInstanceName()) + instancePostfix + "." : string("");
            int activeBlockNdx = layout.getBlockIndex(blockInstanceName.c_str());
            void *basePtr      = blockPointers.find(activeBlockNdx)->second;

            for (UniformBlock::ConstIterator uniformIter = block.begin(); uniformIter != block.end(); uniformIter++)
            {
                const Uniform &uniform = *uniformIter;

                if (uniform.getFlags() & unusedMask)
                    continue; // Don't read from that uniform.

                generateCompareSrc(src, resultVar, uniform.getType(), (srcPrefix + uniform.getName()).c_str(),
                                   (apiPrefix + uniform.getName()).c_str(), layout, basePtr, unusedMask);
            }
        }
    }
}

void generateVertexShader(std::ostringstream &src, glu::GLSLVersion glslVersion, const ShaderInterface &interface,
                          const UniformLayout &layout, const std::map<int, void *> &blockPointers)
{
    DE_ASSERT(isSupportedGLSLVersion(glslVersion));

    src << glu::getGLSLVersionDeclaration(glslVersion) << "\n";
    src << "in highp vec4 a_position;\n";
    src << "out mediump float v_vtxResult;\n";
    src << "\n";

    std::vector<const StructType *> namedStructs;
    interface.getNamedStructs(namedStructs);
    for (std::vector<const StructType *>::const_iterator structIter = namedStructs.begin();
         structIter != namedStructs.end(); structIter++)
        generateDeclaration(src, **structIter, 0);

    for (int blockNdx = 0; blockNdx < interface.getNumUniformBlocks(); blockNdx++)
    {
        const UniformBlock &block = interface.getUniformBlock(blockNdx);
        if (block.getFlags() & DECLARE_VERTEX)
            generateDeclaration(src, block);
    }

    // Comparison utilities.
    src << "\n";
    generateCompareFuncs(src, interface);

    src << "\n"
           "void main (void)\n"
           "{\n"
           "    gl_Position = a_position;\n"
           "    mediump float result = 1.0;\n";

    // Value compare.
    generateCompareSrc(src, "result", interface, layout, blockPointers, true);

    src << "    v_vtxResult = result;\n"
           "}\n";
}

void generateFragmentShader(std::ostringstream &src, glu::GLSLVersion glslVersion, const ShaderInterface &interface,
                            const UniformLayout &layout, const std::map<int, void *> &blockPointers)
{
    DE_ASSERT(isSupportedGLSLVersion(glslVersion));

    src << glu::getGLSLVersionDeclaration(glslVersion) << "\n";
    src << "in mediump float v_vtxResult;\n";
    src << "layout(location = 0) out mediump vec4 dEQP_FragColor;\n";
    src << "\n";

    std::vector<const StructType *> namedStructs;
    interface.getNamedStructs(namedStructs);
    for (std::vector<const StructType *>::const_iterator structIter = namedStructs.begin();
         structIter != namedStructs.end(); structIter++)
        generateDeclaration(src, **structIter, 0);

    for (int blockNdx = 0; blockNdx < interface.getNumUniformBlocks(); blockNdx++)
    {
        const UniformBlock &block = interface.getUniformBlock(blockNdx);
        if (block.getFlags() & DECLARE_FRAGMENT)
            generateDeclaration(src, block);
    }

    // Comparison utilities.
    src << "\n";
    generateCompareFuncs(src, interface);

    src << "\n"
           "void main (void)\n"
           "{\n"
           "    mediump float result = 1.0;\n";

    // Value compare.
    generateCompareSrc(src, "result", interface, layout, blockPointers, false);

    src << "    dEQP_FragColor = vec4(1.0, v_vtxResult, result, 1.0);\n"
           "}\n";
}

void getGLUniformLayout(const glw::Functions &gl, UniformLayout &layout, uint32_t program)
{
    int numActiveUniforms = 0;
    int numActiveBlocks   = 0;

    gl.getProgramiv(program, GL_ACTIVE_UNIFORMS, &numActiveUniforms);
    gl.getProgramiv(program, GL_ACTIVE_UNIFORM_BLOCKS, &numActiveBlocks);

    GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to get number of uniforms and uniform blocks");

    // Block entries.
    layout.blocks.resize(numActiveBlocks);
    for (int blockNdx = 0; blockNdx < numActiveBlocks; blockNdx++)
    {
        BlockLayoutEntry &entry = layout.blocks[blockNdx];
        int size;
        int nameLen;
        int numBlockUniforms;

        gl.getActiveUniformBlockiv(program, (uint32_t)blockNdx, GL_UNIFORM_BLOCK_DATA_SIZE, &size);
        gl.getActiveUniformBlockiv(program, (uint32_t)blockNdx, GL_UNIFORM_BLOCK_NAME_LENGTH, &nameLen);
        gl.getActiveUniformBlockiv(program, (uint32_t)blockNdx, GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS, &numBlockUniforms);

        GLU_EXPECT_NO_ERROR(gl.getError(), "Uniform block query failed");

        // \note Some implementations incorrectly return 0 as name length even though the length should include null terminator.
        std::vector<char> nameBuf(nameLen > 0 ? nameLen : 1);
        gl.getActiveUniformBlockName(program, (uint32_t)blockNdx, (glw::GLsizei)nameBuf.size(), DE_NULL, &nameBuf[0]);

        entry.name = std::string(&nameBuf[0]);
        entry.size = size;
        entry.activeUniformIndices.resize(numBlockUniforms);

        if (numBlockUniforms > 0)
            gl.getActiveUniformBlockiv(program, (uint32_t)blockNdx, GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES,
                                       &entry.activeUniformIndices[0]);

        GLU_EXPECT_NO_ERROR(gl.getError(), "Uniform block query failed");
    }

    if (numActiveUniforms > 0)
    {
        // Uniform entries.
        std::vector<uint32_t> uniformIndices(numActiveUniforms);
        for (int i = 0; i < numActiveUniforms; i++)
            uniformIndices[i] = (uint32_t)i;

        std::vector<int> types(numActiveUniforms);
        std::vector<int> sizes(numActiveUniforms);
        std::vector<int> nameLengths(numActiveUniforms);
        std::vector<int> blockIndices(numActiveUniforms);
        std::vector<int> offsets(numActiveUniforms);
        std::vector<int> arrayStrides(numActiveUniforms);
        std::vector<int> matrixStrides(numActiveUniforms);
        std::vector<int> rowMajorFlags(numActiveUniforms);

        // Execute queries.
        gl.getActiveUniformsiv(program, (glw::GLsizei)uniformIndices.size(), &uniformIndices[0], GL_UNIFORM_TYPE,
                               &types[0]);
        gl.getActiveUniformsiv(program, (glw::GLsizei)uniformIndices.size(), &uniformIndices[0], GL_UNIFORM_SIZE,
                               &sizes[0]);
        gl.getActiveUniformsiv(program, (glw::GLsizei)uniformIndices.size(), &uniformIndices[0], GL_UNIFORM_NAME_LENGTH,
                               &nameLengths[0]);
        gl.getActiveUniformsiv(program, (glw::GLsizei)uniformIndices.size(), &uniformIndices[0], GL_UNIFORM_BLOCK_INDEX,
                               &blockIndices[0]);
        gl.getActiveUniformsiv(program, (glw::GLsizei)uniformIndices.size(), &uniformIndices[0], GL_UNIFORM_OFFSET,
                               &offsets[0]);
        gl.getActiveUniformsiv(program, (glw::GLsizei)uniformIndices.size(), &uniformIndices[0],
                               GL_UNIFORM_ARRAY_STRIDE, &arrayStrides[0]);
        gl.getActiveUniformsiv(program, (glw::GLsizei)uniformIndices.size(), &uniformIndices[0],
                               GL_UNIFORM_MATRIX_STRIDE, &matrixStrides[0]);
        gl.getActiveUniformsiv(program, (glw::GLsizei)uniformIndices.size(), &uniformIndices[0],
                               GL_UNIFORM_IS_ROW_MAJOR, &rowMajorFlags[0]);

        GLU_EXPECT_NO_ERROR(gl.getError(), "Active uniform query failed");

        // Translate to LayoutEntries
        layout.uniforms.resize(numActiveUniforms);
        for (int uniformNdx = 0; uniformNdx < numActiveUniforms; uniformNdx++)
        {
            UniformLayoutEntry &entry = layout.uniforms[uniformNdx];
            std::vector<char> nameBuf(nameLengths[uniformNdx]);
            glw::GLsizei nameLen = 0;
            int size             = 0;
            uint32_t type        = GL_NONE;

            gl.getActiveUniform(program, (uint32_t)uniformNdx, (glw::GLsizei)nameBuf.size(), &nameLen, &size, &type,
                                &nameBuf[0]);

            GLU_EXPECT_NO_ERROR(gl.getError(), "Uniform name query failed");

            // \note glGetActiveUniform() returns length without \0 and glGetActiveUniformsiv() with \0
            if (nameLen + 1 != nameLengths[uniformNdx] || size != sizes[uniformNdx] ||
                type != (uint32_t)types[uniformNdx])
                TCU_FAIL("Values returned by glGetActiveUniform() don't match with values queried with "
                         "glGetActiveUniformsiv().");

            entry.name         = std::string(&nameBuf[0]);
            entry.type         = glu::getDataTypeFromGLType(types[uniformNdx]);
            entry.size         = sizes[uniformNdx];
            entry.blockNdx     = blockIndices[uniformNdx];
            entry.offset       = offsets[uniformNdx];
            entry.arrayStride  = arrayStrides[uniformNdx];
            entry.matrixStride = matrixStrides[uniformNdx];
            entry.isRowMajor   = rowMajorFlags[uniformNdx] != GL_FALSE;
        }
    }
}

void copyUniformData(const UniformLayoutEntry &dstEntry, void *dstBlockPtr, const UniformLayoutEntry &srcEntry,
                     const void *srcBlockPtr)
{
    uint8_t *dstBasePtr       = (uint8_t *)dstBlockPtr + dstEntry.offset;
    const uint8_t *srcBasePtr = (const uint8_t *)srcBlockPtr + srcEntry.offset;

    DE_ASSERT(dstEntry.size <= srcEntry.size);
    DE_ASSERT(dstEntry.type == srcEntry.type);

    int scalarSize     = glu::getDataTypeScalarSize(dstEntry.type);
    bool isMatrix      = glu::isDataTypeMatrix(dstEntry.type);
    const int compSize = sizeof(uint32_t);

    for (int elementNdx = 0; elementNdx < dstEntry.size; elementNdx++)
    {
        uint8_t *dstElemPtr       = dstBasePtr + elementNdx * dstEntry.arrayStride;
        const uint8_t *srcElemPtr = srcBasePtr + elementNdx * srcEntry.arrayStride;

        if (isMatrix)
        {
            int numRows = glu::getDataTypeMatrixNumRows(dstEntry.type);
            int numCols = glu::getDataTypeMatrixNumColumns(dstEntry.type);

            for (int colNdx = 0; colNdx < numCols; colNdx++)
            {
                for (int rowNdx = 0; rowNdx < numRows; rowNdx++)
                {
                    uint8_t *dstCompPtr =
                        dstElemPtr + (dstEntry.isRowMajor ? rowNdx * dstEntry.matrixStride + colNdx * compSize :
                                                            colNdx * dstEntry.matrixStride + rowNdx * compSize);
                    const uint8_t *srcCompPtr =
                        srcElemPtr + (srcEntry.isRowMajor ? rowNdx * srcEntry.matrixStride + colNdx * compSize :
                                                            colNdx * srcEntry.matrixStride + rowNdx * compSize);
                    deMemcpy(dstCompPtr, srcCompPtr, compSize);
                }
            }
        }
        else
            deMemcpy(dstElemPtr, srcElemPtr, scalarSize * compSize);
    }
}

void copyUniformData(const UniformLayout &dstLayout, const std::map<int, void *> &dstBlockPointers,
                     const UniformLayout &srcLayout, const std::map<int, void *> &srcBlockPointers)
{
    // \note Src layout is used as reference in case of activeUniforms happens to be incorrect in dstLayout blocks.
    int numBlocks = (int)srcLayout.blocks.size();

    for (int srcBlockNdx = 0; srcBlockNdx < numBlocks; srcBlockNdx++)
    {
        const BlockLayoutEntry &srcBlock = srcLayout.blocks[srcBlockNdx];
        const void *srcBlockPtr          = srcBlockPointers.find(srcBlockNdx)->second;
        int dstBlockNdx                  = dstLayout.getBlockIndex(srcBlock.name.c_str());
        void *dstBlockPtr                = dstBlockNdx >= 0 ? dstBlockPointers.find(dstBlockNdx)->second : DE_NULL;

        if (dstBlockNdx < 0)
            continue;

        for (vector<int>::const_iterator srcUniformNdxIter = srcBlock.activeUniformIndices.begin();
             srcUniformNdxIter != srcBlock.activeUniformIndices.end(); srcUniformNdxIter++)
        {
            const UniformLayoutEntry &srcEntry = srcLayout.uniforms[*srcUniformNdxIter];
            int dstUniformNdx                  = dstLayout.getUniformIndex(srcEntry.name.c_str());

            if (dstUniformNdx < 0)
                continue;

            copyUniformData(dstLayout.uniforms[dstUniformNdx], dstBlockPtr, srcEntry, srcBlockPtr);
        }
    }
}

} // namespace

class UniformBufferManager
{
public:
    UniformBufferManager(const glu::RenderContext &renderCtx);
    ~UniformBufferManager(void);

    uint32_t allocBuffer(void);

private:
    UniformBufferManager(const UniformBufferManager &other);
    UniformBufferManager &operator=(const UniformBufferManager &other);

    const glu::RenderContext &m_renderCtx;
    std::vector<uint32_t> m_buffers;
};

UniformBufferManager::UniformBufferManager(const glu::RenderContext &renderCtx) : m_renderCtx(renderCtx)
{
}

UniformBufferManager::~UniformBufferManager(void)
{
    if (!m_buffers.empty())
        m_renderCtx.getFunctions().deleteBuffers((glw::GLsizei)m_buffers.size(), &m_buffers[0]);
}

uint32_t UniformBufferManager::allocBuffer(void)
{
    uint32_t buf = 0;

    m_buffers.reserve(m_buffers.size() + 1);
    m_renderCtx.getFunctions().genBuffers(1, &buf);
    GLU_EXPECT_NO_ERROR(m_renderCtx.getFunctions().getError(), "Failed to allocate uniform buffer");
    m_buffers.push_back(buf);

    return buf;
}

} // namespace ub

using namespace ub;

// UniformBlockCase.

UniformBlockCase::UniformBlockCase(tcu::TestContext &testCtx, glu::RenderContext &renderCtx, const char *name,
                                   const char *description, glu::GLSLVersion glslVersion, BufferMode bufferMode)
    : TestCase(testCtx, name, description)
    , m_renderCtx(renderCtx)
    , m_glslVersion(glslVersion)
    , m_bufferMode(bufferMode)
{
    TCU_CHECK_INTERNAL(isSupportedGLSLVersion(glslVersion));
}

UniformBlockCase::~UniformBlockCase(void)
{
}

UniformBlockCase::IterateResult UniformBlockCase::iterate(void)
{
    TestLog &log             = m_testCtx.getLog();
    const glw::Functions &gl = m_renderCtx.getFunctions();
    UniformLayout refLayout;        //!< std140 layout.
    vector<uint8_t> data;           //!< Data.
    map<int, void *> blockPointers; //!< Reference block pointers.

    // Initialize result to pass.
    m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");

    // Compute reference layout.
    computeStd140Layout(refLayout, m_interface);

    // Assign storage for reference values.
    {
        int totalSize = 0;
        for (vector<BlockLayoutEntry>::const_iterator blockIter = refLayout.blocks.begin();
             blockIter != refLayout.blocks.end(); blockIter++)
            totalSize += blockIter->size;
        data.resize(totalSize);

        // Pointers for each block.
        int curOffset = 0;
        for (int blockNdx = 0; blockNdx < (int)refLayout.blocks.size(); blockNdx++)
        {
            blockPointers[blockNdx] = &data[0] + curOffset;
            curOffset += refLayout.blocks[blockNdx].size;
        }
    }

    // Generate values.
    generateValues(refLayout, blockPointers, 1 /* seed */);

    // Generate shaders and build program.
    std::ostringstream vtxSrc;
    std::ostringstream fragSrc;

    generateVertexShader(vtxSrc, m_glslVersion, m_interface, refLayout, blockPointers);
    generateFragmentShader(fragSrc, m_glslVersion, m_interface, refLayout, blockPointers);

    glu::ShaderProgram program(m_renderCtx, glu::makeVtxFragSources(vtxSrc.str(), fragSrc.str()));
    log << program;

    if (!program.isOk())
    {
        // Compile failed.
        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Compile failed");
        return STOP;
    }

    // Query layout from GL.
    UniformLayout glLayout;
    getGLUniformLayout(gl, glLayout, program.getProgram());

    // Print layout to log.
    log << TestLog::Section("ActiveUniformBlocks", "Active Uniform Blocks");
    for (int blockNdx = 0; blockNdx < (int)glLayout.blocks.size(); blockNdx++)
        log << TestLog::Message << blockNdx << ": " << glLayout.blocks[blockNdx] << TestLog::EndMessage;
    log << TestLog::EndSection;

    log << TestLog::Section("ActiveUniforms", "Active Uniforms");
    for (int uniformNdx = 0; uniformNdx < (int)glLayout.uniforms.size(); uniformNdx++)
        log << TestLog::Message << uniformNdx << ": " << glLayout.uniforms[uniformNdx] << TestLog::EndMessage;
    log << TestLog::EndSection;

    // Check that we can even try rendering with given layout.
    if (!checkLayoutIndices(glLayout) || !checkLayoutBounds(glLayout) || !compareTypes(refLayout, glLayout))
    {
        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid layout");
        return STOP; // It is not safe to use the given layout.
    }

    // Verify all std140 blocks.
    if (!compareStd140Blocks(refLayout, glLayout))
        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid std140 layout");

    // Verify all shared blocks - all uniforms should be active, and certain properties match.
    if (!compareSharedBlocks(refLayout, glLayout))
        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid shared layout");

    // Check consistency with index queries
    if (!checkIndexQueries(program.getProgram(), glLayout))
        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Inconsintent block index query results");

    // Use program.
    gl.useProgram(program.getProgram());

    // Assign binding points to all active uniform blocks.
    for (int blockNdx = 0; blockNdx < (int)glLayout.blocks.size(); blockNdx++)
    {
        uint32_t binding = (uint32_t)blockNdx; // \todo [2012-01-25 pyry] Randomize order?
        gl.uniformBlockBinding(program.getProgram(), (uint32_t)blockNdx, binding);
    }

    GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to set uniform block bindings");

    // Allocate buffers, write data and bind to targets.
    UniformBufferManager bufferManager(m_renderCtx);
    if (m_bufferMode == BUFFERMODE_PER_BLOCK)
    {
        int numBlocks = (int)glLayout.blocks.size();
        vector<vector<uint8_t>> glData(numBlocks);
        map<int, void *> glBlockPointers;

        for (int blockNdx = 0; blockNdx < numBlocks; blockNdx++)
        {
            glData[blockNdx].resize(glLayout.blocks[blockNdx].size);
            glBlockPointers[blockNdx] = &glData[blockNdx][0];
        }

        copyUniformData(glLayout, glBlockPointers, refLayout, blockPointers);

        for (int blockNdx = 0; blockNdx < numBlocks; blockNdx++)
        {
            uint32_t buffer  = bufferManager.allocBuffer();
            uint32_t binding = (uint32_t)blockNdx;

            gl.bindBuffer(GL_UNIFORM_BUFFER, buffer);
            gl.bufferData(GL_UNIFORM_BUFFER, (glw::GLsizeiptr)glData[blockNdx].size(), &glData[blockNdx][0],
                          GL_STATIC_DRAW);
            GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to upload uniform buffer data");

            gl.bindBufferBase(GL_UNIFORM_BUFFER, binding, buffer);
            GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBufferBase(GL_UNIFORM_BUFFER) failed");
        }
    }
    else
    {
        DE_ASSERT(m_bufferMode == BUFFERMODE_SINGLE);

        int totalSize        = 0;
        int curOffset        = 0;
        int numBlocks        = (int)glLayout.blocks.size();
        int bindingAlignment = 0;
        map<int, int> glBlockOffsets;

        gl.getIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &bindingAlignment);

        // Compute total size and offsets.
        curOffset = 0;
        for (int blockNdx = 0; blockNdx < numBlocks; blockNdx++)
        {
            if (bindingAlignment > 0)
                curOffset = deRoundUp32(curOffset, bindingAlignment);
            glBlockOffsets[blockNdx] = curOffset;
            curOffset += glLayout.blocks[blockNdx].size;
        }
        totalSize = curOffset;

        // Assign block pointers.
        vector<uint8_t> glData(totalSize);
        map<int, void *> glBlockPointers;

        for (int blockNdx = 0; blockNdx < numBlocks; blockNdx++)
            glBlockPointers[blockNdx] = &glData[glBlockOffsets[blockNdx]];

        // Copy to gl format.
        copyUniformData(glLayout, glBlockPointers, refLayout, blockPointers);

        // Allocate buffer and upload data.
        uint32_t buffer = bufferManager.allocBuffer();
        gl.bindBuffer(GL_UNIFORM_BUFFER, buffer);
        if (!glData.empty())
            gl.bufferData(GL_UNIFORM_BUFFER, (glw::GLsizeiptr)glData.size(), &glData[0], GL_STATIC_DRAW);

        GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to upload uniform buffer data");

        // Bind ranges to binding points.
        for (int blockNdx = 0; blockNdx < numBlocks; blockNdx++)
        {
            uint32_t binding = (uint32_t)blockNdx;
            gl.bindBufferRange(GL_UNIFORM_BUFFER, binding, buffer, (glw::GLintptr)glBlockOffsets[blockNdx],
                               (glw::GLsizeiptr)glLayout.blocks[blockNdx].size);
            GLU_EXPECT_NO_ERROR(gl.getError(), "glBindBufferRange(GL_UNIFORM_BUFFER) failed");
        }
    }

    bool renderOk = render(program.getProgram());
    if (!renderOk)
        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image compare failed");

    return STOP;
}

bool UniformBlockCase::compareStd140Blocks(const UniformLayout &refLayout, const UniformLayout &cmpLayout) const
{
    TestLog &log  = m_testCtx.getLog();
    bool isOk     = true;
    int numBlocks = m_interface.getNumUniformBlocks();

    for (int blockNdx = 0; blockNdx < numBlocks; blockNdx++)
    {
        const UniformBlock &block = m_interface.getUniformBlock(blockNdx);
        bool isArray              = block.isArray();
        std::string instanceName  = string(block.getBlockName()) + (isArray ? "[0]" : "");
        int refBlockNdx           = refLayout.getBlockIndex(instanceName.c_str());
        int cmpBlockNdx           = cmpLayout.getBlockIndex(instanceName.c_str());
        bool isUsed               = (block.getFlags() & (DECLARE_VERTEX | DECLARE_FRAGMENT)) != 0;

        if ((block.getFlags() & LAYOUT_STD140) == 0)
            continue; // Not std140 layout.

        DE_ASSERT(refBlockNdx >= 0);

        if (cmpBlockNdx < 0)
        {
            // Not found, should it?
            if (isUsed)
            {
                log << TestLog::Message << "Error: Uniform block '" << instanceName << "' not found"
                    << TestLog::EndMessage;
                isOk = false;
            }

            continue; // Skip block.
        }

        const BlockLayoutEntry &refBlockLayout = refLayout.blocks[refBlockNdx];
        const BlockLayoutEntry &cmpBlockLayout = cmpLayout.blocks[cmpBlockNdx];

        // \todo [2012-01-24 pyry] Verify that activeUniformIndices is correct.
        // \todo [2012-01-24 pyry] Verify all instances.
        if (refBlockLayout.activeUniformIndices.size() != cmpBlockLayout.activeUniformIndices.size())
        {
            log << TestLog::Message << "Error: Number of active uniforms differ in block '" << instanceName
                << "' (expected " << refBlockLayout.activeUniformIndices.size() << ", got "
                << cmpBlockLayout.activeUniformIndices.size() << ")" << TestLog::EndMessage;
            isOk = false;
        }

        for (vector<int>::const_iterator ndxIter = refBlockLayout.activeUniformIndices.begin();
             ndxIter != refBlockLayout.activeUniformIndices.end(); ndxIter++)
        {
            const UniformLayoutEntry &refEntry = refLayout.uniforms[*ndxIter];
            int cmpEntryNdx                    = cmpLayout.getUniformIndex(refEntry.name.c_str());

            if (cmpEntryNdx < 0)
            {
                log << TestLog::Message << "Error: Uniform '" << refEntry.name << "' not found" << TestLog::EndMessage;
                isOk = false;
                continue;
            }

            const UniformLayoutEntry &cmpEntry = cmpLayout.uniforms[cmpEntryNdx];

            if (refEntry.type != cmpEntry.type || refEntry.size != cmpEntry.size ||
                refEntry.offset != cmpEntry.offset || refEntry.arrayStride != cmpEntry.arrayStride ||
                refEntry.matrixStride != cmpEntry.matrixStride || refEntry.isRowMajor != cmpEntry.isRowMajor)
            {
                log << TestLog::Message << "Error: Layout mismatch in '" << refEntry.name << "':\n"
                    << "  expected: type = " << glu::getDataTypeName(refEntry.type) << ", size = " << refEntry.size
                    << ", offset = " << refEntry.offset << ", array stride = " << refEntry.arrayStride
                    << ", matrix stride = " << refEntry.matrixStride
                    << ", row major = " << (refEntry.isRowMajor ? "true" : "false") << "\n"
                    << "  got: type = " << glu::getDataTypeName(cmpEntry.type) << ", size = " << cmpEntry.size
                    << ", offset = " << cmpEntry.offset << ", array stride = " << cmpEntry.arrayStride
                    << ", matrix stride = " << cmpEntry.matrixStride
                    << ", row major = " << (cmpEntry.isRowMajor ? "true" : "false") << TestLog::EndMessage;
                isOk = false;
            }
        }
    }

    return isOk;
}

bool UniformBlockCase::compareSharedBlocks(const UniformLayout &refLayout, const UniformLayout &cmpLayout) const
{
    TestLog &log  = m_testCtx.getLog();
    bool isOk     = true;
    int numBlocks = m_interface.getNumUniformBlocks();

    for (int blockNdx = 0; blockNdx < numBlocks; blockNdx++)
    {
        const UniformBlock &block = m_interface.getUniformBlock(blockNdx);
        bool isArray              = block.isArray();
        std::string instanceName  = string(block.getBlockName()) + (isArray ? "[0]" : "");
        int refBlockNdx           = refLayout.getBlockIndex(instanceName.c_str());
        int cmpBlockNdx           = cmpLayout.getBlockIndex(instanceName.c_str());
        bool isUsed               = (block.getFlags() & (DECLARE_VERTEX | DECLARE_FRAGMENT)) != 0;

        if ((block.getFlags() & LAYOUT_SHARED) == 0)
            continue; // Not shared layout.

        DE_ASSERT(refBlockNdx >= 0);

        if (cmpBlockNdx < 0)
        {
            // Not found, should it?
            if (isUsed)
            {
                log << TestLog::Message << "Error: Uniform block '" << instanceName << "' not found"
                    << TestLog::EndMessage;
                isOk = false;
            }

            continue; // Skip block.
        }

        const BlockLayoutEntry &refBlockLayout = refLayout.blocks[refBlockNdx];
        const BlockLayoutEntry &cmpBlockLayout = cmpLayout.blocks[cmpBlockNdx];

        if (refBlockLayout.activeUniformIndices.size() != cmpBlockLayout.activeUniformIndices.size())
        {
            log << TestLog::Message << "Error: Number of active uniforms differ in block '" << instanceName
                << "' (expected " << refBlockLayout.activeUniformIndices.size() << ", got "
                << cmpBlockLayout.activeUniformIndices.size() << ")" << TestLog::EndMessage;
            isOk = false;
        }

        for (vector<int>::const_iterator ndxIter = refBlockLayout.activeUniformIndices.begin();
             ndxIter != refBlockLayout.activeUniformIndices.end(); ndxIter++)
        {
            const UniformLayoutEntry &refEntry = refLayout.uniforms[*ndxIter];
            int cmpEntryNdx                    = cmpLayout.getUniformIndex(refEntry.name.c_str());

            if (cmpEntryNdx < 0)
            {
                log << TestLog::Message << "Error: Uniform '" << refEntry.name << "' not found" << TestLog::EndMessage;
                isOk = false;
                continue;
            }

            const UniformLayoutEntry &cmpEntry = cmpLayout.uniforms[cmpEntryNdx];

            if (refEntry.type != cmpEntry.type || refEntry.size != cmpEntry.size ||
                refEntry.isRowMajor != cmpEntry.isRowMajor)
            {
                log << TestLog::Message << "Error: Layout mismatch in '" << refEntry.name << "':\n"
                    << "  expected: type = " << glu::getDataTypeName(refEntry.type) << ", size = " << refEntry.size
                    << ", row major = " << (refEntry.isRowMajor ? "true" : "false") << "\n"
                    << "  got: type = " << glu::getDataTypeName(cmpEntry.type) << ", size = " << cmpEntry.size
                    << ", row major = " << (cmpEntry.isRowMajor ? "true" : "false") << TestLog::EndMessage;
                isOk = false;
            }
        }
    }

    return isOk;
}

bool UniformBlockCase::compareTypes(const UniformLayout &refLayout, const UniformLayout &cmpLayout) const
{
    TestLog &log  = m_testCtx.getLog();
    bool isOk     = true;
    int numBlocks = m_interface.getNumUniformBlocks();

    for (int blockNdx = 0; blockNdx < numBlocks; blockNdx++)
    {
        const UniformBlock &block = m_interface.getUniformBlock(blockNdx);
        bool isArray              = block.isArray();
        int numInstances          = isArray ? block.getArraySize() : 1;

        for (int instanceNdx = 0; instanceNdx < numInstances; instanceNdx++)
        {
            std::ostringstream instanceName;

            instanceName << block.getBlockName();
            if (isArray)
                instanceName << "[" << instanceNdx << "]";

            int cmpBlockNdx = cmpLayout.getBlockIndex(instanceName.str().c_str());

            if (cmpBlockNdx < 0)
                continue;

            const BlockLayoutEntry &cmpBlockLayout = cmpLayout.blocks[cmpBlockNdx];

            for (vector<int>::const_iterator ndxIter = cmpBlockLayout.activeUniformIndices.begin();
                 ndxIter != cmpBlockLayout.activeUniformIndices.end(); ndxIter++)
            {
                const UniformLayoutEntry &cmpEntry = cmpLayout.uniforms[*ndxIter];
                int refEntryNdx                    = refLayout.getUniformIndex(cmpEntry.name.c_str());

                if (refEntryNdx < 0)
                {
                    log << TestLog::Message << "Error: Uniform '" << cmpEntry.name << "' not found in reference layout"
                        << TestLog::EndMessage;
                    isOk = false;
                    continue;
                }

                const UniformLayoutEntry &refEntry = refLayout.uniforms[refEntryNdx];

                // \todo [2012-11-26 pyry] Should we check other properties as well?
                if (refEntry.type != cmpEntry.type)
                {
                    log << TestLog::Message << "Error: Uniform type mismatch in '" << refEntry.name << "':\n"
                        << "  expected: " << glu::getDataTypeName(refEntry.type) << "\n"
                        << "  got: " << glu::getDataTypeName(cmpEntry.type) << TestLog::EndMessage;
                    isOk = false;
                }
            }
        }
    }

    return isOk;
}

bool UniformBlockCase::checkLayoutIndices(const UniformLayout &layout) const
{
    TestLog &log    = m_testCtx.getLog();
    int numUniforms = (int)layout.uniforms.size();
    int numBlocks   = (int)layout.blocks.size();
    bool isOk       = true;

    // Check uniform block indices.
    for (int uniformNdx = 0; uniformNdx < numUniforms; uniformNdx++)
    {
        const UniformLayoutEntry &uniform = layout.uniforms[uniformNdx];

        if (uniform.blockNdx < 0 || !deInBounds32(uniform.blockNdx, 0, numBlocks))
        {
            log << TestLog::Message << "Error: Invalid block index in uniform '" << uniform.name << "'"
                << TestLog::EndMessage;
            isOk = false;
        }
    }

    // Check active uniforms.
    for (int blockNdx = 0; blockNdx < numBlocks; blockNdx++)
    {
        const BlockLayoutEntry &block = layout.blocks[blockNdx];

        for (vector<int>::const_iterator uniformIter = block.activeUniformIndices.begin();
             uniformIter != block.activeUniformIndices.end(); uniformIter++)
        {
            if (!deInBounds32(*uniformIter, 0, numUniforms))
            {
                log << TestLog::Message << "Error: Invalid active uniform index " << *uniformIter << " in block '"
                    << block.name << "'" << TestLog::EndMessage;
                isOk = false;
            }
        }
    }

    return isOk;
}

bool UniformBlockCase::checkLayoutBounds(const UniformLayout &layout) const
{
    TestLog &log    = m_testCtx.getLog();
    int numUniforms = (int)layout.uniforms.size();
    bool isOk       = true;

    for (int uniformNdx = 0; uniformNdx < numUniforms; uniformNdx++)
    {
        const UniformLayoutEntry &uniform = layout.uniforms[uniformNdx];

        if (uniform.blockNdx < 0)
            continue;

        const BlockLayoutEntry &block = layout.blocks[uniform.blockNdx];
        bool isMatrix                 = glu::isDataTypeMatrix(uniform.type);
        int numVecs                   = isMatrix ? (uniform.isRowMajor ? glu::getDataTypeMatrixNumRows(uniform.type) :
                                                                         glu::getDataTypeMatrixNumColumns(uniform.type)) :
                                                   1;
        int numComps       = isMatrix ? (uniform.isRowMajor ? glu::getDataTypeMatrixNumColumns(uniform.type) :
                                                              glu::getDataTypeMatrixNumRows(uniform.type)) :
                                        glu::getDataTypeScalarSize(uniform.type);
        int numElements    = uniform.size;
        const int compSize = sizeof(uint32_t);
        int vecSize        = numComps * compSize;

        int minOffset = 0;
        int maxOffset = 0;

        // For negative strides.
        minOffset = de::min(minOffset, (numVecs - 1) * uniform.matrixStride);
        minOffset = de::min(minOffset, (numElements - 1) * uniform.arrayStride);
        minOffset = de::min(minOffset, (numElements - 1) * uniform.arrayStride + (numVecs - 1) * uniform.matrixStride);

        maxOffset = de::max(maxOffset, vecSize);
        maxOffset = de::max(maxOffset, (numVecs - 1) * uniform.matrixStride + vecSize);
        maxOffset = de::max(maxOffset, (numElements - 1) * uniform.arrayStride + vecSize);
        maxOffset = de::max(maxOffset,
                            (numElements - 1) * uniform.arrayStride + (numVecs - 1) * uniform.matrixStride + vecSize);

        if (uniform.offset + minOffset < 0 || uniform.offset + maxOffset > block.size)
        {
            log << TestLog::Message << "Error: Uniform '" << uniform.name << "' out of block bounds"
                << TestLog::EndMessage;
            isOk = false;
        }
    }

    return isOk;
}

bool UniformBlockCase::checkIndexQueries(uint32_t program, const UniformLayout &layout) const
{
    tcu::TestLog &log        = m_testCtx.getLog();
    const glw::Functions &gl = m_renderCtx.getFunctions();
    bool allOk               = true;

    // \note Spec mandates that uniform blocks are assigned consecutive locations from 0
    //         to ACTIVE_UNIFORM_BLOCKS. BlockLayoutEntries are stored in that order in UniformLayout.
    for (int blockNdx = 0; blockNdx < (int)layout.blocks.size(); blockNdx++)
    {
        const BlockLayoutEntry &block = layout.blocks[blockNdx];
        const int queriedNdx          = gl.getUniformBlockIndex(program, block.name.c_str());

        if (queriedNdx != blockNdx)
        {
            log << TestLog::Message << "ERROR: glGetUniformBlockIndex(" << block.name << ") returned " << queriedNdx
                << ", expected " << blockNdx << "!" << TestLog::EndMessage;
            allOk = false;
        }

        GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformBlockIndex()");
    }

    return allOk;
}

bool UniformBlockCase::render(uint32_t program) const
{
    tcu::TestLog &log        = m_testCtx.getLog();
    const glw::Functions &gl = m_renderCtx.getFunctions();
    de::Random rnd(deStringHash(getName()));
    const tcu::RenderTarget &renderTarget = m_renderCtx.getRenderTarget();
    const int viewportW                   = de::min(renderTarget.getWidth(), 128);
    const int viewportH                   = de::min(renderTarget.getHeight(), 128);
    const int viewportX                   = rnd.getInt(0, renderTarget.getWidth() - viewportW);
    const int viewportY                   = rnd.getInt(0, renderTarget.getHeight() - viewportH);

    gl.clearColor(0.125f, 0.25f, 0.5f, 1.0f);
    gl.clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

    // Draw
    {
        const float position[]   = {-1.0f, -1.0f, 0.0f, 1.0f, -1.0f, +1.0f, 0.0f, 1.0f,
                                    +1.0f, -1.0f, 0.0f, 1.0f, +1.0f, +1.0f, 0.0f, 1.0f};
        const uint16_t indices[] = {0, 1, 2, 2, 1, 3};

        gl.viewport(viewportX, viewportY, viewportW, viewportH);

        glu::VertexArrayBinding posArray = glu::va::Float("a_position", 4, 4, 0, &position[0]);
        glu::draw(m_renderCtx, program, 1, &posArray, glu::pr::Triangles(DE_LENGTH_OF_ARRAY(indices), &indices[0]));
        GLU_EXPECT_NO_ERROR(gl.getError(), "Draw failed");
    }

    // Verify that all pixels are white.
    {
        tcu::Surface pixels(viewportW, viewportH);
        int numFailedPixels = 0;

        glu::readPixels(m_renderCtx, viewportX, viewportY, pixels.getAccess());
        GLU_EXPECT_NO_ERROR(gl.getError(), "Reading pixels failed");

        for (int y = 0; y < pixels.getHeight(); y++)
        {
            for (int x = 0; x < pixels.getWidth(); x++)
            {
                if (pixels.getPixel(x, y) != tcu::RGBA::white())
                    numFailedPixels += 1;
            }
        }

        if (numFailedPixels > 0)
        {
            log << TestLog::Image("Image", "Rendered image", pixels);
            log << TestLog::Message << "Image comparison failed, got " << numFailedPixels << " non-white pixels"
                << TestLog::EndMessage;
        }

        return numFailedPixels == 0;
    }
}

} // namespace gls
} // namespace deqp
