summaryrefslogtreecommitdiffstats
path: root/gfx/angle/src/tests/gl_tests/PathRenderingTest.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/angle/src/tests/gl_tests/PathRenderingTest.cpp')
-rwxr-xr-xgfx/angle/src/tests/gl_tests/PathRenderingTest.cpp1984
1 files changed, 1984 insertions, 0 deletions
diff --git a/gfx/angle/src/tests/gl_tests/PathRenderingTest.cpp b/gfx/angle/src/tests/gl_tests/PathRenderingTest.cpp
new file mode 100755
index 000000000..962531071
--- /dev/null
+++ b/gfx/angle/src/tests/gl_tests/PathRenderingTest.cpp
@@ -0,0 +1,1984 @@
+//
+// Copyright 2016 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// CHROMIUMPathRenderingTest
+// Test CHROMIUM subset of NV_path_rendering
+// This extension allows to render geometric paths as first class GL objects.
+
+#include "test_utils/ANGLETest.h"
+#include "shader_utils.h"
+
+#include "common/angleutils.h"
+
+#include <cmath>
+#include <cstring>
+#include <cstddef>
+#include <fstream>
+
+using namespace angle;
+
+namespace
+{
+
+bool CheckPixels(GLint x,
+ GLint y,
+ GLsizei width,
+ GLsizei height,
+ GLint tolerance,
+ const angle::GLColor &color)
+{
+ for (GLint yy = 0; yy < height; ++yy)
+ {
+ for (GLint xx = 0; xx < width; ++xx)
+ {
+ const auto px = x + xx;
+ const auto py = y + yy;
+ EXPECT_PIXEL_NEAR(px, py, color.R, color.G, color.B, color.A, tolerance);
+ }
+ }
+
+ return true;
+}
+
+void ExpectEqualMatrix(const GLfloat *expected, const GLfloat *actual)
+{
+ for (size_t i = 0; i < 16; ++i)
+ {
+ EXPECT_EQ(expected[i], actual[i]);
+ }
+}
+
+void ExpectEqualMatrix(const GLfloat *expected, const GLint *actual)
+{
+ for (size_t i = 0; i < 16; ++i)
+ {
+ EXPECT_EQ(static_cast<GLint>(std::roundf(expected[i])), actual[i]);
+ }
+}
+
+const int kResolution = 300;
+
+class CHROMIUMPathRenderingTest : public ANGLETest
+{
+ protected:
+ CHROMIUMPathRenderingTest()
+ {
+ setWindowWidth(kResolution);
+ setWindowHeight(kResolution);
+ setConfigRedBits(8);
+ setConfigGreenBits(8);
+ setConfigBlueBits(8);
+ setConfigAlphaBits(8);
+ setConfigDepthBits(8);
+ setConfigStencilBits(8);
+ }
+
+ bool isApplicable() const { return extensionEnabled("GL_CHROMIUM_path_rendering"); }
+
+ void tryAllDrawFunctions(GLuint path, GLenum err)
+ {
+ glStencilFillPathCHROMIUM(path, GL_COUNT_UP_CHROMIUM, 0x7F);
+ EXPECT_GL_ERROR(err);
+
+ glStencilFillPathCHROMIUM(path, GL_COUNT_DOWN_CHROMIUM, 0x7F);
+ EXPECT_GL_ERROR(err);
+
+ glStencilStrokePathCHROMIUM(path, 0x80, 0x80);
+ EXPECT_GL_ERROR(err);
+
+ glCoverFillPathCHROMIUM(path, GL_BOUNDING_BOX_CHROMIUM);
+ EXPECT_GL_ERROR(err);
+
+ glCoverStrokePathCHROMIUM(path, GL_BOUNDING_BOX_CHROMIUM);
+ EXPECT_GL_ERROR(err);
+
+ glStencilThenCoverStrokePathCHROMIUM(path, 0x80, 0x80, GL_BOUNDING_BOX_CHROMIUM);
+ EXPECT_GL_ERROR(err);
+
+ glStencilThenCoverFillPathCHROMIUM(path, GL_COUNT_UP_CHROMIUM, 0x7F,
+ GL_BOUNDING_BOX_CHROMIUM);
+ EXPECT_GL_ERROR(err);
+ }
+};
+
+// Test setting and getting of path rendering matrices.
+TEST_P(CHROMIUMPathRenderingTest, TestMatrix)
+{
+ if (!isApplicable())
+ return;
+
+ static const GLfloat kIdentityMatrix[16] = {1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f};
+
+ static const GLfloat kSeqMatrix[16] = {0.5f, -0.5f, -0.1f, -0.8f, 4.4f, 5.5f,
+ 6.6f, 7.7f, 8.8f, 9.9f, 10.11f, 11.22f,
+ 12.33f, 13.44f, 14.55f, 15.66f};
+
+ static const GLenum kMatrixModes[] = {GL_PATH_MODELVIEW_CHROMIUM, GL_PATH_PROJECTION_CHROMIUM};
+
+ static const GLenum kGetMatrixModes[] = {GL_PATH_MODELVIEW_MATRIX_CHROMIUM,
+ GL_PATH_PROJECTION_MATRIX_CHROMIUM};
+
+ for (size_t i = 0; i < 2; ++i)
+ {
+ GLfloat mf[16];
+ GLint mi[16];
+ std::memset(mf, 0, sizeof(mf));
+ std::memset(mi, 0, sizeof(mi));
+ glGetFloatv(kGetMatrixModes[i], mf);
+ glGetIntegerv(kGetMatrixModes[i], mi);
+ ExpectEqualMatrix(kIdentityMatrix, mf);
+ ExpectEqualMatrix(kIdentityMatrix, mi);
+
+ glMatrixLoadfCHROMIUM(kMatrixModes[i], kSeqMatrix);
+ std::memset(mf, 0, sizeof(mf));
+ std::memset(mi, 0, sizeof(mi));
+ glGetFloatv(kGetMatrixModes[i], mf);
+ glGetIntegerv(kGetMatrixModes[i], mi);
+ ExpectEqualMatrix(kSeqMatrix, mf);
+ ExpectEqualMatrix(kSeqMatrix, mi);
+
+ glMatrixLoadIdentityCHROMIUM(kMatrixModes[i]);
+ std::memset(mf, 0, sizeof(mf));
+ std::memset(mi, 0, sizeof(mi));
+ glGetFloatv(kGetMatrixModes[i], mf);
+ glGetIntegerv(kGetMatrixModes[i], mi);
+ ExpectEqualMatrix(kIdentityMatrix, mf);
+ ExpectEqualMatrix(kIdentityMatrix, mi);
+
+ ASSERT_GL_NO_ERROR();
+ }
+}
+
+// Test that trying to set incorrect matrix target results
+// in a GL error.
+TEST_P(CHROMIUMPathRenderingTest, TestMatrixErrors)
+{
+ if (!isApplicable())
+ return;
+
+ GLfloat mf[16];
+ std::memset(mf, 0, sizeof(mf));
+
+ glMatrixLoadfCHROMIUM(GL_PATH_MODELVIEW_CHROMIUM, mf);
+ ASSERT_GL_NO_ERROR();
+
+ glMatrixLoadIdentityCHROMIUM(GL_PATH_PROJECTION_CHROMIUM);
+ ASSERT_GL_NO_ERROR();
+
+ // Test that invalid matrix targets fail.
+ glMatrixLoadfCHROMIUM(GL_PATH_MODELVIEW_CHROMIUM - 1, mf);
+ EXPECT_GL_ERROR(GL_INVALID_ENUM);
+
+ // Test that invalid matrix targets fail.
+ glMatrixLoadIdentityCHROMIUM(GL_PATH_PROJECTION_CHROMIUM + 1);
+ EXPECT_GL_ERROR(GL_INVALID_ENUM);
+}
+
+// Test basic path create and delete.
+TEST_P(CHROMIUMPathRenderingTest, TestGenDelete)
+{
+ if (!isApplicable())
+ return;
+
+ // This is unspecified in NV_path_rendering.
+ EXPECT_EQ(0u, glGenPathsCHROMIUM(0));
+ EXPECT_GL_ERROR(GL_INVALID_VALUE);
+
+ GLuint path = glGenPathsCHROMIUM(1);
+ EXPECT_NE(0u, path);
+ glDeletePathsCHROMIUM(path, 1);
+ ASSERT_GL_NO_ERROR();
+
+ GLuint first_path = glGenPathsCHROMIUM(5);
+ EXPECT_NE(0u, first_path);
+ glDeletePathsCHROMIUM(first_path, 5);
+ ASSERT_GL_NO_ERROR();
+
+ // Test deleting paths that are not actually allocated:
+ // "unused names in /paths/ are silently ignored".
+ first_path = glGenPathsCHROMIUM(5);
+ EXPECT_NE(0u, first_path);
+ glDeletePathsCHROMIUM(first_path, 6);
+ ASSERT_GL_NO_ERROR();
+
+ GLsizei big_range = 0xffff;
+ first_path = glGenPathsCHROMIUM(big_range);
+ EXPECT_NE(0u, first_path);
+ glDeletePathsCHROMIUM(first_path, big_range);
+ ASSERT_GL_NO_ERROR();
+
+ // Test glIsPathCHROMIUM(). A path object is not considered a path untill
+ // it has actually been specified with a path data.
+
+ path = glGenPathsCHROMIUM(1);
+ ASSERT_TRUE(glIsPathCHROMIUM(path) == GL_FALSE);
+
+ // specify the data.
+ GLubyte commands[] = {GL_MOVE_TO_CHROMIUM, GL_CLOSE_PATH_CHROMIUM};
+ GLfloat coords[] = {50.0f, 50.0f};
+ glPathCommandsCHROMIUM(path, 2, commands, 2, GL_FLOAT, coords);
+ ASSERT_TRUE(glIsPathCHROMIUM(path) == GL_TRUE);
+ glDeletePathsCHROMIUM(path, 1);
+ ASSERT_TRUE(glIsPathCHROMIUM(path) == GL_FALSE);
+}
+
+// Test incorrect path creation and deletion and expect GL errors.
+TEST_P(CHROMIUMPathRenderingTest, TestGenDeleteErrors)
+{
+ if (!isApplicable())
+ return;
+
+ // GenPaths / DeletePaths tests.
+ // std::numeric_limits<GLuint>::max() is wrong for GLsizei.
+ GLuint first_path = glGenPathsCHROMIUM(std::numeric_limits<GLuint>::max());
+ EXPECT_EQ(first_path, 0u);
+ EXPECT_GL_ERROR(GL_INVALID_VALUE);
+
+ first_path = glGenPathsCHROMIUM(-1);
+ EXPECT_EQ(0u, first_path);
+ EXPECT_GL_ERROR(GL_INVALID_VALUE);
+
+ glDeletePathsCHROMIUM(1, -5);
+ EXPECT_GL_ERROR(GL_INVALID_VALUE);
+
+ first_path = glGenPathsCHROMIUM(-1);
+ EXPECT_EQ(0u, first_path);
+ EXPECT_GL_ERROR(GL_INVALID_VALUE);
+}
+
+// Test setting and getting path parameters.
+TEST_P(CHROMIUMPathRenderingTest, TestPathParameter)
+{
+ if (!isApplicable())
+ return;
+
+ GLuint path = glGenPathsCHROMIUM(1);
+
+ // specify the data.
+ GLubyte commands[] = {GL_MOVE_TO_CHROMIUM, GL_CLOSE_PATH_CHROMIUM};
+ GLfloat coords[] = {50.0f, 50.0f};
+ glPathCommandsCHROMIUM(path, 2, commands, 2, GL_FLOAT, coords);
+ ASSERT_GL_NO_ERROR();
+ EXPECT_TRUE(glIsPathCHROMIUM(path) == GL_TRUE);
+
+ static const GLenum kEndCaps[] = {GL_FLAT_CHROMIUM, GL_SQUARE_CHROMIUM, GL_ROUND_CHROMIUM};
+ for (std::size_t i = 0; i < 3; ++i)
+ {
+ GLint x;
+ glPathParameteriCHROMIUM(path, GL_PATH_END_CAPS_CHROMIUM, static_cast<GLenum>(kEndCaps[i]));
+ ASSERT_GL_NO_ERROR();
+ glGetPathParameterivCHROMIUM(path, GL_PATH_END_CAPS_CHROMIUM, &x);
+ ASSERT_GL_NO_ERROR();
+ EXPECT_EQ(kEndCaps[i], static_cast<GLenum>(x));
+
+ GLfloat f;
+ glPathParameterfCHROMIUM(path, GL_PATH_END_CAPS_CHROMIUM,
+ static_cast<GLfloat>(kEndCaps[(i + 1) % 3]));
+ glGetPathParameterfvCHROMIUM(path, GL_PATH_END_CAPS_CHROMIUM, &f);
+ ASSERT_GL_NO_ERROR();
+ EXPECT_EQ(kEndCaps[(i + 1) % 3], static_cast<GLenum>(f));
+ }
+
+ static const GLenum kJoinStyles[] = {GL_MITER_REVERT_CHROMIUM, GL_BEVEL_CHROMIUM,
+ GL_ROUND_CHROMIUM};
+ for (std::size_t i = 0; i < 3; ++i)
+ {
+ GLint x;
+ glPathParameteriCHROMIUM(path, GL_PATH_JOIN_STYLE_CHROMIUM,
+ static_cast<GLenum>(kJoinStyles[i]));
+ ASSERT_GL_NO_ERROR();
+ glGetPathParameterivCHROMIUM(path, GL_PATH_JOIN_STYLE_CHROMIUM, &x);
+ ASSERT_GL_NO_ERROR();
+ EXPECT_EQ(kJoinStyles[i], static_cast<GLenum>(x));
+
+ GLfloat f;
+ glPathParameterfCHROMIUM(path, GL_PATH_JOIN_STYLE_CHROMIUM,
+ static_cast<GLfloat>(kJoinStyles[(i + 1) % 3]));
+ ASSERT_GL_NO_ERROR();
+ glGetPathParameterfvCHROMIUM(path, GL_PATH_JOIN_STYLE_CHROMIUM, &f);
+ ASSERT_GL_NO_ERROR();
+ EXPECT_EQ(kJoinStyles[(i + 1) % 3], static_cast<GLenum>(f));
+ }
+
+ {
+ glPathParameterfCHROMIUM(path, GL_PATH_STROKE_WIDTH_CHROMIUM, 5.0f);
+ ASSERT_GL_NO_ERROR();
+
+ GLfloat f;
+ glGetPathParameterfvCHROMIUM(path, GL_PATH_STROKE_WIDTH_CHROMIUM, &f);
+ EXPECT_EQ(5.0f, f);
+ }
+
+ glDeletePathsCHROMIUM(path, 1);
+}
+
+// Test that setting incorrect path parameter generates GL error.
+TEST_P(CHROMIUMPathRenderingTest, TestPathParameterErrors)
+{
+ if (!isApplicable())
+ return;
+
+ GLuint path = glGenPathsCHROMIUM(1);
+
+ // PathParameter*: Wrong value for the pname should fail.
+ glPathParameteriCHROMIUM(path, GL_PATH_JOIN_STYLE_CHROMIUM, GL_FLAT_CHROMIUM);
+ EXPECT_GL_ERROR(GL_INVALID_ENUM);
+
+ glPathParameterfCHROMIUM(path, GL_PATH_END_CAPS_CHROMIUM, GL_MITER_REVERT_CHROMIUM);
+ EXPECT_GL_ERROR(GL_INVALID_ENUM);
+
+ // PathParameter*: Wrong floating-point value should fail.
+ glPathParameterfCHROMIUM(path, GL_PATH_STROKE_WIDTH_CHROMIUM, -0.1f);
+ EXPECT_GL_ERROR(GL_INVALID_VALUE);
+
+ // PathParameter*: Wrong pname should fail.
+ glPathParameteriCHROMIUM(path, GL_PATH_STROKE_WIDTH_CHROMIUM - 1, 5);
+ EXPECT_GL_ERROR(GL_INVALID_ENUM);
+
+ glDeletePathsCHROMIUM(path, 1);
+}
+
+// Test expected path object state.
+TEST_P(CHROMIUMPathRenderingTest, TestPathObjectState)
+{
+ if (!isApplicable())
+ return;
+
+ glViewport(0, 0, kResolution, kResolution);
+ glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+ glStencilMask(0xffffffff);
+ glClearStencil(0);
+ glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+ glPathStencilFuncCHROMIUM(GL_ALWAYS, 0, 0xFF);
+ glStencilOp(GL_KEEP, GL_KEEP, GL_ZERO);
+ ASSERT_GL_NO_ERROR();
+
+ // Test that trying to draw non-existing paths does not produce errors or results.
+ GLuint non_existing_paths[] = {0, 55, 74744};
+ for (auto &p : non_existing_paths)
+ {
+ EXPECT_TRUE(glIsPathCHROMIUM(p) == GL_FALSE);
+ ASSERT_GL_NO_ERROR();
+ tryAllDrawFunctions(p, GL_NO_ERROR);
+ }
+
+ // Path name marked as used but without path object state causes
+ // a GL error upon any draw command.
+ GLuint path = glGenPathsCHROMIUM(1);
+ EXPECT_TRUE(glIsPathCHROMIUM(path) == GL_FALSE);
+ tryAllDrawFunctions(path, GL_INVALID_OPERATION);
+ glDeletePathsCHROMIUM(path, 1);
+
+ // Document a bit of an inconsistency: path name marked as used but without
+ // path object state causes a GL error upon any draw command (tested above).
+ // Path name that had path object state, but then was "cleared", still has a
+ // path object state, even though the state is empty.
+ path = glGenPathsCHROMIUM(1);
+ EXPECT_TRUE(glIsPathCHROMIUM(path) == GL_FALSE);
+
+ GLubyte commands[] = {GL_MOVE_TO_CHROMIUM, GL_CLOSE_PATH_CHROMIUM};
+ GLfloat coords[] = {50.0f, 50.0f};
+ glPathCommandsCHROMIUM(path, 2, commands, 2, GL_FLOAT, coords);
+ EXPECT_TRUE(glIsPathCHROMIUM(path) == GL_TRUE);
+
+ glPathCommandsCHROMIUM(path, 0, NULL, 0, GL_FLOAT, NULL);
+ EXPECT_TRUE(glIsPathCHROMIUM(path) == GL_TRUE); // The surprise.
+
+ tryAllDrawFunctions(path, GL_NO_ERROR);
+ glDeletePathsCHROMIUM(path, 1);
+
+ // Make sure nothing got drawn by the drawing commands that should not produce
+ // anything.
+ const angle::GLColor black = {0, 0, 0, 0};
+ EXPECT_TRUE(CheckPixels(0, 0, kResolution, kResolution, 0, black));
+}
+
+// Test that trying to use path object that doesn't exist generates
+// a GL error.
+TEST_P(CHROMIUMPathRenderingTest, TestUnnamedPathsErrors)
+{
+ if (!isApplicable())
+ return;
+
+ // Unnamed paths: Trying to create a path object with non-existing path name
+ // produces error. (Not a error in real NV_path_rendering).
+ ASSERT_GL_NO_ERROR();
+ GLubyte commands[] = {GL_MOVE_TO_CHROMIUM, GL_CLOSE_PATH_CHROMIUM};
+ GLfloat coords[] = {50.0f, 50.0f};
+ glPathCommandsCHROMIUM(555, 2, commands, 2, GL_FLOAT, coords);
+ EXPECT_GL_ERROR(GL_INVALID_OPERATION);
+
+ // PathParameter*: Using non-existing path object produces error.
+ ASSERT_GL_NO_ERROR();
+ glPathParameterfCHROMIUM(555, GL_PATH_STROKE_WIDTH_CHROMIUM, 5.0f);
+ EXPECT_GL_ERROR(GL_INVALID_OPERATION);
+
+ ASSERT_GL_NO_ERROR();
+ glPathParameteriCHROMIUM(555, GL_PATH_JOIN_STYLE_CHROMIUM, GL_ROUND_CHROMIUM);
+ EXPECT_GL_ERROR(GL_INVALID_OPERATION);
+}
+
+// Test that setting incorrect path data generates a GL error.
+TEST_P(CHROMIUMPathRenderingTest, TestPathCommandsErrors)
+{
+ if (!isApplicable())
+ return;
+
+ static const GLenum kInvalidCoordType = GL_NONE;
+
+ GLuint path = glGenPathsCHROMIUM(1);
+ GLubyte commands[] = {GL_MOVE_TO_CHROMIUM, GL_CLOSE_PATH_CHROMIUM};
+ GLfloat coords[] = {50.0f, 50.0f};
+
+ glPathCommandsCHROMIUM(path, 2, commands, -4, GL_FLOAT, coords);
+ EXPECT_GL_ERROR(GL_INVALID_VALUE);
+
+ glPathCommandsCHROMIUM(path, -1, commands, 2, GL_FLOAT, coords);
+ EXPECT_GL_ERROR(GL_INVALID_VALUE);
+
+ glPathCommandsCHROMIUM(path, 2, commands, 2, kInvalidCoordType, coords);
+ EXPECT_GL_ERROR(GL_INVALID_ENUM);
+
+ // incorrect number of coordinates
+ glPathCommandsCHROMIUM(path, 2, commands, std::numeric_limits<GLsizei>::max(), GL_FLOAT,
+ coords);
+ EXPECT_GL_ERROR(GL_INVALID_VALUE);
+
+ // This should fail due to cmd count + coord count * short size.
+ glPathCommandsCHROMIUM(path, 2, commands, std::numeric_limits<GLsizei>::max(), GL_SHORT,
+ coords);
+ EXPECT_GL_ERROR(GL_INVALID_OPERATION);
+
+ glDeletePathsCHROMIUM(path, 1);
+}
+
+// Test that trying to render a path with invalid arguments
+// generates a GL error.
+TEST_P(CHROMIUMPathRenderingTest, TestPathRenderingInvalidArgs)
+{
+ if (!isApplicable())
+ return;
+
+ GLuint path = glGenPathsCHROMIUM(1);
+ glPathCommandsCHROMIUM(path, 0, NULL, 0, GL_FLOAT, NULL);
+
+ // Verify that normal calls work.
+ glStencilFillPathCHROMIUM(path, GL_COUNT_UP_CHROMIUM, 0x7F);
+ ASSERT_GL_NO_ERROR();
+ glStencilThenCoverFillPathCHROMIUM(path, GL_COUNT_UP_CHROMIUM, 0x7F, GL_BOUNDING_BOX_CHROMIUM);
+ ASSERT_GL_NO_ERROR();
+
+ // Using invalid fill mode causes INVALID_ENUM.
+ glStencilFillPathCHROMIUM(path, GL_COUNT_UP_CHROMIUM - 1, 0x7F);
+ EXPECT_GL_ERROR(GL_INVALID_ENUM);
+ glStencilThenCoverFillPathCHROMIUM(path, GL_COUNT_UP_CHROMIUM - 1, 0x7F,
+ GL_BOUNDING_BOX_CHROMIUM);
+ EXPECT_GL_ERROR(GL_INVALID_ENUM);
+
+ // Using invalid cover mode causes INVALID_ENUM.
+ glCoverFillPathCHROMIUM(path, GL_CONVEX_HULL_CHROMIUM - 1);
+ EXPECT_EQ(static_cast<GLenum>(GL_INVALID_ENUM), glGetError());
+ glStencilThenCoverFillPathCHROMIUM(path, GL_COUNT_UP_CHROMIUM, 0x7F,
+ GL_BOUNDING_BOX_CHROMIUM + 1);
+ EXPECT_GL_ERROR(GL_INVALID_ENUM);
+
+ // Using mask+1 not being power of two causes INVALID_VALUE with up/down fill mode
+ glStencilFillPathCHROMIUM(path, GL_COUNT_UP_CHROMIUM, 0x40);
+ EXPECT_GL_ERROR(GL_INVALID_VALUE);
+
+ glStencilThenCoverFillPathCHROMIUM(path, GL_COUNT_DOWN_CHROMIUM, 12, GL_BOUNDING_BOX_CHROMIUM);
+ EXPECT_GL_ERROR(GL_INVALID_VALUE);
+
+ // check incorrect instance parameters.
+
+ // CoverFillPathInstanced
+ {
+ glCoverFillPathInstancedCHROMIUM(-1, GL_UNSIGNED_INT, &path, 0, GL_CONVEX_HULL_CHROMIUM,
+ GL_NONE, NULL);
+ EXPECT_GL_ERROR(GL_INVALID_VALUE);
+
+ glCoverFillPathInstancedCHROMIUM(1, GL_FLOAT, &path, 0, GL_CONVEX_HULL_CHROMIUM, GL_NONE,
+ NULL);
+ EXPECT_GL_ERROR(GL_INVALID_ENUM);
+
+ glCoverFillPathInstancedCHROMIUM(1, GL_UNSIGNED_INT, NULL, 0, GL_CONVEX_HULL_CHROMIUM,
+ GL_NONE, NULL);
+ EXPECT_GL_ERROR(GL_INVALID_VALUE);
+
+ glCoverFillPathInstancedCHROMIUM(1, GL_UNSIGNED_INT, &path, 0, GL_UNSIGNED_INT, GL_NONE,
+ NULL);
+ EXPECT_GL_ERROR(GL_INVALID_ENUM);
+
+ glCoverFillPathInstancedCHROMIUM(1, GL_UNSIGNED_INT, &path, 0, GL_CONVEX_HULL_CHROMIUM,
+ GL_UNSIGNED_INT, NULL);
+ EXPECT_GL_ERROR(GL_INVALID_ENUM);
+
+ glCoverFillPathInstancedCHROMIUM(1, GL_UNSIGNED_INT, &path, 0, GL_CONVEX_HULL_CHROMIUM,
+ GL_TRANSLATE_X_CHROMIUM, NULL);
+ EXPECT_GL_ERROR(GL_INVALID_VALUE);
+ }
+
+ // CoverStrokePathInstanced
+ {
+ glCoverStrokePathInstancedCHROMIUM(-1, GL_UNSIGNED_INT, &path, 0, GL_CONVEX_HULL_CHROMIUM,
+ GL_NONE, NULL);
+ EXPECT_GL_ERROR(GL_INVALID_VALUE);
+
+ glCoverStrokePathInstancedCHROMIUM(1, GL_FLOAT, &path, 0, GL_CONVEX_HULL_CHROMIUM, GL_NONE,
+ NULL);
+ EXPECT_GL_ERROR(GL_INVALID_ENUM);
+
+ glCoverStrokePathInstancedCHROMIUM(1, GL_UNSIGNED_INT, NULL, 0, GL_CONVEX_HULL_CHROMIUM,
+ GL_NONE, NULL);
+ EXPECT_GL_ERROR(GL_INVALID_VALUE);
+
+ glCoverStrokePathInstancedCHROMIUM(1, GL_UNSIGNED_INT, &path, 0, GL_UNSIGNED_INT, GL_NONE,
+ NULL);
+ EXPECT_GL_ERROR(GL_INVALID_ENUM);
+
+ glCoverStrokePathInstancedCHROMIUM(1, GL_UNSIGNED_INT, &path, 0, GL_CONVEX_HULL_CHROMIUM,
+ GL_UNSIGNED_INT, NULL);
+ EXPECT_GL_ERROR(GL_INVALID_ENUM);
+
+ glCoverStrokePathInstancedCHROMIUM(1, GL_UNSIGNED_INT, &path, 0, GL_CONVEX_HULL_CHROMIUM,
+ GL_TRANSLATE_X_CHROMIUM, NULL);
+ EXPECT_GL_ERROR(GL_INVALID_VALUE);
+ }
+
+ // StencilFillPathInstanced
+ {
+ glStencilFillPathInstancedCHROMIUM(-1, GL_UNSIGNED_INT, &path, 0, GL_COUNT_UP_CHROMIUM, 0x0,
+ GL_NONE, NULL);
+ EXPECT_GL_ERROR(GL_INVALID_VALUE);
+
+ glStencilFillPathInstancedCHROMIUM(1, GL_FLOAT, &path, 0, GL_COUNT_UP_CHROMIUM, 0x0,
+ GL_NONE, NULL);
+ EXPECT_GL_ERROR(GL_INVALID_ENUM);
+
+ glStencilFillPathInstancedCHROMIUM(1, GL_UNSIGNED_INT, NULL, 0, GL_COUNT_UP_CHROMIUM, 0x0,
+ GL_NONE, NULL);
+ EXPECT_GL_ERROR(GL_INVALID_VALUE);
+
+ glStencilFillPathInstancedCHROMIUM(1, GL_UNSIGNED_INT, &path, 0, GL_UNSIGNED_INT, 0x0,
+ GL_NONE, NULL);
+ EXPECT_GL_ERROR(GL_INVALID_ENUM);
+
+ glStencilFillPathInstancedCHROMIUM(1, GL_UNSIGNED_INT, &path, 0, GL_COUNT_UP_CHROMIUM, 0x2,
+ GL_NONE, NULL);
+ EXPECT_GL_ERROR(GL_INVALID_VALUE);
+
+ glStencilFillPathInstancedCHROMIUM(1, GL_UNSIGNED_INT, &path, 0, GL_COUNT_UP_CHROMIUM, 0x0,
+ GL_UNSIGNED_INT, NULL);
+ EXPECT_GL_ERROR(GL_INVALID_ENUM);
+
+ glStencilFillPathInstancedCHROMIUM(1, GL_UNSIGNED_INT, &path, 0, GL_COUNT_UP_CHROMIUM, 0x0,
+ GL_TRANSLATE_X_CHROMIUM, NULL);
+ EXPECT_GL_ERROR(GL_INVALID_VALUE);
+ }
+
+ // StencilStrokePathInstanced
+ {
+ glStencilStrokePathInstancedCHROMIUM(-1, GL_UNSIGNED_INT, &path, 0, 0x00, 0x00, GL_NONE,
+ NULL);
+ EXPECT_GL_ERROR(GL_INVALID_VALUE);
+
+ glStencilStrokePathInstancedCHROMIUM(1, GL_FLOAT, &path, 0, 0x00, 0x00, GL_NONE, NULL);
+ EXPECT_GL_ERROR(GL_INVALID_ENUM);
+
+ glStencilStrokePathInstancedCHROMIUM(1, GL_UNSIGNED_INT, nullptr, 0, 0x00, 0x00, GL_NONE,
+ NULL);
+ EXPECT_GL_ERROR(GL_INVALID_VALUE);
+
+ glStencilStrokePathInstancedCHROMIUM(1, GL_UNSIGNED_INT, &path, 0, 0x00, 0x00,
+ GL_UNSIGNED_INT, NULL);
+ EXPECT_GL_ERROR(GL_INVALID_ENUM);
+
+ glStencilStrokePathInstancedCHROMIUM(1, GL_UNSIGNED_INT, &path, 0, 0x00, 0x00,
+ GL_TRANSLATE_X_CHROMIUM, NULL);
+ EXPECT_GL_ERROR(GL_INVALID_VALUE);
+ }
+
+ // StencilThenCoverFillPathInstanced
+ {
+ glStencilThenCoverFillPathInstancedCHROMIUM(-1, GL_UNSIGNED_INT, &path, 0,
+ GL_COUNT_UP_CHROMIUM, 0, GL_COUNT_UP_CHROMIUM,
+ GL_NONE, NULL);
+ EXPECT_GL_ERROR(GL_INVALID_VALUE);
+
+ glStencilThenCoverFillPathInstancedCHROMIUM(1, GL_FLOAT, &path, 0, GL_CONVEX_HULL_CHROMIUM,
+ 0, GL_COUNT_UP_CHROMIUM, GL_NONE, NULL);
+ EXPECT_GL_ERROR(GL_INVALID_ENUM);
+
+ glStencilThenCoverFillPathInstancedCHROMIUM(1, GL_UNSIGNED_INT, NULL, 0,
+ GL_CONVEX_HULL_CHROMIUM, 0,
+ GL_COUNT_UP_CHROMIUM, GL_NONE, NULL);
+ EXPECT_GL_ERROR(GL_INVALID_VALUE);
+
+ glStencilThenCoverFillPathInstancedCHROMIUM(1, GL_UNSIGNED_INT, &path, 0, GL_UNSIGNED_INT,
+ 0, GL_COUNT_UP_CHROMIUM, GL_NONE, NULL);
+ EXPECT_GL_ERROR(GL_INVALID_ENUM);
+
+ glStencilThenCoverFillPathInstancedCHROMIUM(1, GL_UNSIGNED_INT, &path, 0,
+ GL_CONVEX_HULL_CHROMIUM, 0, GL_UNSIGNED_INT,
+ GL_NONE, NULL);
+ EXPECT_GL_ERROR(GL_INVALID_ENUM);
+
+ glStencilThenCoverFillPathInstancedCHROMIUM(1, GL_UNSIGNED_INT, &path, 0,
+ GL_CONVEX_HULL_CHROMIUM, 0,
+ GL_COUNT_UP_CHROMIUM, GL_FLOAT, NULL);
+ EXPECT_GL_ERROR(GL_INVALID_ENUM);
+
+ glStencilThenCoverFillPathInstancedCHROMIUM(
+ 1, GL_UNSIGNED_INT, &path, 0, GL_CONVEX_HULL_CHROMIUM, 0, GL_COUNT_UP_CHROMIUM,
+ GL_TRANSLATE_X_CHROMIUM, NULL);
+ EXPECT_GL_ERROR(GL_INVALID_VALUE);
+ }
+
+ // StencilThenCoverStrokePathInstanced
+ {
+ glStencilThenCoverStrokePathInstancedCHROMIUM(-1, GL_UNSIGNED_INT, &path, 0, 0x0, 0x0,
+ GL_CONVEX_HULL_CHROMIUM, GL_NONE, NULL);
+ EXPECT_GL_ERROR(GL_INVALID_VALUE);
+
+ glStencilThenCoverStrokePathInstancedCHROMIUM(1, GL_FLOAT, &path, 0, 0x0, 0x0,
+ GL_CONVEX_HULL_CHROMIUM, GL_NONE, NULL);
+ EXPECT_GL_ERROR(GL_INVALID_ENUM);
+
+ glStencilThenCoverStrokePathInstancedCHROMIUM(1, GL_UNSIGNED_INT, NULL, 0, 0x0, 0x0,
+ GL_CONVEX_HULL_CHROMIUM, GL_NONE, NULL);
+ EXPECT_GL_ERROR(GL_INVALID_VALUE);
+
+ glStencilThenCoverStrokePathInstancedCHROMIUM(1, GL_UNSIGNED_INT, &path, 0, 0x0, 0x0,
+ GL_FLOAT, GL_NONE, NULL);
+ EXPECT_GL_ERROR(GL_INVALID_ENUM);
+
+ glStencilThenCoverStrokePathInstancedCHROMIUM(1, GL_UNSIGNED_INT, &path, 0, 0x0, 0x0,
+ GL_CONVEX_HULL_CHROMIUM, GL_FLOAT, NULL);
+ EXPECT_GL_ERROR(GL_INVALID_ENUM);
+
+ glStencilThenCoverStrokePathInstancedCHROMIUM(1, GL_UNSIGNED_INT, &path, 0, 0x0, 0x0,
+ GL_CONVEX_HULL_CHROMIUM,
+ GL_TRANSLATE_X_CHROMIUM, NULL);
+ EXPECT_GL_ERROR(GL_INVALID_VALUE);
+ }
+
+ glDeletePathsCHROMIUM(path, 1);
+}
+
+const GLfloat kProjectionMatrix[16] = {2.0f / kResolution,
+ 0.0f,
+ 0.0f,
+ 0.0f,
+ 0.0f,
+ 2.0f / kResolution,
+ 0.0f,
+ 0.0f,
+ 0.0f,
+ 0.0f,
+ -1.0f,
+ 0.0f,
+ -1.0f,
+ -1.0f,
+ 0.0f,
+ 1.0f};
+
+class CHROMIUMPathRenderingDrawTest : public ANGLETest
+{
+ protected:
+ CHROMIUMPathRenderingDrawTest()
+ {
+ setWindowWidth(kResolution);
+ setWindowHeight(kResolution);
+ setConfigRedBits(8);
+ setConfigGreenBits(8);
+ setConfigBlueBits(8);
+ setConfigAlphaBits(8);
+ setConfigDepthBits(8);
+ setConfigStencilBits(8);
+ }
+
+ bool isApplicable() const { return extensionEnabled("GL_CHROMIUM_path_rendering"); }
+
+ void setupStateForTestPattern()
+ {
+ glViewport(0, 0, kResolution, kResolution);
+ glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+ glStencilMask(0xffffffff);
+ glClearStencil(0);
+ glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+ glEnable(GL_STENCIL_TEST);
+
+ static const char *kVertexShaderSource =
+ "void main() {\n"
+ " gl_Position = vec4(1.0); \n"
+ "}";
+
+ static const char *kFragmentShaderSource =
+ "precision mediump float;\n"
+ "uniform vec4 color;\n"
+ "void main() {\n"
+ " gl_FragColor = color;\n"
+ "}";
+
+ GLuint program = CompileProgram(kVertexShaderSource, kFragmentShaderSource);
+ glUseProgram(program);
+ mColorLoc = glGetUniformLocation(program, "color");
+ glDeleteProgram(program);
+
+ // Set up orthogonal projection with near/far plane distance of 2.
+ glMatrixLoadfCHROMIUM(GL_PATH_PROJECTION_CHROMIUM, kProjectionMatrix);
+ glMatrixLoadIdentityCHROMIUM(GL_PATH_MODELVIEW_CHROMIUM);
+
+ ASSERT_GL_NO_ERROR();
+ }
+
+ void setupPathStateForTestPattern(GLuint path)
+ {
+ static const GLubyte kCommands[] = {GL_MOVE_TO_CHROMIUM, GL_LINE_TO_CHROMIUM,
+ GL_QUADRATIC_CURVE_TO_CHROMIUM,
+ GL_CUBIC_CURVE_TO_CHROMIUM, GL_CLOSE_PATH_CHROMIUM};
+
+ static const GLfloat kCoords[] = {50.0f, 50.0f, 75.0f, 75.0f, 100.0f, 62.5f, 50.0f,
+ 25.5f, 0.0f, 62.5f, 50.0f, 50.0f, 25.0f, 75.0f};
+
+ glPathCommandsCHROMIUM(path, 5, kCommands, 14, GL_FLOAT, kCoords);
+ glPathParameterfCHROMIUM(path, GL_PATH_STROKE_WIDTH_CHROMIUM, 5.0f);
+ glPathParameterfCHROMIUM(path, GL_PATH_MITER_LIMIT_CHROMIUM, 1.0f);
+ glPathParameterfCHROMIUM(path, GL_PATH_STROKE_BOUND_CHROMIUM, .02f);
+ glPathParameteriCHROMIUM(path, GL_PATH_JOIN_STYLE_CHROMIUM, GL_ROUND_CHROMIUM);
+ glPathParameteriCHROMIUM(path, GL_PATH_END_CAPS_CHROMIUM, GL_SQUARE_CHROMIUM);
+ ASSERT_GL_NO_ERROR();
+ }
+
+ void verifyTestPatternFill(GLfloat flx, GLfloat fly)
+ {
+ static const GLint kFillCoords[] = {55, 54, 50, 28, 66, 63};
+ static const angle::GLColor kBlue = {0, 0, 255, 255};
+
+ GLint x = static_cast<GLint>(flx);
+ GLint y = static_cast<GLint>(fly);
+
+ for (size_t i = 0; i < 6; i += 2)
+ {
+ GLint fx = kFillCoords[i];
+ GLint fy = kFillCoords[i + 1];
+ EXPECT_TRUE(CheckPixels(x + fx, y + fy, 1, 1, 0, kBlue));
+ }
+ }
+ void verifyTestPatternBg(GLfloat fx, GLfloat fy)
+ {
+ static const GLint kBackgroundCoords[] = {80, 80, 20, 20, 90, 1};
+ static const angle::GLColor kExpectedColor = {0, 0, 0, 0};
+
+ GLint x = static_cast<GLint>(fx);
+ GLint y = static_cast<GLint>(fy);
+
+ for (size_t i = 0; i < 6; i += 2)
+ {
+ GLint bx = kBackgroundCoords[i];
+ GLint by = kBackgroundCoords[i + 1];
+ EXPECT_TRUE(CheckPixels(x + bx, y + by, 1, 1, 0, kExpectedColor));
+ }
+ }
+
+ void verifyTestPatternStroke(GLfloat fx, GLfloat fy)
+ {
+ GLint x = static_cast<GLint>(fx);
+ GLint y = static_cast<GLint>(fy);
+
+ // Inside the stroke we should have green.
+ static const angle::GLColor kGreen = {0, 255, 0, 255};
+ EXPECT_TRUE(CheckPixels(x + 50, y + 53, 1, 1, 0, kGreen));
+ EXPECT_TRUE(CheckPixels(x + 26, y + 76, 1, 1, 0, kGreen));
+
+ // Outside the path we should have black.
+ static const angle::GLColor black = {0, 0, 0, 0};
+ EXPECT_TRUE(CheckPixels(x + 10, y + 10, 1, 1, 0, black));
+ EXPECT_TRUE(CheckPixels(x + 80, y + 80, 1, 1, 0, black));
+ }
+
+ GLuint mColorLoc;
+};
+
+// Tests that basic path rendering functions work.
+TEST_P(CHROMIUMPathRenderingDrawTest, TestPathRendering)
+{
+ if (!isApplicable())
+ return;
+
+ static const float kBlue[] = {0.0f, 0.0f, 1.0f, 1.0f};
+ static const float kGreen[] = {0.0f, 1.0f, 0.0f, 1.0f};
+
+ setupStateForTestPattern();
+
+ GLuint path = glGenPathsCHROMIUM(1);
+ setupPathStateForTestPattern(path);
+
+ // Do the stencil fill, cover fill, stencil stroke, cover stroke
+ // in unconventional order:
+ // 1) stencil the stroke in stencil high bit
+ // 2) stencil the fill in low bits
+ // 3) cover the fill
+ // 4) cover the stroke
+ // This is done to check that glPathStencilFunc works, eg the mask
+ // goes through. Stencil func is not tested ATM, for simplicity.
+
+ glPathStencilFuncCHROMIUM(GL_ALWAYS, 0, 0xFF);
+ glStencilStrokePathCHROMIUM(path, 0x80, 0x80);
+
+ glPathStencilFuncCHROMIUM(GL_ALWAYS, 0, 0x7F);
+ glStencilFillPathCHROMIUM(path, GL_COUNT_UP_CHROMIUM, 0x7F);
+
+ glStencilFunc(GL_LESS, 0, 0x7F);
+ glStencilOp(GL_KEEP, GL_KEEP, GL_ZERO);
+ glUniform4fv(mColorLoc, 1, kBlue);
+ glCoverFillPathCHROMIUM(path, GL_BOUNDING_BOX_CHROMIUM);
+
+ glStencilFunc(GL_EQUAL, 0x80, 0x80);
+ glStencilOp(GL_KEEP, GL_KEEP, GL_ZERO);
+ glUniform4fv(mColorLoc, 1, kGreen);
+ glCoverStrokePathCHROMIUM(path, GL_CONVEX_HULL_CHROMIUM);
+
+ glDeletePathsCHROMIUM(path, 1);
+
+ ASSERT_GL_NO_ERROR();
+
+ // Verify the image.
+ verifyTestPatternFill(0, 0);
+ verifyTestPatternBg(0, 0);
+ verifyTestPatternStroke(0, 0);
+}
+
+// Test that StencilThen{Stroke,Fill} path rendering functions work
+TEST_P(CHROMIUMPathRenderingDrawTest, TestPathRenderingThenFunctions)
+{
+ if (!isApplicable())
+ return;
+
+ static float kBlue[] = {0.0f, 0.0f, 1.0f, 1.0f};
+ static float kGreen[] = {0.0f, 1.0f, 0.0f, 1.0f};
+
+ setupStateForTestPattern();
+
+ GLuint path = glGenPathsCHROMIUM(1);
+ setupPathStateForTestPattern(path);
+
+ glPathStencilFuncCHROMIUM(GL_ALWAYS, 0, 0xFF);
+ glStencilFunc(GL_EQUAL, 0x80, 0x80);
+ glStencilOp(GL_KEEP, GL_KEEP, GL_ZERO);
+ glUniform4fv(mColorLoc, 1, kGreen);
+ glStencilThenCoverStrokePathCHROMIUM(path, 0x80, 0x80, GL_BOUNDING_BOX_CHROMIUM);
+
+ glPathStencilFuncCHROMIUM(GL_ALWAYS, 0, 0x7F);
+ glStencilFunc(GL_LESS, 0, 0x7F);
+ glStencilOp(GL_KEEP, GL_KEEP, GL_ZERO);
+ glUniform4fv(mColorLoc, 1, kBlue);
+ glStencilThenCoverFillPathCHROMIUM(path, GL_COUNT_UP_CHROMIUM, 0x7F, GL_CONVEX_HULL_CHROMIUM);
+
+ glDeletePathsCHROMIUM(path, 1);
+
+ // Verify the image.
+ verifyTestPatternFill(0, 0);
+ verifyTestPatternBg(0, 0);
+ verifyTestPatternStroke(0, 0);
+}
+
+// Tests that drawing with *Instanced functions work.
+TEST_P(CHROMIUMPathRenderingDrawTest, TestPathRenderingInstanced)
+{
+ if (!isApplicable())
+ return;
+
+ static const float kBlue[] = {0.0f, 0.0f, 1.0f, 1.0f};
+ static const float kGreen[] = {0.0f, 1.0f, 0.0f, 1.0f};
+
+ setupStateForTestPattern();
+
+ GLuint path = glGenPathsCHROMIUM(1);
+ setupPathStateForTestPattern(path);
+
+ const GLuint kPaths[] = {1, 1, 1, 1, 1};
+ const GLsizei kPathCount = 5;
+ const GLfloat kShapeSize = 80.0f;
+ static const GLfloat kTransforms[kPathCount * 12] = {
+ 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
+ 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, kShapeSize, 0.0f, 0.0f,
+ 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, kShapeSize * 2, 0.0f, 0.0f,
+ 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, kShapeSize, 0.0f,
+ 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, kShapeSize, kShapeSize, 0.0f};
+
+ // The test pattern is the same as in the simple draw case above,
+ // except that the path is drawn kPathCount times with different offsets.
+ glPathStencilFuncCHROMIUM(GL_ALWAYS, 0, 0xFF);
+ glStencilStrokePathInstancedCHROMIUM(kPathCount, GL_UNSIGNED_INT, kPaths, path - 1, 0x80, 0x80,
+ GL_AFFINE_3D_CHROMIUM, kTransforms);
+
+ glPathStencilFuncCHROMIUM(GL_ALWAYS, 0, 0x7F);
+ glUniform4fv(mColorLoc, 1, kBlue);
+ glStencilFillPathInstancedCHROMIUM(kPathCount, GL_UNSIGNED_INT, kPaths, path - 1,
+ GL_COUNT_UP_CHROMIUM, 0x7F, GL_AFFINE_3D_CHROMIUM,
+ kTransforms);
+
+ ASSERT_GL_NO_ERROR();
+
+ glStencilFunc(GL_LESS, 0, 0x7F);
+ glStencilOp(GL_KEEP, GL_KEEP, GL_ZERO);
+ glCoverFillPathInstancedCHROMIUM(kPathCount, GL_UNSIGNED_INT, kPaths, path - 1,
+ GL_BOUNDING_BOX_OF_BOUNDING_BOXES_CHROMIUM,
+ GL_AFFINE_3D_CHROMIUM, kTransforms);
+
+ ASSERT_GL_NO_ERROR();
+
+ glStencilFunc(GL_EQUAL, 0x80, 0x80);
+ glStencilOp(GL_KEEP, GL_KEEP, GL_ZERO);
+ glUniform4fv(mColorLoc, 1, kGreen);
+ glCoverStrokePathInstancedCHROMIUM(kPathCount, GL_UNSIGNED_INT, kPaths, path - 1,
+ GL_BOUNDING_BOX_OF_BOUNDING_BOXES_CHROMIUM,
+ GL_AFFINE_3D_CHROMIUM, kTransforms);
+
+ ASSERT_GL_NO_ERROR();
+
+ glDeletePathsCHROMIUM(path, 1);
+
+ // Verify the image.
+ verifyTestPatternFill(0.0f, 0.0f);
+ verifyTestPatternBg(0.0f, 0.0f);
+ verifyTestPatternStroke(0.0f, 0.0f);
+
+ verifyTestPatternFill(kShapeSize, 0.0f);
+ verifyTestPatternBg(kShapeSize, 0.0f);
+ verifyTestPatternStroke(kShapeSize, 0.0f);
+
+ verifyTestPatternFill(kShapeSize * 2, 0.0f);
+ verifyTestPatternBg(kShapeSize * 2, 0.0f);
+ verifyTestPatternStroke(kShapeSize * 2, 0.0f);
+
+ verifyTestPatternFill(0.0f, kShapeSize);
+ verifyTestPatternBg(0.0f, kShapeSize);
+ verifyTestPatternStroke(0.0f, kShapeSize);
+
+ verifyTestPatternFill(kShapeSize, kShapeSize);
+ verifyTestPatternBg(kShapeSize, kShapeSize);
+ verifyTestPatternStroke(kShapeSize, kShapeSize);
+}
+
+// Test that instanced fill/stroke then cover functions work.
+TEST_P(CHROMIUMPathRenderingDrawTest, TestPathRenderingThenFunctionsInstanced)
+{
+ if (!isApplicable())
+ return;
+
+ static const float kBlue[] = {0.0f, 0.0f, 1.0f, 1.0f};
+ static const float kGreen[] = {0.0f, 1.0f, 0.0f, 1.0f};
+
+ setupStateForTestPattern();
+
+ GLuint path = glGenPathsCHROMIUM(1);
+ setupPathStateForTestPattern(path);
+
+ const GLuint kPaths[] = {1, 1, 1, 1, 1};
+ const GLsizei kPathCount = 5;
+ const GLfloat kShapeSize = 80.0f;
+ static const GLfloat kTransforms[] = {
+ 0.0f, 0.0f, kShapeSize, 0.0f, kShapeSize * 2,
+ 0.0f, 0.0f, kShapeSize, kShapeSize, kShapeSize,
+ };
+
+ glPathStencilFuncCHROMIUM(GL_ALWAYS, 0, 0xFF);
+ glStencilFunc(GL_EQUAL, 0x80, 0x80);
+ glStencilOp(GL_KEEP, GL_KEEP, GL_ZERO);
+ glUniform4fv(mColorLoc, 1, kGreen);
+ glStencilThenCoverStrokePathInstancedCHROMIUM(
+ kPathCount, GL_UNSIGNED_INT, kPaths, path - 1, 0x80, 0x80,
+ GL_BOUNDING_BOX_OF_BOUNDING_BOXES_CHROMIUM, GL_TRANSLATE_2D_CHROMIUM, kTransforms);
+
+ ASSERT_GL_NO_ERROR();
+
+ glPathStencilFuncCHROMIUM(GL_ALWAYS, 0, 0x7F);
+ glStencilFunc(GL_LESS, 0, 0x7F);
+ glStencilOp(GL_KEEP, GL_KEEP, GL_ZERO);
+ glUniform4fv(mColorLoc, 1, kBlue);
+ glStencilThenCoverFillPathInstancedCHROMIUM(
+ kPathCount, GL_UNSIGNED_INT, kPaths, path - 1, GL_COUNT_UP_CHROMIUM, 0x7F,
+ GL_BOUNDING_BOX_OF_BOUNDING_BOXES_CHROMIUM, GL_TRANSLATE_2D_CHROMIUM, kTransforms);
+
+ ASSERT_GL_NO_ERROR();
+
+ glDeletePathsCHROMIUM(path, 1);
+
+ // Verify the image.
+ verifyTestPatternFill(0.0f, 0.0f);
+ verifyTestPatternBg(0.0f, 0.0f);
+ verifyTestPatternStroke(0.0f, 0.0f);
+
+ verifyTestPatternFill(kShapeSize, 0.0f);
+ verifyTestPatternBg(kShapeSize, 0.0f);
+ verifyTestPatternStroke(kShapeSize, 0.0f);
+
+ verifyTestPatternFill(kShapeSize * 2, 0.0f);
+ verifyTestPatternBg(kShapeSize * 2, 0.0f);
+ verifyTestPatternStroke(kShapeSize * 2, 0.0f);
+
+ verifyTestPatternFill(0.0f, kShapeSize);
+ verifyTestPatternBg(0.0f, kShapeSize);
+ verifyTestPatternStroke(0.0f, kShapeSize);
+
+ verifyTestPatternFill(kShapeSize, kShapeSize);
+ verifyTestPatternBg(kShapeSize, kShapeSize);
+ verifyTestPatternStroke(kShapeSize, kShapeSize);
+}
+
+
+// This class implements a test that draws a grid of v-shapes. The grid is
+// drawn so that even rows (from the bottom) are drawn with DrawArrays and odd
+// rows are drawn with path rendering. It can be used to test various texturing
+// modes, comparing how the fill would work in normal GL rendering and how to
+// setup same sort of fill with path rendering.
+// The texturing test is parametrized to run the test with and without
+// ANGLE name hashing.
+class CHROMIUMPathRenderingWithTexturingTest : public ANGLETest
+{
+ protected:
+ CHROMIUMPathRenderingWithTexturingTest() : mProgram(0)
+ {
+ setWindowWidth(kResolution);
+ setWindowHeight(kResolution);
+ setConfigRedBits(8);
+ setConfigGreenBits(8);
+ setConfigBlueBits(8);
+ setConfigAlphaBits(8);
+ setConfigDepthBits(8);
+ setConfigStencilBits(8);
+ }
+
+ bool isApplicable() const { return extensionEnabled("GL_CHROMIUM_path_rendering"); }
+
+ void TearDown() override
+ {
+ if (mProgram)
+ {
+ glDeleteProgram(mProgram);
+ ASSERT_GL_NO_ERROR();
+ }
+
+ ANGLETest::TearDown();
+ }
+
+ void SetUp() override
+ {
+ ANGLETest::SetUp();
+ mBindUniformLocation = reinterpret_cast<PFNGLBINDUNIFORMLOCATIONCHROMIUMPROC>(
+ eglGetProcAddress("glBindUniformLocationCHROMIUM"));
+ }
+
+ // Sets up the GL program state for the test.
+ // Vertex shader needs at least following variables:
+ // uniform mat4 view_matrix;
+ // uniform mat? color_matrix; (accessible with kColorMatrixLocation)
+ // uniform vec2 model_translate;
+ // attribute vec2 position;
+ // varying vec4 color;
+ //
+ // Fragment shader needs at least following variables:
+ // varying vec4 color;
+ //
+ // (? can be anything)
+ void compileProgram(const char *vertexShaderSource, const char *fragmentShaderSource)
+ {
+ glViewport(0, 0, kResolution, kResolution);
+ glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+ glStencilMask(0xffffffff);
+ glClearStencil(0);
+ glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+ ASSERT_GL_NO_ERROR();
+
+ GLuint vShader = compileShader(GL_VERTEX_SHADER, vertexShaderSource);
+ GLuint fShader = compileShader(GL_FRAGMENT_SHADER, fragmentShaderSource);
+ ASSERT_NE(0u, vShader);
+ ASSERT_NE(0u, fShader);
+
+ mProgram = glCreateProgram();
+
+ glAttachShader(mProgram, vShader);
+ glAttachShader(mProgram, fShader);
+ glDeleteShader(vShader);
+ glDeleteShader(fShader);
+
+ ASSERT_GL_NO_ERROR();
+ }
+
+ void bindProgram()
+ {
+ glBindAttribLocation(mProgram, kPositionLocation, "position");
+ mBindUniformLocation(mProgram, kViewMatrixLocation, "view_matrix");
+ mBindUniformLocation(mProgram, kColorMatrixLocation, "color_matrix");
+ mBindUniformLocation(mProgram, kModelTranslateLocation, "model_translate");
+ glBindFragmentInputLocationCHROMIUM(mProgram, kColorFragmentInputLocation, "color");
+
+ ASSERT_GL_NO_ERROR();
+ }
+
+ bool linkProgram()
+ {
+ glLinkProgram(mProgram);
+
+ GLint linked = 0;
+ glGetProgramiv(mProgram, GL_LINK_STATUS, &linked);
+ if (linked)
+ {
+ glUseProgram(mProgram);
+ }
+
+ return (linked == 1);
+ }
+
+ void drawTestPattern()
+ {
+ // This v-shape is used both for DrawArrays and path rendering.
+ static const GLfloat kVertices[] = {75.0f, 75.0f, 50.0f, 25.5f, 50.0f, 50.0f, 25.0f, 75.0f};
+
+ GLuint vbo = 0;
+ glGenBuffers(1, &vbo);
+ glBindBuffer(GL_ARRAY_BUFFER, vbo);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(kVertices), kVertices, GL_STATIC_DRAW);
+ glEnableVertexAttribArray(kPositionLocation);
+ glVertexAttribPointer(kPositionLocation, 2, GL_FLOAT, GL_FALSE, 0, 0);
+
+ // Setup state for drawing the shape with path rendering.
+ glPathStencilFuncCHROMIUM(GL_ALWAYS, 0, 0x7F);
+ glStencilFunc(GL_LESS, 0, 0x7F);
+ glStencilOp(GL_KEEP, GL_KEEP, GL_ZERO);
+ glMatrixLoadfCHROMIUM(GL_PATH_PROJECTION_CHROMIUM, kProjectionMatrix);
+ glMatrixLoadIdentityCHROMIUM(GL_PATH_MODELVIEW_CHROMIUM);
+
+ static const GLubyte kCommands[] = {GL_MOVE_TO_CHROMIUM, GL_LINE_TO_CHROMIUM,
+ GL_LINE_TO_CHROMIUM, GL_LINE_TO_CHROMIUM,
+ GL_CLOSE_PATH_CHROMIUM};
+
+ static const GLfloat kCoords[] = {
+ kVertices[0], kVertices[1], kVertices[2], kVertices[3],
+ kVertices[6], kVertices[7], kVertices[4], kVertices[5],
+ };
+
+ GLuint path = glGenPathsCHROMIUM(1);
+ glPathCommandsCHROMIUM(path, static_cast<GLsizei>(ArraySize(kCommands)), kCommands,
+ static_cast<GLsizei>(ArraySize(kCoords)), GL_FLOAT, kCoords);
+ ASSERT_GL_NO_ERROR();
+
+ GLfloat path_model_translate[16] = {
+ 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f,
+ };
+
+ // Draws the shapes. Every even row from the bottom is drawn with
+ // DrawArrays, odd row with path rendering. The shader program is
+ // the same for the both draws.
+ for (int j = 0; j < kTestRows; ++j)
+ {
+ for (int i = 0; i < kTestColumns; ++i)
+ {
+ if (j % 2 == 0)
+ {
+ glDisable(GL_STENCIL_TEST);
+ glUniform2f(kModelTranslateLocation, static_cast<GLfloat>(i * kShapeWidth),
+ static_cast<GLfloat>(j * kShapeHeight));
+ glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+ }
+ else
+ {
+ glEnable(GL_STENCIL_TEST);
+ path_model_translate[12] = static_cast<GLfloat>(i * kShapeWidth);
+ path_model_translate[13] = static_cast<GLfloat>(j * kShapeHeight);
+ glMatrixLoadfCHROMIUM(GL_PATH_MODELVIEW_CHROMIUM, path_model_translate);
+ glStencilThenCoverFillPathCHROMIUM(path, GL_COUNT_UP_CHROMIUM, 0x7F,
+ GL_BOUNDING_BOX_CHROMIUM);
+ }
+ }
+ }
+ ASSERT_GL_NO_ERROR();
+
+ glDisableVertexAttribArray(kPositionLocation);
+ glDeleteBuffers(1, &vbo);
+ glDeletePathsCHROMIUM(path, 1);
+ ASSERT_GL_NO_ERROR();
+ }
+
+ enum
+ {
+ kShapeWidth = 75,
+ kShapeHeight = 75,
+ kTestRows = kResolution / kShapeHeight,
+ kTestColumns = kResolution / kShapeWidth,
+ };
+
+ typedef void(GL_APIENTRYP PFNGLBINDUNIFORMLOCATIONCHROMIUMPROC)(GLuint mProgram,
+ GLint location,
+ const GLchar *name);
+ PFNGLBINDUNIFORMLOCATIONCHROMIUMPROC mBindUniformLocation = nullptr;
+
+ GLuint mProgram;
+
+ // This uniform be can set by the test. It should be used to set the color for
+ // drawing with DrawArrays.
+ static const GLint kColorMatrixLocation = 4;
+
+ // This fragment input can be set by the test. It should be used to set the
+ // color for drawing with path rendering.
+ static const GLint kColorFragmentInputLocation = 7;
+
+ static const GLint kModelTranslateLocation = 3;
+ static const GLint kPositionLocation = 0;
+ static const GLint kViewMatrixLocation = 7;
+};
+
+// Test success and error cases for binding fragment input location.
+TEST_P(CHROMIUMPathRenderingWithTexturingTest, TestBindFragmentInputLocation)
+{
+ if (!isApplicable())
+ return;
+
+ // original NV_path_rendering specification doesn't define whether the
+ // fragment shader input variables should be defined in the vertex shader or
+ // not. In fact it doesn't even require a vertex shader.
+ // However the GLES3.1 spec basically says that fragment inputs are
+ // either built-ins or come from the previous shader stage.
+ // (ยง 14.1, Fragment Shader Variables).
+ // Additionally there are many places that are based on the assumption of having
+ // a vertex shader (command buffer, angle) so we're going to stick to the same
+ // semantics and require a vertex shader and to have the vertex shader define the
+ // varying fragment shader input.
+
+ // clang-format off
+ const char* kVertexShaderSource =
+ "varying vec4 color;\n"
+ "void main() {}\n";
+
+ const char* kFragmentShaderSource =
+ "precision mediump float;\n"
+ "varying vec4 color;\n"
+ "void main() {\n"
+ " gl_FragColor = vec4(1.0);\n"
+ "}\n";
+
+ // clang-format on
+ compileProgram(kVertexShaderSource, kFragmentShaderSource);
+
+ enum kBindLocations
+ {
+ kColorLocation = 5,
+ kFragColorLocation = 6
+ };
+
+ // successful bind.
+ glBindFragmentInputLocationCHROMIUM(mProgram, kColorLocation, "color");
+ ASSERT_GL_NO_ERROR();
+
+ // any name can be bound and names that do not actually exist in the program after
+ // linking are ignored.
+ glBindFragmentInputLocationCHROMIUM(mProgram, kColorLocation, "doesnt_exist");
+ ASSERT_GL_NO_ERROR();
+
+ // illegal program
+ glBindFragmentInputLocationCHROMIUM(mProgram + 1, kColorLocation, "color");
+ EXPECT_GL_ERROR(GL_INVALID_OPERATION);
+
+ // illegal bind (built-in)
+ glBindFragmentInputLocationCHROMIUM(mProgram, kFragColorLocation, "gl_FragColor");
+ EXPECT_GL_ERROR(GL_INVALID_OPERATION);
+
+ glBindFragmentInputLocationCHROMIUM(mProgram, kFragColorLocation, NULL);
+ EXPECT_GL_ERROR(GL_INVALID_VALUE);
+
+ glBindFragmentInputLocationCHROMIUM(mProgram, 0xffffff, "color");
+ EXPECT_GL_ERROR(GL_INVALID_VALUE);
+
+ ASSERT_TRUE(linkProgram() == true);
+
+ const GLfloat kCoefficients16[] = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f,
+ 9.0f, 10.0f, 11.0f, 12.0f, 13.0f, 14.0f, 15.0f, 16.0f};
+
+ glProgramPathFragmentInputGenCHROMIUM(mProgram, kColorLocation, GL_EYE_LINEAR_CHROMIUM, 4,
+ kCoefficients16);
+ ASSERT_GL_NO_ERROR();
+
+ glProgramPathFragmentInputGenCHROMIUM(mProgram, -1, GL_EYE_LINEAR_CHROMIUM, 4, kCoefficients16);
+ ASSERT_GL_NO_ERROR();
+}
+
+// Test fragment input interpolation in CHROMIUM_EYE coordinates.
+TEST_P(CHROMIUMPathRenderingWithTexturingTest, TestProgramPathFragmentInputGenCHROMIUM_EYE)
+{
+ if (!isApplicable())
+ return;
+
+ // clang-format off
+ const char *kVertexShaderSource =
+ "uniform mat4 view_matrix;\n"
+ "uniform mat4 color_matrix;\n"
+ "uniform vec2 model_translate;\n"
+ "attribute vec2 position;\n"
+ "varying vec3 color;\n"
+ "void main() {\n"
+ " vec4 p = vec4(model_translate + position, 1.0, 1.0);\n"
+ " color = (color_matrix * p).rgb;\n"
+ " gl_Position = view_matrix * p;\n"
+ "}\n";
+
+ const char *kFragmentShaderSource =
+ "precision mediump float;\n"
+ "varying vec3 color;\n"
+ "void main() {\n"
+ " gl_FragColor = vec4(color, 1.0);\n"
+ "}\n";
+ // clang-format on
+
+ compileProgram(kVertexShaderSource, kFragmentShaderSource);
+ bindProgram();
+ ASSERT_TRUE(linkProgram() == true);
+
+ glUniformMatrix4fv(kViewMatrixLocation, 1, GL_FALSE, kProjectionMatrix);
+
+ static const GLfloat kColorMatrix[16] = {
+ 1.0f / kResolution, 0.0f, 0.0f, 0.0f,
+ 0.0f, 1.0f / kResolution, 0.0f, 0.0f,
+ 0.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 0.0f, 0.0f};
+
+ glUniformMatrix4fv(kColorMatrixLocation, 1, GL_FALSE, kColorMatrix);
+
+ // This is the functionality we are testing: ProgramPathFragmentInputGen
+ // does the same work as the color transform in vertex shader.
+ static const GLfloat kColorCoefficients[12] = {
+ 1.0f / kResolution, 0.0f, 0.0f,
+ 0.0f, 0.0f, 1.0f / kResolution,
+ 0.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 0.0f};
+ glProgramPathFragmentInputGenCHROMIUM(mProgram, kColorFragmentInputLocation,
+ GL_EYE_LINEAR_CHROMIUM, 3, kColorCoefficients);
+ ASSERT_GL_NO_ERROR();
+
+ drawTestPattern();
+
+ const GLfloat kFillCoords[6] = {59.0f, 50.0f, 50.0f, 28.0f, 66.0f, 63.0f};
+
+ for (int j = 0; j < kTestRows; ++j)
+ {
+ for (int i = 0; i < kTestColumns; ++i)
+ {
+ for (size_t k = 0; k < ArraySize(kFillCoords); k += 2)
+ {
+ const float fx = kFillCoords[k];
+ const float fy = kFillCoords[k + 1];
+ const float px = static_cast<float>(i * kShapeWidth);
+ const float py = static_cast<float>(j * kShapeHeight);
+
+ angle::GLColor color;
+ color.R = static_cast<GLubyte>(std::roundf((px + fx) / kResolution * 255.0f));
+ color.G = static_cast<GLubyte>(std::roundf((py + fy) / kResolution * 255.0f));
+ color.B = 0;
+ color.A = 255;
+ CheckPixels(static_cast<GLint>(px + fx), static_cast<GLint>(py + fy), 1, 1, 2,
+ color);
+ }
+ }
+ }
+}
+
+// Test fragment input interpolation in CHROMIUM_OBJECT coordinates.
+TEST_P(CHROMIUMPathRenderingWithTexturingTest, TestProgramPathFragmentInputGenCHROMIUM_OBJECT)
+{
+ if (!isApplicable())
+ return;
+
+ // clang-format off
+ const char *kVertexShaderSource =
+ "uniform mat4 view_matrix;\n"
+ "uniform mat4 color_matrix;\n"
+ "uniform vec2 model_translate;\n"
+ "attribute vec2 position;\n"
+ "varying vec3 color;\n"
+ "void main() {\n"
+ " color = (color_matrix * vec4(position, 1.0, 1.0)).rgb;\n"
+ " vec4 p = vec4(model_translate + position, 1.0, 1.0);\n"
+ " gl_Position = view_matrix * p;\n"
+ "}";
+
+ const char *kFragmentShaderSource =
+ "precision mediump float;\n"
+ "varying vec3 color;\n"
+ "void main() {\n"
+ " gl_FragColor = vec4(color.rgb, 1.0);\n"
+ "}";
+ // clang-format on
+
+ compileProgram(kVertexShaderSource, kFragmentShaderSource);
+ bindProgram();
+ ASSERT_TRUE(linkProgram() == true);
+
+ glUniformMatrix4fv(kViewMatrixLocation, 1, GL_FALSE, kProjectionMatrix);
+
+ static const GLfloat kColorMatrix[16] = {
+ 1.0f / kShapeWidth, 0.0f, 0.0f, 0.0f,
+ 0.0f, 1.0f / kShapeHeight, 0.0f, 0.0f,
+ 0.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 0.0f, 0.0f };
+ glUniformMatrix4fv(kColorMatrixLocation, 1, GL_FALSE, kColorMatrix);
+
+ // This is the functionality we are testing: ProgramPathFragmentInputGen
+ // does the same work as the color transform in vertex shader.
+ static const GLfloat kColorCoefficients[9] = {
+ 1.0f / kShapeWidth, 0.0f, 0.0f, 0.0f, 1.0f / kShapeHeight, 0.0f, 0.0f, 0.0f, 0.0f};
+ glProgramPathFragmentInputGenCHROMIUM(mProgram, kColorFragmentInputLocation,
+ GL_OBJECT_LINEAR_CHROMIUM, 3, kColorCoefficients);
+
+ ASSERT_GL_NO_ERROR();
+
+ drawTestPattern();
+
+ const GLfloat kFillCoords[6] = {59.0f, 50.0f, 50.0f, 28.0f, 66.0f, 63.0f};
+
+ for (int j = 0; j < kTestRows; ++j)
+ {
+ for (int i = 0; i < kTestColumns; ++i)
+ {
+ for (size_t k = 0; k < ArraySize(kFillCoords); k += 2)
+ {
+ const float fx = kFillCoords[k];
+ const float fy = kFillCoords[k + 1];
+ const float px = static_cast<float>(i * kShapeWidth);
+ const float py = static_cast<float>(j * kShapeHeight);
+
+ angle::GLColor color;
+ color.R = static_cast<GLubyte>(std::roundf(fx / kShapeWidth * 255.0f));
+ color.G = static_cast<GLubyte>(std::roundf(fy / kShapeHeight * 255.0f));
+ color.B = 0;
+ color.A = 255;
+ CheckPixels(static_cast<GLint>(px + fx), static_cast<GLint>(py + fy), 1, 1, 2,
+ color);
+ }
+ }
+ }
+}
+
+// Test success and error cases for setting interpolation parameters.
+TEST_P(CHROMIUMPathRenderingWithTexturingTest, TestProgramPathFragmentInputGenArgs)
+{
+ if (!isApplicable())
+ return;
+
+ // clang-format off
+ const char *kVertexShaderSource =
+ "varying vec2 vec2_var;\n"
+ "varying vec3 vec3_var;\n"
+ "varying vec4 vec4_var;\n"
+ "varying float float_var;\n"
+ "varying mat2 mat2_var;\n"
+ "varying mat3 mat3_var;\n"
+ "varying mat4 mat4_var;\n"
+ "attribute float avoid_opt;\n"
+ "void main() {\n"
+ " vec2_var = vec2(1.0, 2.0 + avoid_opt);\n"
+ " vec3_var = vec3(1.0, 2.0, 3.0 + avoid_opt);\n"
+ " vec4_var = vec4(1.0, 2.0, 3.0, 4.0 + avoid_opt);\n"
+ " float_var = 5.0 + avoid_opt;\n"
+ " mat2_var = mat2(2.0 + avoid_opt);\n"
+ " mat3_var = mat3(3.0 + avoid_opt);\n"
+ " mat4_var = mat4(4.0 + avoid_opt);\n"
+ " gl_Position = vec4(1.0);\n"
+ "}";
+
+ const char* kFragmentShaderSource =
+ "precision mediump float;\n"
+ "varying vec2 vec2_var;\n"
+ "varying vec3 vec3_var;\n"
+ "varying vec4 vec4_var;\n"
+ "varying float float_var;\n"
+ "varying mat2 mat2_var;\n"
+ "varying mat3 mat3_var;\n"
+ "varying mat4 mat4_var;\n"
+ "void main() {\n"
+ " gl_FragColor = vec4(vec2_var, 0, 0) + vec4(vec3_var, 0) + vec4_var + "
+ " vec4(float_var) + "
+ " vec4(mat2_var[0][0], mat3_var[1][1], mat4_var[2][2], 1);\n"
+ "}";
+ // clang-format on
+
+ enum
+ {
+ kVec2Location = 0,
+ kVec3Location,
+ kVec4Location,
+ kFloatLocation,
+ kMat2Location,
+ kMat3Location,
+ kMat4Location,
+ };
+ struct
+ {
+ GLint location;
+ const char *name;
+ GLint components;
+ } variables[] = {
+ {kVec2Location, "vec2_var", 2},
+ {kVec3Location, "vec3_var", 3},
+ {kVec4Location, "vec4_var", 4},
+ {kFloatLocation, "float_var", 1},
+ // If a varying is not single-precision floating-point scalar or
+ // vector, it always causes an invalid operation.
+ {kMat2Location, "mat2_var", -1},
+ {kMat3Location, "mat3_var", -1},
+ {kMat4Location, "mat4_var", -1},
+ };
+
+ compileProgram(kVertexShaderSource, kFragmentShaderSource);
+
+ for (size_t i = 0; i < ArraySize(variables); ++i)
+ {
+ glBindFragmentInputLocationCHROMIUM(mProgram, variables[i].location, variables[i].name);
+ }
+
+ // test that using invalid (not linked) program is an invalid operation.
+ // See similar calls at the end of the test for discussion about the arguments.
+ glProgramPathFragmentInputGenCHROMIUM(mProgram, -1, GL_NONE, 0, NULL);
+ EXPECT_GL_ERROR(GL_INVALID_OPERATION);
+
+ ASSERT_TRUE(linkProgram() == true);
+
+ const GLfloat kCoefficients16[] = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f,
+ 9.0f, 10.0f, 11.0f, 12.0f, 13.0f, 14.0f, 15.0f, 16.0f};
+ const GLenum kGenModes[] = {GL_NONE, GL_EYE_LINEAR_CHROMIUM, GL_OBJECT_LINEAR_CHROMIUM,
+ GL_CONSTANT_CHROMIUM};
+
+ for (size_t variable = 0; variable < ArraySize(variables); ++variable)
+ {
+ for (GLint components = 0; components <= 4; ++components)
+ {
+ for (size_t genmode = 0; genmode < ArraySize(kGenModes); ++genmode)
+ {
+ glProgramPathFragmentInputGenCHROMIUM(mProgram, variables[variable].location,
+ kGenModes[genmode], components,
+ kCoefficients16);
+
+ if (components == 0 && kGenModes[genmode] == GL_NONE)
+ {
+ if (variables[variable].components == -1)
+ {
+ // Clearing a fragment input that is not single-precision floating
+ // point scalar or vector is an invalid operation.
+ ASSERT_GL_ERROR(GL_INVALID_OPERATION);
+ }
+ else
+ {
+ // Clearing a valid fragment input is ok.
+ ASSERT_GL_NO_ERROR();
+ }
+ }
+ else if (components == 0 || kGenModes[genmode] == GL_NONE)
+ {
+ ASSERT_GL_ERROR(GL_INVALID_VALUE);
+ }
+ else
+ {
+ if (components == variables[variable].components)
+ {
+ // Setting a generator for a single-precision floating point
+ // scalar or vector fragment input is ok.
+ ASSERT_GL_NO_ERROR();
+ }
+ else
+ {
+ // Setting a generator when components do not match is an invalid operation.
+ ASSERT_GL_ERROR(GL_INVALID_OPERATION);
+ }
+ }
+ }
+ }
+ }
+
+ enum
+ {
+ kValidGenMode = GL_CONSTANT_CHROMIUM,
+ kValidComponents = 3,
+ kInvalidGenMode = 0xAB,
+ kInvalidComponents = 5,
+ };
+
+ // The location == -1 would mean fragment input was optimized away. At the
+ // time of writing, -1 can not happen because the only way to obtain the
+ // location numbers is through bind. Test just to be consistent.
+ glProgramPathFragmentInputGenCHROMIUM(mProgram, -1, kValidGenMode, kValidComponents,
+ kCoefficients16);
+ ASSERT_GL_NO_ERROR();
+
+ // Test that even though the spec says location == -1 causes the operation to
+ // be skipped, the verification of other parameters is still done. This is a
+ // GL policy.
+ glProgramPathFragmentInputGenCHROMIUM(mProgram, -1, kInvalidGenMode, kValidComponents,
+ kCoefficients16);
+ ASSERT_GL_ERROR(GL_INVALID_ENUM);
+
+ glProgramPathFragmentInputGenCHROMIUM(mProgram, -1, kInvalidGenMode, kInvalidComponents,
+ kCoefficients16);
+ ASSERT_GL_ERROR(GL_INVALID_ENUM);
+
+ glProgramPathFragmentInputGenCHROMIUM(mProgram, -1, kValidGenMode, kInvalidComponents,
+ kCoefficients16);
+ ASSERT_GL_ERROR(GL_INVALID_VALUE);
+
+ glDeleteProgram(mProgram);
+
+ // Test that using invalid (deleted) program is an invalid operation.
+ EXPECT_FALSE(glIsProgram(mProgram) == GL_FALSE);
+
+ glProgramPathFragmentInputGenCHROMIUM(mProgram, -1, kValidGenMode, kValidComponents,
+ kCoefficients16);
+ ASSERT_GL_ERROR(GL_INVALID_OPERATION);
+
+ glProgramPathFragmentInputGenCHROMIUM(mProgram, -1, kInvalidGenMode, kValidComponents,
+ kCoefficients16);
+ ASSERT_GL_ERROR(GL_INVALID_OPERATION);
+
+ glProgramPathFragmentInputGenCHROMIUM(mProgram, -1, kInvalidGenMode, kInvalidComponents,
+ kCoefficients16);
+ ASSERT_GL_ERROR(GL_INVALID_OPERATION);
+
+ glProgramPathFragmentInputGenCHROMIUM(mProgram, -1, kValidGenMode, kInvalidComponents,
+ kCoefficients16);
+ ASSERT_GL_ERROR(GL_INVALID_OPERATION);
+
+ mProgram = 0u;
+}
+
+// Test that having input statically aliased fragment inputs the linking fails
+// and then succeeds when the conflict is resolved.
+TEST_P(CHROMIUMPathRenderingWithTexturingTest, TestConflictingBind)
+{
+ if (!isApplicable())
+ return;
+
+ // clang-format off
+ const char* kVertexShaderSource =
+ "attribute vec4 position;\n"
+ "varying vec4 colorA;\n"
+ "varying vec4 colorB;\n"
+ "void main() {\n"
+ " gl_Position = position;\n"
+ " colorA = position + vec4(1);\n"
+ " colorB = position + vec4(2);\n"
+ "}";
+
+ const char* kFragmentShaderSource =
+ "precision mediump float;\n"
+ "varying vec4 colorA;\n"
+ "varying vec4 colorB;\n"
+ "void main() {\n"
+ " gl_FragColor = colorA + colorB;\n"
+ "}";
+ // clang-format on
+
+ const GLint kColorALocation = 3;
+ const GLint kColorBLocation = 4;
+
+ compileProgram(kVertexShaderSource, kFragmentShaderSource);
+
+ glBindFragmentInputLocationCHROMIUM(mProgram, kColorALocation, "colorA");
+ // Bind colorB to location a, causing conflicts. Linking should fail.
+ glBindFragmentInputLocationCHROMIUM(mProgram, kColorALocation, "colorB");
+
+ // Should fail now.
+ ASSERT_TRUE(linkProgram() == false);
+ ASSERT_GL_NO_ERROR();
+
+ // Resolve the bind conflict.
+ glBindFragmentInputLocationCHROMIUM(mProgram, kColorBLocation, "colorB");
+
+ ASSERT_TRUE(linkProgram() == true);
+ ASSERT_GL_NO_ERROR();
+}
+
+// Test binding with array variables, using zero indices. Tests that
+// binding colorA[0] with explicit "colorA[0]" as well as "colorA" produces
+// a correct location that can be used with PathProgramFragmentInputGen.
+// For path rendering, colorA[0] is bound to a location. The input generator for
+// the location is set to produce vec4(0, 0.1, 0, 0.1).
+// The default varying, color, is bound to a location and its generator
+// will produce vec4(10.0). The shader program produces green pixels.
+// For vertex-based rendering, the vertex shader produces the same effect as
+// the input generator for path rendering.
+TEST_P(CHROMIUMPathRenderingWithTexturingTest, BindFragmentInputArray)
+{
+ if (!isApplicable())
+ return;
+
+ //clang-format off
+ const char* kVertexShaderSource =
+ "uniform mat4 view_matrix;\n"
+ "uniform mat4 color_matrix;\n"
+ "uniform vec2 model_translate;\n"
+ "attribute vec2 position;\n"
+ "varying vec4 color;\n"
+ "varying vec4 colorA[4];\n"
+ "void main() {\n"
+ " vec4 p = vec4(model_translate + position, 1, 1);\n"
+ " gl_Position = view_matrix * p;\n"
+ " colorA[0] = vec4(0.0, 0.1, 0, 0.1);\n"
+ " colorA[1] = vec4(0.2);\n"
+ " colorA[2] = vec4(0.3);\n"
+ " colorA[3] = vec4(0.4);\n"
+ " color = vec4(10.0);\n"
+ "}";
+
+ const char* kFragmentShaderSource =
+ "precision mediump float;\n"
+ "varying vec4 color;\n"
+ "varying vec4 colorA[4];\n"
+ "void main() {\n"
+ " gl_FragColor = colorA[0] * color;\n"
+ "}";
+ // clang-format on
+
+ const GLint kColorA0Location = 4;
+ const GLint kUnusedLocation = 5;
+ const GLfloat kColorA0[] = {0.0f, 0.1f, 0.0f, 0.1f};
+ const GLfloat kColor[] = {10.0f, 10.0f, 10.0f, 10.0f};
+ const GLfloat kFillCoords[6] = {59.0f, 50.0f, 50.0f, 28.0f, 66.0f, 63.0f};
+
+ for (int pass = 0; pass < 2; ++pass)
+ {
+ compileProgram(kVertexShaderSource, kFragmentShaderSource);
+ if (pass == 0)
+ {
+ glBindFragmentInputLocationCHROMIUM(mProgram, kUnusedLocation, "colorA[0]");
+ glBindFragmentInputLocationCHROMIUM(mProgram, kColorA0Location, "colorA");
+ }
+ else
+ {
+ glBindFragmentInputLocationCHROMIUM(mProgram, kUnusedLocation, "colorA");
+ glBindFragmentInputLocationCHROMIUM(mProgram, kColorA0Location, "colorA[0]");
+ }
+
+ bindProgram();
+
+ ASSERT_TRUE(linkProgram() == true);
+
+ glUniformMatrix4fv(kViewMatrixLocation, 1, GL_FALSE, kProjectionMatrix);
+ glProgramPathFragmentInputGenCHROMIUM(mProgram, kColorA0Location, GL_CONSTANT_CHROMIUM, 4, kColorA0);
+ glProgramPathFragmentInputGenCHROMIUM(mProgram, kColorFragmentInputLocation, GL_CONSTANT_CHROMIUM, 4, kColor);
+ ASSERT_GL_NO_ERROR();
+
+ drawTestPattern();
+
+ for (int j = 0; j < kTestRows; ++j)
+ {
+ for (int i = 0; i < kTestColumns; ++i)
+ {
+ for (size_t k = 0; k < ArraySize(kFillCoords); k += 2)
+ {
+ const float fx = kFillCoords[k];
+ const float fy = kFillCoords[k + 1];
+ const float px = static_cast<float>(i * kShapeWidth);
+ const float py = static_cast<float>(j * kShapeHeight);
+
+ angle::GLColor color;
+ color.R = 0;
+ color.G = 255;
+ color.B = 0;
+ color.A = 255;
+ CheckPixels(static_cast<GLint>(px + fx), static_cast<GLint>(py + fy), 1, 1, 2,
+ color);
+ }
+ }
+ }
+ }
+}
+
+// Test binding array variables. This is like BindFragmentInputArray.
+// Currently disabled since it seems there's a driver bug with the
+// older drivers. This should work with driver >= 364.12
+TEST_P(CHROMIUMPathRenderingWithTexturingTest,
+ DISABLED_BindFragmentInputArrayNonZeroIndex)
+{
+ if (!isApplicable())
+ return;
+
+ // clang-format off
+ const char* kVertexShaderSource =
+ "uniform mat4 view_matrix;\n"
+ "uniform mat4 color_matrix;\n"
+ "uniform vec2 model_translate;\n"
+ "attribute vec2 position;\n"
+ "varying vec4 color;\n"
+ "varying vec4 colorA[4];\n"
+ "void main() {\n"
+ " vec4 p = vec4(model_translate + position, 1, 1);\n"
+ " gl_Position = view_matrix * p;\n"
+ " colorA[0] = vec4(0, 0.1, 0, 0.1);\n"
+ " colorA[1] = vec4(0, 1, 0, 1);\n"
+ " colorA[2] = vec4(0, 0.8, 0, 0.8);\n"
+ " colorA[3] = vec4(0, 0.5, 0, 0.5);\n"
+ " color = vec4(0.2);\n"
+ "}\n";
+
+ const char* kFragmentShaderSource =
+ "precision mediump float;\n"
+ "varying vec4 colorA[4];\n"
+ "varying vec4 color;\n"
+ "void main() {\n"
+ " gl_FragColor = (colorA[0] * colorA[1]) +\n"
+ " colorA[2] + (colorA[3] * color);\n"
+ "}\n";
+ // clang-format on
+
+ const GLint kColorA0Location = 4;
+ const GLint kColorA1Location = 1;
+ const GLint kColorA2Location = 2;
+ const GLint kColorA3Location = 3;
+ const GLint kUnusedLocation = 5;
+ const GLfloat kColorA0[] = {0.0f, 0.1f, 0.0f, 0.1f};
+ const GLfloat kColorA1[] = {0.0f, 1.0f, 0.0f, 1.0f};
+ const GLfloat kColorA2[] = {0.0f, 0.8f, 0.0f, 0.8f};
+ const GLfloat kColorA3[] = {0.0f, 0.5f, 0.0f, 0.5f};
+ const GLfloat kColor[] = {0.2f, 0.2f, 0.2f, 0.2f};
+ const GLfloat kFillCoords[6] = {59.0f, 50.0f, 50.0f, 28.0f, 66.0f, 63.0f};
+
+ compileProgram(kVertexShaderSource, kFragmentShaderSource);
+
+ glBindFragmentInputLocationCHROMIUM(mProgram, kUnusedLocation, "colorA[0]");
+ glBindFragmentInputLocationCHROMIUM(mProgram, kColorA1Location, "colorA[1]");
+ glBindFragmentInputLocationCHROMIUM(mProgram, kColorA2Location, "colorA[2]");
+ glBindFragmentInputLocationCHROMIUM(mProgram, kColorA3Location, "colorA[3]");
+ glBindFragmentInputLocationCHROMIUM(mProgram, kColorA0Location, "colorA");
+ ASSERT_GL_NO_ERROR();
+
+ bindProgram();
+ ASSERT_TRUE(linkProgram() == true);
+
+ glUniformMatrix4fv(kViewMatrixLocation, 1, GL_FALSE, kProjectionMatrix);
+
+ glProgramPathFragmentInputGenCHROMIUM(mProgram, kColorA0Location, GL_CONSTANT_CHROMIUM, 4, kColorA0);
+ glProgramPathFragmentInputGenCHROMIUM(mProgram, kColorA1Location, GL_CONSTANT_CHROMIUM, 4, kColorA1);
+ glProgramPathFragmentInputGenCHROMIUM(mProgram, kColorA2Location, GL_CONSTANT_CHROMIUM, 4, kColorA2);
+ glProgramPathFragmentInputGenCHROMIUM(mProgram, kColorA3Location, GL_CONSTANT_CHROMIUM, 4, kColorA3);
+ glProgramPathFragmentInputGenCHROMIUM(mProgram, kColorFragmentInputLocation,
+ GL_CONSTANT_CHROMIUM, 4, kColor);
+ ASSERT_GL_NO_ERROR();
+
+ drawTestPattern();
+
+ for (int j = 0; j < kTestRows; ++j)
+ {
+ for (int i = 0; i < kTestColumns; ++i)
+ {
+ for (size_t k = 0; k < ArraySize(kFillCoords); k += 2)
+ {
+ const float fx = kFillCoords[k];
+ const float fy = kFillCoords[k + 1];
+ const float px = static_cast<float>(i * kShapeWidth);
+ const float py = static_cast<float>(j * kShapeHeight);
+
+ angle::GLColor color;
+ color.R = 0;
+ color.G = 255;
+ color.B = 0;
+ color.A = 255;
+ CheckPixels(static_cast<GLint>(px + fx), static_cast<GLint>(py + fy), 1, 1, 2,
+ color);
+ }
+ }
+ }
+}
+
+TEST_P(CHROMIUMPathRenderingWithTexturingTest, UnusedFragmentInputUpdate)
+{
+ if (!isApplicable())
+ return;
+
+ // clang-format off
+ const char* kVertexShaderString =
+ "attribute vec4 a_position;\n"
+ "void main() {\n"
+ " gl_Position = a_position;\n"
+ "}";
+
+ const char* kFragmentShaderString =
+ "precision mediump float;\n"
+ "uniform vec4 u_colorA;\n"
+ "uniform float u_colorU;\n"
+ "uniform vec4 u_colorC;\n"
+ "void main() {\n"
+ " gl_FragColor = u_colorA + u_colorC;\n"
+ "}";
+ // clang-format on
+
+ const GLint kColorULocation = 1;
+ const GLint kNonexistingLocation = 5;
+ const GLint kUnboundLocation = 6;
+
+ compileProgram(kVertexShaderString, kFragmentShaderString);
+
+ glBindFragmentInputLocationCHROMIUM(mProgram, kColorULocation, "u_colorU");
+
+ // The non-existing input should behave like existing but optimized away input.
+ glBindFragmentInputLocationCHROMIUM(mProgram, kNonexistingLocation, "nonexisting");
+
+ // Let A and C be assigned automatic locations.
+ ASSERT_TRUE(linkProgram() == true);
+
+ const GLfloat kColor[16] = {};
+
+ // No errors on bound locations, since caller does not know
+ // if the driver optimizes them away or not.
+ glProgramPathFragmentInputGenCHROMIUM(mProgram, kColorULocation, GL_CONSTANT_CHROMIUM, 1, kColor);
+ ASSERT_GL_NO_ERROR();
+
+ // No errors on bound locations of names that do not exist
+ // in the shader. Otherwise it would be inconsistent wrt the
+ // optimization case.
+ glProgramPathFragmentInputGenCHROMIUM(mProgram, kNonexistingLocation, GL_CONSTANT_CHROMIUM, 1, kColor);
+ ASSERT_GL_NO_ERROR();
+
+ // The above are equal to updating -1.
+ glProgramPathFragmentInputGenCHROMIUM(mProgram, -1, GL_CONSTANT_CHROMIUM, 1, kColor);
+ ASSERT_GL_NO_ERROR();
+
+ // No errors when updating with other type either.
+ // The type can not be known with the non-existing case.
+ glProgramPathFragmentInputGenCHROMIUM(mProgram, kColorULocation, GL_CONSTANT_CHROMIUM, 4, kColor);
+ ASSERT_GL_NO_ERROR();
+
+ glProgramPathFragmentInputGenCHROMIUM(mProgram, kNonexistingLocation, GL_CONSTANT_CHROMIUM, 4, kColor);
+ ASSERT_GL_NO_ERROR();
+
+ glProgramPathFragmentInputGenCHROMIUM(mProgram, -1, GL_CONSTANT_CHROMIUM, 4, kColor);
+ ASSERT_GL_NO_ERROR();
+
+ // Updating an unbound, non-existing location still causes an error.
+ glProgramPathFragmentInputGenCHROMIUM(mProgram, kUnboundLocation, GL_CONSTANT_CHROMIUM, 4, kColor);
+ ASSERT_GL_ERROR(GL_INVALID_OPERATION);
+}
+
+
+} // namespace
+
+ANGLE_INSTANTIATE_TEST(CHROMIUMPathRenderingTest,
+ ES2_OPENGL(),
+ ES2_OPENGLES(),
+ ES3_OPENGL(),
+ ES3_OPENGLES());
+ANGLE_INSTANTIATE_TEST(CHROMIUMPathRenderingDrawTest,
+ ES2_OPENGL(),
+ ES2_OPENGLES(),
+ ES3_OPENGL(),
+ ES3_OPENGLES());
+
+ANGLE_INSTANTIATE_TEST(CHROMIUMPathRenderingWithTexturingTest,
+ ES2_OPENGL(),
+ ES2_OPENGLES(),
+ ES3_OPENGL(),
+ ES3_OPENGLES()); \ No newline at end of file