Add support for GL_MESA_framebuffer_flip_y 3/*

This is a third CL that adds tests that exercise
the extension in various use cases.

Bug: chromium:1231934
Change-Id: Iae3192cd0985150b6844a2855a9a048a54353655
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/3365195
Reviewed-by: Shahbaz Youssefi <syoussefi@chromium.org>
Reviewed-by: Jonah Ryan-Davis <jonahr@google.com>
Commit-Queue: Maksim Sisov <msisov@igalia.com>
diff --git a/src/libANGLE/renderer/gl/renderergl_utils.cpp b/src/libANGLE/renderer/gl/renderergl_utils.cpp
index 80af233..e6e6b3c 100644
--- a/src/libANGLE/renderer/gl/renderergl_utils.cpp
+++ b/src/libANGLE/renderer/gl/renderergl_utils.cpp
@@ -1801,13 +1801,8 @@
     extensions->YUVTargetEXT = functions->hasGLESExtension("GL_EXT_YUV_target");
 
     // GL_MESA_framebuffer_flip_y
-    if (functions->isAtLeastGL(gl::Version(4, 3)) ||
-        functions->hasGLExtension("GL_MESA_framebuffer_flip_y") ||
-        functions->isAtLeastGLES(gl::Version(3, 1)) ||
-        functions->hasGLESExtension("GL_MESA_framebuffer_flip_y"))
-    {
-        extensions->framebufferFlipYMESA = true;
-    }
+    extensions->framebufferFlipYMESA = functions->hasGLESExtension("GL_MESA_framebuffer_flip_y") ||
+                                       functions->hasGLExtension("GL_MESA_framebuffer_flip_y");
 
     // GL_KHR_parallel_shader_compile
     extensions->parallelShaderCompileKHR = true;
diff --git a/src/tests/gl_tests/BlitFramebufferANGLETest.cpp b/src/tests/gl_tests/BlitFramebufferANGLETest.cpp
index 4abb7d8..06afbf8 100644
--- a/src/tests/gl_tests/BlitFramebufferANGLETest.cpp
+++ b/src/tests/gl_tests/BlitFramebufferANGLETest.cpp
@@ -350,6 +350,67 @@
         return true;
     }
 
+    void BlitStencilTestHelper(bool mesaYFlip)
+    {
+        glBindFramebuffer(GL_FRAMEBUFFER, mUserFBO);
+
+        if (mesaYFlip)
+        {
+            ASSERT_TRUE(IsGLExtensionEnabled("GL_MESA_framebuffer_flip_y"));
+            glFramebufferParameteriMESA(GL_FRAMEBUFFER, GL_FRAMEBUFFER_FLIP_Y_MESA, 1);
+        }
+
+        glClearColor(0.0, 1.0, 0.0, 1.0);
+        glClearStencil(0x0);
+        glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+        // Scissor half the screen so we fill the stencil only halfway
+        glScissor(0, 0, getWindowWidth(), getWindowHeight() / 2);
+        glEnable(GL_SCISSOR_TEST);
+
+        // fill the stencil buffer with 0x1
+        glStencilFunc(GL_ALWAYS, 0x1, 0xFF);
+        glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
+        glEnable(GL_STENCIL_TEST);
+        drawQuad(mRedProgram, essl1_shaders::PositionAttrib(), 0.3f);
+
+        glDisable(GL_SCISSOR_TEST);
+
+        glBindFramebuffer(GL_DRAW_FRAMEBUFFER_ANGLE, mOriginalFBO);
+        glBindFramebuffer(GL_READ_FRAMEBUFFER_ANGLE, mUserFBO);
+
+        // These clears are not useful in theory because we're copying over them, but its
+        // helpful in debugging if we see white in any result.
+        glClearColor(1.0, 1.0, 1.0, 1.0);
+        glClearStencil(0x0);
+        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+
+        glBlitFramebufferANGLE(0, 0, getWindowWidth(), getWindowHeight(), 0, 0, getWindowWidth(),
+                               getWindowHeight(), GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT,
+                               GL_NEAREST);
+
+        EXPECT_GL_NO_ERROR();
+
+        glBindFramebuffer(GL_FRAMEBUFFER, mOriginalFBO);
+
+        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 4, getWindowHeight() / 4, GLColor::red);
+        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 4, 3 * getWindowHeight() / 4, GLColor::green);
+        EXPECT_PIXEL_COLOR_EQ(3 * getWindowWidth() / 4, getWindowHeight() / 4, GLColor::red);
+        EXPECT_PIXEL_COLOR_EQ(3 * getWindowWidth() / 4, 3 * getWindowHeight() / 4, GLColor::green);
+
+        glStencilFunc(GL_EQUAL, 0x1, 0xFF);  // only pass if stencil buffer at pixel reads 0x1
+
+        drawQuad(mBlueProgram, essl1_shaders::PositionAttrib(),
+                 0.8f);  // blue quad will draw if stencil buffer was copied
+
+        glDisable(GL_STENCIL_TEST);
+
+        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 4, getWindowHeight() / 4, GLColor::blue);
+        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 4, 3 * getWindowHeight() / 4, GLColor::green);
+        EXPECT_PIXEL_COLOR_EQ(3 * getWindowWidth() / 4, getWindowHeight() / 4, GLColor::blue);
+        EXPECT_PIXEL_COLOR_EQ(3 * getWindowWidth() / 4, 3 * getWindowHeight() / 4, GLColor::green);
+    }
+
     GLuint mCheckerProgram;
     GLuint mBlueProgram;
     GLuint mRedProgram;
@@ -501,6 +562,267 @@
     EXPECT_PIXEL_COLOR_EQ(3 * getWindowWidth() / 4, 3 * getWindowHeight() / 4, GLColor::yellow);
 }
 
+// Blit color to default framebuffer from another framebuffer with GL_MESA_framebuffer_flip_y.
+TEST_P(BlitFramebufferANGLETest, BlitColorWithMesaYFlipSrc)
+{
+    // OpenGL ES 3.0 / GL_NV_framebuffer_blit required for flip.
+    ANGLE_SKIP_TEST_IF(
+        (getClientMajorVersion() < 3 && !IsGLExtensionEnabled("GL_NV_framebuffer_blit")) ||
+        !IsGLExtensionEnabled("GL_MESA_framebuffer_flip_y"));
+
+    glBindFramebuffer(GL_FRAMEBUFFER, mUserFBO);
+
+    glFramebufferParameteriMESA(GL_FRAMEBUFFER, GL_FRAMEBUFFER_FLIP_Y_MESA, 1);
+
+    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+
+    EXPECT_GL_NO_ERROR();
+
+    drawQuad(mCheckerProgram, essl1_shaders::PositionAttrib(), 0.8f);
+
+    EXPECT_GL_NO_ERROR();
+
+    // Blit to default from y-flipped.
+    glBindFramebuffer(GL_READ_FRAMEBUFFER_ANGLE, mUserFBO);
+    glBindFramebuffer(GL_DRAW_FRAMEBUFFER_ANGLE, mOriginalFBO);
+
+    const int fboTargetWidth  = getWindowHeight() / 2;
+    const int fboTargetHeight = getWindowHeight() / 2;
+
+    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+
+    glBlitFramebuffer(0, 0, getWindowWidth(), getWindowHeight(), 0, 0, fboTargetWidth,
+                      fboTargetHeight, GL_COLOR_BUFFER_BIT, GL_NEAREST);
+
+    EXPECT_GL_NO_ERROR();
+
+    glBindFramebuffer(GL_FRAMEBUFFER, mOriginalFBO);
+
+    EXPECT_PIXEL_COLOR_EQ(fboTargetWidth / 4, fboTargetHeight / 4, GLColor::red);
+    EXPECT_PIXEL_COLOR_EQ(fboTargetWidth / 4, 3 * fboTargetHeight / 4, GLColor::green);
+    EXPECT_PIXEL_COLOR_EQ(3 * fboTargetWidth / 4, fboTargetHeight / 4, GLColor::blue);
+    EXPECT_PIXEL_COLOR_EQ(3 * fboTargetWidth / 4, 3 * fboTargetHeight / 4, GLColor::yellow);
+}
+
+// Blit color to y-flipped with GL_MESA_framebuffer_flip_y framebuffer from normal framebuffer.
+TEST_P(BlitFramebufferANGLETest, BlitColorWithMesaYFlipDst)
+{
+    // OpenGL ES 3.0 / GL_NV_framebuffer_blit required for flip.
+    ANGLE_SKIP_TEST_IF(
+        (getClientMajorVersion() < 3 && !IsGLExtensionEnabled("GL_NV_framebuffer_blit")) ||
+        !IsGLExtensionEnabled("GL_MESA_framebuffer_flip_y"));
+
+    glBindFramebuffer(GL_FRAMEBUFFER, mOriginalFBO);
+
+    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+
+    drawQuad(mCheckerProgram, essl1_shaders::PositionAttrib(), 0.8f);
+
+    EXPECT_GL_NO_ERROR();
+
+    // Blit to default from y-flipped.
+    glBindFramebuffer(GL_READ_FRAMEBUFFER_ANGLE, mOriginalFBO);
+    glBindFramebuffer(GL_DRAW_FRAMEBUFFER_ANGLE, mUserFBO);
+
+    glFramebufferParameteriMESA(GL_DRAW_FRAMEBUFFER_ANGLE, GL_FRAMEBUFFER_FLIP_Y_MESA, 1);
+
+    const int fboTargetWidth  = getWindowWidth() / 2;
+    const int fboTargetHeight = getWindowHeight();
+
+    glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
+    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+
+    glBlitFramebuffer(0, 0, getWindowWidth(), getWindowHeight(), 0, 0, fboTargetWidth,
+                      fboTargetHeight, GL_COLOR_BUFFER_BIT, GL_NEAREST);
+    glBlitFramebuffer(0, 0, getWindowWidth(), getWindowHeight(), getWindowWidth() / 2, 0,
+                      getWindowWidth(), getWindowHeight() / 2, GL_COLOR_BUFFER_BIT, GL_NEAREST);
+
+    glFramebufferParameteriMESA(GL_DRAW_FRAMEBUFFER_ANGLE, GL_FRAMEBUFFER_FLIP_Y_MESA, 0);
+
+    EXPECT_GL_NO_ERROR();
+
+    glBindFramebuffer(GL_FRAMEBUFFER, mUserFBO);
+
+    // Left side have inverted checker pattern.
+    EXPECT_PIXEL_COLOR_EQ(fboTargetWidth / 4, fboTargetHeight / 4, GLColor::green);
+    EXPECT_PIXEL_COLOR_EQ(fboTargetWidth / 4, 3 * fboTargetHeight / 4, GLColor::red);
+    EXPECT_PIXEL_COLOR_EQ(3 * fboTargetWidth / 4, fboTargetHeight / 4, GLColor::yellow);
+    EXPECT_PIXEL_COLOR_EQ(3 * fboTargetWidth / 4, 3 * fboTargetHeight / 4, GLColor::blue);
+
+    // Right side is split to 2 parts where upper part have non y-flipped checker pattern and the
+    // bottom one has white color.
+    EXPECT_PIXEL_COLOR_EQ(5 * getWindowWidth() / 8, 5 * getWindowHeight() / 8, GLColor::green);
+    EXPECT_PIXEL_COLOR_EQ(5 * getWindowWidth() / 8, 7 * getWindowHeight() / 8, GLColor::red);
+    EXPECT_PIXEL_COLOR_EQ(7 * getWindowWidth() / 8, 5 * getWindowHeight() / 8, GLColor::yellow);
+    EXPECT_PIXEL_COLOR_EQ(7 * getWindowWidth() / 8, 7 * getWindowHeight() / 8, GLColor::blue);
+
+    EXPECT_PIXEL_RECT_EQ(4 * getWindowWidth() / 8, 0, getWindowWidth() / 4, getWindowHeight() / 2,
+                         GLColor::white);
+}
+
+// Blit color to/from y-flipped with GL_MESA_framebuffer_flip_y framebuffers where dst framebuffer
+// have different size.
+TEST_P(BlitFramebufferANGLETest, BlitColorWithMesaYFlipSrcDst)
+{
+    // OpenGL ES 3.0 / GL_NV_framebuffer_blit required for flip.
+    ANGLE_SKIP_TEST_IF(
+        (getClientMajorVersion() < 3 && !IsGLExtensionEnabled("GL_NV_framebuffer_blit")) ||
+        !IsGLExtensionEnabled("GL_MESA_framebuffer_flip_y"));
+
+    // Create a custom framebuffer as the default one cannot be flipped.
+    GLTexture tex0;
+    glBindTexture(GL_TEXTURE_2D, tex0);
+    const int fb0Width  = getWindowWidth() / 2;
+    const int fb0Height = getWindowHeight() / 2;
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fb0Width, fb0Height, 0, GL_RGBA, GL_UNSIGNED_BYTE,
+                 nullptr);
+
+    GLFramebuffer fb0;
+    glBindFramebuffer(GL_FRAMEBUFFER, fb0);
+    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex0, 0);
+    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
+
+    glBindFramebuffer(GL_FRAMEBUFFER, mUserFBO);
+
+    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+
+    drawQuad(mCheckerProgram, essl1_shaders::PositionAttrib(), 0.8f);
+
+    EXPECT_GL_NO_ERROR();
+
+    // Blit to default from y-flipped.
+    glBindFramebuffer(GL_READ_FRAMEBUFFER_ANGLE, mUserFBO);
+    glBindFramebuffer(GL_DRAW_FRAMEBUFFER_ANGLE, fb0);
+
+    glFramebufferParameteriMESA(GL_DRAW_FRAMEBUFFER_ANGLE, GL_FRAMEBUFFER_FLIP_Y_MESA, 1);
+    glFramebufferParameteriMESA(GL_READ_FRAMEBUFFER_ANGLE, GL_FRAMEBUFFER_FLIP_Y_MESA, 1);
+
+    const int fboTargetWidth  = fb0Width / 2;
+    const int fboTargetHeight = fb0Height;
+
+    glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
+    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+
+    glBlitFramebuffer(0, 0, getWindowWidth(), getWindowHeight(), 0, 0, fboTargetWidth,
+                      fboTargetHeight, GL_COLOR_BUFFER_BIT, GL_NEAREST);
+    glBlitFramebuffer(0, 0, getWindowWidth(), getWindowHeight(), fb0Width / 2, 0, fb0Width,
+                      fb0Height / 2, GL_COLOR_BUFFER_BIT, GL_NEAREST);
+
+    EXPECT_GL_NO_ERROR();
+
+    glBindFramebuffer(GL_FRAMEBUFFER, fb0);
+
+    glFramebufferParameteriMESA(GL_FRAMEBUFFER, GL_FRAMEBUFFER_FLIP_Y_MESA, 0);
+
+    // Left side have inverted checker pattern.
+    EXPECT_PIXEL_COLOR_EQ(fboTargetWidth / 4, fboTargetHeight / 4, GLColor::red);
+    EXPECT_PIXEL_COLOR_EQ(fboTargetWidth / 4, 3 * fboTargetHeight / 4, GLColor::green);
+    EXPECT_PIXEL_COLOR_EQ(3 * fboTargetWidth / 4, fboTargetHeight / 4, GLColor::blue);
+    EXPECT_PIXEL_COLOR_EQ(3 * fboTargetWidth / 4, 3 * fboTargetHeight / 4, GLColor::yellow);
+
+    // Right side is split to 2 parts where upper part have y-flipped checker pattern and the
+    // bottom one has white color.
+    EXPECT_PIXEL_COLOR_EQ(5 * fb0Width / 8, 5 * fb0Height / 8, GLColor::red);
+    EXPECT_PIXEL_COLOR_EQ(5 * fb0Width / 8, 7 * fb0Height / 8, GLColor::green);
+    EXPECT_PIXEL_COLOR_EQ(7 * fb0Width / 8, 5 * fb0Height / 8, GLColor::blue);
+    EXPECT_PIXEL_COLOR_EQ(7 * fb0Width / 8, 7 * fb0Height / 8, GLColor::yellow);
+
+    EXPECT_PIXEL_RECT_EQ(4 * fb0Width / 8, 0, fb0Width / 4, fb0Height / 2, GLColor::white);
+}
+
+// Same as BlitColorWithMesaYFlip but uses an integer buffer format.
+TEST_P(BlitFramebufferANGLETest, BlitColorWithMesaYFlipInteger)
+{
+    // OpenGL ES 3.0 / GL_NV_framebuffer_blit required for flip.
+    ANGLE_SKIP_TEST_IF(
+        (getClientMajorVersion() < 3 || !IsGLExtensionEnabled("GL_NV_framebuffer_blit")) ||
+        !IsGLExtensionEnabled("GL_MESA_framebuffer_flip_y"));
+
+    GLTexture tex0;
+    glBindTexture(GL_TEXTURE_2D, tex0);
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8I, getWindowWidth(), getWindowHeight(), 0,
+                 GL_RGBA_INTEGER, GL_BYTE, nullptr);
+
+    GLFramebuffer fb0;
+    glBindFramebuffer(GL_FRAMEBUFFER, fb0);
+    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex0, 0);
+    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
+
+    glFramebufferParameteriMESA(GL_FRAMEBUFFER, GL_FRAMEBUFFER_FLIP_Y_MESA, 1);
+
+    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+
+    drawQuad(mCheckerProgram, essl1_shaders::PositionAttrib(), 0.8f);
+
+    EXPECT_GL_NO_ERROR();
+
+    GLTexture tex1;
+    glBindTexture(GL_TEXTURE_2D, tex1);
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8I, getWindowWidth(), getWindowHeight(), 0,
+                 GL_RGBA_INTEGER, GL_BYTE, nullptr);
+
+    GLFramebuffer fb1;
+    glBindFramebuffer(GL_FRAMEBUFFER, fb1);
+    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex1, 0);
+
+    // Blit to default from y-flipped.
+    glBindFramebuffer(GL_READ_FRAMEBUFFER_ANGLE, fb0);
+    glBindFramebuffer(GL_DRAW_FRAMEBUFFER_ANGLE, fb1);
+
+    const int fb1_target_width  = getWindowHeight() / 3;
+    const int fb1_target_height = getWindowHeight() / 3;
+
+    glClearColor(0.0f, 1.0f, 1.0f, 1.0f);
+    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+
+    glBlitFramebuffer(0, 0, getWindowWidth(), getWindowHeight(), 0, 0, fb1_target_width,
+                      fb1_target_height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
+
+    EXPECT_GL_NO_ERROR();
+
+    glBindFramebuffer(GL_FRAMEBUFFER, fb1);
+
+    // The colors outside the target must remain the same.
+    EXPECT_PIXEL_8I(getWindowWidth() - 1, getWindowHeight() - 1, 0, 127, 127, 127);
+    EXPECT_PIXEL_8I(getWindowWidth() - 1, 0, 0, 127, 127, 127);
+    EXPECT_PIXEL_8I(0, getWindowHeight() - 1, 0, 127, 127, 127);
+    EXPECT_PIXEL_8I(fb1_target_width, fb1_target_height, 0, 127, 127, 127);
+
+    // While inside must change.
+    EXPECT_PIXEL_8I(fb1_target_width / 4, fb1_target_height / 4, 127, 0, 0, 127);
+    EXPECT_PIXEL_8I(fb1_target_width / 4, 3 * fb1_target_height / 4, 0, 127, 0, 127);
+    EXPECT_PIXEL_8I(3 * fb1_target_width / 4, fb1_target_height / 4, 0, 0, 127, 127);
+    EXPECT_PIXEL_8I(3 * fb1_target_width / 4, 3 * fb1_target_height / 4, 127, 127, 0, 127);
+
+    // Blit from y-flipped to default.
+    glBindFramebuffer(GL_READ_FRAMEBUFFER_ANGLE, fb1);
+    glBindFramebuffer(GL_DRAW_FRAMEBUFFER_ANGLE, fb0);
+
+    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+
+    // Set y-flip flag so that y-flipped frame buffer blit to the original fbo in reverse. This
+    // should result in flipping y back.
+    glFramebufferParameteriMESA(GL_DRAW_FRAMEBUFFER_ANGLE, GL_FRAMEBUFFER_FLIP_Y_MESA, 1);
+
+    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+    glBlitFramebuffer(0, 0, fb1_target_width, fb1_target_height, 0, 0, getWindowWidth(),
+                      getWindowHeight(), GL_COLOR_BUFFER_BIT, GL_NEAREST);
+
+    // And explicitly disable y-flip so that read does not implicitly use this flag.
+    glFramebufferParameteriMESA(GL_DRAW_FRAMEBUFFER_ANGLE, GL_FRAMEBUFFER_FLIP_Y_MESA, 0);
+
+    EXPECT_GL_NO_ERROR();
+
+    glBindFramebuffer(GL_FRAMEBUFFER, fb0);
+
+    EXPECT_PIXEL_8I(getWindowWidth() / 4, getWindowHeight() / 4, 0, 127, 0, 127);
+    EXPECT_PIXEL_8I(getWindowWidth() / 4, 3 * getWindowHeight() / 4, 127, 0, 0, 127);
+    EXPECT_PIXEL_8I(3 * getWindowWidth() / 4, getWindowHeight() / 4, 127, 127, 0, 127);
+    EXPECT_PIXEL_8I(3 * getWindowWidth() / 4, 3 * getWindowHeight() / 4, 0, 0, 127, 127);
+}
+
 // Draw to system framebuffer, blit whole-buffer color to user-created framebuffer.
 TEST_P(BlitFramebufferANGLETest, ReverseColorBlit)
 {
@@ -927,58 +1249,22 @@
     // http://anglebug.com/5396
     ANGLE_SKIP_TEST_IF(IsAMD() && IsD3D9());
 
-    glBindFramebuffer(GL_FRAMEBUFFER, mUserFBO);
+    BlitStencilTestHelper(false /* mesaFlipY */);
+}
 
-    glClearColor(0.0, 1.0, 0.0, 1.0);
-    glClearStencil(0x0);
-    glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+// Same as BlitStencil, but with y-flip flag set.
+TEST_P(BlitFramebufferANGLETest, BlitStencilWithMesaYFlip)
+{
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_ANGLE_framebuffer_blit") ||
+                       !IsGLExtensionEnabled("GL_MESA_framebuffer_flip_y"));
 
-    // Scissor half the screen so we fill the stencil only halfway
-    glScissor(0, 0, getWindowWidth(), getWindowHeight() / 2);
-    glEnable(GL_SCISSOR_TEST);
+    // http://anglebug.com/2205
+    ANGLE_SKIP_TEST_IF(IsIntel() && IsD3D9());
 
-    // fill the stencil buffer with 0x1
-    glStencilFunc(GL_ALWAYS, 0x1, 0xFF);
-    glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
-    glEnable(GL_STENCIL_TEST);
-    drawQuad(mRedProgram, essl1_shaders::PositionAttrib(), 0.3f);
+    // http://anglebug.com/5396
+    ANGLE_SKIP_TEST_IF(IsAMD() && IsD3D9());
 
-    glDisable(GL_SCISSOR_TEST);
-
-    glBindFramebuffer(GL_DRAW_FRAMEBUFFER_ANGLE, mOriginalFBO);
-    glBindFramebuffer(GL_READ_FRAMEBUFFER_ANGLE, mUserFBO);
-
-    // These clears are not useful in theory because we're copying over them, but its
-    // helpful in debugging if we see white in any result.
-    glClearColor(1.0, 1.0, 1.0, 1.0);
-    glClearStencil(0x0);
-    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
-
-    // depth blit request should be silently ignored, because the read FBO has no depth attachment
-    glBlitFramebufferANGLE(0, 0, getWindowWidth(), getWindowHeight(), 0, 0, getWindowWidth(),
-                           getWindowHeight(), GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT,
-                           GL_NEAREST);
-
-    EXPECT_GL_NO_ERROR();
-
-    glBindFramebuffer(GL_FRAMEBUFFER, mOriginalFBO);
-
-    EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 4, getWindowHeight() / 4, GLColor::red);
-    EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 4, 3 * getWindowHeight() / 4, GLColor::green);
-    EXPECT_PIXEL_COLOR_EQ(3 * getWindowWidth() / 4, getWindowHeight() / 4, GLColor::red);
-    EXPECT_PIXEL_COLOR_EQ(3 * getWindowWidth() / 4, 3 * getWindowHeight() / 4, GLColor::green);
-
-    glStencilFunc(GL_EQUAL, 0x1, 0xFF);  // only pass if stencil buffer at pixel reads 0x1
-
-    drawQuad(mBlueProgram, essl1_shaders::PositionAttrib(),
-             0.8f);  // blue quad will draw if stencil buffer was copied
-
-    glDisable(GL_STENCIL_TEST);
-
-    EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 4, getWindowHeight() / 4, GLColor::blue);
-    EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 4, 3 * getWindowHeight() / 4, GLColor::green);
-    EXPECT_PIXEL_COLOR_EQ(3 * getWindowWidth() / 4, getWindowHeight() / 4, GLColor::blue);
-    EXPECT_PIXEL_COLOR_EQ(3 * getWindowWidth() / 4, 3 * getWindowHeight() / 4, GLColor::green);
+    BlitStencilTestHelper(true /* mesaFlipY */);
 }
 
 // make sure that attempting to blit a partial depth buffer issues an error
@@ -1235,6 +1521,69 @@
         glBindFramebuffer(GL_FRAMEBUFFER, *fbo);
         drawQuad(checkerProgram.get(), essl1_shaders::PositionAttrib(), 0.5f);
     }
+
+    void BlitDepthStencilPixelByPixelTestHelper(bool mesaYFlip)
+    {
+        if (mesaYFlip)
+            ASSERT_TRUE(IsGLExtensionEnabled("GL_MESA_framebuffer_flip_y"));
+
+        ANGLE_GL_PROGRAM(drawRed, essl3_shaders::vs::Simple(), essl3_shaders::fs::Red());
+
+        glViewport(0, 0, 128, 1);
+        glEnable(GL_DEPTH_TEST);
+
+        GLFramebuffer srcFramebuffer;
+        GLRenderbuffer srcRenderbuffer;
+        glBindFramebuffer(GL_FRAMEBUFFER, srcFramebuffer);
+        if (mesaYFlip)
+            glFramebufferParameteriMESA(GL_FRAMEBUFFER, GL_FRAMEBUFFER_FLIP_Y_MESA, 1);
+        glBindRenderbuffer(GL_RENDERBUFFER, srcRenderbuffer);
+        glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 128, 1);
+        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
+                                  srcRenderbuffer);
+        glClearDepthf(1.0f);
+        glClear(GL_DEPTH_BUFFER_BIT);
+
+        drawQuad(drawRed, essl1_shaders::PositionAttrib(), 0.0f, 0.5f);
+        glViewport(0, 0, 256, 2);
+
+        GLFramebuffer dstFramebuffer;
+        GLRenderbuffer dstRenderbuffer;
+        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, dstFramebuffer);
+        glBindRenderbuffer(GL_RENDERBUFFER, dstRenderbuffer);
+        glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 256, 2);
+        glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
+                                  dstRenderbuffer);
+
+        GLTexture dstColor;
+        glBindTexture(GL_TEXTURE_2D, dstColor);
+        glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 256, 2);
+        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dstColor, 0);
+
+        glBindFramebuffer(GL_READ_FRAMEBUFFER, srcFramebuffer);
+        glBlitFramebuffer(0, 0, 128, 1, 0, 0, 256, 2, GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT,
+                          GL_NEAREST);
+
+        glBindFramebuffer(GL_FRAMEBUFFER, dstFramebuffer);
+        glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
+        glClear(GL_COLOR_BUFFER_BIT);
+        glDepthMask(false);
+        glDepthFunc(GL_LESS);
+        drawQuad(drawRed, essl1_shaders::PositionAttrib(), -0.01f, 0.5f);
+        EXPECT_PIXEL_RECT_EQ(64, 0, 128, 1, GLColor::red);
+
+        ANGLE_GL_PROGRAM(drawBlue, essl3_shaders::vs::Simple(), essl3_shaders::fs::Blue());
+        glEnable(GL_DEPTH_TEST);
+        glDepthMask(false);
+        glDepthFunc(GL_GREATER);
+        if (mesaYFlip)
+            glFramebufferParameteriMESA(GL_FRAMEBUFFER, GL_FRAMEBUFFER_FLIP_Y_MESA, 1);
+        drawQuad(drawBlue, essl1_shaders::PositionAttrib(), 0.01f, 0.5f);
+        if (mesaYFlip)
+            EXPECT_PIXEL_RECT_EQ(64, 0, 128, 1, GLColor::green);
+        else
+            EXPECT_PIXEL_RECT_EQ(64, 0, 128, 1, GLColor::blue);
+    }
 };
 
 class BlitFramebufferTestES31 : public BlitFramebufferTest
@@ -2552,55 +2901,15 @@
 // Test blitting a depthStencil buffer with multiple depth values to a larger size.
 TEST_P(BlitFramebufferTest, BlitDepthStencilPixelByPixel)
 {
-    ANGLE_GL_PROGRAM(drawRed, essl3_shaders::vs::Simple(), essl3_shaders::fs::Red());
+    BlitDepthStencilPixelByPixelTestHelper(false /* mesaYFlip */);
+}
 
-    glViewport(0, 0, 128, 1);
-    glEnable(GL_DEPTH_TEST);
+// Same as BlitDepthStencilPixelByPixel, but with y-flip flag set.
+TEST_P(BlitFramebufferTest, BlitDepthStencilPixelByPixelMesaYFlip)
+{
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_MESA_framebuffer_flip_y"));
 
-    GLFramebuffer srcFramebuffer;
-    GLRenderbuffer srcRenderbuffer;
-    glBindFramebuffer(GL_FRAMEBUFFER, srcFramebuffer);
-    glBindRenderbuffer(GL_RENDERBUFFER, srcRenderbuffer);
-    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 128, 1);
-    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
-                              srcRenderbuffer);
-    glClearDepthf(1.0f);
-    glClear(GL_DEPTH_BUFFER_BIT);
-
-    drawQuad(drawRed, essl1_shaders::PositionAttrib(), 0.0f, 0.5f);
-    glViewport(0, 0, 256, 2);
-
-    GLFramebuffer dstFramebuffer;
-    GLRenderbuffer dstRenderbuffer;
-    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, dstFramebuffer);
-    glBindRenderbuffer(GL_RENDERBUFFER, dstRenderbuffer);
-    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 256, 2);
-    glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
-                              dstRenderbuffer);
-
-    GLTexture dstColor;
-    glBindTexture(GL_TEXTURE_2D, dstColor);
-    glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 256, 2);
-    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dstColor, 0);
-
-    glBindFramebuffer(GL_READ_FRAMEBUFFER, srcFramebuffer);
-    glBlitFramebuffer(0, 0, 128, 1, 0, 0, 256, 2, GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT,
-                      GL_NEAREST);
-
-    glBindFramebuffer(GL_FRAMEBUFFER, dstFramebuffer);
-    glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
-    glClear(GL_COLOR_BUFFER_BIT);
-    glDepthMask(false);
-    glDepthFunc(GL_LESS);
-    drawQuad(drawRed, essl1_shaders::PositionAttrib(), -0.01f, 0.5f);
-    EXPECT_PIXEL_RECT_EQ(64, 0, 128, 1, GLColor::red);
-
-    ANGLE_GL_PROGRAM(drawBlue, essl3_shaders::vs::Simple(), essl3_shaders::fs::Blue());
-    glEnable(GL_DEPTH_TEST);
-    glDepthMask(false);
-    glDepthFunc(GL_GREATER);
-    drawQuad(drawBlue, essl1_shaders::PositionAttrib(), 0.01f, 0.5f);
-    EXPECT_PIXEL_RECT_EQ(64, 0, 128, 1, GLColor::blue);
+    BlitDepthStencilPixelByPixelTestHelper(true /* mesaYFlip */);
 }
 
 // Test that a draw call to a small FBO followed by a resolve of a large FBO works.
@@ -2718,6 +3027,227 @@
     EXPECT_PIXEL_COLOR_EQ(3 * kWidth / 4, 3 * kHeight / 4, GLColor::yellow);
 }
 
+// Test resolving a multisampled texture with blit. Draw flipped, resolve with read fbo flipped.
+TEST_P(BlitFramebufferTestES31, MultisampleFlippedResolveReadWithBlitAndFlippedDraw)
+{
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_MESA_framebuffer_flip_y"));
+
+    constexpr int kSize = 16;
+    glViewport(0, 0, kSize, kSize);
+
+    GLFramebuffer msaaFBO;
+    glBindFramebuffer(GL_FRAMEBUFFER, msaaFBO.get());
+
+    glFramebufferParameteriMESA(GL_FRAMEBUFFER, GL_FRAMEBUFFER_FLIP_Y_MESA, 1);
+
+    GLTexture texture;
+    glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, texture.get());
+    glTexStorage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 4, GL_RGBA8, kSize, kSize, false);
+    ASSERT_GL_NO_ERROR();
+    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE,
+                           texture.get(), 0);
+    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
+
+    ANGLE_GL_PROGRAM(gradientProgram, essl31_shaders::vs::Passthrough(),
+                     essl31_shaders::fs::RedGreenGradient());
+    drawQuad(gradientProgram, essl31_shaders::PositionAttrib(), 0.5f, 1.0f, true);
+    ASSERT_GL_NO_ERROR();
+
+    // Create another FBO to resolve the multisample buffer into.
+    GLTexture resolveTexture;
+    GLFramebuffer resolveFBO;
+    glBindTexture(GL_TEXTURE_2D, resolveTexture);
+    constexpr int kResolveFBOWidth  = kSize - 3;
+    constexpr int kResolveFBOHeight = kSize - 2;
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kResolveFBOWidth, kResolveFBOHeight, 0, GL_RGBA,
+                 GL_UNSIGNED_BYTE, nullptr);
+    glBindFramebuffer(GL_FRAMEBUFFER, resolveFBO);
+    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, resolveTexture, 0);
+    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
+
+    glBindFramebuffer(GL_READ_FRAMEBUFFER, msaaFBO);
+    glFramebufferParameteriMESA(GL_READ_FRAMEBUFFER, GL_FRAMEBUFFER_FLIP_Y_MESA, 1);
+    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, resolveFBO);
+    glBlitFramebuffer(0, 0, kResolveFBOWidth, kResolveFBOHeight, 0, 0, kResolveFBOWidth,
+                      kResolveFBOHeight, GL_COLOR_BUFFER_BIT, GL_NEAREST);
+    ASSERT_GL_NO_ERROR();
+
+    glBindFramebuffer(GL_READ_FRAMEBUFFER, resolveFBO);
+    constexpr uint8_t kHalfPixelGradient = 256 / kSize / 2;
+    EXPECT_PIXEL_NEAR(0, 0, kHalfPixelGradient, kHalfPixelGradient, 0, 255, 1.0);
+    EXPECT_PIXEL_NEAR(kResolveFBOWidth - 1, 0, 199, kHalfPixelGradient, 0, 255, 1.0);
+    EXPECT_PIXEL_NEAR(0, kResolveFBOHeight - 1, kHalfPixelGradient, 215, 0, 255, 1.0);
+    EXPECT_PIXEL_NEAR(kResolveFBOWidth - 1, kResolveFBOHeight - 1, 199, 215, 0, 255, 1.0);
+}
+
+// Test resolving a multisampled texture with blit. Draw non-flipped, resolve with read fbo flipped.
+TEST_P(BlitFramebufferTestES31, MultisampleFlippedResolveReadWithBlitAndNonFlippedDraw)
+{
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_MESA_framebuffer_flip_y"));
+
+    constexpr int kSize = 16;
+    glViewport(0, 0, kSize, kSize);
+
+    GLFramebuffer msaaFBO;
+    glBindFramebuffer(GL_FRAMEBUFFER, msaaFBO.get());
+
+    // Draw non-flipped - explicitly set y-flip to 0.
+    glFramebufferParameteriMESA(GL_FRAMEBUFFER, GL_FRAMEBUFFER_FLIP_Y_MESA, 0);
+
+    GLTexture texture;
+    glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, texture.get());
+    glTexStorage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 4, GL_RGBA8, kSize, kSize, false);
+    ASSERT_GL_NO_ERROR();
+    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE,
+                           texture.get(), 0);
+    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
+
+    ANGLE_GL_PROGRAM(gradientProgram, essl31_shaders::vs::Passthrough(),
+                     essl31_shaders::fs::RedGreenGradient());
+    drawQuad(gradientProgram, essl31_shaders::PositionAttrib(), 0.5f, 1.0f, true);
+    ASSERT_GL_NO_ERROR();
+
+    // Create another FBO to resolve the multisample buffer into.
+    GLTexture resolveTexture;
+    GLFramebuffer resolveFBO;
+    glBindTexture(GL_TEXTURE_2D, resolveTexture);
+    constexpr int kResolveFBOWidth  = kSize - 3;
+    constexpr int kResolveFBOHeight = kSize - 2;
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kResolveFBOWidth, kResolveFBOHeight, 0, GL_RGBA,
+                 GL_UNSIGNED_BYTE, nullptr);
+    glBindFramebuffer(GL_FRAMEBUFFER, resolveFBO);
+    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, resolveTexture, 0);
+    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
+
+    glBindFramebuffer(GL_READ_FRAMEBUFFER, msaaFBO);
+    // Resolve with read fbo flipped and draw fbo non-flipped
+    glFramebufferParameteriMESA(GL_READ_FRAMEBUFFER, GL_FRAMEBUFFER_FLIP_Y_MESA, 1);
+    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, resolveFBO);
+    glBlitFramebuffer(0, 0, kResolveFBOWidth, kResolveFBOHeight, 0, 0, kResolveFBOWidth,
+                      kResolveFBOHeight, GL_COLOR_BUFFER_BIT, GL_NEAREST);
+    ASSERT_GL_NO_ERROR();
+
+    glBindFramebuffer(GL_READ_FRAMEBUFFER, resolveFBO);
+    constexpr uint8_t kHalfPixelGradient = 256 / kSize / 2;
+    EXPECT_PIXEL_NEAR(0, 0, kHalfPixelGradient, 255 - kHalfPixelGradient, 0, 255, 1.0);
+    EXPECT_PIXEL_NEAR(kResolveFBOWidth - 1, 0, 199, 255 - kHalfPixelGradient, 0, 255, 1.0);
+    EXPECT_PIXEL_NEAR(0, kResolveFBOHeight - 1, kHalfPixelGradient, 40, 0, 255, 1.0);
+    EXPECT_PIXEL_NEAR(kResolveFBOWidth - 1, kResolveFBOHeight - 1, 199, 40, 0, 255, 1.0);
+}
+
+// Test resolving a multisampled texture with blit. Draw non-flipped, resolve with draw fbo flipped
+TEST_P(BlitFramebufferTestES31, MultisampleFlippedResolveDrawWithBlitAndNonFlippedDraw)
+{
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_MESA_framebuffer_flip_y"));
+
+    constexpr int kSize = 16;
+    glViewport(0, 0, kSize, kSize);
+
+    GLFramebuffer msaaFBO;
+    glBindFramebuffer(GL_FRAMEBUFFER, msaaFBO.get());
+
+    // Draw non-flipped - explicitly set y-flip to 0.
+    glFramebufferParameteriMESA(GL_FRAMEBUFFER, GL_FRAMEBUFFER_FLIP_Y_MESA, 0);
+
+    GLTexture texture;
+    glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, texture.get());
+    glTexStorage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 4, GL_RGBA8, kSize, kSize, false);
+    ASSERT_GL_NO_ERROR();
+    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE,
+                           texture.get(), 0);
+    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
+
+    ANGLE_GL_PROGRAM(gradientProgram, essl31_shaders::vs::Passthrough(),
+                     essl31_shaders::fs::RedGreenGradient());
+    drawQuad(gradientProgram, essl31_shaders::PositionAttrib(), 0.5f, 1.0f, true);
+    ASSERT_GL_NO_ERROR();
+
+    // Create another FBO to resolve the multisample buffer into.
+    GLTexture resolveTexture;
+    GLFramebuffer resolveFBO;
+    glBindTexture(GL_TEXTURE_2D, resolveTexture);
+    constexpr int kResolveFBOWidth  = kSize - 3;
+    constexpr int kResolveFBOHeight = kSize - 2;
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kResolveFBOWidth, kResolveFBOHeight, 0, GL_RGBA,
+                 GL_UNSIGNED_BYTE, nullptr);
+    glBindFramebuffer(GL_FRAMEBUFFER, resolveFBO);
+    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, resolveTexture, 0);
+    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
+
+    glBindFramebuffer(GL_READ_FRAMEBUFFER, msaaFBO);
+    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, resolveFBO);
+    // Resolve with draw fbo flipped and read fbo non-flipped.
+    glFramebufferParameteriMESA(GL_READ_FRAMEBUFFER, GL_FRAMEBUFFER_FLIP_Y_MESA, 0);
+    glFramebufferParameteriMESA(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_FLIP_Y_MESA, 1);
+    glBlitFramebuffer(0, 0, kResolveFBOWidth, kResolveFBOHeight, 0, 0, kResolveFBOWidth,
+                      kResolveFBOHeight, GL_COLOR_BUFFER_BIT, GL_NEAREST);
+    ASSERT_GL_NO_ERROR();
+
+    glBindFramebuffer(GL_READ_FRAMEBUFFER, resolveFBO);
+    constexpr uint8_t kHalfPixelGradient = 256 / kSize / 2;
+    EXPECT_PIXEL_NEAR(0, 0, kHalfPixelGradient, kHalfPixelGradient, 0, 255, 1.0);
+    EXPECT_PIXEL_NEAR(kResolveFBOWidth - 1, 0, 199, kHalfPixelGradient, 0, 255, 1.0);
+    EXPECT_PIXEL_NEAR(0, kResolveFBOHeight - 1, kHalfPixelGradient, 215, 0, 255, 1.0);
+    EXPECT_PIXEL_NEAR(kResolveFBOWidth - 1, kResolveFBOHeight - 1, 199, 215, 0, 255, 1.0);
+}
+
+// Test resolving a multisampled texture with blit. Draw non-flipped, resolve with both read and
+// draw fbos flipped
+TEST_P(BlitFramebufferTestES31, MultisampleFlippedResolveWithBlitAndNonFlippedDraw)
+{
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_MESA_framebuffer_flip_y"));
+
+    constexpr int kSize = 16;
+    glViewport(0, 0, kSize, kSize);
+
+    GLFramebuffer msaaFBO;
+    glBindFramebuffer(GL_FRAMEBUFFER, msaaFBO.get());
+
+    // Draw non-flipped - explicitly set y-flip to 0.
+    glFramebufferParameteriMESA(GL_FRAMEBUFFER, GL_FRAMEBUFFER_FLIP_Y_MESA, 0);
+
+    GLTexture texture;
+    glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, texture.get());
+    glTexStorage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 4, GL_RGBA8, kSize, kSize, false);
+    ASSERT_GL_NO_ERROR();
+    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE,
+                           texture.get(), 0);
+    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
+
+    ANGLE_GL_PROGRAM(gradientProgram, essl31_shaders::vs::Passthrough(),
+                     essl31_shaders::fs::RedGreenGradient());
+    drawQuad(gradientProgram, essl31_shaders::PositionAttrib(), 0.5f, 1.0f, true);
+    ASSERT_GL_NO_ERROR();
+
+    // Create another FBO to resolve the multisample buffer into.
+    GLTexture resolveTexture;
+    GLFramebuffer resolveFBO;
+    constexpr int kResolveFBOWidth  = kSize - 3;
+    constexpr int kResolveFBOHeight = kSize - 2;
+    glBindTexture(GL_TEXTURE_2D, resolveTexture);
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kResolveFBOWidth, kResolveFBOHeight, 0, GL_RGBA,
+                 GL_UNSIGNED_BYTE, nullptr);
+    glBindFramebuffer(GL_FRAMEBUFFER, resolveFBO);
+    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, resolveTexture, 0);
+    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
+
+    glBindFramebuffer(GL_READ_FRAMEBUFFER, msaaFBO);
+    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, resolveFBO);
+    // Resolve with draw and read fbo flipped.
+    glFramebufferParameteriMESA(GL_READ_FRAMEBUFFER, GL_FRAMEBUFFER_FLIP_Y_MESA, 1);
+    glFramebufferParameteriMESA(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_FLIP_Y_MESA, 1);
+    glBlitFramebuffer(0, 0, kResolveFBOWidth, kResolveFBOHeight, 0, 0, kResolveFBOWidth,
+                      kResolveFBOHeight, GL_COLOR_BUFFER_BIT, GL_NEAREST);
+    ASSERT_GL_NO_ERROR();
+
+    glBindFramebuffer(GL_READ_FRAMEBUFFER, resolveFBO);
+    constexpr uint8_t kHalfPixelGradient = 256 / kSize / 2;
+    EXPECT_PIXEL_NEAR(0, 0, kHalfPixelGradient, 255 - kHalfPixelGradient, 0, 255, 1.0);
+    EXPECT_PIXEL_NEAR(kResolveFBOWidth - 1, 0, 199, 255 - kHalfPixelGradient, 0, 255, 1.0);
+    EXPECT_PIXEL_NEAR(0, kResolveFBOHeight - 1, kHalfPixelGradient, 40, 0, 255, 1.0);
+    EXPECT_PIXEL_NEAR(kResolveFBOWidth - 1, kResolveFBOHeight - 1, 199, 40, 0, 255, 1.0);
+}
+
 // Use this to select which configurations (e.g. which renderer, which GLES major version) these
 // tests should be run against.
 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(BlitFramebufferANGLETest);
diff --git a/src/tests/gl_tests/CopyTexImageTest.cpp b/src/tests/gl_tests/CopyTexImageTest.cpp
index 10c6313..951f642 100644
--- a/src/tests/gl_tests/CopyTexImageTest.cpp
+++ b/src/tests/gl_tests/CopyTexImageTest.cpp
@@ -90,6 +90,33 @@
         EXPECT_PIXEL_NEAR((xs + xe) / 2, (ys + ye) / 2, data[0], data[1], data[2], data[3], 1.0);
     }
 
+    void verifyCheckeredResults(GLuint texture,
+                                const GLubyte data0[4],
+                                const GLubyte data1[4],
+                                const GLubyte data2[4],
+                                const GLubyte data3[4],
+                                GLint fboWidth,
+                                GLint fboHeight)
+    {
+        glViewport(0, 0, fboWidth, fboHeight);
+
+        glBindFramebuffer(GL_FRAMEBUFFER, 0);
+
+        // Draw a quad with the target texture
+        glUseProgram(mTextureProgram);
+        glBindTexture(GL_TEXTURE_2D, texture);
+        glUniform1i(mTextureUniformLocation, 0);
+
+        drawQuad(mTextureProgram, essl1_shaders::PositionAttrib(), 0.5f);
+
+        // Expect that the rendered quad has the same color as the source texture
+        EXPECT_PIXEL_EQ(fboWidth / 4, fboHeight / 4, data0[0], data0[1], data0[2], data0[3]);
+        EXPECT_PIXEL_EQ(fboWidth / 4, 3 * fboHeight / 4, data1[0], data1[1], data1[2], data1[3]);
+        EXPECT_PIXEL_EQ(3 * fboWidth / 4, fboHeight / 4, data2[0], data2[1], data2[2], data2[3]);
+        EXPECT_PIXEL_EQ(3 * fboWidth / 4, 3 * fboHeight / 4, data3[0], data3[1], data3[2],
+                        data3[3]);
+    }
+
     void runCopyTexImageTest(GLenum format, GLubyte expected[3][4])
     {
         GLTexture tex;
@@ -116,6 +143,47 @@
         }
     }
 
+    // x, y, width, height specify the portion of fbo to be copied into tex.
+    // flip_y specifies if the glCopyTextImage must be done from y-flipped fbo.
+    void runCopyTexImageTestCheckered(GLenum format,
+                                      const uint32_t x[3],
+                                      const uint32_t y[3],
+                                      const uint32_t width[3],
+                                      const uint32_t height[3],
+                                      const GLubyte expectedData0[4],
+                                      const GLubyte expectedData1[4],
+                                      const GLubyte expectedData2[4],
+                                      const GLubyte expectedData3[4],
+                                      bool mesaFlipY)
+    {
+        GLTexture tex;
+        glBindTexture(GL_TEXTURE_2D, tex);
+
+        // Disable mipmapping
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+        // Perform the copy multiple times.
+        for (size_t i = 0; i < kFboCount; ++i)
+        {
+            glViewport(0, 0, kFboSizes[i], kFboSizes[i]);
+            glBindFramebuffer(GL_FRAMEBUFFER, mFbos[i]);
+
+            ANGLE_GL_PROGRAM(checkerProgram, essl1_shaders::vs::Passthrough(),
+                             essl1_shaders::fs::Checkered());
+            drawQuad(checkerProgram.get(), essl1_shaders::PositionAttrib(), 0.5f);
+            EXPECT_GL_NO_ERROR();
+
+            if (mesaFlipY)
+                glFramebufferParameteriMESA(GL_FRAMEBUFFER, GL_FRAMEBUFFER_FLIP_Y_MESA, 1);
+            glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, x[i], y[i], width[i], height[i], 0);
+            ASSERT_GL_NO_ERROR();
+
+            verifyCheckeredResults(tex, expectedData0, expectedData1, expectedData2, expectedData3,
+                                   kFboSizes[i], kFboSizes[i]);
+        }
+    }
+
     void runCopyTexSubImageTest(GLenum format, GLubyte expected[3][4])
     {
         GLTexture tex;
@@ -566,6 +634,107 @@
     }
 }
 
+// Tests image copy from y-flipped fbo works.
+TEST_P(CopyTexImageTest, CopyTexImageMesaYFlip)
+{
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_MESA_framebuffer_flip_y"));
+
+    std::array<uint32_t, 3> copyOrigin{0};
+    std::array<uint32_t, 3> copySize;
+    for (size_t i = 0; i < kFboCount; i++)
+        copySize[i] = kFboSizes[i];
+
+    initializeResources(GL_RGBA, GL_UNSIGNED_BYTE);
+    runCopyTexImageTestCheckered(GL_RGBA, copyOrigin.data(), copyOrigin.data(), copySize.data(),
+                                 copySize.data(), GLColor::green.data(), GLColor::red.data(),
+                                 GLColor::yellow.data(), GLColor::blue.data(),
+                                 true /* mesaFlipY */);
+}
+
+// Tests image partial copy from y-flipped fbo works.
+TEST_P(CopyTexImageTest, CopyTexImageMesaYFlipPartial)
+{
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_MESA_framebuffer_flip_y"));
+
+    std::array<uint32_t, kFboCount> copyX;
+    std::array<uint32_t, kFboCount> copyY{0};
+    std::array<uint32_t, kFboCount> copyWidth;
+    std::array<uint32_t, kFboCount> copyHeight;
+
+    for (size_t i = 0; i < kFboCount; i++)
+    {
+        copyX[i]      = kFboSizes[i] / 2;
+        copyHeight[i] = kFboSizes[i];
+    }
+    copyWidth = copyX;
+
+    initializeResources(GL_RGBA, GL_UNSIGNED_BYTE);
+    runCopyTexImageTestCheckered(GL_RGBA, copyX.data(), copyY.data(), copyWidth.data(),
+                                 copyHeight.data(), GLColor::yellow.data(), GLColor::blue.data(),
+                                 GLColor::yellow.data(), GLColor::blue.data(),
+                                 true /* mesaFlipY */);
+}
+
+// Tests subimage copy from y-flipped fbo works.
+TEST_P(CopyTexImageTest, CopyTexSubImageMesaYFlip)
+{
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_MESA_framebuffer_flip_y"));
+
+    GLuint format = GL_RGBA;
+    initializeResources(format, GL_UNSIGNED_BYTE);
+
+    glViewport(0, 0, kFboSizes[0], kFboSizes[0]);
+    glBindFramebuffer(GL_FRAMEBUFFER, mFbos[0]);
+
+    ANGLE_GL_PROGRAM(checkerProgram, essl1_shaders::vs::Passthrough(),
+                     essl1_shaders::fs::Checkered());
+    drawQuad(checkerProgram.get(), essl1_shaders::PositionAttrib(), 0.5f);
+    EXPECT_GL_NO_ERROR();
+
+    GLTexture tex;
+    glBindTexture(GL_TEXTURE_2D, tex);
+
+    // Disable mipmapping
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+    // Create the texture with copy of the first fbo.
+    glBindFramebuffer(GL_FRAMEBUFFER, mFbos[0]);
+    glFramebufferParameteriMESA(GL_FRAMEBUFFER, GL_FRAMEBUFFER_FLIP_Y_MESA, 1);
+    glCopyTexImage2D(GL_TEXTURE_2D, 0, format, 0, 0, kFboSizes[0], kFboSizes[0], 0);
+    ASSERT_GL_NO_ERROR();
+
+    // Make sure out-of-bound writes to the texture return invalid value.
+    glBindFramebuffer(GL_FRAMEBUFFER, mFbos[1]);
+    drawQuad(checkerProgram.get(), essl1_shaders::PositionAttrib(), 0.5f);
+    EXPECT_GL_NO_ERROR();
+
+    glFramebufferParameteriMESA(GL_FRAMEBUFFER, GL_FRAMEBUFFER_FLIP_Y_MESA, 1);
+
+    // xoffset < 0 and yoffset < 0
+    glCopyTexSubImage2D(GL_TEXTURE_2D, 0, -1, -1, 0, 0, kFboSizes[0], kFboSizes[0]);
+    ASSERT_GL_ERROR(GL_INVALID_VALUE);
+
+    // xoffset + width > w and yoffset + height > h
+    glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 1, 1, 0, 0, kFboSizes[0], kFboSizes[0]);
+    ASSERT_GL_ERROR(GL_INVALID_VALUE);
+
+    // xoffset + width > w and yoffset + height > h, out of bounds
+    glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, -1, -1, 1 + kFboSizes[0], 1 + kFboSizes[0]);
+    ASSERT_GL_ERROR(GL_INVALID_VALUE);
+
+    // Copy the second fbo over a portion of the image.
+    GLint offset = kFboSizes[0] / 2;
+    GLint extent = kFboSizes[0] - offset;
+    glCopyTexSubImage2D(GL_TEXTURE_2D, 0, offset, offset, kFboSizes[1] / 2, kFboSizes[1] / 2,
+                        extent, extent);
+    ASSERT_GL_NO_ERROR();
+
+    // Only part of the image is changed.
+    verifyCheckeredResults(tex, GLColor::green.data(), GLColor::red.data(), GLColor::yellow.data(),
+                           GLColor::blue.data(), kFboSizes[0], kFboSizes[0]);
+}
+
 // specialization of CopyTexImageTest is added so that some tests can be explicitly run with an ES3
 // context
 class CopyTexImageTestES3 : public CopyTexImageTest
diff --git a/src/tests/gl_tests/CopyTextureTest.cpp b/src/tests/gl_tests/CopyTextureTest.cpp
index ab4fac8..8534c16 100644
--- a/src/tests/gl_tests/CopyTextureTest.cpp
+++ b/src/tests/gl_tests/CopyTextureTest.cpp
@@ -115,7 +115,7 @@
 };
 
 using CopyTextureVariationsTestParams =
-    std::tuple<angle::PlatformParameters, GLenum, GLenum, bool, bool, bool>;
+    std::tuple<angle::PlatformParameters, GLenum, GLenum, bool, bool, bool, GLint>;
 
 std::string CopyTextureVariationsTestPrint(
     const ::testing::TestParamInfo<CopyTextureVariationsTestParams> &paramsInfo)
@@ -184,6 +184,10 @@
     {
         out << "UnmultiplyAlpha";
     }
+    if (std::get<6>(params))
+    {
+        out << "MesaYFlip";
+    }
 
     return out.str();
 }
@@ -422,13 +426,18 @@
                          GLenum destFormat,
                          bool flipY,
                          bool premultiplyAlpha,
-                         bool unmultiplyAlpha)
+                         bool unmultiplyAlpha,
+                         GLint mesaYFlipParam)
     {
         if (!checkExtensions(sourceFormat, destFormat))
         {
             return;
         }
 
+        const bool hasMesaFbFlipYExt = IsGLExtensionEnabled("GL_MESA_framebuffer_flip_y");
+        if (mesaYFlipParam && !hasMesaFbFlipYExt)
+            ASSERT_TRUE(hasMesaFbFlipYExt);
+
         if (sourceFormat == GL_LUMINANCE || sourceFormat == GL_LUMINANCE_ALPHA ||
             sourceFormat == GL_ALPHA || destFormat == GL_LUMINANCE ||
             destFormat == GL_LUMINANCE_ALPHA || destFormat == GL_ALPHA)
@@ -457,6 +466,11 @@
             initializeSourceTexture(sourceTarget, sourceFormat, &srcColors[i * componentCount],
                                     componentCount);
 
+            if (hasMesaFbFlipYExt)
+            {
+                glFramebufferParameteriMESA(GL_FRAMEBUFFER, GL_FRAMEBUFFER_FLIP_Y_MESA,
+                                            mesaYFlipParam);
+            }
             glCopyTextureCHROMIUM(mTextures[0], 0, GL_TEXTURE_2D, mTextures[1], 0, destFormat,
                                   GL_UNSIGNED_BYTE, flipY, premultiplyAlpha, unmultiplyAlpha);
 
@@ -467,17 +481,37 @@
 
             if (flipY)
             {
-                EXPECT_PIXEL_COLOR_NEAR(0, 0, destColors[i + 2], 1.0);
-                EXPECT_PIXEL_COLOR_NEAR(1, 0, destColors[i + 3], 1.0);
-                EXPECT_PIXEL_COLOR_NEAR(0, 1, destColors[i + 0], 1.0);
-                EXPECT_PIXEL_COLOR_NEAR(1, 1, destColors[i + 1], 1.0);
+                if (mesaYFlipParam)
+                {
+                    EXPECT_PIXEL_COLOR_NEAR(0, 0, destColors[i + 0], 1.0);
+                    EXPECT_PIXEL_COLOR_NEAR(1, 0, destColors[i + 1], 1.0);
+                    EXPECT_PIXEL_COLOR_NEAR(0, 1, destColors[i + 2], 1.0);
+                    EXPECT_PIXEL_COLOR_NEAR(1, 1, destColors[i + 3], 1.0);
+                }
+                else
+                {
+                    EXPECT_PIXEL_COLOR_NEAR(0, 0, destColors[i + 2], 1.0);
+                    EXPECT_PIXEL_COLOR_NEAR(1, 0, destColors[i + 3], 1.0);
+                    EXPECT_PIXEL_COLOR_NEAR(0, 1, destColors[i + 0], 1.0);
+                    EXPECT_PIXEL_COLOR_NEAR(1, 1, destColors[i + 1], 1.0);
+                }
             }
             else
             {
-                EXPECT_PIXEL_COLOR_NEAR(0, 0, destColors[i + 0], 1.0);
-                EXPECT_PIXEL_COLOR_NEAR(1, 0, destColors[i + 1], 1.0);
-                EXPECT_PIXEL_COLOR_NEAR(0, 1, destColors[i + 2], 1.0);
-                EXPECT_PIXEL_COLOR_NEAR(1, 1, destColors[i + 3], 1.0);
+                if (mesaYFlipParam)
+                {
+                    EXPECT_PIXEL_COLOR_NEAR(0, 0, destColors[i + 2], 1.0);
+                    EXPECT_PIXEL_COLOR_NEAR(1, 0, destColors[i + 3], 1.0);
+                    EXPECT_PIXEL_COLOR_NEAR(0, 1, destColors[i + 0], 1.0);
+                    EXPECT_PIXEL_COLOR_NEAR(1, 1, destColors[i + 1], 1.0);
+                }
+                else
+                {
+                    EXPECT_PIXEL_COLOR_NEAR(0, 0, destColors[i + 0], 1.0);
+                    EXPECT_PIXEL_COLOR_NEAR(1, 0, destColors[i + 1], 1.0);
+                    EXPECT_PIXEL_COLOR_NEAR(0, 1, destColors[i + 2], 1.0);
+                    EXPECT_PIXEL_COLOR_NEAR(1, 1, destColors[i + 3], 1.0);
+                }
             }
 
             EXPECT_GL_NO_ERROR();
@@ -489,13 +523,18 @@
                             GLenum destFormat,
                             bool flipY,
                             bool premultiplyAlpha,
-                            bool unmultiplyAlpha)
+                            bool unmultiplyAlpha,
+                            GLint mesaYFlipParam)
     {
         if (!checkExtensions(sourceFormat, destFormat))
         {
             return;
         }
 
+        const bool hasMesaFbFlipYExt = IsGLExtensionEnabled("GL_MESA_framebuffer_flip_y");
+        if (mesaYFlipParam && !hasMesaFbFlipYExt)
+            ASSERT_TRUE(hasMesaFbFlipYExt);
+
         if (sourceFormat == GL_LUMINANCE || sourceFormat == GL_LUMINANCE_ALPHA ||
             sourceFormat == GL_ALPHA || destFormat == GL_LUMINANCE ||
             destFormat == GL_LUMINANCE_ALPHA || destFormat == GL_ALPHA)
@@ -528,6 +567,11 @@
             glTexImage2D(GL_TEXTURE_2D, 0, destFormat, 2, 2, 0, destFormat, GL_UNSIGNED_BYTE,
                          nullptr);
 
+            if (hasMesaFbFlipYExt)
+            {
+                glFramebufferParameteriMESA(GL_FRAMEBUFFER, GL_FRAMEBUFFER_FLIP_Y_MESA,
+                                            mesaYFlipParam);
+            }
             glCopySubTextureCHROMIUM(mTextures[0], 0, GL_TEXTURE_2D, mTextures[1], 0, 0, 0, 0, 0, 2,
                                      2, flipY, premultiplyAlpha, unmultiplyAlpha);
 
@@ -542,17 +586,37 @@
 
             if (flipY)
             {
-                EXPECT_PIXEL_COLOR_NEAR(0, 0, destColors[i + 2], 1.0);
-                EXPECT_PIXEL_COLOR_NEAR(1, 0, destColors[i + 3], 1.0);
-                EXPECT_PIXEL_COLOR_NEAR(0, 1, destColors[i + 0], 1.0);
-                EXPECT_PIXEL_COLOR_NEAR(1, 1, destColors[i + 1], 1.0);
+                if (mesaYFlipParam)
+                {
+                    EXPECT_PIXEL_COLOR_NEAR(0, 0, destColors[i + 0], 1.0);
+                    EXPECT_PIXEL_COLOR_NEAR(1, 0, destColors[i + 1], 1.0);
+                    EXPECT_PIXEL_COLOR_NEAR(0, 1, destColors[i + 2], 1.0);
+                    EXPECT_PIXEL_COLOR_NEAR(1, 1, destColors[i + 3], 1.0);
+                }
+                else
+                {
+                    EXPECT_PIXEL_COLOR_NEAR(0, 0, destColors[i + 2], 1.0);
+                    EXPECT_PIXEL_COLOR_NEAR(1, 0, destColors[i + 3], 1.0);
+                    EXPECT_PIXEL_COLOR_NEAR(0, 1, destColors[i + 0], 1.0);
+                    EXPECT_PIXEL_COLOR_NEAR(1, 1, destColors[i + 1], 1.0);
+                }
             }
             else
             {
-                EXPECT_PIXEL_COLOR_NEAR(0, 0, destColors[i + 0], 1.0);
-                EXPECT_PIXEL_COLOR_NEAR(1, 0, destColors[i + 1], 1.0);
-                EXPECT_PIXEL_COLOR_NEAR(0, 1, destColors[i + 2], 1.0);
-                EXPECT_PIXEL_COLOR_NEAR(1, 1, destColors[i + 3], 1.0);
+                if (mesaYFlipParam)
+                {
+                    EXPECT_PIXEL_COLOR_NEAR(0, 0, destColors[i + 2], 1.0);
+                    EXPECT_PIXEL_COLOR_NEAR(1, 0, destColors[i + 3], 1.0);
+                    EXPECT_PIXEL_COLOR_NEAR(0, 1, destColors[i + 0], 1.0);
+                    EXPECT_PIXEL_COLOR_NEAR(1, 1, destColors[i + 1], 1.0);
+                }
+                else
+                {
+                    EXPECT_PIXEL_COLOR_NEAR(0, 0, destColors[i + 0], 1.0);
+                    EXPECT_PIXEL_COLOR_NEAR(1, 0, destColors[i + 1], 1.0);
+                    EXPECT_PIXEL_COLOR_NEAR(0, 1, destColors[i + 2], 1.0);
+                    EXPECT_PIXEL_COLOR_NEAR(1, 1, destColors[i + 3], 1.0);
+                }
             }
 
             EXPECT_GL_NO_ERROR();
@@ -904,6 +968,7 @@
     GL_ALPHA, GL_RGB, GL_RGBA, GL_LUMINANCE, GL_LUMINANCE_ALPHA, GL_BGRA_EXT};
 constexpr GLenum kCopyTextureVariationsDstFormats[] = {GL_RGB, GL_RGBA, GL_BGRA_EXT,
                                                        GL_SRGB_ALPHA_EXT};
+constexpr GLint kMesaYFlips[]                       = {0, 1};
 }  // anonymous namespace
 
 TEST_P(CopyTextureVariationsTest, CopyTexture)
@@ -917,32 +982,57 @@
         ANGLE_SKIP_TEST_IF(IsWindows7() && IsNVIDIA() && IsOpenGLES());
     }
 
+    if (std::get<6>(GetParam()))
+    {
+        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_MESA_framebuffer_flip_y"));
+    }
+
     testCopyTexture(GL_TEXTURE_2D, std::get<1>(GetParam()), std::get<2>(GetParam()),
-                    std::get<3>(GetParam()), std::get<4>(GetParam()), std::get<5>(GetParam()));
+                    std::get<3>(GetParam()), std::get<4>(GetParam()), std::get<5>(GetParam()),
+                    std::get<6>(GetParam()));
 }
 
 TEST_P(CopyTextureVariationsTest, CopySubTexture)
 {
     // http://anglebug.com/5723
     ANGLE_SKIP_TEST_IF(IsOzone());
+
+    if (std::get<6>(GetParam()))
+    {
+        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_MESA_framebuffer_flip_y"));
+    }
+
     testCopySubTexture(GL_TEXTURE_2D, std::get<1>(GetParam()), std::get<2>(GetParam()),
-                       std::get<3>(GetParam()), std::get<4>(GetParam()), std::get<5>(GetParam()));
+                       std::get<3>(GetParam()), std::get<4>(GetParam()), std::get<5>(GetParam()),
+                       std::get<6>(GetParam()));
 }
 
 TEST_P(CopyTextureVariationsTest, CopyTextureRectangle)
 {
     ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_texture_rectangle"));
 
+    if (std::get<6>(GetParam()))
+    {
+        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_MESA_framebuffer_flip_y"));
+    }
+
     testCopyTexture(GL_TEXTURE_RECTANGLE_ANGLE, std::get<1>(GetParam()), std::get<2>(GetParam()),
-                    std::get<3>(GetParam()), std::get<4>(GetParam()), std::get<5>(GetParam()));
+                    std::get<3>(GetParam()), std::get<4>(GetParam()), std::get<5>(GetParam()),
+                    std::get<6>(GetParam()));
 }
 
 TEST_P(CopyTextureVariationsTest, CopySubTextureRectangle)
 {
     ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_texture_rectangle"));
 
+    if (std::get<6>(GetParam()))
+    {
+        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_MESA_framebuffer_flip_y"));
+    }
+
     testCopySubTexture(GL_TEXTURE_RECTANGLE_ANGLE, std::get<1>(GetParam()), std::get<2>(GetParam()),
-                       std::get<3>(GetParam()), std::get<4>(GetParam()), std::get<5>(GetParam()));
+                       std::get<3>(GetParam()), std::get<4>(GetParam()), std::get<5>(GetParam()),
+                       std::get<6>(GetParam()));
 }
 
 // Test that copying to cube maps works
@@ -2534,13 +2624,14 @@
 #endif
 
 ANGLE_INSTANTIATE_TEST_ES2(CopyTextureTest);
-ANGLE_INSTANTIATE_TEST_COMBINE_5(CopyTextureVariationsTest,
+ANGLE_INSTANTIATE_TEST_COMBINE_6(CopyTextureVariationsTest,
                                  CopyTextureVariationsTestPrint,
                                  testing::ValuesIn(kCopyTextureVariationsSrcFormats),
                                  testing::ValuesIn(kCopyTextureVariationsDstFormats),
                                  testing::Bool(),  // flipY
                                  testing::Bool(),  // premultiplyAlpha
                                  testing::Bool(),  // unmultiplyAlpha
+                                 testing::ValuesIn(kMesaYFlips),
                                  ES2_D3D9(),
                                  ES2_D3D11(),
                                  ES2_OPENGL(),
diff --git a/src/tests/gl_tests/FramebufferTest.cpp b/src/tests/gl_tests/FramebufferTest.cpp
index 9e71e2d..1d82fd6 100644
--- a/src/tests/gl_tests/FramebufferTest.cpp
+++ b/src/tests/gl_tests/FramebufferTest.cpp
@@ -1863,6 +1863,43 @@
     ASSERT_GL_NO_ERROR();
 }
 
+// Tests that draw to Y-flipped FBO results in correct pixels.
+TEST_P(FramebufferTest_ES31, BasicDrawToYFlippedFBO)
+{
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_MESA_framebuffer_flip_y"));
+
+    constexpr int kSize = 16;
+    glViewport(0, 0, kSize, kSize);
+
+    GLFramebuffer fbo;
+    glBindFramebuffer(GL_FRAMEBUFFER, fbo.get());
+
+    glFramebufferParameteriMESA(GL_FRAMEBUFFER, GL_FRAMEBUFFER_FLIP_Y_MESA, 1);
+
+    GLTexture texture;
+    glBindTexture(GL_TEXTURE_2D, texture.get());
+    glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kSize, kSize);
+    ASSERT_GL_NO_ERROR();
+    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture.get(), 0);
+    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
+
+    ANGLE_GL_PROGRAM(gradientProgram, essl31_shaders::vs::Passthrough(),
+                     essl31_shaders::fs::RedGreenGradient());
+    drawQuad(gradientProgram, essl31_shaders::PositionAttrib(), 0.5f, 1.0f, true);
+    ASSERT_GL_NO_ERROR();
+
+    // Remove the flag so that glReadPixels do not implicitly use that.
+    glFramebufferParameteriMESA(GL_FRAMEBUFFER, GL_FRAMEBUFFER_FLIP_Y_MESA, 0);
+
+    constexpr uint8_t kHalfPixelGradient = 256 / kSize / 2;
+    EXPECT_PIXEL_NEAR(0, 0, kHalfPixelGradient, 255 - kHalfPixelGradient, 0, 255, 1.0);
+    EXPECT_PIXEL_NEAR(kSize - 1, 0, 255 - kHalfPixelGradient, 255 - kHalfPixelGradient, 0, 255,
+                      1.0);
+    EXPECT_PIXEL_NEAR(0, kSize - 1, kHalfPixelGradient, kHalfPixelGradient, 0, 255, 1.0);
+    EXPECT_PIXEL_NEAR(kSize - 1, kSize - 1, 255 - kHalfPixelGradient, kHalfPixelGradient, 0, 255,
+                      1.0);
+}
+
 // Test resolving a multisampled texture with blit
 TEST_P(FramebufferTest_ES31, MultisampleResolveWithBlit)
 {
diff --git a/src/tests/test_utils/angle_test_instantiate.h b/src/tests/test_utils/angle_test_instantiate.h
index 9437d17..abf8f5a 100644
--- a/src/tests/test_utils/angle_test_instantiate.h
+++ b/src/tests/test_utils/angle_test_instantiate.h
@@ -256,6 +256,14 @@
                              testing::Combine(ANGLE_INSTANTIATE_TEST_PLATFORMS(testName),         \
                                               combine1, combine2, combine3, combine4, combine5),  \
                              print)
+#define ANGLE_INSTANTIATE_TEST_COMBINE_6(testName, print, combine1, combine2, combine3, combine4,  \
+                                         combine5, combine6, first, ...)                           \
+    const decltype(first) testName##params[] = {first, ##__VA_ARGS__};                             \
+    INSTANTIATE_TEST_SUITE_P(                                                                      \
+        , testName,                                                                                \
+        testing::Combine(ANGLE_INSTANTIATE_TEST_PLATFORMS(testName), combine1, combine2, combine3, \
+                         combine4, combine5, combine6),                                            \
+        print)
 
 // Checks if a config is expected to be supported by checking a system-based allow list.
 bool IsConfigAllowlisted(const SystemInfo &systemInfo, const PlatformParameters &param);