Support CHROMIUM_path_rendering
This is partial support for CHROMIUM_path_rendering
and implements basic path management and non-instanced
rendering.
BUG=angleproject:1382
Change-Id: I9c0e88183e0a915d522889323933439d25b45b5f
Reviewed-on: https://chromium-review.googlesource.com/348630
Reviewed-by: Jamie Madill <jmadill@chromium.org>
Commit-Queue: Jamie Madill <jmadill@chromium.org>
diff --git a/src/tests/gl_tests/PathRenderingTest.cpp b/src/tests/gl_tests/PathRenderingTest.cpp
new file mode 100644
index 0000000..e1032c7
--- /dev/null
+++ b/src/tests/gl_tests/PathRenderingTest.cpp
@@ -0,0 +1,742 @@
+//
+// 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 <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_COLOR_EQ(px, py, color);
+ }
+ }
+
+ 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);
+ EXPECT_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);
+ EXPECT_TRUE(glIsPathCHROMIUM(path) == GL_TRUE);
+ glDeletePathsCHROMIUM(path, 1);
+ EXPECT_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<GLenum>(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<GLenum>(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);
+
+ // For instanced variants, we need this to error the same way
+ // regardless of whether # of paths == 0 would cause an early return.
+
+ // TODO: enable this once instanced path rendering is implemented.
+ // for (int path_count = 0; path_count <= 1; ++path_count)
+ // {
+ // glStencilFillPathInstancedCHROMIUM(path_count, GL_UNSIGNED_INT, &path, 0,
+ // GL_COUNT_UP_CHROMIUM - 1, 0x7F, GL_NONE, NULL);
+ // EXPECT_EQ(static_cast<GLenum>(GL_INVALID_ENUM), glGetError());
+ // glStencilThenCoverFillPathInstancedCHROMIUM(
+ // path_count, GL_UNSIGNED_INT, &path, 0, GL_COUNT_UP_CHROMIUM - 1, 0x7F,
+ // GL_BOUNDING_BOX_OF_BOUNDING_BOXES_CHROMIUM, GL_NONE, NULL);
+ // EXPECT_EQ(static_cast<GLenum>(GL_INVALID_ENUM), glGetError());
+ // }
+
+ // 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);
+
+ // TODO: enable this once instanced path rendering is implemented.
+ // for (int path_count = 0; path_count <= 1; ++path_count)
+ // {
+ // glStencilFillPathInstancedCHROMIUM(path_count, GL_UNSIGNED_INT, &path, 0,
+ // GL_COUNT_UP_CHROMIUM, 0x30, GL_NONE, NULL);
+ // EXPECT_EQ(static_cast<GLenum>(GL_INVALID_VALUE), glGetError());
+ // glStencilThenCoverFillPathInstancedCHROMIUM(
+ // path_count, GL_UNSIGNED_INT, &path, 0, GL_COUNT_DOWN_CHROMIUM, 0xFE,
+ // GL_BOUNDING_BOX_OF_BOUNDING_BOXES_CHROMIUM, GL_NONE, NULL);
+ // EXPECT_EQ(static_cast<GLenum>(GL_INVALID_VALUE), glGetError());
+ // }
+
+ 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(float x, float y)
+ {
+ static const float kFillCoords[] = {55.0f, 54.0f, 50.0f, 28.0f, 66.0f, 63.0f};
+ static const angle::GLColor kBlue = {0, 0, 255, 255};
+
+ for (size_t i = 0; i < 6; i += 2)
+ {
+ float fx = kFillCoords[i];
+ float fy = kFillCoords[i + 1];
+ EXPECT_TRUE(CheckPixels(x + fx, y + fy, 1, 1, 0, kBlue));
+ }
+ }
+ void verifyTestPatternBg(float x, float y)
+ {
+ static const float kBackgroundCoords[] = {80.0f, 80.0f, 20.0f, 20.0f, 90.0f, 1.0f};
+ static const angle::GLColor kExpectedColor = {0, 0, 0, 0};
+
+ for (size_t i = 0; i < 6; i += 2)
+ {
+ float bx = kBackgroundCoords[i];
+ float by = kBackgroundCoords[i + 1];
+ EXPECT_TRUE(CheckPixels(x + bx, y + by, 1, 1, 0, kExpectedColor));
+ }
+ }
+
+ void verifyTestPatternStroke(float x, float y)
+ {
+ // 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.0f, 0.0f);
+ verifyTestPatternBg(0.0f, 0.0f);
+ verifyTestPatternStroke(0.0f, 0.0f);
+}
+
+// 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.0f, 0.0f);
+ verifyTestPatternBg(0.0f, 0.0f);
+ verifyTestPatternStroke(0.0f, 0.0f);
+}
+
+} // 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());