Allow 32-bit float formats in ES2 CopyTexSubImage.

These corner case formats can come about from using floating
point textures and trying to copy between them. There currently
isn't a good alternative in WebGL 1 for copies.

BUG=angle:850

Change-Id: I3dc29e42b5ec7dcf071185bc09c6b3e9e3cb3207
Reviewed-on: https://chromium-review.googlesource.com/241048
Tested-by: Jamie Madill <jmadill@chromium.org>
Reviewed-by: Jamie Madill <jmadill@chromium.org>
diff --git a/src/libANGLE/validationES2.cpp b/src/libANGLE/validationES2.cpp
index e5c3da5..9eece1b 100644
--- a/src/libANGLE/validationES2.cpp
+++ b/src/libANGLE/validationES2.cpp
@@ -411,7 +411,8 @@
 
     gl::Framebuffer *framebuffer = context->getState().getReadFramebuffer();
     GLenum colorbufferFormat = framebuffer->getReadColorbuffer()->getInternalFormat();
-    GLenum textureFormat = gl::GetInternalFormatInfo(textureInternalFormat).format;
+    const auto &internalFormatInfo = gl::GetInternalFormatInfo(textureInternalFormat);
+    GLenum textureFormat = internalFormatInfo.format;
 
     // [OpenGL ES 2.0.24] table 3.9
     if (isSubImage)
@@ -448,7 +449,11 @@
                   colorbufferFormat != GL_RGB8_OES &&
                   colorbufferFormat != GL_RGBA4 &&
                   colorbufferFormat != GL_RGB5_A1 &&
-                  colorbufferFormat != GL_RGBA8_OES)
+                  colorbufferFormat != GL_RGBA8_OES &&
+                  colorbufferFormat != GL_R32F &&
+                  colorbufferFormat != GL_RG32F &&
+                  colorbufferFormat != GL_RGB32F &&
+                  colorbufferFormat != GL_RGBA32F)
               {
                   context->recordError(Error(GL_INVALID_OPERATION));
                   return false;
@@ -460,7 +465,10 @@
                   colorbufferFormat != GL_RGB8_OES &&
                   colorbufferFormat != GL_RGBA4 &&
                   colorbufferFormat != GL_RGB5_A1 &&
-                  colorbufferFormat != GL_RGBA8_OES)
+                  colorbufferFormat != GL_RGBA8_OES &&
+                  colorbufferFormat != GL_RG32F &&
+                  colorbufferFormat != GL_RGB32F &&
+                  colorbufferFormat != GL_RGBA32F)
               {
                   context->recordError(Error(GL_INVALID_OPERATION));
                   return false;
@@ -471,7 +479,9 @@
                 colorbufferFormat != GL_RGB8_OES &&
                 colorbufferFormat != GL_RGBA4 &&
                 colorbufferFormat != GL_RGB5_A1 &&
-                colorbufferFormat != GL_RGBA8_OES)
+                colorbufferFormat != GL_RGBA8_OES &&
+                colorbufferFormat != GL_RGB32F &&
+                colorbufferFormat != GL_RGBA32F)
             {
                 context->recordError(Error(GL_INVALID_OPERATION));
                 return false;
@@ -481,7 +491,8 @@
           case GL_RGBA:
             if (colorbufferFormat != GL_RGBA4 &&
                 colorbufferFormat != GL_RGB5_A1 &&
-                colorbufferFormat != GL_RGBA8_OES)
+                colorbufferFormat != GL_RGBA8_OES &&
+                colorbufferFormat != GL_RGBA32F)
             {
                 context->recordError(Error(GL_INVALID_OPERATION));
                 return false;
@@ -501,6 +512,13 @@
             context->recordError(Error(GL_INVALID_OPERATION));
             return false;
         }
+
+        if (internalFormatInfo.type == GL_FLOAT &&
+            !context->getExtensions().textureFloat)
+        {
+            context->recordError(Error(GL_INVALID_OPERATION));
+            return false;
+        }
     }
     else
     {
diff --git a/tests/angle_tests/TextureTest.cpp b/tests/angle_tests/TextureTest.cpp
index 1a496d3..468e4a2 100644
--- a/tests/angle_tests/TextureTest.cpp
+++ b/tests/angle_tests/TextureTest.cpp
@@ -6,7 +6,7 @@
 template<typename T>
 class TextureTest : public ANGLETest
 {
-protected:
+  protected:
     TextureTest() : ANGLETest(T::GetGlesMajorVersion(), T::GetPlatform())
     {
         setWindowWidth(128);
@@ -103,6 +103,114 @@
         ANGLETest::TearDown();
     }
 
+    // Tests CopyTexSubImage with floating point textures of various formats.
+    void testFloatCopySubImage(int sourceImageChannels, int destImageChannels)
+    {
+        GLfloat sourceImageData[4][16] =
+        {
+            { // R
+                1.0f,
+                0.0f,
+                0.0f,
+                1.0f
+            },
+            { // RG
+                1.0f, 0.0f,
+                0.0f, 1.0f,
+                0.0f, 0.0f,
+                1.0f, 1.0f
+            },
+            { // RGB
+                1.0f, 0.0f, 0.0f,
+                0.0f, 1.0f, 0.0f,
+                0.0f, 0.0f, 1.0f,
+                1.0f, 1.0f, 0.0f
+            },
+            { // RGBA
+                1.0f, 0.0f, 0.0f, 1.0f,
+                0.0f, 1.0f, 0.0f, 1.0f,
+                0.0f, 0.0f, 1.0f, 1.0f,
+                1.0f, 1.0f, 0.0f, 1.0f
+            },
+        };
+
+        GLenum imageFormats[] =
+        {
+            GL_R32F,
+            GL_RG32F,
+            GL_RGB32F,
+            GL_RGBA32F,
+        };
+
+        GLenum sourceUnsizedFormats[] =
+        {
+            GL_RED,
+            GL_RG,
+            GL_RGB,
+            GL_RGBA,
+        };
+
+        GLuint textures[2];
+
+        glGenTextures(2, textures);
+
+        GLfloat *imageData = sourceImageData[sourceImageChannels - 1];
+        GLenum sourceImageFormat = imageFormats[sourceImageChannels - 1];
+        GLenum sourceUnsizedFormat = sourceUnsizedFormats[sourceImageChannels - 1];
+        GLenum destImageFormat = imageFormats[destImageChannels - 1];
+
+        glBindTexture(GL_TEXTURE_2D, textures[0]);
+        glTexStorage2DEXT(GL_TEXTURE_2D, 1, sourceImageFormat, 2, 2);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 2, 2, sourceUnsizedFormat, GL_FLOAT, imageData);
+
+        if (sourceImageChannels < 4 && !extensionEnabled("GL_EXT_texture_rg"))
+        {
+            // This is not supported
+            ASSERT_GL_ERROR(GL_INVALID_OPERATION);
+        }
+        else
+        {
+            ASSERT_GL_NO_ERROR();
+        }
+
+        GLuint fbo;
+        glGenFramebuffers(1, &fbo);
+        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textures[0], 0);
+
+        glBindTexture(GL_TEXTURE_2D, textures[1]);
+        glTexStorage2DEXT(GL_TEXTURE_2D, 1, destImageFormat, 2, 2);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+        glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, 2, 2);
+        ASSERT_GL_NO_ERROR();
+
+        glBindFramebuffer(GL_FRAMEBUFFER, 0);
+        drawQuad(m2DProgram, "position", 0.5f);
+        swapBuffers();
+
+        int testImageChannels = std::min(sourceImageChannels, destImageChannels);
+
+        EXPECT_PIXEL_EQ(0, 0, 255, 0, 0, 255);
+        if (testImageChannels > 1)
+        {
+            EXPECT_PIXEL_EQ(getWindowHeight() - 1, 0, 0, 255, 0, 255);
+            EXPECT_PIXEL_EQ(getWindowHeight() - 1, getWindowWidth() - 1, 255, 255, 0, 255);
+            if (testImageChannels > 2)
+            {
+                EXPECT_PIXEL_EQ(0, getWindowWidth() - 1, 0, 0, 255, 255);
+            }
+        }
+
+        glDeleteFramebuffers(1, &fbo);
+        glDeleteTextures(2, textures);
+
+        ASSERT_GL_NO_ERROR();
+    }
+
     GLuint mTexture2D;
     GLuint mTextureCube;
 
@@ -334,3 +442,61 @@
         EXPECT_PIXEL_EQ(width / 4, height / 4, 255, 0, 0, 255);
     }
 }
+
+// See description on testFloatCopySubImage
+// TODO(jmadill): Fix sampling from unused channels on D3D9
+TYPED_TEST(TextureTest, DISABLED_CopySubImageFloat_R_R)
+{
+    testFloatCopySubImage(1, 1);
+}
+
+// TODO(jmadill): Fix sampling from unused channels on D3D9
+TYPED_TEST(TextureTest, DISABLED_CopySubImageFloat_RG_R)
+{
+    testFloatCopySubImage(2, 1);
+}
+
+// TODO(jmadill): Fix sampling from unused channels on D3D9
+TYPED_TEST(TextureTest, DISABLED_CopySubImageFloat_RG_RG)
+{
+    testFloatCopySubImage(2, 2);
+}
+
+// TODO(jmadill): Fix sampling from unused channels on D3D9
+TYPED_TEST(TextureTest, DISABLED_CopySubImageFloat_RGB_R)
+{
+    testFloatCopySubImage(3, 1);
+}
+
+// TODO(jmadill): Fix sampling from unused channels on D3D9
+TYPED_TEST(TextureTest, DISABLED_CopySubImageFloat_RGB_RG)
+{
+    testFloatCopySubImage(3, 2);
+}
+
+TYPED_TEST(TextureTest, CopySubImageFloat_RGB_RGB)
+{
+    testFloatCopySubImage(3, 3);
+}
+
+// TODO(jmadill): Fix sampling from unused channels on D3D9
+TYPED_TEST(TextureTest, DISABLED_CopySubImageFloat_RGBA_R)
+{
+    testFloatCopySubImage(4, 1);
+}
+
+// TODO(jmadill): Fix sampling from unused channels on D3D9
+TYPED_TEST(TextureTest, DISABLED_CopySubImageFloat_RGBA_RG)
+{
+    testFloatCopySubImage(4, 2);
+}
+
+TYPED_TEST(TextureTest, CopySubImageFloat_RGBA_RGB)
+{
+    testFloatCopySubImage(4, 3);
+}
+
+TYPED_TEST(TextureTest, CopySubImageFloat_RGBA_RGBA)
+{
+    testFloatCopySubImage(4, 4);
+}