Fix the computation of the output range in DualSourceBlendTests

This patch fixes the computation of the output range in
DualSourceBlendTests by doing the float-to-unorm8 conversion with
0.6f ULP tolerance at integer scale. Note that this algorithm
follows the `FLOAT->UNORM` conversion rule in current D3D SPEC.

Fixed: dawn:2543
Bug: chromium:341973423
Test: dawn_end2end_tests
Change-Id: I10c3cc28a25ff6ebc761771d1198fa3baabf62ce
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/189240
Commit-Queue: Jiawei Shao <jiawei.shao@intel.com>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Austin Eng <enga@chromium.org>
diff --git a/src/dawn/tests/end2end/DualSourceBlendTests.cpp b/src/dawn/tests/end2end/DualSourceBlendTests.cpp
index c473f77..b3f71c6 100644
--- a/src/dawn/tests/end2end/DualSourceBlendTests.cpp
+++ b/src/dawn/tests/end2end/DualSourceBlendTests.cpp
@@ -83,7 +83,127 @@
         utils::RGBA8 testColorIndex1;
     };
 
-    void RunTest(TestParams params, const utils::RGBA8& expectation) {
+    std::array<float, 4> RGBA8ToVec4F32(utils::RGBA8 rgba) {
+        return {rgba.r / 255.f, rgba.g / 255.f, rgba.b / 255.f, rgba.a / 255.f};
+    }
+
+    std::array<float, 4> ApplyBlendOperation(wgpu::BlendFactor blendFactor,
+                                             const std::array<float, 4>& currentBlendColorF32,
+                                             const std::array<float, 4>& dstF32,
+                                             const std::array<float, 4>& src0F32,
+                                             const std::array<float, 4>& src1F32) {
+        std::array<float, 4> idealBlendOutputF32;
+        // Currently in this test blendComponents are same for both color and alpha so we can
+        // compute them together.
+        switch (blendFactor) {
+            case wgpu::BlendFactor::Zero:
+                idealBlendOutputF32 = {};
+                break;
+            case wgpu::BlendFactor::One:
+                idealBlendOutputF32 = currentBlendColorF32;
+                break;
+            case wgpu::BlendFactor::Src:
+                for (uint32_t i = 0; i < idealBlendOutputF32.size(); ++i) {
+                    idealBlendOutputF32[i] = currentBlendColorF32[i] * src0F32[i];
+                }
+                break;
+            case wgpu::BlendFactor::Src1:
+                for (uint32_t i = 0; i < idealBlendOutputF32.size(); ++i) {
+                    idealBlendOutputF32[i] = currentBlendColorF32[i] * src1F32[i];
+                }
+                break;
+            case wgpu::BlendFactor::SrcAlpha:
+                for (uint32_t i = 0; i < idealBlendOutputF32.size(); ++i) {
+                    idealBlendOutputF32[i] = currentBlendColorF32[i] * src0F32[3];
+                }
+                break;
+            case wgpu::BlendFactor::Src1Alpha:
+                for (uint32_t i = 0; i < 4; ++i) {
+                    idealBlendOutputF32[i] = currentBlendColorF32[i] * src1F32[3];
+                }
+                break;
+            case wgpu::BlendFactor::OneMinusSrc:
+                for (uint32_t i = 0; i < 4; ++i) {
+                    idealBlendOutputF32[i] = currentBlendColorF32[i] * (1.f - src0F32[i]);
+                }
+                break;
+            case wgpu::BlendFactor::OneMinusSrc1:
+                for (uint32_t i = 0; i < 4; ++i) {
+                    idealBlendOutputF32[i] = currentBlendColorF32[i] * (1.f - src1F32[i]);
+                }
+                break;
+            case wgpu::BlendFactor::OneMinusSrcAlpha:
+                for (uint32_t i = 0; i < 4; ++i) {
+                    idealBlendOutputF32[i] = currentBlendColorF32[i] * (1.f - src0F32[3]);
+                }
+                break;
+            case wgpu::BlendFactor::OneMinusSrc1Alpha:
+                for (uint32_t i = 0; i < 4; ++i) {
+                    idealBlendOutputF32[i] = currentBlendColorF32[i] * (1.f - src1F32[3]);
+                }
+                break;
+            default:
+                DAWN_UNREACHABLE();
+        }
+        return idealBlendOutputF32;
+    }
+
+    std::array<utils::RGBA8, 2> GetRGBA8ExpectationRange(const TestParams& params) {
+        std::array dstF32 = RGBA8ToVec4F32(params.baseColor);
+        std::array src0F32 = RGBA8ToVec4F32(params.testColorIndex0);
+        std::array src1F32 = RGBA8ToVec4F32(params.testColorIndex1);
+
+        std::array idealBlendSrcOperationOutputF32 =
+            ApplyBlendOperation(params.srcBlendFactor, src0F32, dstF32, src0F32, src1F32);
+        std::array idealBlendDstOperationOutputF32 =
+            ApplyBlendOperation(params.dstBlendFactor, dstF32, dstF32, src0F32, src1F32);
+
+        std::array<utils::RGBA8, 2> rgba8ExpectationRange;
+        // In this test the blend operation is always `wgpu::BlendOperation::Add`.
+        for (uint32_t i = 0; i < 4; ++i) {
+            float idealBlendOperationUnorm8Unquantized =
+                (idealBlendSrcOperationOutputF32[i] + idealBlendDstOperationOutputF32[i]) * 255.f +
+                0.5f;
+
+            // The float-to-unorm conversion is permitted tolerance of 0.6f ULP (on the integer
+            // side). This means that after converting from float to integer scale, any value within
+            // 0.6f ULP of a representable target format value is permitted to map to that value.
+            // See the chapter "Integer Conversion / FLOAT->UNORM" in D3D SPEC for more details:
+            // https://microsoft.github.io/DirectX-Specs/d3d/archive/D3D11_3_FunctionalSpec.htm
+            switch (i) {
+                case 0:
+                    rgba8ExpectationRange[0].r =
+                        static_cast<uint8_t>(idealBlendOperationUnorm8Unquantized - 0.6f);
+                    rgba8ExpectationRange[1].r =
+                        static_cast<uint8_t>(idealBlendOperationUnorm8Unquantized + 0.6f);
+                    break;
+                case 1:
+                    rgba8ExpectationRange[0].g =
+                        static_cast<uint8_t>(idealBlendOperationUnorm8Unquantized - 0.6f);
+                    rgba8ExpectationRange[1].g =
+                        static_cast<uint8_t>(idealBlendOperationUnorm8Unquantized + 0.6f);
+                    break;
+                case 2:
+                    rgba8ExpectationRange[0].b =
+                        static_cast<uint8_t>(idealBlendOperationUnorm8Unquantized - 0.6f);
+                    rgba8ExpectationRange[1].b =
+                        static_cast<uint8_t>(idealBlendOperationUnorm8Unquantized + 0.6f);
+                    break;
+                case 3:
+                    rgba8ExpectationRange[0].a =
+                        static_cast<uint8_t>(idealBlendOperationUnorm8Unquantized - 0.6f);
+                    rgba8ExpectationRange[1].a =
+                        static_cast<uint8_t>(idealBlendOperationUnorm8Unquantized + 0.6f);
+                    break;
+                default:
+                    DAWN_UNREACHABLE();
+            }
+        }
+
+        return rgba8ExpectationRange;
+    }
+
+    void RunTest(TestParams params) {
         wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, R"(
                 enable chromium_internal_dual_source_blending;
 
@@ -157,10 +277,9 @@
         wgpu::CommandBuffer commands = encoder.Finish();
         queue.Submit(1, &commands);
 
-        utils::RGBA8 expectationMinusOne = utils::RGBA8(expectation.r - 1, expectation.g - 1,
-                                                        expectation.b - 1, expectation.a - 1);
-        EXPECT_PIXEL_RGBA8_BETWEEN(expectation, expectationMinusOne, renderPass.color, kRTSize / 2,
-                                   kRTSize / 2);
+        std::array expectationRange = GetRGBA8ExpectationRange(params);
+        EXPECT_PIXEL_RGBA8_BETWEEN(expectationRange[0], expectationRange[1], renderPass.color,
+                                   kRTSize / 2, kRTSize / 2);
     }
 
     // Create a bind group to set the colors as a uniform buffer
@@ -188,9 +307,6 @@
 
 // Test that Src and Src1 BlendFactors work with dual source blending.
 TEST_P(DualSourceBlendTests, BlendFactorSrc1) {
-    // TODO(crbug.com/dawn/2543): diagnose this on Pixel 6 AP1A.240405.002
-    DAWN_SUPPRESS_TEST_IF(IsVulkan() && IsAndroid() && IsARM());
-
     // Test source blend factor with source index 0
     TestParams params;
     params.srcBlendFactor = wgpu::BlendFactor::Src;
@@ -198,27 +314,24 @@
     params.baseColor = utils::RGBA8(100, 150, 200, 250);
     params.testColorIndex0 = utils::RGBA8(100, 150, 200, 250);
     params.testColorIndex1 = utils::RGBA8(32, 64, 96, 128);
-    RunTest(params, utils::RGBA8(39, 88, 157, 245));
+    RunTest(params);
 
     // Test source blend factor with source index 1
     params.srcBlendFactor = wgpu::BlendFactor::Src1;
-    RunTest(params, utils::RGBA8(13, 38, 75, 125));
+    RunTest(params);
 
     // Test destination blend factor with source index 0
     params.srcBlendFactor = wgpu::BlendFactor::Zero;
     params.dstBlendFactor = wgpu::BlendFactor::Src;
-    RunTest(params, utils::RGBA8(39, 88, 157, 245));
+    RunTest(params);
 
     // Test destination blend factor with source index 1
     params.dstBlendFactor = wgpu::BlendFactor::Src1;
-    RunTest(params, utils::RGBA8(13, 38, 75, 125));
+    RunTest(params);
 }
 
 // Test that SrcAlpha and SrcAlpha1 BlendFactors work with dual source blending.
 TEST_P(DualSourceBlendTests, BlendFactorSrc1Alpha) {
-    // TODO(crbug.com/dawn/2543): diagnose this on Pixel 6 AP1A.240405.002
-    DAWN_SUPPRESS_TEST_IF(IsVulkan() && IsAndroid() && IsARM());
-
     // Test source blend factor with source alpha index 0
     TestParams params;
     params.srcBlendFactor = wgpu::BlendFactor::SrcAlpha;
@@ -226,20 +339,20 @@
     params.baseColor = utils::RGBA8(100, 150, 200, 250);
     params.testColorIndex0 = utils::RGBA8(100, 150, 200, 250);
     params.testColorIndex1 = utils::RGBA8(32, 64, 96, 128);
-    RunTest(params, utils::RGBA8(98, 147, 196, 245));
+    RunTest(params);
 
     // Test source blend factor with source alpha index 1
     params.srcBlendFactor = wgpu::BlendFactor::Src1Alpha;
-    RunTest(params, utils::RGBA8(50, 75, 100, 125));
+    RunTest(params);
 
     // Test destination blend factor with source alpha index 0
     params.srcBlendFactor = wgpu::BlendFactor::Zero;
     params.dstBlendFactor = wgpu::BlendFactor::SrcAlpha;
-    RunTest(params, utils::RGBA8(98, 147, 196, 245));
+    RunTest(params);
 
     // Test destination blend factor with source alpha index 1
     params.dstBlendFactor = wgpu::BlendFactor::Src1Alpha;
-    RunTest(params, utils::RGBA8(50, 75, 100, 125));
+    RunTest(params);
 }
 
 // Test that OneMinusSrc and OneMinusSrc1 BlendFactors work with dual source blending.
@@ -251,20 +364,20 @@
     params.baseColor = utils::RGBA8(100, 150, 200, 250);
     params.testColorIndex0 = utils::RGBA8(100, 150, 200, 250);
     params.testColorIndex1 = utils::RGBA8(32, 64, 96, 128);
-    RunTest(params, utils::RGBA8(61, 62, 43, 5));
+    RunTest(params);
 
     // Test source blend factor with one minus source index 1
     params.srcBlendFactor = wgpu::BlendFactor::OneMinusSrc1;
-    RunTest(params, utils::RGBA8(87, 112, 125, 125));
+    RunTest(params);
 
     // Test destination blend factor with one minus source index 0
     params.srcBlendFactor = wgpu::BlendFactor::Zero;
     params.dstBlendFactor = wgpu::BlendFactor::OneMinusSrc;
-    RunTest(params, utils::RGBA8(61, 62, 43, 5));
+    RunTest(params);
 
     // Test destination blend factor with one minus source index 1
     params.dstBlendFactor = wgpu::BlendFactor::OneMinusSrc1;
-    RunTest(params, utils::RGBA8(87, 112, 125, 125));
+    RunTest(params);
 }
 
 // Test that OneMinusSrcAlpha and OneMinusSrc1Alpha BlendFactors work with dual source blending.
@@ -276,20 +389,20 @@
     params.baseColor = utils::RGBA8(100, 150, 200, 250);
     params.testColorIndex0 = utils::RGBA8(100, 150, 200, 96);
     params.testColorIndex1 = utils::RGBA8(32, 64, 96, 160);
-    RunTest(params, utils::RGBA8(62, 94, 125, 60));
+    RunTest(params);
 
     // Test source blend factor with one minus source alpha index 1
     params.srcBlendFactor = wgpu::BlendFactor::OneMinusSrc1Alpha;
-    RunTest(params, utils::RGBA8(37, 56, 75, 36));
+    RunTest(params);
 
     // Test destination blend factor with one minus source alpha index 0
     params.srcBlendFactor = wgpu::BlendFactor::Zero;
     params.dstBlendFactor = wgpu::BlendFactor::OneMinusSrcAlpha;
-    RunTest(params, utils::RGBA8(62, 94, 125, 156));
+    RunTest(params);
 
     // Test destination blend factor with one minus source alpha index 1
     params.dstBlendFactor = wgpu::BlendFactor::OneMinusSrc1Alpha;
-    RunTest(params, utils::RGBA8(37, 56, 75, 93));
+    RunTest(params);
 }
 
 DAWN_INSTANTIATE_TEST(DualSourceBlendTests,