Work around NVIDIA GLSL vector-scalar op bug

This adds a new AST transform VectorizeVectorScalarArithmetic. The AST
transform works around incorrect handling of certain types of GLSL
arithmetic operations by NVIDIA's GL driver. It works around only the
most common cases where the bug reproduces, since detecting all the
cases would take more sophisticated analysis of the code than what
is currently easily implementable in ANGLE.

When a float add operator has both vector and scalar operands, the AST
transform turns the scalar operand into a vector operand. Example:

vec4 f;
f += 1.0;

gets turned into:

vec4 f;
f += vec4(1.0);

When a vector constructor contains a binary scalar float
multiplication or division operation as its only argument, the AST
transform turns both operands of the binary operation into vector
operands. Example:

float f, g;
vec4(f * g);

gets turned into:

float f, g;
vec4(vec4(f) * vec4(g));

Another example with compound assignment:

float f, g;
vec4(f *= g);

gets turned into:

float f, g;
vec4 s0 = vec4(f);
(s0 *= g, f = s0.x), s0;

This latter transformation only works in case the compound assignment
left hand expression doesn't have side effects.

BUG=chromium:772651
TEST=angle_end2end_tests

Change-Id: I84ec04287793c56a94845a725785439565debdaf
Reviewed-on: https://chromium-review.googlesource.com/721321
Commit-Queue: Olli Etuaho <oetuaho@nvidia.com>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
diff --git a/src/tests/gl_tests/GLSLTest.cpp b/src/tests/gl_tests/GLSLTest.cpp
index 4ef2e80..8a95d81 100644
--- a/src/tests/gl_tests/GLSLTest.cpp
+++ b/src/tests/gl_tests/GLSLTest.cpp
@@ -3796,6 +3796,65 @@
     EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
 }
 
+// Test vector/scalar arithmetic (in this case multiplication and addition). Meant to reproduce a
+// bug that appeared in NVIDIA OpenGL drivers and that is worked around by
+// VectorizeVectorScalarArithmetic AST transform.
+TEST_P(GLSLTest, VectorScalarMultiplyAndAddInLoop)
+{
+    const std::string &fragmentShader =
+        R"(
+
+        precision mediump float;
+
+        void main() {
+            gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);
+            for (int i = 0; i < 2; i++)
+            {
+                gl_FragColor += (2.0 * gl_FragCoord.x);
+            }
+            if (gl_FragColor.g == gl_FragColor.r &&
+                gl_FragColor.b == gl_FragColor.r &&
+                gl_FragColor.a == gl_FragColor.r)
+            {
+                gl_FragColor = vec4(0, 1, 0, 1);
+            }
+        })";
+
+    ANGLE_GL_PROGRAM(program, mSimpleVSSource, fragmentShader);
+    drawQuad(program.get(), "inputAttribute", 0.5f);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
+}
+
+// Test vector/scalar arithmetic (in this case compound division and addition). Meant to reproduce a
+// bug that appeared in NVIDIA OpenGL drivers and that is worked around by
+// VectorizeVectorScalarArithmetic AST transform.
+TEST_P(GLSLTest, VectorScalarDivideAndAddInLoop)
+{
+    const std::string &fragmentShader =
+        R"(
+
+        precision mediump float;
+
+        void main() {
+            gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);
+            for (int i = 0; i < 2; i++)
+            {
+                float x = gl_FragCoord.x;
+                gl_FragColor = gl_FragColor + (x /= 2.0);
+            }
+            if (gl_FragColor.g == gl_FragColor.r &&
+                gl_FragColor.b == gl_FragColor.r &&
+                gl_FragColor.a == gl_FragColor.r)
+            {
+                gl_FragColor = vec4(0, 1, 0, 1);
+            }
+        })";
+
+    ANGLE_GL_PROGRAM(program, mSimpleVSSource, fragmentShader);
+    drawQuad(program.get(), "inputAttribute", 0.5f);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
+}
+
 // Use this to select which configurations (e.g. which renderer, which GLES major version) these tests should be run against.
 ANGLE_INSTANTIATE_TEST(GLSLTest,
                        ES2_D3D9(),