//
// Copyright (c) 2002-2012 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
#include "gtest/gtest.h"
#include "angle_gl.h"
#include "common/utilities.h"
#include "common/angleutils.h"
#include "compiler/translator/VariablePacker.h"

static sh::GLenum types[] = {
  GL_FLOAT_MAT4,            // 0
  GL_FLOAT_MAT2,            // 1
  GL_FLOAT_VEC4,            // 2
  GL_INT_VEC4,              // 3
  GL_BOOL_VEC4,             // 4
  GL_FLOAT_MAT3,            // 5
  GL_FLOAT_VEC3,            // 6
  GL_INT_VEC3,              // 7
  GL_BOOL_VEC3,             // 8
  GL_FLOAT_VEC2,            // 9
  GL_INT_VEC2,              // 10
  GL_BOOL_VEC2,             // 11
  GL_FLOAT,                 // 12
  GL_INT,                   // 13
  GL_BOOL,                  // 14
  GL_SAMPLER_2D,            // 15
  GL_SAMPLER_CUBE,          // 16
  GL_SAMPLER_EXTERNAL_OES,  // 17
  GL_SAMPLER_2D_RECT_ARB,   // 18
  GL_UNSIGNED_INT,          // 19
  GL_UNSIGNED_INT_VEC2,     // 20
  GL_UNSIGNED_INT_VEC3,     // 21
  GL_UNSIGNED_INT_VEC4,     // 22
  GL_FLOAT_MAT2x3,          // 23
  GL_FLOAT_MAT2x4,          // 24
  GL_FLOAT_MAT3x2,          // 25
  GL_FLOAT_MAT3x4,          // 26
  GL_FLOAT_MAT4x2,          // 27
  GL_FLOAT_MAT4x3,          // 28
  GL_SAMPLER_3D,            // 29
  GL_SAMPLER_2D_ARRAY,      // 30
  GL_SAMPLER_2D_SHADOW,     // 31
  GL_SAMPLER_CUBE_SHADOW,   // 32
  GL_SAMPLER_2D_ARRAY_SHADOW, // 33
  GL_INT_SAMPLER_2D,        // 34
  GL_INT_SAMPLER_CUBE,      // 35
  GL_INT_SAMPLER_3D,        // 36
  GL_INT_SAMPLER_2D_ARRAY,  // 37
  GL_UNSIGNED_INT_SAMPLER_2D, // 38
  GL_UNSIGNED_INT_SAMPLER_CUBE, // 39
  GL_UNSIGNED_INT_SAMPLER_3D, // 40
  GL_UNSIGNED_INT_SAMPLER_2D_ARRAY, // 41
};

static sh::GLenum nonSqMatTypes[] = {
  GL_FLOAT_MAT2x3,
  GL_FLOAT_MAT2x4,
  GL_FLOAT_MAT3x2,
  GL_FLOAT_MAT3x4,
  GL_FLOAT_MAT4x2,
  GL_FLOAT_MAT4x3
};

TEST(VariablePacking, Pack) {
    VariablePacker packer;
    std::vector<sh::ShaderVariable> vars;
    const int kMaxRows = 16;
    // test no vars.
    EXPECT_TRUE(packer.CheckVariablesWithinPackingLimits(kMaxRows, vars));

    for (size_t tt = 0; tt < ArraySize(types); ++tt)
    {
        sh::GLenum type            = types[tt];
        int num_rows               = VariablePacker::GetNumRows(type);
        int num_components_per_row = VariablePacker::GetNumComponentsPerRow(type);
        // Check 1 of the type.
        vars.clear();
        vars.push_back(sh::ShaderVariable(type, 0));
        EXPECT_TRUE(packer.CheckVariablesWithinPackingLimits(kMaxRows, vars));

        // Check exactly the right amount of 1 type as an array.
        int num_vars = kMaxRows / num_rows;
        vars.clear();
        vars.push_back(sh::ShaderVariable(type, num_vars == 1 ? 0 : num_vars));
        EXPECT_TRUE(packer.CheckVariablesWithinPackingLimits(kMaxRows, vars));

        // test too many
        vars.clear();
        vars.push_back(sh::ShaderVariable(type, num_vars == 0 ? 0 : (num_vars + 1)));
        EXPECT_FALSE(packer.CheckVariablesWithinPackingLimits(kMaxRows, vars));

        // Check exactly the right amount of 1 type as individual vars.
        num_vars =
            kMaxRows / num_rows * ((num_components_per_row > 2) ? 1 : (4 / num_components_per_row));
        vars.clear();
        for (int ii = 0; ii < num_vars; ++ii)
        {
            vars.push_back(sh::ShaderVariable(type, 0));
        }
        EXPECT_TRUE(packer.CheckVariablesWithinPackingLimits(kMaxRows, vars));

        // Check 1 too many.
        vars.push_back(sh::ShaderVariable(type, 0));
        EXPECT_FALSE(packer.CheckVariablesWithinPackingLimits(kMaxRows, vars));
    }

    // Test example from GLSL ES 3.0 spec chapter 11.
    vars.clear();
    vars.push_back(sh::ShaderVariable(GL_FLOAT_VEC4, 0));
    vars.push_back(sh::ShaderVariable(GL_FLOAT_MAT3, 0));
    vars.push_back(sh::ShaderVariable(GL_FLOAT_MAT3, 0));
    vars.push_back(sh::ShaderVariable(GL_FLOAT_VEC2, 6));
    vars.push_back(sh::ShaderVariable(GL_FLOAT_VEC2, 4));
    vars.push_back(sh::ShaderVariable(GL_FLOAT_VEC2, 0));
    vars.push_back(sh::ShaderVariable(GL_FLOAT, 3));
    vars.push_back(sh::ShaderVariable(GL_FLOAT, 2));
    vars.push_back(sh::ShaderVariable(GL_FLOAT, 0));
    EXPECT_TRUE(packer.CheckVariablesWithinPackingLimits(kMaxRows, vars));
}

TEST(VariablePacking, PackSizes) {
  for (size_t tt = 0; tt < ArraySize(types); ++tt) {
    GLenum type = types[tt];

    int expectedComponents = gl::VariableComponentCount(type);
    int expectedRows = gl::VariableRowCount(type);

    if (type == GL_FLOAT_MAT2) {
      expectedComponents = 4;
    } else if (gl::IsMatrixType(type)) {
      int squareSize = std::max(gl::VariableRowCount(type),
          gl::VariableColumnCount(type));
      expectedComponents = squareSize;
      expectedRows = squareSize;
    }

    EXPECT_EQ(expectedComponents,
      VariablePacker::GetNumComponentsPerRow(type));
    EXPECT_EQ(expectedRows, VariablePacker::GetNumRows(type));
  }
}

// Check special assumptions about packing non-square mats
TEST(VariablePacking, NonSquareMats) {

  for (size_t mt = 0; mt < ArraySize(nonSqMatTypes); ++mt) {
    
    GLenum type = nonSqMatTypes[mt];

    int rows = gl::VariableRowCount(type);
    int cols = gl::VariableColumnCount(type);
    int squareSize = std::max(rows, cols);

    std::vector<sh::ShaderVariable> vars;
    vars.push_back(sh::ShaderVariable(type, 0));

    // Fill columns
    for (int row = 0; row < squareSize; row++) {
      for (int col = squareSize; col < 4; ++col) {
        vars.push_back(sh::ShaderVariable(GL_FLOAT, 0));
      }
    }

    VariablePacker packer;

    EXPECT_TRUE(packer.CheckVariablesWithinPackingLimits(squareSize, vars));

    // and one scalar and packing should fail
    vars.push_back(sh::ShaderVariable(GL_FLOAT, 0));
    EXPECT_FALSE(packer.CheckVariablesWithinPackingLimits(squareSize, vars));
  }
}

// Scalar type variables can be packed sharing rows with other variables.
TEST(VariablePacking, ReuseRows)
{
    VariablePacker packer;
    std::vector<sh::ShaderVariable> vars;
    const int kMaxRows = 512;

    // uniform bool u0[129];
    // uniform bool u1[129];
    // uniform bool u2[129];
    // uniform bool u3[129];
    {
        int num_arrays = 4;
        int num_elements_per_array = kMaxRows / num_arrays + 1;
        for (int ii = 0; ii < num_arrays; ++ii)
        {
            vars.push_back(sh::ShaderVariable(GL_BOOL, num_elements_per_array));
        }
        EXPECT_TRUE(packer.CheckVariablesWithinPackingLimits(kMaxRows, vars));
    }

    vars.clear();
    // uniform vec2 u0[257];
    // uniform float u1[257];
    // uniform int u1[257];
    {
        int num_elements_per_array = kMaxRows / 2 + 1;
        vars.push_back(sh::ShaderVariable(GL_FLOAT_VEC2, num_elements_per_array));
        vars.push_back(sh::ShaderVariable(GL_FLOAT, num_elements_per_array));
        vars.push_back(sh::ShaderVariable(GL_INT, num_elements_per_array));
        EXPECT_TRUE(packer.CheckVariablesWithinPackingLimits(kMaxRows, vars));
    }
}

// Check the packer supports and flattens structures.
TEST(VariablePacking, Struct)
{
    VariablePacker packer;
    std::vector<sh::ShaderVariable> fields;
    const int kMaxRows = 16;

    // Test example from GLSL ES 3.0 spec chapter 11, but with structs
    std::vector<sh::ShaderVariable> vars;
    vars.push_back(sh::ShaderVariable(GL_STRUCT_ANGLEX, 0));

    sh::ShaderVariable &parentStruct = vars[0];
    parentStruct.fields.push_back(sh::ShaderVariable(GL_FLOAT_VEC4, 0));
    parentStruct.fields.push_back(sh::ShaderVariable(GL_FLOAT_MAT3, 0));

    parentStruct.fields.push_back(sh::ShaderVariable(GL_STRUCT_ANGLEX, 0));
    sh::ShaderVariable &innerStruct = parentStruct.fields.back();
    innerStruct.fields.push_back(sh::ShaderVariable(GL_FLOAT_MAT3, 0));
    innerStruct.fields.push_back(sh::ShaderVariable(GL_FLOAT_VEC2, 6));
    innerStruct.fields.push_back(sh::ShaderVariable(GL_FLOAT_VEC2, 4));

    parentStruct.fields.push_back(sh::ShaderVariable(GL_FLOAT_VEC2, 0));
    parentStruct.fields.push_back(sh::ShaderVariable(GL_FLOAT, 3));
    parentStruct.fields.push_back(sh::ShaderVariable(GL_FLOAT, 2));
    parentStruct.fields.push_back(sh::ShaderVariable(GL_FLOAT, 0));

    EXPECT_TRUE(packer.CheckVariablesWithinPackingLimits(kMaxRows, vars));
}
