ES31: Add glGetProgramInterfaceiv API

Add API entry and validation checks(GLES 3.1 section 7.3).
Add the first 4 interfaces(PROGRAM_INPUT, PROGRAM_OUTPUT,
UNIFORM and UNIFORM_BLOCK) implementation.

BUG=angleproject:1920
TEST=angle_end2end_tests:ProgramInterfaceTestES31.*

Change-Id: Iab80ba332e2a5e2b3e677039359e60a420e3d6b0
Reviewed-on: https://chromium-review.googlesource.com/642729
Commit-Queue: Jamie Madill <jmadill@chromium.org>
Reviewed-by: Jamie Madill <jmadill@chromium.org>
diff --git a/src/common/utilities.cpp b/src/common/utilities.cpp
index ab1c3ba..03cb57c 100644
--- a/src/common/utilities.cpp
+++ b/src/common/utilities.cpp
@@ -773,6 +773,20 @@
 }
 
 template <>
+GLint ConvertToGLint(uint32_t param)
+{
+    uint32_t max = static_cast<uint32_t>(std::numeric_limits<GLint>::max());
+    return static_cast<GLint>(param >= max ? max : param);
+}
+
+template <>
+GLint ConvertToGLint(uint64_t param)
+{
+    uint64_t max = static_cast<uint64_t>(std::numeric_limits<GLint>::max());
+    return static_cast<GLint>(param >= max ? max : param);
+}
+
+template <>
 GLint ConvertToGLint(GLfloat param)
 {
     return iround<GLint>(param);
diff --git a/src/common/utilities.h b/src/common/utilities.h
index 1f007ca..b3d5d7d 100644
--- a/src/common/utilities.h
+++ b/src/common/utilities.h
@@ -74,6 +74,11 @@
 template <typename outT> outT uiround(GLfloat value) { return static_cast<outT>(value + 0.5f); }
 
 // Helper for converting arbitrary GL types to other GL types used in queries and state setting
+
+// TODO(jie.a.chen@intel.com): Add the conversion rule for all helpers as the spec requires:
+// "If a value is so large in magnitude that it cannot be represented with the requested type,"
+// "then the nearest value representable using the requested type is returned."
+
 template <typename ParamType>
 GLuint ConvertToGLuint(ParamType param)
 {
@@ -87,6 +92,13 @@
 {
     return static_cast<GLint>(param);
 }
+
+template <>
+GLint ConvertToGLint(uint32_t param);
+
+template <>
+GLint ConvertToGLint(uint64_t param);
+
 template <>
 GLint ConvertToGLint(GLfloat param);
 
diff --git a/src/libANGLE/Context.cpp b/src/libANGLE/Context.cpp
index 53b6d32..a1f3214 100644
--- a/src/libANGLE/Context.cpp
+++ b/src/libANGLE/Context.cpp
@@ -2097,6 +2097,15 @@
                            length, params);
 }
 
+void Context::getProgramInterfaceiv(GLuint program,
+                                    GLenum programInterface,
+                                    GLenum pname,
+                                    GLint *params)
+{
+    const auto *programObject = getProgram(program);
+    QueryProgramInterfaceiv(programObject, programInterface, pname, params);
+}
+
 Error Context::handleError(const Error &error)
 {
     if (error.isError())
diff --git a/src/libANGLE/Context.h b/src/libANGLE/Context.h
index b6b5ba4..35c6e33 100644
--- a/src/libANGLE/Context.h
+++ b/src/libANGLE/Context.h
@@ -213,6 +213,11 @@
                               GLsizei *length,
                               GLint *params);
 
+    void getProgramInterfaceiv(GLuint program,
+                               GLenum programInterface,
+                               GLenum pname,
+                               GLint *params);
+
     Buffer *getBuffer(GLuint handle) const;
     FenceNV *getFenceNV(GLuint handle);
     Sync *getSync(GLsync handle) const;
diff --git a/src/libANGLE/queryutils.cpp b/src/libANGLE/queryutils.cpp
index 46bc656..9d8b2c8 100644
--- a/src/libANGLE/queryutils.cpp
+++ b/src/libANGLE/queryutils.cpp
@@ -445,19 +445,19 @@
     switch (prop)
     {
         case GL_TYPE:
-            return var.type;
+            return ConvertToGLint(var.type);
 
         case GL_ARRAY_SIZE:
             // TODO(jie.a.chen@intel.com): check array of array.
             if (var.isArray() && !var.isStruct())
             {
-                return static_cast<GLint>(var.elementCount());
+                return ConvertToGLint(var.elementCount());
             }
             return 1;
 
         case GL_NAME_LENGTH:
         {
-            GLint length = static_cast<GLint>(var.name.size());
+            size_t length = var.name.size();
             if (var.isArray())
             {
                 // Counts "[0]".
@@ -465,7 +465,7 @@
             }
             // ES31 spec p84: This counts the terminating null char.
             ++length;
-            return length;
+            return ConvertToGLint(length);
         }
 
         case GL_LOCATION:
@@ -527,6 +527,105 @@
     }
 }
 
+GLint QueryProgramInterfaceActiveResources(const Program *program, GLenum programInterface)
+{
+    switch (programInterface)
+    {
+        case GL_PROGRAM_INPUT:
+            return ConvertToGLint(program->getAttributes().size());
+
+        case GL_PROGRAM_OUTPUT:
+            return ConvertToGLint(program->getState().getOutputVariables().size());
+
+        case GL_UNIFORM:
+            return ConvertToGLint(program->getState().getUniforms().size());
+
+        case GL_UNIFORM_BLOCK:
+            return ConvertToGLint(program->getState().getUniformBlocks().size());
+
+        // TODO(jie.a.chen@intel.com): more interfaces.
+        case GL_TRANSFORM_FEEDBACK_VARYING:
+        case GL_BUFFER_VARIABLE:
+        case GL_SHADER_STORAGE_BLOCK:
+        case GL_ATOMIC_COUNTER_BUFFER:
+            UNIMPLEMENTED();
+            return 0;
+
+        default:
+            UNREACHABLE();
+            return 0;
+    }
+}
+
+template <typename T, typename M>
+GLint FindMaxSize(const std::vector<T> &resources, M member)
+{
+    GLint max = 0;
+    for (const T &resource : resources)
+    {
+        max = std::max(max, ConvertToGLint((resource.*member).size()));
+    }
+    return max;
+}
+
+GLint QueryProgramInterfaceMaxNameLength(const Program *program, GLenum programInterface)
+{
+    GLint maxNameLength = 0;
+    switch (programInterface)
+    {
+        case GL_PROGRAM_INPUT:
+            maxNameLength = FindMaxSize(program->getAttributes(), &sh::Attribute::name);
+            break;
+
+        case GL_PROGRAM_OUTPUT:
+            maxNameLength =
+                FindMaxSize(program->getState().getOutputVariables(), &sh::OutputVariable::name);
+            break;
+
+        case GL_UNIFORM:
+            maxNameLength = FindMaxSize(program->getState().getUniforms(), &LinkedUniform::name);
+            break;
+
+        case GL_UNIFORM_BLOCK:
+            maxNameLength =
+                FindMaxSize(program->getState().getUniformBlocks(), &UniformBlock::name);
+            break;
+
+        // TODO(jie.a.chen@intel.com): more interfaces.
+        case GL_TRANSFORM_FEEDBACK_VARYING:
+        case GL_BUFFER_VARIABLE:
+        case GL_SHADER_STORAGE_BLOCK:
+            UNIMPLEMENTED();
+            return 0;
+
+        default:
+            UNREACHABLE();
+            return 0;
+    }
+    // This length includes an extra character for the null terminator.
+    return (maxNameLength == 0 ? 0 : maxNameLength + 1);
+}
+
+GLint QueryProgramInterfaceMaxNumActiveVariables(const Program *program, GLenum programInterface)
+{
+    switch (programInterface)
+    {
+        case GL_UNIFORM_BLOCK:
+            return FindMaxSize(program->getState().getUniformBlocks(),
+                               &UniformBlock::memberIndexes);
+
+        // TODO(jie.a.chen@intel.com): more interfaces.
+        case GL_SHADER_STORAGE_BLOCK:
+        case GL_ATOMIC_COUNTER_BUFFER:
+            UNIMPLEMENTED();
+            return 0;
+
+        default:
+            UNREACHABLE();
+            return 0;
+    }
+}
+
 }  // anonymous namespace
 
 void QueryFramebufferAttachmentParameteriv(const Framebuffer *framebuffer,
@@ -1261,6 +1360,30 @@
     }
 }
 
+void QueryProgramInterfaceiv(const Program *program,
+                             GLenum programInterface,
+                             GLenum pname,
+                             GLint *params)
+{
+    switch (pname)
+    {
+        case GL_ACTIVE_RESOURCES:
+            *params = QueryProgramInterfaceActiveResources(program, programInterface);
+            break;
+
+        case GL_MAX_NAME_LENGTH:
+            *params = QueryProgramInterfaceMaxNameLength(program, programInterface);
+            break;
+
+        case GL_MAX_NUM_ACTIVE_VARIABLES:
+            *params = QueryProgramInterfaceMaxNumActiveVariables(program, programInterface);
+            break;
+
+        default:
+            UNREACHABLE();
+    }
+}
+
 }  // namespace gl
 
 namespace egl
diff --git a/src/libANGLE/queryutils.h b/src/libANGLE/queryutils.h
index e47776c..1ae7343 100644
--- a/src/libANGLE/queryutils.h
+++ b/src/libANGLE/queryutils.h
@@ -136,6 +136,11 @@
                             GLsizei *length,
                             GLint *params);
 
+void QueryProgramInterfaceiv(const Program *program,
+                             GLenum programInterface,
+                             GLenum pname,
+                             GLint *params);
+
 }  // namespace gl
 
 namespace egl
diff --git a/src/libANGLE/validationES31.cpp b/src/libANGLE/validationES31.cpp
index 09719ce..cbf44e7 100644
--- a/src/libANGLE/validationES31.cpp
+++ b/src/libANGLE/validationES31.cpp
@@ -1153,7 +1153,7 @@
 {
     if (context->getClientVersion() < ES_3_1)
     {
-        context->handleError(InvalidOperation() << "Context does not support GLES3.1.");
+        ANGLE_VALIDATION_ERR(context, InvalidOperation(), ES31Required);
         return false;
     }
 
@@ -1198,4 +1198,67 @@
     return true;
 }
 
+bool ValidateGetProgramInterfaceiv(Context *context,
+                                   GLuint program,
+                                   GLenum programInterface,
+                                   GLenum pname,
+                                   GLint *params)
+{
+    if (context->getClientVersion() < ES_3_1)
+    {
+        ANGLE_VALIDATION_ERR(context, InvalidOperation(), ES31Required);
+        return false;
+    }
+
+    Program *programObject = GetValidProgram(context, program);
+    if (programObject == nullptr)
+    {
+        return false;
+    }
+
+    if (!ValidateProgramInterface(programInterface))
+    {
+        context->handleError(InvalidEnum() << "Invalid program interface.");
+        return false;
+    }
+
+    switch (pname)
+    {
+        case GL_ACTIVE_RESOURCES:
+        case GL_MAX_NAME_LENGTH:
+        case GL_MAX_NUM_ACTIVE_VARIABLES:
+            break;
+
+        default:
+            context->handleError(InvalidEnum() << "Unknown property of program interface.");
+            return false;
+    }
+
+    if (pname == GL_MAX_NAME_LENGTH && programInterface == GL_ATOMIC_COUNTER_BUFFER)
+    {
+        context->handleError(InvalidOperation()
+                             << "Active atomic counter resources are not assigned name strings.");
+        return false;
+    }
+
+    if (pname == GL_MAX_NUM_ACTIVE_VARIABLES)
+    {
+        switch (programInterface)
+        {
+            case GL_ATOMIC_COUNTER_BUFFER:
+            case GL_SHADER_STORAGE_BLOCK:
+            case GL_UNIFORM_BLOCK:
+                break;
+
+            default:
+                context->handleError(
+                    InvalidOperation()
+                    << "MAX_NUM_ACTIVE_VARIABLES requires a buffer or block interface.");
+                return false;
+        }
+    }
+
+    return true;
+}
+
 }  // namespace gl
diff --git a/src/libANGLE/validationES31.h b/src/libANGLE/validationES31.h
index a48eac1..d3deb8b 100644
--- a/src/libANGLE/validationES31.h
+++ b/src/libANGLE/validationES31.h
@@ -80,6 +80,12 @@
                                   GLsizei *length,
                                   GLint *params);
 
+bool ValidateGetProgramInterfaceiv(Context *context,
+                                   GLuint program,
+                                   GLenum programInterface,
+                                   GLenum pname,
+                                   GLint *params);
+
 bool ValidateBindVertexBuffer(ValidationContext *context,
                               GLuint bindingIndex,
                               GLuint buffer,
diff --git a/src/libGLESv2/entry_points_gles_3_1.cpp b/src/libGLESv2/entry_points_gles_3_1.cpp
index 3d01fd4..8b5a18b 100644
--- a/src/libGLESv2/entry_points_gles_3_1.cpp
+++ b/src/libGLESv2/entry_points_gles_3_1.cpp
@@ -130,11 +130,12 @@
     Context *context = GetValidGlobalContext();
     if (context)
     {
-        if (!context->skipValidation())
+        if (!context->skipValidation() &&
+            !ValidateGetProgramInterfaceiv(context, program, programInterface, pname, params))
         {
-            context->handleError(InvalidOperation() << "Entry point not implemented");
+            return;
         }
-        UNIMPLEMENTED();
+        context->getProgramInterfaceiv(program, programInterface, pname, params);
     }
 }
 
diff --git a/src/tests/gl_tests/ProgramInterfaceTest.cpp b/src/tests/gl_tests/ProgramInterfaceTest.cpp
index e0ae489..12cf2b5 100644
--- a/src/tests/gl_tests/ProgramInterfaceTest.cpp
+++ b/src/tests/gl_tests/ProgramInterfaceTest.cpp
@@ -259,5 +259,80 @@
     EXPECT_GL_ERROR(GL_INVALID_OPERATION);
 }
 
+// Tests glGetProgramInterfaceiv.
+TEST_P(ProgramInterfaceTestES31, GetProgramInterface)
+{
+    const std::string &vertexShaderSource =
+        "#version 310 es\n"
+        "precision highp float;\n"
+        "in highp vec4 position;\n"
+        "void main()\n"
+        "{\n"
+        "    gl_Position = position;\n"
+        "}";
+
+    const std::string &fragmentShaderSource =
+        "#version 310 es\n"
+        "precision highp float;\n"
+        "uniform vec4 color;\n"
+        "out vec4 oColor;\n"
+        "uniform ub {\n"
+        "    vec4 mem0;\n"
+        "    vec4 mem1;\n"
+        "} instance;\n"
+        "void main()\n"
+        "{\n"
+        "    oColor = color;\n"
+        "}";
+
+    ANGLE_GL_PROGRAM(program, vertexShaderSource, fragmentShaderSource);
+
+    GLint num;
+    glGetProgramInterfaceiv(program, GL_PROGRAM_INPUT, GL_ACTIVE_RESOURCES, &num);
+    EXPECT_GL_NO_ERROR();
+    EXPECT_EQ(1, num);
+
+    glGetProgramInterfaceiv(program, GL_PROGRAM_INPUT, GL_MAX_NAME_LENGTH, &num);
+    EXPECT_GL_NO_ERROR();
+    EXPECT_EQ(9, num);
+
+    glGetProgramInterfaceiv(program, GL_PROGRAM_INPUT, GL_MAX_NUM_ACTIVE_VARIABLES, &num);
+    EXPECT_GL_ERROR(GL_INVALID_OPERATION);
+
+    glGetProgramInterfaceiv(program, GL_PROGRAM_OUTPUT, GL_ACTIVE_RESOURCES, &num);
+    EXPECT_GL_NO_ERROR();
+    EXPECT_EQ(1, num);
+
+    glGetProgramInterfaceiv(program, GL_PROGRAM_OUTPUT, GL_MAX_NAME_LENGTH, &num);
+    EXPECT_GL_NO_ERROR();
+    EXPECT_EQ(7, num);
+
+    glGetProgramInterfaceiv(program, GL_PROGRAM_OUTPUT, GL_MAX_NUM_ACTIVE_VARIABLES, &num);
+    EXPECT_GL_ERROR(GL_INVALID_OPERATION);
+
+    glGetProgramInterfaceiv(program, GL_UNIFORM_BLOCK, GL_ACTIVE_RESOURCES, &num);
+    EXPECT_GL_NO_ERROR();
+    EXPECT_EQ(1, num);
+
+    glGetProgramInterfaceiv(program, GL_UNIFORM_BLOCK, GL_MAX_NAME_LENGTH, &num);
+    EXPECT_GL_NO_ERROR();
+    EXPECT_EQ(3, num);
+
+    glGetProgramInterfaceiv(program, GL_UNIFORM_BLOCK, GL_MAX_NUM_ACTIVE_VARIABLES, &num);
+    EXPECT_GL_NO_ERROR();
+    EXPECT_EQ(2, num);  // mem0, mem1
+
+    glGetProgramInterfaceiv(program, GL_UNIFORM, GL_ACTIVE_RESOURCES, &num);
+    EXPECT_GL_NO_ERROR();
+    EXPECT_EQ(3, num);
+
+    glGetProgramInterfaceiv(program, GL_UNIFORM, GL_MAX_NAME_LENGTH, &num);
+    EXPECT_GL_NO_ERROR();
+    EXPECT_EQ(8, num);  // "ub.mem0"
+
+    glGetProgramInterfaceiv(program, GL_UNIFORM, GL_MAX_NUM_ACTIVE_VARIABLES, &num);
+    EXPECT_GL_ERROR(GL_INVALID_OPERATION);
+}
+
 ANGLE_INSTANTIATE_TEST(ProgramInterfaceTestES31, ES31_OPENGL(), ES31_D3D11(), ES31_OPENGLES());
 }  // anonymous namespace