diff options
Diffstat (limited to 'gfx/angle/src/tests/gl_tests/PathRenderingTest.cpp')
-rwxr-xr-x | gfx/angle/src/tests/gl_tests/PathRenderingTest.cpp | 1984 |
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 |