add ext_float_blend feature and test

Bug: chromium:930993
Change-Id: I8edbd01c5c9f1ed63243cc4a42f6de44c92db8bd
Reviewed-on: https://chromium-review.googlesource.com/c/1481242
Commit-Queue: Shrek Shao <shrekshao@google.com>
Reviewed-by: Jamie Madill <jmadill@google.com>
diff --git a/src/libANGLE/Caps.cpp b/src/libANGLE/Caps.cpp
index f7793d8..a49a93a 100644
--- a/src/libANGLE/Caps.cpp
+++ b/src/libANGLE/Caps.cpp
@@ -255,6 +255,7 @@
       multiviewMultisample(false),
       blendFuncExtended(false),
       maxDualSourceDrawBuffers(0),
+      floatBlend(false),
       memorySize(false),
       textureMultisample(false),
       multiDraw(false)
@@ -909,6 +910,7 @@
         map["GL_OES_texture_storage_multisample_2d_array"] = enableableExtension(&Extensions::textureStorageMultisample2DArray);
         map["GL_ANGLE_multiview_multisample"] = enableableExtension(&Extensions::multiviewMultisample);
         map["GL_EXT_blend_func_extended"] = enableableExtension(&Extensions::blendFuncExtended);
+        map["GL_EXT_float_blend"] = enableableExtension(&Extensions::floatBlend);
         map["GL_ANGLE_texture_multisample"] = enableableExtension(&Extensions::textureMultisample);
         map["GL_ANGLE_multi_draw"] = enableableExtension(&Extensions::multiDraw);
         map["GL_ANGLE_provoking_vertex"] = enableableExtension(&Extensions::provokingVertex);
diff --git a/src/libANGLE/Caps.h b/src/libANGLE/Caps.h
index ff835a0..bbd17dd 100644
--- a/src/libANGLE/Caps.h
+++ b/src/libANGLE/Caps.h
@@ -466,6 +466,9 @@
     bool blendFuncExtended;
     GLuint maxDualSourceDrawBuffers;
 
+    // GL_EXT_float_blend
+    bool floatBlend;
+
     // GL_ANGLE_memory_size
     bool memorySize;
 
diff --git a/src/libANGLE/ErrorStrings.h b/src/libANGLE/ErrorStrings.h
index df89e94..473fe01 100644
--- a/src/libANGLE/ErrorStrings.h
+++ b/src/libANGLE/ErrorStrings.h
@@ -474,6 +474,7 @@
 MSG kUnknownParameter = "Unknown parameter value.";
 MSG kUnsizedInternalFormatUnsupported = "Internalformat is one of the unsupported unsized base internalformats.";
 MSG kUnsupportedDrawModeForTransformFeedback = "The draw command is unsupported when transform feedback is active and not paused.";
+MSG kUnsupportedFloatBlending = "GL_BLEND with floating-point color attachments requires the EXT_float_blend extension.";
 MSG kVertexArrayNoBuffer = "An enabled vertex array has no buffer.";
 MSG kVertexArrayNoBufferPointer = "An enabled vertex array has no buffer and no pointer.";
 MSG kVertexBufferBoundForTransformFeedback = "It is undefined behavior to use a vertex buffer that is bound for transform feedback.";
diff --git a/src/libANGLE/Framebuffer.cpp b/src/libANGLE/Framebuffer.cpp
index ec99a04..11e1d90 100644
--- a/src/libANGLE/Framebuffer.cpp
+++ b/src/libANGLE/Framebuffer.cpp
@@ -1758,6 +1758,16 @@
                              textureIndex, resource, numViews, baseViewIndex, multiviewLayout,
                              viewportOffsets);
 
+            if (!resource)
+            {
+                mFloat32ColorAttachmentBits.reset(colorIndex);
+            }
+            else
+            {
+                updateFloat32ColorAttachmentBits(
+                    colorIndex, resource->getAttachmentFormat(binding, textureIndex).info);
+            }
+
             // TODO(jmadill): ASSERT instead of checking the attachment exists in
             // formsRenderingFeedbackLoopWith
             bool enabled = (type != GL_NONE && getDrawBufferState(colorIndex) != GL_NONE);
@@ -1829,6 +1839,15 @@
 
     // Mark the appropriate init flag.
     mState.mResourceNeedsInit.set(index, attachment->initState() == InitState::MayNeedInit);
+
+    // Update mFloat32ColorAttachmentBits Cache
+    if (index < DIRTY_BIT_COLOR_ATTACHMENT_MAX)
+    {
+        ASSERT(index != DIRTY_BIT_DEPTH_ATTACHMENT);
+        ASSERT(index != DIRTY_BIT_STENCIL_ATTACHMENT);
+        updateFloat32ColorAttachmentBits(index - DIRTY_BIT_COLOR_ATTACHMENT_0,
+                                         attachment->getFormat().info);
+    }
 }
 
 FramebufferAttachment *Framebuffer::getAttachmentFromSubjectIndex(angle::SubjectIndex index)
diff --git a/src/libANGLE/Framebuffer.h b/src/libANGLE/Framebuffer.h
index 8295410..219384b 100644
--- a/src/libANGLE/Framebuffer.h
+++ b/src/libANGLE/Framebuffer.h
@@ -337,6 +337,11 @@
     using DirtyBits = angle::BitSet<DIRTY_BIT_MAX>;
     bool hasAnyDirtyBit() const { return mDirtyBits.any(); }
 
+    bool hasActiveFloat32ColorAttachment() const
+    {
+        return (mFloat32ColorAttachmentBits & getDrawBufferMask()).any();
+    }
+
     bool hasResourceThatNeedsInit() const { return mState.mResourceNeedsInit.any(); }
 
     angle::Result syncState(const Context *context);
@@ -418,6 +423,12 @@
 
     FramebufferAttachment *getAttachmentFromSubjectIndex(angle::SubjectIndex index);
 
+    ANGLE_INLINE void updateFloat32ColorAttachmentBits(size_t index,
+                                                       const gl::InternalFormat *format)
+    {
+        mFloat32ColorAttachmentBits.set(index, format->type == GL_FLOAT);
+    }
+
     FramebufferState mState;
     rx::FramebufferImpl *mImpl;
 
@@ -427,6 +438,7 @@
     angle::ObserverBinding mDirtyStencilAttachmentBinding;
 
     DirtyBits mDirtyBits;
+    DrawBufferMask mFloat32ColorAttachmentBits;
 
     // The dirty bits guard is checked when we get a dependent state change message. We verify that
     // we don't set a dirty bit that isn't already set, when inside the dirty bits syncState.
diff --git a/src/libANGLE/renderer/gl/renderergl_utils.cpp b/src/libANGLE/renderer/gl/renderergl_utils.cpp
index d0356dc..c0d3bfa 100644
--- a/src/libANGLE/renderer/gl/renderergl_utils.cpp
+++ b/src/libANGLE/renderer/gl/renderergl_utils.cpp
@@ -1345,6 +1345,11 @@
         extensions->maxDualSourceDrawBuffers = 1;
     }
 
+    // EXT_float_blend
+    // Assume all desktop driver supports this by default.
+    extensions->floatBlend = functions->standard == STANDARD_GL_DESKTOP ||
+                             functions->hasGLESExtension("GL_EXT_float_blend");
+
     // GL_CHROMIUM_compressed_texture_etc
     // Expose this extension only when we support the formats or we're running on top of a native
     // ES driver.
diff --git a/src/libANGLE/validationES.cpp b/src/libANGLE/validationES.cpp
index 0361a42..80e743a 100644
--- a/src/libANGLE/validationES.cpp
+++ b/src/libANGLE/validationES.cpp
@@ -2623,6 +2623,12 @@
         }
     }
 
+    if (!extensions.floatBlend && context->getState().isBlendEnabled() &&
+        framebuffer->hasActiveFloat32ColorAttachment())
+    {
+        return kUnsupportedFloatBlending;
+    }
+
     if (!framebuffer->isComplete(context))
     {
         // Note: this error should be generated as INVALID_FRAMEBUFFER_OPERATION.
diff --git a/src/tests/gl_tests/BlendMinMaxTest.cpp b/src/tests/gl_tests/BlendMinMaxTest.cpp
index 97ab18e..5eeb42d 100644
--- a/src/tests/gl_tests/BlendMinMaxTest.cpp
+++ b/src/tests/gl_tests/BlendMinMaxTest.cpp
@@ -171,7 +171,7 @@
 
 TEST_P(BlendMinMaxTest, RGBA32F)
 {
-    ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3 ||
+    ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3 || !extensionEnabled("GL_EXT_float_blend") ||
                        !extensionEnabled("GL_EXT_color_buffer_float"));
 
     // Ignore SDK layers messages on D3D11 FL 9.3 (http://anglebug.com/1284)
diff --git a/src/tests/gl_tests/WebGLCompatibilityTest.cpp b/src/tests/gl_tests/WebGLCompatibilityTest.cpp
index e0869e7..5ce992f 100644
--- a/src/tests/gl_tests/WebGLCompatibilityTest.cpp
+++ b/src/tests/gl_tests/WebGLCompatibilityTest.cpp
@@ -43,6 +43,7 @@
     "GL_OES_texture_float",
     "GL_OES_texture_float_linear",
     "GL_EXT_color_buffer_float",
+    "GL_EXT_float_blend",
     "GL_CHROMIUM_color_buffer_float_rgba",
     "GL_CHROMIUM_color_buffer_float_rgb",
 };
@@ -213,6 +214,49 @@
             0, 0, GLColor32F(floatData[0], floatData[1], floatData[2], floatData[3]), 1.0f);
     }
 
+    void TestExtFloatBlend(bool shouldBlend)
+    {
+        constexpr char kVS[] =
+            "void main()\n"
+            "{\n"
+            "   gl_Position = vec4(0, 0, 0, 1);\n"
+            "}\n";
+
+        constexpr char kFS[] =
+            "void main()\n"
+            "{\n"
+            "   gl_FragColor = vec4(0, 1, 0, 1);\n"
+            "}\n";
+
+        ANGLE_GL_PROGRAM(program, kVS, kFS);
+        glUseProgram(program);
+
+        GLTexture texture;
+        glBindTexture(GL_TEXTURE_2D, texture);
+        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, 1, 1, 0, GL_RGBA, GL_FLOAT, nullptr);
+        EXPECT_GL_NO_ERROR();
+
+        GLFramebuffer fbo;
+        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
+        ASSERT_EGLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
+
+        glDisable(GL_BLEND);
+        glDrawArrays(GL_POINTS, 0, 1);
+        EXPECT_GL_NO_ERROR();
+
+        glEnable(GL_BLEND);
+        glDrawArrays(GL_POINTS, 0, 1);
+        if (shouldBlend)
+        {
+            EXPECT_GL_NO_ERROR();
+        }
+        else
+        {
+            EXPECT_GL_ERROR(GL_INVALID_OPERATION);
+        }
+    }
+
     void TestDifferentStencilMaskAndRef(GLenum errIfMismatch);
 
     // Called from RenderingFeedbackLoopWithDrawBuffersEXT.
@@ -2861,6 +2905,234 @@
     }
 }
 
+// Test that has float color attachment caching works when color attachments change, by calling draw
+// command when blending is enabled
+TEST_P(WebGLCompatibilityTest, FramebufferFloatColorAttachment)
+{
+    if (getClientMajorVersion() >= 3)
+    {
+        ANGLE_SKIP_TEST_IF(!ensureExtensionEnabled("GL_EXT_color_buffer_float"));
+    }
+    else
+    {
+        ANGLE_SKIP_TEST_IF(!ensureExtensionEnabled("GL_OES_texture_float"));
+        ANGLE_SKIP_TEST_IF(!ensureExtensionEnabled("GL_CHROMIUM_color_buffer_float_rgba"));
+    }
+
+    constexpr char kVS[] =
+        "void main()\n"
+        "{\n"
+        "   gl_Position = vec4(0, 0, 0, 1);\n"
+        "}\n";
+
+    constexpr char kFS[] =
+        "void main()\n"
+        "{\n"
+        "   gl_FragColor = vec4(0, 1, 0, 1);\n"
+        "}\n";
+
+    ANGLE_GL_PROGRAM(program, kVS, kFS);
+    glUseProgram(program);
+
+    glEnable(GL_BLEND);
+
+    GLTexture texture1;
+    glBindTexture(GL_TEXTURE_2D, texture1);
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
+    EXPECT_GL_NO_ERROR();
+
+    GLTexture texture2;
+    glBindTexture(GL_TEXTURE_2D, texture2);
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, 1, 1, 0, GL_RGBA, GL_FLOAT, nullptr);
+    EXPECT_GL_NO_ERROR();
+
+    GLFramebuffer fbo1;
+    glBindFramebuffer(GL_FRAMEBUFFER, fbo1);
+    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture1, 0);
+    ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
+
+    GLFramebuffer fbo2;
+    glBindFramebuffer(GL_FRAMEBUFFER, fbo2);
+    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture2, 0);
+    ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
+    glDrawArrays(GL_POINTS, 0, 1);
+    EXPECT_GL_ERROR(GL_INVALID_OPERATION);
+
+    glDisable(GL_BLEND);
+    glDrawArrays(GL_POINTS, 0, 1);
+    EXPECT_GL_NO_ERROR();
+    glEnable(GL_BLEND);
+
+    glBindFramebuffer(GL_FRAMEBUFFER, fbo1);
+    glDrawArrays(GL_POINTS, 0, 1);
+
+    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0,
+                           0);  // test unbind
+    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture2, 0);
+    ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
+
+    glDrawArrays(GL_POINTS, 0, 1);
+    EXPECT_GL_ERROR(GL_INVALID_OPERATION);
+
+    glDisable(GL_BLEND);
+    glDrawArrays(GL_POINTS, 0, 1);
+    EXPECT_GL_NO_ERROR();
+    glEnable(GL_BLEND);
+
+    glBindTexture(GL_TEXTURE_2D, texture2);
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
+
+    glDrawArrays(GL_POINTS, 0, 1);
+    EXPECT_GL_NO_ERROR();
+}
+
+// Test that has float color attachment caching works with multiple color attachments bound to a
+// Framebuffer
+TEST_P(WebGLCompatibilityTest, FramebufferFloatColorAttachmentMRT)
+{
+    bool isWebGL2 = getClientMajorVersion() >= 3;
+    if (isWebGL2)
+    {
+        ANGLE_SKIP_TEST_IF(!ensureExtensionEnabled("GL_EXT_color_buffer_float"));
+
+        constexpr char kVS[] =
+            "#version 300 es\n"
+            "void main()\n"
+            "{\n"
+            "   gl_Position = vec4(0, 0, 0, 1);\n"
+            "}\n";
+
+        constexpr char kFS[] =
+            "#version 300 es\n"
+            "precision lowp float;\n"
+            "layout(location = 0) out vec4 o_color0;\n"
+            "layout(location = 1) out vec4 o_color1;\n"
+            "void main()\n"
+            "{\n"
+            "   o_color0 = vec4(1, 0, 0, 1);\n"
+            "   o_color1 = vec4(0, 1, 0, 1);\n"
+            "}\n";
+
+        ANGLE_GL_PROGRAM(program, kVS, kFS);
+        glUseProgram(program);
+    }
+    else
+    {
+        ANGLE_SKIP_TEST_IF(!ensureExtensionEnabled("GL_OES_texture_float"));
+        ANGLE_SKIP_TEST_IF(!ensureExtensionEnabled("GL_CHROMIUM_color_buffer_float_rgba"));
+        ANGLE_SKIP_TEST_IF(!ensureExtensionEnabled("GL_EXT_draw_buffers"));
+
+        constexpr char kVS[] =
+            "void main()\n"
+            "{\n"
+            "   gl_Position = vec4(0, 0, 0, 1);\n"
+            "}\n";
+
+        constexpr char kFS[] =
+            "#extension GL_EXT_draw_buffers : require\n"
+            "precision lowp float;\n"
+            "void main()\n"
+            "{\n"
+            "   gl_FragData[0] = vec4(1, 0, 0, 1);\n"
+            "   gl_FragData[1] = vec4(0, 1, 0, 1);\n"
+            "}\n";
+
+        ANGLE_GL_PROGRAM(program, kVS, kFS);
+        glUseProgram(program);
+    }
+
+    glEnable(GL_BLEND);
+
+    GLTexture texture1;
+    glBindTexture(GL_TEXTURE_2D, texture1);
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
+    EXPECT_GL_NO_ERROR();
+
+    GLTexture texture2;
+    glBindTexture(GL_TEXTURE_2D, texture2);
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
+    EXPECT_GL_NO_ERROR();
+
+    GLTexture textureF1;
+    glBindTexture(GL_TEXTURE_2D, textureF1);
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, 1, 1, 0, GL_RGBA, GL_FLOAT, nullptr);
+    EXPECT_GL_NO_ERROR();
+
+    GLTexture textureF2;
+    glBindTexture(GL_TEXTURE_2D, textureF2);
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, 1, 1, 0, GL_RGBA, GL_FLOAT, nullptr);
+    EXPECT_GL_NO_ERROR();
+
+    GLFramebuffer fbo;
+    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture1, 0);
+    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, texture2, 0);
+    ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
+
+    GLenum drawbuffers[] = {GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1};
+    if (isWebGL2)
+    {
+        glDrawBuffers(2, drawbuffers);
+    }
+    else
+    {
+        glDrawBuffersEXT(2, drawbuffers);
+    }
+
+    glDrawArrays(GL_POINTS, 0, 1);
+    EXPECT_GL_NO_ERROR();
+
+    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureF1, 0);
+    glDrawArrays(GL_POINTS, 0, 1);
+    EXPECT_GL_ERROR(GL_INVALID_OPERATION);
+
+    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, textureF2, 0);
+    glDrawArrays(GL_POINTS, 0, 1);
+    EXPECT_GL_ERROR(GL_INVALID_OPERATION);
+
+    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture1, 0);
+    glDrawArrays(GL_POINTS, 0, 1);
+    EXPECT_GL_ERROR(GL_INVALID_OPERATION);
+
+    if (isWebGL2)
+    {
+        // WebGL 1 will report a FRAMEBUFFER_UNSUPPORTED for one unsigned_byte and one float
+        // attachment bound to one FBO at the same time
+        glDrawBuffers(1, drawbuffers);
+        ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
+        glDrawArrays(GL_POINTS, 0, 1);
+        EXPECT_GL_NO_ERROR();
+        glDrawBuffers(2, drawbuffers);
+    }
+
+    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, texture2, 0);
+    glDrawArrays(GL_POINTS, 0, 1);
+    EXPECT_GL_NO_ERROR();
+}
+
+// Test if blending of float32 color attachment generates GL_INVALID_OPERATION when
+// GL_EXT_float_blend is not enabled
+TEST_P(WebGLCompatibilityTest, FloatBlend)
+{
+    ANGLE_SKIP_TEST_IF(!extensionRequestable("GL_EXT_float_blend"));
+    if (getClientMajorVersion() >= 3)
+    {
+        ANGLE_SKIP_TEST_IF(!ensureExtensionEnabled("GL_EXT_color_buffer_float"));
+    }
+    else
+    {
+        ANGLE_SKIP_TEST_IF(!ensureExtensionEnabled("GL_OES_texture_float"));
+        ANGLE_SKIP_TEST_IF(!ensureExtensionEnabled("GL_CHROMIUM_color_buffer_float_rgba"));
+    }
+
+    TestExtFloatBlend(false);
+
+    glRequestExtensionANGLE("GL_EXT_float_blend");
+    ASSERT_GL_NO_ERROR();
+
+    TestExtFloatBlend(true);
+}
+
 TEST_P(WebGLCompatibilityTest, R16FTextures)
 {
     constexpr float readPixelsData[] = {-5000.0f, 0.0f, 0.0f, 1.0f};