Implement program binary in ProgramGL.

BUG=angleproject:882

Change-Id: I8d57c185066e9fc0c1b8def09bc48d80ad97d328
Reviewed-on: https://chromium-review.googlesource.com/303901
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Geoff Lang <geofflang@chromium.org>
diff --git a/src/libANGLE/renderer/gl/FunctionsGL.cpp b/src/libANGLE/renderer/gl/FunctionsGL.cpp
index bd7d773..52f99e5 100644
--- a/src/libANGLE/renderer/gl/FunctionsGL.cpp
+++ b/src/libANGLE/renderer/gl/FunctionsGL.cpp
@@ -961,6 +961,11 @@
     AssignGLExtensionEntryPoint(extensions, "GL_ARB_transform_feedback3", loadProcAddress("glEndQueryIndexed"), &endQueryIndexed);
     AssignGLExtensionEntryPoint(extensions, "GL_ARB_transform_feedback3", loadProcAddress("glGetQueryIndexediv"), &getQueryIndexediv);
 
+    // GL_ARB_get_program_binary
+    AssignGLExtensionEntryPoint(extensions, "GL_ARB_get_program_binary", loadProcAddress("glGetProgramBinary"), &getProgramBinary);
+    AssignGLExtensionEntryPoint(extensions, "GL_ARB_get_program_binary", loadProcAddress("glProgramBinary"), &programBinary);
+    AssignGLExtensionEntryPoint(extensions, "GL_ARB_get_program_binary", loadProcAddress("glProgramParameteri"), &programParameteri);
+
     // 1.0
     if (isAtLeastGL(gl::Version(1, 0)))
     {
@@ -1791,6 +1796,10 @@
     AssignGLExtensionEntryPoint(extensions, "GL_OES_EGL_image", loadProcAddress("glEGLImageTargetRenderbufferStorageOES"), &eglImageTargetRenderbufferStorageOES);
     AssignGLExtensionEntryPoint(extensions, "GL_OES_EGL_image", loadProcAddress("glEGLImageTargetTexture2DOES"), &eglImageTargetTexture2DOES);
 
+    // GL_OES_get_program_binary
+    AssignGLExtensionEntryPoint(extensions, "GL_OES_get_program_binary", loadProcAddress("glGetProgramBinaryOES"), &getProgramBinary);
+    AssignGLExtensionEntryPoint(extensions, "GL_OES_get_program_binary", loadProcAddress("glProgramBinaryOES"), &programBinary);
+
     // 2.0
     if (isAtLeastGLES(gl::Version(2, 0)))
     {
diff --git a/src/libANGLE/renderer/gl/ProgramGL.cpp b/src/libANGLE/renderer/gl/ProgramGL.cpp
index 6a2bc44..f5d3a48 100644
--- a/src/libANGLE/renderer/gl/ProgramGL.cpp
+++ b/src/libANGLE/renderer/gl/ProgramGL.cpp
@@ -43,25 +43,58 @@
 
 LinkResult ProgramGL::load(gl::InfoLog &infoLog, gl::BinaryInputStream *stream)
 {
-    UNIMPLEMENTED();
-    return LinkResult(false, gl::Error(GL_INVALID_OPERATION));
+    preLink();
+
+    // Read the binary format, size and blob
+    GLenum binaryFormat   = stream->readInt<GLenum>();
+    GLint binaryLength    = stream->readInt<GLint>();
+    const uint8_t *binary = stream->data() + stream->offset();
+    stream->skip(binaryLength);
+
+    // Load the binary
+    mFunctions->programBinary(mProgramID, binaryFormat, binary, binaryLength);
+
+    // Verify that the program linked
+    if (!checkLinkStatus(infoLog))
+    {
+        return LinkResult(false, gl::Error(GL_NO_ERROR));
+    }
+
+    postLink();
+
+    return LinkResult(true, gl::Error(GL_NO_ERROR));
 }
 
 gl::Error ProgramGL::save(gl::BinaryOutputStream *stream)
 {
-    UNIMPLEMENTED();
-    return gl::Error(GL_INVALID_OPERATION);
+    GLint binaryLength = 0;
+    mFunctions->getProgramiv(mProgramID, GL_PROGRAM_BINARY_LENGTH, &binaryLength);
+
+    std::vector<uint8_t> binary(binaryLength);
+    GLenum binaryFormat = GL_NONE;
+    mFunctions->getProgramBinary(mProgramID, binaryLength, &binaryLength, &binaryFormat,
+                                 &binary[0]);
+
+    stream->writeInt(binaryFormat);
+    stream->writeInt(binaryLength);
+    stream->writeBytes(&binary[0], binaryLength);
+
+    return gl::Error(GL_NO_ERROR);
 }
 
 void ProgramGL::setBinaryRetrievableHint(bool retrievable)
 {
-    UNIMPLEMENTED();
+    // glProgramParameteri isn't always available on ES backends.
+    if (mFunctions->programParameteri)
+    {
+        mFunctions->programParameteri(mProgramID, GL_PROGRAM_BINARY_RETRIEVABLE_HINT,
+                                      retrievable ? GL_TRUE : GL_FALSE);
+    }
 }
 
 LinkResult ProgramGL::link(const gl::Data &data, gl::InfoLog &infoLog)
 {
-    // Reset the program state, delete the current program if one exists
-    reset();
+    preLink();
 
     // Set the transform feedback state
     std::vector<const GLchar *> transformFeedbackVaryings;
@@ -86,11 +119,8 @@
             &transformFeedbackVaryings[0], mData.getTransformFeedbackBufferMode());
     }
 
-    const gl::Shader *vertexShader   = mData.getAttachedVertexShader();
-    const gl::Shader *fragmentShader = mData.getAttachedFragmentShader();
-
-    const ShaderGL *vertexShaderGL   = GetImplAs<ShaderGL>(vertexShader);
-    const ShaderGL *fragmentShaderGL = GetImplAs<ShaderGL>(fragmentShader);
+    const ShaderGL *vertexShaderGL   = GetImplAs<ShaderGL>(mData.getAttachedVertexShader());
+    const ShaderGL *fragmentShaderGL = GetImplAs<ShaderGL>(mData.getAttachedFragmentShader());
 
     // Attach the shaders
     mFunctions->attachShader(mProgramID, vertexShaderGL->getShaderID());
@@ -115,38 +145,8 @@
     mFunctions->detachShader(mProgramID, fragmentShaderGL->getShaderID());
 
     // Verify the link
-    GLint linkStatus = GL_FALSE;
-    mFunctions->getProgramiv(mProgramID, GL_LINK_STATUS, &linkStatus);
-    if (linkStatus == GL_FALSE)
+    if (!checkLinkStatus(infoLog))
     {
-        // Linking failed, put the error into the info log
-        GLint infoLogLength = 0;
-        mFunctions->getProgramiv(mProgramID, GL_INFO_LOG_LENGTH, &infoLogLength);
-
-        std::string warning;
-
-        // Info log length includes the null terminator, so 1 means that the info log is an empty
-        // string.
-        if (infoLogLength > 1)
-        {
-            std::vector<char> buf(infoLogLength);
-            mFunctions->getProgramInfoLog(mProgramID, infoLogLength, nullptr, &buf[0]);
-
-            mFunctions->deleteProgram(mProgramID);
-            mProgramID = 0;
-
-            infoLog << buf.data();
-
-            warning = FormatString("Program link failed unexpectedly: %s", buf.data());
-        }
-        else
-        {
-            warning = "Program link failed unexpectedly with no info log.";
-        }
-        ANGLEPlatformCurrent()->logWarning(warning.c_str());
-        TRACE("\n%s", warning.c_str());
-
-        // TODO, return GL_OUT_OF_MEMORY or just fail the link? This is an unexpected case
         return LinkResult(false, gl::Error(GL_NO_ERROR));
     }
 
@@ -155,51 +155,7 @@
         mStateManager->forceUseProgram(mProgramID);
     }
 
-    // Query the uniform information
-    ASSERT(mUniformRealLocationMap.empty());
-    const auto &uniformLocations = mData.getUniformLocations();
-    const auto &uniforms = mData.getUniforms();
-    mUniformRealLocationMap.resize(uniformLocations.size(), GL_INVALID_INDEX);
-    for (size_t uniformLocation = 0; uniformLocation < uniformLocations.size(); uniformLocation++)
-    {
-        const auto &entry = uniformLocations[uniformLocation];
-        if (!entry.used)
-        {
-            continue;
-        }
-
-        // From the spec:
-        // "Locations for sequential array indices are not required to be sequential."
-        const gl::LinkedUniform &uniform = uniforms[entry.index];
-        std::stringstream fullNameStr;
-        fullNameStr << uniform.name;
-        if (uniform.isArray())
-        {
-            fullNameStr << "[" << entry.element << "]";
-        }
-        const std::string &fullName = fullNameStr.str();
-
-        GLint realLocation = mFunctions->getUniformLocation(mProgramID, fullName.c_str());
-        mUniformRealLocationMap[uniformLocation] = realLocation;
-    }
-
-    mUniformIndexToSamplerIndex.resize(mData.getUniforms().size(), GL_INVALID_INDEX);
-
-    for (size_t uniformId = 0; uniformId < uniforms.size(); ++uniformId)
-    {
-        const gl::LinkedUniform &linkedUniform = uniforms[uniformId];
-
-        if (!linkedUniform.isSampler() || !linkedUniform.staticUse)
-            continue;
-
-        mUniformIndexToSamplerIndex[uniformId] = mSamplerBindings.size();
-
-        // If uniform is a sampler type, insert it into the mSamplerBindings array
-        SamplerBindingGL samplerBinding;
-        samplerBinding.textureType = gl::SamplerTypeToTextureType(linkedUniform.type);
-        samplerBinding.boundTextureUnits.resize(linkedUniform.elementCount(), 0);
-        mSamplerBindings.push_back(samplerBinding);
-    }
+    postLink();
 
     return LinkResult(true, gl::Error(GL_NO_ERROR));
 }
@@ -369,14 +325,6 @@
     }
 }
 
-void ProgramGL::reset()
-{
-    mUniformRealLocationMap.clear();
-    mUniformBlockRealLocationMap.clear();
-    mSamplerBindings.clear();
-    mUniformIndexToSamplerIndex.clear();
-}
-
 GLuint ProgramGL::getProgramID() const
 {
     return mProgramID;
@@ -433,4 +381,102 @@
     return true;
 }
 
+void ProgramGL::preLink()
+{
+    // Reset the program state
+    mUniformRealLocationMap.clear();
+    mUniformBlockRealLocationMap.clear();
+    mSamplerBindings.clear();
+    mUniformIndexToSamplerIndex.clear();
+}
+
+bool ProgramGL::checkLinkStatus(gl::InfoLog &infoLog)
+{
+    GLint linkStatus = GL_FALSE;
+    mFunctions->getProgramiv(mProgramID, GL_LINK_STATUS, &linkStatus);
+    if (linkStatus == GL_FALSE)
+    {
+        // Linking failed, put the error into the info log
+        GLint infoLogLength = 0;
+        mFunctions->getProgramiv(mProgramID, GL_INFO_LOG_LENGTH, &infoLogLength);
+
+        std::string warning;
+
+        // Info log length includes the null terminator, so 1 means that the info log is an empty
+        // string.
+        if (infoLogLength > 1)
+        {
+            std::vector<char> buf(infoLogLength);
+            mFunctions->getProgramInfoLog(mProgramID, infoLogLength, nullptr, &buf[0]);
+
+            mFunctions->deleteProgram(mProgramID);
+            mProgramID = 0;
+
+            infoLog << buf.data();
+
+            warning = FormatString("Program link failed unexpectedly: %s", buf.data());
+        }
+        else
+        {
+            warning = "Program link failed unexpectedly with no info log.";
+        }
+        ANGLEPlatformCurrent()->logWarning(warning.c_str());
+        TRACE("\n%s", warning.c_str());
+
+        // TODO, return GL_OUT_OF_MEMORY or just fail the link? This is an unexpected case
+        return false;
+    }
+
+    return true;
+}
+
+void ProgramGL::postLink()
+{
+    // Query the uniform information
+    ASSERT(mUniformRealLocationMap.empty());
+    const auto &uniformLocations = mData.getUniformLocations();
+    const auto &uniforms = mData.getUniforms();
+    mUniformRealLocationMap.resize(uniformLocations.size(), GL_INVALID_INDEX);
+    for (size_t uniformLocation = 0; uniformLocation < uniformLocations.size(); uniformLocation++)
+    {
+        const auto &entry = uniformLocations[uniformLocation];
+        if (!entry.used)
+        {
+            continue;
+        }
+
+        // From the spec:
+        // "Locations for sequential array indices are not required to be sequential."
+        const gl::LinkedUniform &uniform = uniforms[entry.index];
+        std::stringstream fullNameStr;
+        fullNameStr << uniform.name;
+        if (uniform.isArray())
+        {
+            fullNameStr << "[" << entry.element << "]";
+        }
+        const std::string &fullName = fullNameStr.str();
+
+        GLint realLocation = mFunctions->getUniformLocation(mProgramID, fullName.c_str());
+        mUniformRealLocationMap[uniformLocation] = realLocation;
+    }
+
+    mUniformIndexToSamplerIndex.resize(mData.getUniforms().size(), GL_INVALID_INDEX);
+
+    for (size_t uniformId = 0; uniformId < uniforms.size(); ++uniformId)
+    {
+        const gl::LinkedUniform &linkedUniform = uniforms[uniformId];
+
+        if (!linkedUniform.isSampler() || !linkedUniform.staticUse)
+            continue;
+
+        mUniformIndexToSamplerIndex[uniformId] = mSamplerBindings.size();
+
+        // If uniform is a sampler type, insert it into the mSamplerBindings array
+        SamplerBindingGL samplerBinding;
+        samplerBinding.textureType = gl::SamplerTypeToTextureType(linkedUniform.type);
+        samplerBinding.boundTextureUnits.resize(linkedUniform.elementCount(), 0);
+        mSamplerBindings.push_back(samplerBinding);
+    }
+}
+
 }  // namespace rx
diff --git a/src/libANGLE/renderer/gl/ProgramGL.h b/src/libANGLE/renderer/gl/ProgramGL.h
index a7b1888..4ed4867 100644
--- a/src/libANGLE/renderer/gl/ProgramGL.h
+++ b/src/libANGLE/renderer/gl/ProgramGL.h
@@ -72,7 +72,9 @@
     const std::vector<SamplerBindingGL> &getAppliedSamplerUniforms() const;
 
   private:
-    void reset();
+    void preLink();
+    bool checkLinkStatus(gl::InfoLog &infoLog);
+    void postLink();
 
     // Helper function, makes it simpler to type.
     GLint uniLoc(GLint glLocation) const { return mUniformRealLocationMap[glLocation]; }
diff --git a/src/libANGLE/renderer/gl/renderergl_utils.cpp b/src/libANGLE/renderer/gl/renderergl_utils.cpp
index 29675cd..a35ab90 100644
--- a/src/libANGLE/renderer/gl/renderergl_utils.cpp
+++ b/src/libANGLE/renderer/gl/renderergl_utils.cpp
@@ -289,6 +289,24 @@
         // Doesn't impact supported version
     }
 
+    if (functions->isAtLeastGL(gl::Version(4, 1)) ||
+        functions->hasGLExtension("GL_ARB_get_program_binary") ||
+        functions->isAtLeastGLES(gl::Version(3, 0)) ||
+        functions->hasGLExtension("GL_OES_get_program_binary"))
+    {
+        // Able to support the GL_PROGRAM_BINARY_ANGLE format as long as another program binary
+        // format is available.
+        GLint numBinaryFormats = QuerySingleGLInt(functions, GL_NUM_PROGRAM_BINARY_FORMATS_OES);
+        if (numBinaryFormats > 0)
+        {
+            caps->programBinaryFormats.push_back(GL_PROGRAM_BINARY_ANGLE);
+        }
+    }
+    else
+    {
+        // Doesn't impact supported version
+    }
+
     // glGetShaderPrecisionFormat is not available until desktop GL version 4.1 or GL_ARB_ES2_compatibility exists
     if (functions->isAtLeastGL(gl::Version(4, 1)) || functions->hasGLExtension("GL_ARB_ES2_compatibility") ||
         functions->isAtLeastGLES(gl::Version(2, 0)))
@@ -581,6 +599,7 @@
     extensions->setTextureExtensionSupport(*textureCapsMap);
     extensions->elementIndexUint = functions->standard == STANDARD_GL_DESKTOP ||
                                    functions->isAtLeastGLES(gl::Version(3, 0)) || functions->hasGLESExtension("GL_OES_element_index_uint");
+    extensions->getProgramBinary = caps->programBinaryFormats.size() > 0;
     extensions->readFormatBGRA = functions->isAtLeastGL(gl::Version(1, 2)) || functions->hasGLExtension("GL_EXT_bgra") ||
                                  functions->hasGLESExtension("GL_EXT_read_format_bgra");
     extensions->mapBuffer = functions->isAtLeastGL(gl::Version(1, 5)) ||
diff --git a/src/tests/deqp_support/deqp_gles3_test_expectations.txt b/src/tests/deqp_support/deqp_gles3_test_expectations.txt
index 219590a..24cb819 100644
--- a/src/tests/deqp_support/deqp_gles3_test_expectations.txt
+++ b/src/tests/deqp_support/deqp_gles3_test_expectations.txt
@@ -405,7 +405,6 @@
 1323 LINUX : dEQP-GLES3.functional.fbo.depth.depth_write_clamp.depth32f_stencil8 = FAIL
 1323 LINUX : dEQP-GLES3.functional.implementation_limits.max_fragment_uniform_components = FAIL
 1323 LINUX : dEQP-GLES3.functional.implementation_limits.max_vertex_uniform_components = FAIL
-1323 LINUX : dEQP-GLES3.functional.negative_api.shader.program_binary = FAIL
 1323 LINUX : dEQP-GLES3.functional.negative_api.state.get_integeri_v = FAIL
 1323 LINUX : dEQP-GLES3.functional.rasterization.fbo.rbo_multisample_max.primitives.points = FAIL
 1323 LINUX : dEQP-GLES3.functional.shaders.builtin_functions.precision.refract.highp_fragment.scalar = FAIL