PPO: Fix updating sampler uniforms between draws

Updating sampler uniforms when using PPOs is currently broken, since the
Context/State use the currently bound ProgramExecutable which belongs to
the PPO, but the updates only happen to the Program's executable that
the uniform belongs to.

This change updates Program::updateSamplerUniform() to update any PPO
ProgramExecutables with the updated texture information when a Program's
sampler uniforms are updated, so the Context/State use the correct data.

Bug: b/182409935
Test: ProgramPipelineTest31.SampleTextureAThenTextureB
Test: SamplersTest31.SampleTextureAThenTextureB
Change-Id: I3c4e156c6e0c781e706f321f0bd12baf484ff42a
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2797951
Commit-Queue: Tim Van Patten <timvp@google.com>
Reviewed-by: Cody Northrop <cnorthrop@google.com>
Reviewed-by: Shahbaz Youssefi <syoussefi@chromium.org>
diff --git a/src/libANGLE/Observer.h b/src/libANGLE/Observer.h
index 18aa4e7..35b462a 100644
--- a/src/libANGLE/Observer.h
+++ b/src/libANGLE/Observer.h
@@ -55,6 +55,8 @@
     // Indicates an external change to the default framebuffer.
     SurfaceChanged,
 
+    // Indicates a separable program's textures or images changed in the ProgramExecutable.
+    ProgramTextureOrImageBindingChanged,
     // Indicates a separable program was successfully re-linked.
     ProgramRelinked,
     // Indicates a separable program's sampler uniforms were updated.
diff --git a/src/libANGLE/Program.cpp b/src/libANGLE/Program.cpp
index 4a00306..c030891 100644
--- a/src/libANGLE/Program.cpp
+++ b/src/libANGLE/Program.cpp
@@ -4445,6 +4445,14 @@
             }
         }
 
+        // Update the observing PPO's executable, if any.
+        // Do this before any of the Context work, since that uses the current ProgramExecutable,
+        // which will be the PPO's if this Program is bound to it, rather than this Program's.
+        if (isSeparable())
+        {
+            onStateChange(angle::SubjectMessage::ProgramTextureOrImageBindingChanged);
+        }
+
         // Notify context.
         if (context)
         {
diff --git a/src/libANGLE/ProgramPipeline.cpp b/src/libANGLE/ProgramPipeline.cpp
index b005b08..25e89c2 100644
--- a/src/libANGLE/ProgramPipeline.cpp
+++ b/src/libANGLE/ProgramPipeline.cpp
@@ -660,8 +660,9 @@
 {
     switch (message)
     {
-        case angle::SubjectMessage::SubjectChanged:
+        case angle::SubjectMessage::ProgramTextureOrImageBindingChanged:
             mState.mIsLinked = false;
+            mState.mExecutable->mActiveSamplerRefCounts.fill(0);
             mState.updateExecutableTextures();
             break;
         case angle::SubjectMessage::ProgramRelinked:
diff --git a/src/libANGLE/State.cpp b/src/libANGLE/State.cpp
index 4bc0d3c..84b0dd2 100644
--- a/src/libANGLE/State.cpp
+++ b/src/libANGLE/State.cpp
@@ -3581,7 +3581,7 @@
                                      : nullptr;
         updateTextureBinding(context, textureUnit, activeTexture);
 
-        mExecutable->onStateChange(angle::SubjectMessage::SubjectChanged);
+        mExecutable->onStateChange(angle::SubjectMessage::ProgramTextureOrImageBindingChanged);
     }
 }
 
@@ -3618,7 +3618,7 @@
             mDirtyObjects.set(DIRTY_OBJECT_IMAGES_INIT);
         }
 
-        mExecutable->onStateChange(angle::SubjectMessage::SubjectChanged);
+        mExecutable->onStateChange(angle::SubjectMessage::ProgramTextureOrImageBindingChanged);
     }
 }
 
diff --git a/src/tests/gl_tests/ProgramPipelineTest.cpp b/src/tests/gl_tests/ProgramPipelineTest.cpp
index e90370d..584ba29 100644
--- a/src/tests/gl_tests/ProgramPipelineTest.cpp
+++ b/src/tests/gl_tests/ProgramPipelineTest.cpp
@@ -835,6 +835,85 @@
     EXPECT_PIXEL_COLOR_EQ(w, h, GLColor::yellow);
 }
 
+// Test that updating a sampler uniform in a separable program behaves correctly with PPOs.
+TEST_P(ProgramPipelineTest31, SampleTextureAThenTextureB)
+{
+    ANGLE_SKIP_TEST_IF(!IsVulkan());
+
+    constexpr int kWidth  = 2;
+    constexpr int kHeight = 2;
+
+    const GLchar *vertString = R"(#version 310 es
+precision highp float;
+in vec2 a_position;
+out vec2 texCoord;
+void main()
+{
+    gl_Position = vec4(a_position, 0, 1);
+    texCoord = a_position * 0.5 + vec2(0.5);
+})";
+
+    const GLchar *fragString = R"(#version 310 es
+precision highp float;
+in vec2 texCoord;
+uniform sampler2D tex;
+out vec4 my_FragColor;
+void main()
+{
+    my_FragColor = texture(tex, texCoord);
+})";
+
+    std::array<GLColor, kWidth *kHeight> redColor = {
+        {GLColor::red, GLColor::red, GLColor::red, GLColor::red}};
+    std::array<GLColor, kWidth *kHeight> greenColor = {
+        {GLColor::green, GLColor::green, GLColor::green, GLColor::green}};
+
+    // Create a red texture and bind to texture unit 0
+    GLTexture redTex;
+    glActiveTexture(GL_TEXTURE0);
+    glBindTexture(GL_TEXTURE_2D, redTex);
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kWidth, kHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE,
+                 redColor.data());
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+    ASSERT_GL_NO_ERROR();
+    // Create a green texture and bind to texture unit 1
+    GLTexture greenTex;
+    glActiveTexture(GL_TEXTURE1);
+    glBindTexture(GL_TEXTURE_2D, greenTex);
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kWidth, kHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE,
+                 greenColor.data());
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+    glActiveTexture(GL_TEXTURE0);
+    ASSERT_GL_NO_ERROR();
+
+    bindProgramPipeline(vertString, fragString);
+
+    GLint location1 = glGetUniformLocation(mFragProg, "tex");
+    ASSERT_NE(location1, -1);
+    glActiveShaderProgram(mPipeline, mFragProg);
+    ASSERT_GL_NO_ERROR();
+
+    glEnable(GL_BLEND);
+    glBlendEquation(GL_FUNC_ADD);
+    glBlendFunc(GL_ONE, GL_ONE);
+
+    // Draw red
+    glUniform1i(location1, 0);
+    ASSERT_GL_NO_ERROR();
+    drawQuadWithPPO("a_position", 0.5f, 1.0f);
+    ASSERT_GL_NO_ERROR();
+
+    // Draw green
+    glUniform1i(location1, 1);
+    ASSERT_GL_NO_ERROR();
+    drawQuadWithPPO("a_position", 0.5f, 1.0f);
+    ASSERT_GL_NO_ERROR();
+
+    EXPECT_PIXEL_RECT_EQ(0, 0, kWidth, kHeight, GLColor::yellow);
+}
+
 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(ProgramPipelineTest);
 ANGLE_INSTANTIATE_TEST_ES3_AND_ES31(ProgramPipelineTest);
 
diff --git a/src/tests/gl_tests/SamplersTest.cpp b/src/tests/gl_tests/SamplersTest.cpp
index 96ff853..6554f79 100644
--- a/src/tests/gl_tests/SamplersTest.cpp
+++ b/src/tests/gl_tests/SamplersTest.cpp
@@ -37,6 +37,9 @@
     }
 };
 
+class SamplersTest31 : public SamplersTest
+{};
+
 // Verify that samplerParameterf supports TEXTURE_MAX_ANISOTROPY_EXT valid values.
 TEST_P(SamplersTest, ValidTextureSamplerMaxAnisotropyExt)
 {
@@ -85,7 +88,95 @@
     validateInvalidAnisotropy(sampler, maxValue);
 }
 
+// Test that updating a sampler uniform in a program behaves correctly.
+TEST_P(SamplersTest31, SampleTextureAThenTextureB)
+{
+    ANGLE_SKIP_TEST_IF(!IsVulkan());
+
+    constexpr int kWidth  = 2;
+    constexpr int kHeight = 2;
+
+    const GLchar *vertString = R"(#version 310 es
+precision highp float;
+in vec2 a_position;
+out vec2 texCoord;
+void main()
+{
+    gl_Position = vec4(a_position, 0, 1);
+    texCoord = a_position * 0.5 + vec2(0.5);
+})";
+
+    const GLchar *fragString = R"(#version 310 es
+precision highp float;
+in vec2 texCoord;
+uniform sampler2D tex;
+out vec4 my_FragColor;
+void main()
+{
+    my_FragColor = texture(tex, texCoord);
+})";
+
+    std::array<GLColor, kWidth *kHeight> redColor = {
+        {GLColor::red, GLColor::red, GLColor::red, GLColor::red}};
+    std::array<GLColor, kWidth *kHeight> greenColor = {
+        {GLColor::green, GLColor::green, GLColor::green, GLColor::green}};
+
+    // Create a red texture and bind to texture unit 0
+    GLTexture redTex;
+    glActiveTexture(GL_TEXTURE0);
+    glBindTexture(GL_TEXTURE_2D, redTex);
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kWidth, kHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE,
+                 redColor.data());
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+    ASSERT_GL_NO_ERROR();
+    // Create a green texture and bind to texture unit 1
+    GLTexture greenTex;
+    glActiveTexture(GL_TEXTURE1);
+    glBindTexture(GL_TEXTURE_2D, greenTex);
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kWidth, kHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE,
+                 greenColor.data());
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+    glActiveTexture(GL_TEXTURE0);
+    ASSERT_GL_NO_ERROR();
+
+    GLProgram program;
+    program.makeRaster(vertString, fragString);
+    ASSERT_NE(0u, program.get());
+    glUseProgram(program);
+
+    GLint location = glGetUniformLocation(program, "tex");
+    ASSERT_NE(location, -1);
+    ASSERT_GL_NO_ERROR();
+
+    // Draw red
+    glUniform1i(location, 0);
+    ASSERT_GL_NO_ERROR();
+    drawQuad(program, "a_position", 0.5f);
+    ASSERT_GL_NO_ERROR();
+
+    glEnable(GL_BLEND);
+    glBlendEquation(GL_FUNC_ADD);
+    glBlendFunc(GL_ONE, GL_ONE);
+
+    // Draw green
+    glUniform1i(location, 1);
+    ASSERT_GL_NO_ERROR();
+    drawQuad(program, "a_position", 0.5f);
+    ASSERT_GL_NO_ERROR();
+
+    // Draw red
+    glUniform1i(location, 0);
+    ASSERT_GL_NO_ERROR();
+    drawQuad(program, "a_position", 0.5f);
+    ASSERT_GL_NO_ERROR();
+
+    EXPECT_PIXEL_RECT_EQ(0, 0, kWidth, kHeight, GLColor::yellow);
+}
+
 // Samplers are only supported on ES3.
 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(SamplersTest);
 ANGLE_INSTANTIATE_TEST_ES3(SamplersTest);
+ANGLE_INSTANTIATE_TEST_ES31(SamplersTest31);
 }  // namespace angle