Implement full program binary support for ES3.

Refactor validation to be used in both the OES and ES3 entry points.

BUG=angleproject:600
BUG=angleproject:1101

Change-Id: I2008c4ea04ce07910f03ae0b997f8a77b66203d8
Reviewed-on: https://chromium-review.googlesource.com/316620
Reviewed-by: Jamie Madill <jmadill@chromium.org>
Tested-by: Geoff Lang <geofflang@chromium.org>
diff --git a/src/libANGLE/Program.cpp b/src/libANGLE/Program.cpp
index 9347419..4a99c88 100644
--- a/src/libANGLE/Program.cpp
+++ b/src/libANGLE/Program.cpp
@@ -225,7 +225,8 @@
 Program::Data::Data()
     : mAttachedFragmentShader(nullptr),
       mAttachedVertexShader(nullptr),
-      mTransformFeedbackBufferMode(GL_INTERLEAVED_ATTRIBS)
+      mTransformFeedbackBufferMode(GL_INTERLEAVED_ATTRIBS),
+      mBinaryRetrieveableHint(false)
 {
 }
 
@@ -791,6 +792,18 @@
     return length;
 }
 
+void Program::setBinaryRetrievableHint(bool retrievable)
+{
+    // TODO(jmadill) : replace with dirty bits
+    mProgram->setBinaryRetrievableHint(retrievable);
+    mData.mBinaryRetrieveableHint = retrievable;
+}
+
+bool Program::getBinaryRetrievableHint() const
+{
+    return mData.mBinaryRetrieveableHint;
+}
+
 void Program::release()
 {
     mRefCount--;
diff --git a/src/libANGLE/Program.h b/src/libANGLE/Program.h
index ac06ff2..f538ce4 100644
--- a/src/libANGLE/Program.h
+++ b/src/libANGLE/Program.h
@@ -215,6 +215,8 @@
 
         // TODO(jmadill): use unordered/hash map when available
         std::map<int, VariableLocation> mOutputVariables;
+
+        bool mBinaryRetrieveableHint;
     };
 
     Program(rx::ImplFactory *factory, ResourceManager *manager, GLuint handle);
@@ -237,6 +239,8 @@
     Error loadBinary(GLenum binaryFormat, const void *binary, GLsizei length);
     Error saveBinary(GLenum *binaryFormat, void *binary, GLsizei bufSize, GLsizei *length) const;
     GLint getBinaryLength() const;
+    void setBinaryRetrievableHint(bool retrievable);
+    bool getBinaryRetrievableHint() const;
 
     int getInfoLogLength() const;
     void getInfoLog(GLsizei bufSize, GLsizei *length, char *infoLog) const;
diff --git a/src/libANGLE/renderer/ProgramImpl.h b/src/libANGLE/renderer/ProgramImpl.h
index a9d54b8..1e68804 100644
--- a/src/libANGLE/renderer/ProgramImpl.h
+++ b/src/libANGLE/renderer/ProgramImpl.h
@@ -37,6 +37,7 @@
 
     virtual LinkResult load(gl::InfoLog &infoLog, gl::BinaryInputStream *stream) = 0;
     virtual gl::Error save(gl::BinaryOutputStream *stream) = 0;
+    virtual void setBinaryRetrievableHint(bool retrievable) = 0;
 
     virtual LinkResult link(const gl::Data &data, gl::InfoLog &infoLog) = 0;
     virtual GLboolean validate(const gl::Caps &caps, gl::InfoLog *infoLog) = 0;
diff --git a/src/libANGLE/renderer/ProgramImpl_mock.h b/src/libANGLE/renderer/ProgramImpl_mock.h
index dbc1534..0db2330 100644
--- a/src/libANGLE/renderer/ProgramImpl_mock.h
+++ b/src/libANGLE/renderer/ProgramImpl_mock.h
@@ -25,6 +25,7 @@
 
     MOCK_METHOD2(load, LinkResult(gl::InfoLog &, gl::BinaryInputStream *));
     MOCK_METHOD1(save, gl::Error(gl::BinaryOutputStream *));
+    MOCK_METHOD1(setBinaryRetrievableHint, void(bool));
 
     MOCK_METHOD2(link, LinkResult(const gl::Data &, gl::InfoLog &));
     MOCK_METHOD2(validate, GLboolean(const gl::Caps &, gl::InfoLog *));
diff --git a/src/libANGLE/renderer/d3d/ProgramD3D.cpp b/src/libANGLE/renderer/d3d/ProgramD3D.cpp
index d6a2a27..7f44d58 100644
--- a/src/libANGLE/renderer/d3d/ProgramD3D.cpp
+++ b/src/libANGLE/renderer/d3d/ProgramD3D.cpp
@@ -1104,6 +1104,10 @@
     return gl::Error(GL_NO_ERROR);
 }
 
+void ProgramD3D::setBinaryRetrievableHint(bool /* retrievable */)
+{
+}
+
 gl::Error ProgramD3D::getPixelExecutableForFramebuffer(const gl::Framebuffer *fbo,
                                                        ShaderExecutableD3D **outExecutable)
 {
diff --git a/src/libANGLE/renderer/d3d/ProgramD3D.h b/src/libANGLE/renderer/d3d/ProgramD3D.h
index b5eea9b..5ea15aa 100644
--- a/src/libANGLE/renderer/d3d/ProgramD3D.h
+++ b/src/libANGLE/renderer/d3d/ProgramD3D.h
@@ -150,8 +150,9 @@
     bool usesGeometryShader(GLenum drawMode) const;
     bool usesInstancedPointSpriteEmulation() const;
 
-    LinkResult load(gl::InfoLog &infoLog, gl::BinaryInputStream *stream);
-    gl::Error save(gl::BinaryOutputStream *stream);
+    LinkResult load(gl::InfoLog &infoLog, gl::BinaryInputStream *stream) override;
+    gl::Error save(gl::BinaryOutputStream *stream) override;
+    void setBinaryRetrievableHint(bool retrievable) override;
 
     gl::Error getPixelExecutableForFramebuffer(const gl::Framebuffer *fbo,
                                                ShaderExecutableD3D **outExectuable);
diff --git a/src/libANGLE/renderer/gl/ProgramGL.cpp b/src/libANGLE/renderer/gl/ProgramGL.cpp
index c4739d1..f5adc3b 100644
--- a/src/libANGLE/renderer/gl/ProgramGL.cpp
+++ b/src/libANGLE/renderer/gl/ProgramGL.cpp
@@ -47,6 +47,11 @@
     return gl::Error(GL_INVALID_OPERATION);
 }
 
+void ProgramGL::setBinaryRetrievableHint(bool retrievable)
+{
+    UNIMPLEMENTED();
+}
+
 LinkResult ProgramGL::link(const gl::Data &data, gl::InfoLog &infoLog)
 {
     // Reset the program state, delete the current program if one exists
diff --git a/src/libANGLE/renderer/gl/ProgramGL.h b/src/libANGLE/renderer/gl/ProgramGL.h
index 377ba0f..fa34368 100644
--- a/src/libANGLE/renderer/gl/ProgramGL.h
+++ b/src/libANGLE/renderer/gl/ProgramGL.h
@@ -33,6 +33,7 @@
 
     LinkResult load(gl::InfoLog &infoLog, gl::BinaryInputStream *stream) override;
     gl::Error save(gl::BinaryOutputStream *stream) override;
+    void setBinaryRetrievableHint(bool retrievable) override;
 
     LinkResult link(const gl::Data &data, gl::InfoLog &infoLog) override;
     GLboolean validate(const gl::Caps &caps, gl::InfoLog *infoLog) override;
diff --git a/src/libANGLE/validationES.cpp b/src/libANGLE/validationES.cpp
index d5e7fba..baabae8 100644
--- a/src/libANGLE/validationES.cpp
+++ b/src/libANGLE/validationES.cpp
@@ -2223,4 +2223,49 @@
 
     return true;
 }
+
+bool ValidateProgramBinaryBase(Context *context,
+                               GLuint program,
+                               GLenum binaryFormat,
+                               const void *binary,
+                               GLint length)
+{
+    Program *programObject = GetValidProgram(context, program);
+    if (programObject == nullptr)
+    {
+        return false;
+    }
+
+    const std::vector<GLenum> &programBinaryFormats = context->getCaps().programBinaryFormats;
+    if (std::find(programBinaryFormats.begin(), programBinaryFormats.end(), binaryFormat) ==
+        programBinaryFormats.end())
+    {
+        context->recordError(Error(GL_INVALID_ENUM, "Program binary format is not valid."));
+        return false;
+    }
+
+    return true;
+}
+
+bool ValidateGetProgramBinaryBase(Context *context,
+                                  GLuint program,
+                                  GLsizei bufSize,
+                                  GLsizei *length,
+                                  GLenum *binaryFormat,
+                                  void *binary)
+{
+    Program *programObject = GetValidProgram(context, program);
+    if (programObject == nullptr)
+    {
+        return false;
+    }
+
+    if (!programObject->isLinked())
+    {
+        context->recordError(Error(GL_INVALID_OPERATION, "Program is not linked."));
+        return false;
+    }
+
+    return true;
+}
 }
diff --git a/src/libANGLE/validationES.h b/src/libANGLE/validationES.h
index 041c725..fb595fd 100644
--- a/src/libANGLE/validationES.h
+++ b/src/libANGLE/validationES.h
@@ -147,6 +147,18 @@
 bool ValidateDeleteVertexArraysBase(Context *context, GLsizei n);
 bool ValidateGenVertexArraysBase(Context *context, GLsizei n);
 
+bool ValidateProgramBinaryBase(Context *context,
+                               GLuint program,
+                               GLenum binaryFormat,
+                               const void *binary,
+                               GLint length);
+bool ValidateGetProgramBinaryBase(Context *context,
+                                  GLuint program,
+                                  GLsizei bufSize,
+                                  GLsizei *length,
+                                  GLenum *binaryFormat,
+                                  void *binary);
+
 // Error messages shared here for use in testing.
 extern const char *g_ExceedsMaxElementErrorMessage;
 }
diff --git a/src/libANGLE/validationES2.cpp b/src/libANGLE/validationES2.cpp
index dc05584..b9814d5 100644
--- a/src/libANGLE/validationES2.cpp
+++ b/src/libANGLE/validationES2.cpp
@@ -1037,4 +1037,35 @@
 
     return true;
 }
+
+bool ValidateProgramBinaryOES(Context *context,
+                              GLuint program,
+                              GLenum binaryFormat,
+                              const void *binary,
+                              GLint length)
+{
+    if (!context->getExtensions().getProgramBinary)
+    {
+        context->recordError(Error(GL_INVALID_OPERATION, "Extension not enabled"));
+        return false;
+    }
+
+    return ValidateProgramBinaryBase(context, program, binaryFormat, binary, length);
+}
+
+bool ValidateGetProgramBinaryOES(Context *context,
+                                 GLuint program,
+                                 GLsizei bufSize,
+                                 GLsizei *length,
+                                 GLenum *binaryFormat,
+                                 void *binary)
+{
+    if (!context->getExtensions().getProgramBinary)
+    {
+        context->recordError(Error(GL_INVALID_OPERATION, "Extension not enabled"));
+        return false;
+    }
+
+    return ValidateGetProgramBinaryBase(context, program, bufSize, length, binaryFormat, binary);
+}
 }
diff --git a/src/libANGLE/validationES2.h b/src/libANGLE/validationES2.h
index 44166af..c81727e 100644
--- a/src/libANGLE/validationES2.h
+++ b/src/libANGLE/validationES2.h
@@ -38,6 +38,18 @@
 bool ValidateDeleteVertexArraysOES(Context *context, GLsizei n);
 bool ValidateGenVertexArraysOES(Context *context, GLsizei n);
 bool ValidateIsVertexArrayOES(Context *context);
+
+bool ValidateProgramBinaryOES(Context *context,
+                              GLuint program,
+                              GLenum binaryFormat,
+                              const void *binary,
+                              GLint length);
+bool ValidateGetProgramBinaryOES(Context *context,
+                                 GLuint program,
+                                 GLsizei bufSize,
+                                 GLsizei *length,
+                                 GLenum *binaryFormat,
+                                 void *binary);
 }
 
 #endif // LIBANGLE_VALIDATION_ES2_H_
diff --git a/src/libANGLE/validationES3.cpp b/src/libANGLE/validationES3.cpp
index 97dac8b..8ffdc20 100644
--- a/src/libANGLE/validationES3.cpp
+++ b/src/libANGLE/validationES3.cpp
@@ -1322,4 +1322,61 @@
 
     return true;
 }
+
+bool ValidateProgramBinary(Context *context,
+                           GLuint program,
+                           GLenum binaryFormat,
+                           const void *binary,
+                           GLint length)
+{
+    if (context->getClientVersion() < 3)
+    {
+        context->recordError(Error(GL_INVALID_OPERATION));
+        return false;
+    }
+
+    return ValidateProgramBinaryBase(context, program, binaryFormat, binary, length);
+}
+
+bool ValidateGetProgramBinary(Context *context,
+                              GLuint program,
+                              GLsizei bufSize,
+                              GLsizei *length,
+                              GLenum *binaryFormat,
+                              void *binary)
+{
+    if (context->getClientVersion() < 3)
+    {
+        context->recordError(Error(GL_INVALID_OPERATION));
+        return false;
+    }
+
+    return ValidateGetProgramBinaryBase(context, program, bufSize, length, binaryFormat, binary);
+}
+
+bool ValidateProgramParameter(Context *context, GLuint program, GLenum pname, GLint value)
+{
+    if (context->getClientVersion() < 3)
+    {
+        context->recordError(Error(GL_INVALID_OPERATION));
+        return false;
+    }
+
+    if (GetValidProgram(context, program) == nullptr)
+    {
+        return false;
+    }
+
+    switch (pname)
+    {
+        case GL_PROGRAM_BINARY_RETRIEVABLE_HINT:
+            break;
+
+        default:
+            context->recordError(Error(GL_INVALID_ENUM, "Invalid pname: 0x%X", pname));
+            return false;
+    }
+
+    return true;
+}
 }
diff --git a/src/libANGLE/validationES3.h b/src/libANGLE/validationES3.h
index c1d7b74..b029b64 100644
--- a/src/libANGLE/validationES3.h
+++ b/src/libANGLE/validationES3.h
@@ -59,6 +59,19 @@
 bool ValidateDeleteVertexArrays(Context *context, GLsizei n);
 bool ValidateGenVertexArrays(Context *context, GLsizei n);
 bool ValidateIsVertexArray(Context *context);
+
+bool ValidateProgramBinary(Context *context,
+                           GLuint program,
+                           GLenum binaryFormat,
+                           const void *binary,
+                           GLint length);
+bool ValidateGetProgramBinary(Context *context,
+                              GLuint program,
+                              GLsizei bufSize,
+                              GLsizei *length,
+                              GLenum *binaryFormat,
+                              void *binary);
+bool ValidateProgramParameter(Context *context, GLuint program, GLenum pname, GLint value);
 }
 
 #endif // LIBANGLE_VALIDATION_ES3_H_
diff --git a/src/libGLESv2/entry_points_gles_2_0.cpp b/src/libGLESv2/entry_points_gles_2_0.cpp
index ebd087c..06840d1 100644
--- a/src/libGLESv2/entry_points_gles_2_0.cpp
+++ b/src/libGLESv2/entry_points_gles_2_0.cpp
@@ -2082,6 +2082,7 @@
               case GL_TRANSFORM_FEEDBACK_BUFFER_MODE:
               case GL_TRANSFORM_FEEDBACK_VARYINGS:
               case GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH:
+              case GL_PROGRAM_BINARY_RETRIEVABLE_HINT:
                 context->recordError(Error(GL_INVALID_ENUM));
                 return;
             }
@@ -2134,6 +2135,9 @@
           case GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH:
             *params = programObject->getTransformFeedbackVaryingMaxLength();
             break;
+          case GL_PROGRAM_BINARY_RETRIEVABLE_HINT:
+              *params = programObject->getBinaryRetrievableHint();
+              break;
 
           default:
             context->recordError(Error(GL_INVALID_ENUM));
diff --git a/src/libGLESv2/entry_points_gles_2_0_ext.cpp b/src/libGLESv2/entry_points_gles_2_0_ext.cpp
index 1531ad0..91c55be 100644
--- a/src/libGLESv2/entry_points_gles_2_0_ext.cpp
+++ b/src/libGLESv2/entry_points_gles_2_0_ext.cpp
@@ -751,14 +751,14 @@
     Context *context = GetValidGlobalContext();
     if (context)
     {
-        Program *programObject = context->getProgram(program);
-
-        if (!programObject || !programObject->isLinked())
+        if (!ValidateGetProgramBinaryOES(context, program, bufSize, length, binaryFormat, binary))
         {
-            context->recordError(Error(GL_INVALID_OPERATION));
             return;
         }
 
+        Program *programObject = context->getProgram(program);
+        ASSERT(programObject != nullptr);
+
         Error error = programObject->saveBinary(binaryFormat, binary, bufSize, length);
         if (error.isError())
         {
@@ -776,19 +776,13 @@
     Context *context = GetValidGlobalContext();
     if (context)
     {
-        const std::vector<GLenum> &programBinaryFormats = context->getCaps().programBinaryFormats;
-        if (std::find(programBinaryFormats.begin(), programBinaryFormats.end(), binaryFormat) == programBinaryFormats.end())
+        if (!ValidateProgramBinaryOES(context, program, binaryFormat, binary, length))
         {
-            context->recordError(Error(GL_INVALID_ENUM));
             return;
         }
 
         Program *programObject = context->getProgram(program);
-        if (!programObject)
-        {
-            context->recordError(Error(GL_INVALID_OPERATION));
-            return;
-        }
+        ASSERT(programObject != nullptr);
 
         Error error = programObject->loadBinary(binaryFormat, binary, length);
         if (error.isError())
diff --git a/src/libGLESv2/entry_points_gles_3_0.cpp b/src/libGLESv2/entry_points_gles_3_0.cpp
index c08a944..8ef1314 100644
--- a/src/libGLESv2/entry_points_gles_3_0.cpp
+++ b/src/libGLESv2/entry_points_gles_3_0.cpp
@@ -3072,14 +3072,20 @@
     Context *context = GetValidGlobalContext();
     if (context)
     {
-        if (context->getClientVersion() < 3)
+        if (!ValidateGetProgramBinary(context, program, bufSize, length, binaryFormat, binary))
         {
-            context->recordError(Error(GL_INVALID_OPERATION));
             return;
         }
 
-        // TODO: Pipe through to the OES extension for now, needs proper validation
-        return GetProgramBinaryOES(program, bufSize, length, binaryFormat, binary);
+        Program *programObject = context->getProgram(program);
+        ASSERT(programObject != nullptr);
+
+        Error error = programObject->saveBinary(binaryFormat, binary, bufSize, length);
+        if (error.isError())
+        {
+            context->recordError(error);
+            return;
+        }
     }
 }
 
@@ -3091,14 +3097,20 @@
     Context *context = GetValidGlobalContext();
     if (context)
     {
-        if (context->getClientVersion() < 3)
+        if (!ValidateProgramBinary(context, program, binaryFormat, binary, length))
         {
-            context->recordError(Error(GL_INVALID_OPERATION));
             return;
         }
 
-        // TODO: Pipe through to the OES extension for now, needs proper validation
-        return ProgramBinaryOES(program, binaryFormat, binary, length);
+        Program *programObject = context->getProgram(program);
+        ASSERT(programObject != nullptr);
+
+        Error error = programObject->loadBinary(binaryFormat, binary, length);
+        if (error.isError())
+        {
+            context->recordError(error);
+            return;
+        }
     }
 }
 
@@ -3110,14 +3122,23 @@
     Context *context = GetValidGlobalContext();
     if (context)
     {
-        if (context->getClientVersion() < 3)
+        if (!ValidateProgramParameter(context, program, pname, value))
         {
-            context->recordError(Error(GL_INVALID_OPERATION));
             return;
         }
 
-        // glProgramParameteri
-        UNIMPLEMENTED();
+        gl::Program *programObject = context->getProgram(program);
+        ASSERT(programObject != nullptr);
+
+        switch (pname)
+        {
+            case GL_PROGRAM_BINARY_RETRIEVABLE_HINT:
+                programObject->setBinaryRetrievableHint(value != GL_FALSE);
+                break;
+
+            default:
+                UNREACHABLE();
+        }
     }
 }
 
diff --git a/src/tests/deqp_support/deqp_gles3_test_expectations.txt b/src/tests/deqp_support/deqp_gles3_test_expectations.txt
index 8ba0858..55303e9 100644
--- a/src/tests/deqp_support/deqp_gles3_test_expectations.txt
+++ b/src/tests/deqp_support/deqp_gles3_test_expectations.txt
@@ -735,7 +735,6 @@
 1101 WIN : dEQP-GLES3.functional.state_query.sampler.sampler_texture_max_lod_getsamplerparameteri = FAIL
 1101 WIN : dEQP-GLES3.functional.state_query.fbo.framebuffer_attachment_x_size_rbo = FAIL
 1101 WIN : dEQP-GLES3.functional.state_query.shader.program_attached_shaders = FAIL
-1101 WIN : dEQP-GLES3.functional.state_query.shader.program_binary = FAIL
 1101 WIN : dEQP-GLES3.functional.polygon_offset.fixed16_render_with_units = FAIL
 1101 WIN : dEQP-GLES3.functional.polygon_offset.fixed24_render_with_units = FAIL
 1101 WIN : dEQP-GLES3.functional.lifetime.gen.transform_feedback = FAIL