Include the fragment output locations in the program cache key.

If the user rebinds the output locations and relinks a program, the
wrong program may be loaded from the cache.

TEST=gl_tests: TranslatorVariants/EXTBlendFuncExtended*

BUG=angleproject:4535

Change-Id: If9a9c2ad935ea4d01c3fe4313810d221e9c9ce38
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2131252
Commit-Queue: Geoff Lang <geofflang@chromium.org>
Reviewed-by: Courtney Goeltzenleuchter <courtneygo@google.com>
diff --git a/src/libANGLE/MemoryProgramCache.cpp b/src/libANGLE/MemoryProgramCache.cpp
index 299b2e3..7fea58c 100644
--- a/src/libANGLE/MemoryProgramCache.cpp
+++ b/src/libANGLE/MemoryProgramCache.cpp
@@ -116,6 +116,7 @@
 
     // Hash pre-link program properties.
     hashStream << program->getAttributeBindings() << program->getUniformLocationBindings()
+               << program->getFragmentOutputLocations() << program->getFragmentOutputIndexes()
                << program->getState().getTransformFeedbackVaryingNames()
                << program->getState().getTransformFeedbackBufferMode()
                << program->getState().getOutputLocations()
diff --git a/src/libANGLE/Program.cpp b/src/libANGLE/Program.cpp
index 8801048..1a20de6 100644
--- a/src/libANGLE/Program.cpp
+++ b/src/libANGLE/Program.cpp
@@ -2371,6 +2371,18 @@
     return mState.mUniformLocationBindings;
 }
 
+const gl::ProgramAliasedBindings &Program::getFragmentOutputLocations() const
+{
+    ASSERT(mLinkResolved);
+    return mFragmentOutputLocations;
+}
+
+const gl::ProgramAliasedBindings &Program::getFragmentOutputIndexes() const
+{
+    ASSERT(mLinkResolved);
+    return mFragmentOutputIndexes;
+}
+
 ComponentTypeMask Program::getDrawBufferTypeMask() const
 {
     ASSERT(mLinkResolved);
diff --git a/src/libANGLE/Program.h b/src/libANGLE/Program.h
index 5788ffb..690c3bd 100644
--- a/src/libANGLE/Program.h
+++ b/src/libANGLE/Program.h
@@ -807,6 +807,8 @@
 
     const ProgramBindings &getAttributeBindings() const;
     const ProgramAliasedBindings &getUniformLocationBindings() const;
+    const ProgramAliasedBindings &getFragmentOutputLocations() const;
+    const ProgramAliasedBindings &getFragmentOutputIndexes() const;
 
     int getNumViews() const
     {
diff --git a/src/tests/egl_tests/EGLBlobCacheTest.cpp b/src/tests/egl_tests/EGLBlobCacheTest.cpp
index 8cf8499..3b23083 100644
--- a/src/tests/egl_tests/EGLBlobCacheTest.cpp
+++ b/src/tests/egl_tests/EGLBlobCacheTest.cpp
@@ -108,10 +108,7 @@
 
     void testTearDown() override { gApplicationCache.clear(); }
 
-    bool programBinaryAvailable()
-    {
-        return (getClientMajorVersion() >= 3 || IsGLExtensionEnabled("GL_OES_get_program_binary"));
-    }
+    bool programBinaryAvailable() { return IsGLExtensionEnabled("GL_OES_get_program_binary"); }
 
     bool mHasBlobCache;
 };
@@ -232,4 +229,57 @@
     EXPECT_EGL_ERROR(EGL_BAD_PARAMETER);
 }
 
-ANGLE_INSTANTIATE_TEST_ES2(EGLBlobCacheTest);
+// Regression test for including the fragment output locatins in the program key.
+// http://anglebug.com/4535
+TEST_P(EGLBlobCacheTest, FragmentOutputLocationKey)
+{
+    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_blend_func_extended") ||
+                       getClientMajorVersion() < 3);
+
+    EGLDisplay display = getEGLWindow()->getDisplay();
+
+    EXPECT_TRUE(mHasBlobCache);
+    eglSetBlobCacheFuncsANDROID(display, SetBlob, GetBlob);
+    ASSERT_EGL_SUCCESS();
+
+    // Compile a shader so it puts something in the cache
+    if (programBinaryAvailable())
+    {
+        constexpr char kFragmentShaderSrc[] = R"(#version 300 es
+#extension GL_EXT_blend_func_extended : require
+precision mediump float;
+uniform vec4 src;
+uniform vec4 src1;
+out vec4 FragData;
+out vec4 SecondaryFragData;
+void main() {
+    FragData = src;
+    SecondaryFragData = src1;
+})";
+
+        constexpr char kVertexShaderSrc[] = R"(#version 300 es
+in vec4 position;
+void main() {
+    gl_Position = position;
+})";
+
+        GLuint program = CompileProgram(kVertexShaderSrc, kFragmentShaderSrc, [](GLuint p) {
+            glBindFragDataLocationEXT(p, 0, "FragData[0]");
+            glBindFragDataLocationIndexedEXT(p, 0, 1, "SecondaryFragData[0]");
+        });
+        ASSERT_NE(0u, program);
+        EXPECT_EQ(CacheOpResult::SetSuccess, gLastCacheOpResult);
+        gLastCacheOpResult = CacheOpResult::ValueNotSet;
+
+        // Re-link the program with different fragment output bindings
+        program = CompileProgram(kVertexShaderSrc, kFragmentShaderSrc, [](GLuint p) {
+            glBindFragDataLocationEXT(p, 0, "FragData");
+            glBindFragDataLocationIndexedEXT(p, 0, 1, "SecondaryFragData");
+        });
+        ASSERT_NE(0u, program);
+        EXPECT_EQ(CacheOpResult::SetSuccess, gLastCacheOpResult);
+        gLastCacheOpResult = CacheOpResult::ValueNotSet;
+    }
+}
+
+ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(EGLBlobCacheTest);