Vulkan: Invalidate sampler if any YCbCr parameter changes

Even if the external format stays the same, other parameters
like dataspace format may change, and this also requires
a new pipeline layout to be created.

Bug: b/491850591
Test: Google Meet
Test: dEQP-GL*
Test: dEQP-EGL*
Change-Id: Iec3df8379ecc8aebd0ce0e49cfcf5c87baa54612
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/7702372
Reviewed-by: Cody Northrop <cnorthrop@google.com>
Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org>
Reviewed-by: Shahbaz Youssefi <syoussefi@chromium.org>
diff --git a/src/libANGLE/renderer/vulkan/TextureVk.cpp b/src/libANGLE/renderer/vulkan/TextureVk.cpp
index e0aacab..3de8b87 100644
--- a/src/libANGLE/renderer/vulkan/TextureVk.cpp
+++ b/src/libANGLE/renderer/vulkan/TextureVk.cpp
@@ -2348,17 +2348,17 @@
     bool nextImageRequiresImmutableSampler =
         nextImage && nextImage->valid() && nextImage->hasImmutableSampler();
 
-    // Has the external format changed?
-    bool externalFormatChanged = false;
+    // Have the YCbCr conversion parameters changed?
+    bool ycbcrConversionChanged = false;
     if (previousImageHadImmutableSampler && nextImageRequiresImmutableSampler)
     {
-        externalFormatChanged =
-            previousImage->getExternalFormat() != nextImage->getExternalFormat();
+        ycbcrConversionChanged =
+            previousImage->getYcbcrConversionDesc() != nextImage->getYcbcrConversionDesc();
     }
 
     // Handle transition of immutable sampler state
     if ((previousImageHadImmutableSampler != nextImageRequiresImmutableSampler) ||
-        externalFormatChanged)
+        ycbcrConversionChanged)
     {
         // The immutable sampler state is dirty.
         resetSampler();
diff --git a/src/tests/gl_tests/ImageTest.cpp b/src/tests/gl_tests/ImageTest.cpp
index 5f610f5..411bb51 100644
--- a/src/tests/gl_tests/ImageTest.cpp
+++ b/src/tests/gl_tests/ImageTest.cpp
@@ -3751,6 +3751,70 @@
     destroyAndroidHardwareBuffer(yv12Source);
 }
 
+// Testing source AHB EGL images, target 2D external texture, cycling through YUV dataspaces.
+TEST_P(ImageTest, SourceAHBTarget2DExternalCycleThroughYuvDataspacesNoData)
+{
+    EGLWindow *window = getEGLWindow();
+
+    ANGLE_SKIP_TEST_IF(!hasOESExt() || !hasBaseExt() || !has2DTextureExt());
+    ANGLE_SKIP_TEST_IF(!hasAndroidImageNativeBufferExt() || !hasAndroidHardwareBufferSupport());
+
+    // Create YCbCr BT601 source and image but without initial data
+    AHardwareBuffer *ycbcrBT601Source;
+    EGLImageKHR ycbcrBT601Image;
+    createEGLImageAndroidHardwareBufferSource(2, 2, 1, AHARDWAREBUFFER_FORMAT_Y8Cb8Cr8_420,
+                                              kDefaultAHBYUVUsage, kDefaultAttribs, {},
+                                              &ycbcrBT601Source, &ycbcrBT601Image);
+    EXPECT_NE(ycbcrBT601Source, nullptr);
+    EXPECT_NE(ycbcrBT601Image, EGL_NO_IMAGE_KHR);
+
+    // Create YCbCr BT709 source and image but without initial data
+    //
+    // There is no API for creating an AHB with a specific dataspace, but Android[1] specifies
+    // that YCbCr buffers smaller than 720p may be assumed to be BT601, while buffers at least
+    // 720p may be assumed to be BT709. So we create a 720p image and hope the gralloc
+    // implementation will pick the correct dataspace.
+    //
+    // [1] hardware/interfaces/graphics/common/aidl/android/hardware/graphics/common/Dataspace.aidl
+    AHardwareBuffer *ycbcrBT709Source;
+    EGLImageKHR ycbcrBT709Image;
+    createEGLImageAndroidHardwareBufferSource(1280, 720, 1, AHARDWAREBUFFER_FORMAT_Y8Cb8Cr8_420,
+                                              kDefaultAHBYUVUsage, kDefaultAttribs, {},
+                                              &ycbcrBT709Source, &ycbcrBT709Image);
+    EXPECT_NE(ycbcrBT709Source, nullptr);
+    EXPECT_NE(ycbcrBT709Image, EGL_NO_IMAGE_KHR);
+
+    // Create a texture target to bind the egl image
+    GLTexture target;
+    glBindTexture(GL_TEXTURE_EXTERNAL_OES, target);
+    // Disable mipmapping
+    glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+    glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+    ASSERT_GL_NO_ERROR();
+
+    // Bind YCbCr BT601 image
+    glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, ycbcrBT601Image);
+    // Draw while sampling should result in no EGL/GL errors
+    glUseProgram(mTextureExternalProgram);
+    glUniform1i(mTextureExternalUniformLocation, 0);
+    drawQuad(mTextureExternalProgram, "position", 0.5f);
+    ASSERT_GL_NO_ERROR();
+
+    // Bind YCbCr BT709 image
+    glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, ycbcrBT709Image);
+    // Draw while sampling should result in no EGL/GL errors
+    glUseProgram(mTextureExternalProgram);
+    glUniform1i(mTextureExternalUniformLocation, 0);
+    drawQuad(mTextureExternalProgram, "position", 0.5f);
+    ASSERT_GL_NO_ERROR();
+
+    // Clean up
+    eglDestroyImageKHR(window->getDisplay(), ycbcrBT601Image);
+    destroyAndroidHardwareBuffer(ycbcrBT601Source);
+    eglDestroyImageKHR(window->getDisplay(), ycbcrBT709Image);
+    destroyAndroidHardwareBuffer(ycbcrBT709Source);
+}
+
 // Testing source AHB EGL images, target 2D external texture, cycling through RGB and YUV sources.
 TEST_P(ImageTest, SourceAHBTarget2DExternalCycleThroughRgbAndYuvSources)
 {