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());