blob: 375b56898104228f569e27d1bf10504c7c8c8a83 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#include <GLES2/gl2extchromium.h>
#include <stddef.h>
#include <stdint.h>
#include <cmath>
#include "base/command_line.h"
#include "base/stl_util.h"
#include "gpu/command_buffer/service/gpu_switches.h"
#include "gpu/command_buffer/tests/gl_manager.h"
#include "gpu/command_buffer/tests/gl_test_utils.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#define SHADER(Src) #Src
namespace {
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>(roundf(expected[i])), actual[i]);
}
}
}
namespace gpu {
class CHROMIUMPathRenderingTest : public testing::Test {
protected:
static const GLsizei kResolution = 300;
void SetUp() override {
GLManager::Options options;
InitializeContextFeatures(&options);
gl_.Initialize(options);
}
virtual void InitializeContextFeatures(GLManager::Options* options) {
options->size = gfx::Size(kResolution, kResolution);
}
void TearDown() override { gl_.Destroy(); }
bool IsApplicable() const {
return GLTestHelper::HasExtension("GL_CHROMIUM_path_rendering");
}
void TryAllDrawFunctions(GLuint path, GLenum expected_error) {
glStencilFillPathCHROMIUM(path, GL_COUNT_UP_CHROMIUM, 0x7F);
EXPECT_EQ(expected_error, glGetError());
glStencilFillPathCHROMIUM(path, GL_COUNT_UP_CHROMIUM, 0x7F);
EXPECT_EQ(expected_error, glGetError());
glStencilStrokePathCHROMIUM(path, 0x80, 0x80);
EXPECT_EQ(expected_error, glGetError());
glCoverFillPathCHROMIUM(path, GL_BOUNDING_BOX_CHROMIUM);
EXPECT_EQ(expected_error, glGetError());
glCoverStrokePathCHROMIUM(path, GL_BOUNDING_BOX_CHROMIUM);
EXPECT_EQ(expected_error, glGetError());
glStencilThenCoverStrokePathCHROMIUM(path, 0x80, 0x80,
GL_BOUNDING_BOX_CHROMIUM);
EXPECT_EQ(expected_error, glGetError());
glStencilThenCoverFillPathCHROMIUM(path, GL_COUNT_UP_CHROMIUM, 0x7F,
GL_BOUNDING_BOX_CHROMIUM);
EXPECT_EQ(expected_error, glGetError());
}
GLManager gl_;
};
class CHROMIUMPathRenderingDrawTest : public CHROMIUMPathRenderingTest {
protected:
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);
static const char* kVertexShaderSource =
SHADER(void main() { gl_Position = vec4(1); });
static const char* kFragmentShaderSource =
SHADER(precision mediump float; uniform vec4 color;
void main() { gl_FragColor = color; });
GLuint program =
GLTestHelper::LoadProgram(kVertexShaderSource, kFragmentShaderSource);
glUseProgram(program);
color_loc_ = 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);
glEnable(GL_STENCIL_TEST);
GLTestHelper::CheckGLError("no errors at state setup", __LINE__);
}
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, base::size(kCommands), kCommands,
base::size(kCoords), 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);
}
void VerifyTestPatternFill(float x, float y) {
SCOPED_TRACE(testing::Message() << "Verifying fill at " << x << "," << y);
static const float kFillCoords[] = {55.0f, 54.0f, 50.0f,
28.0f, 66.0f, 63.0f};
static const uint8_t kBlue[] = {0, 0, 255, 255};
for (size_t i = 0; i < base::size(kFillCoords); i += 2) {
float fx = kFillCoords[i];
float fy = kFillCoords[i + 1];
EXPECT_TRUE(
GLTestHelper::CheckPixels(x + fx, y + fy, 1, 1, 0, kBlue, nullptr));
}
}
void VerifyTestPatternBg(float x, float y) {
SCOPED_TRACE(testing::Message() << "Verifying background at " << x << ","
<< y);
const float kBackgroundCoords[] = {80.0f, 80.0f, 20.0f, 20.0f, 90.0f, 1.0f};
const uint8_t kExpectedColor[] = {0, 0, 0, 0};
for (size_t i = 0; i < base::size(kBackgroundCoords); i += 2) {
float bx = kBackgroundCoords[i];
float by = kBackgroundCoords[i + 1];
EXPECT_TRUE(GLTestHelper::CheckPixels(x + bx, y + by, 1, 1, 0,
kExpectedColor, nullptr));
}
}
void VerifyTestPatternStroke(float x, float y) {
SCOPED_TRACE(testing::Message() << "Verifying stroke at " << x << "," << y);
// Inside the stroke we should have green.
const uint8_t kGreen[] = {0, 255, 0, 255};
EXPECT_TRUE(
GLTestHelper::CheckPixels(x + 50, y + 53, 1, 1, 0, kGreen, nullptr));
EXPECT_TRUE(
GLTestHelper::CheckPixels(x + 26, y + 76, 1, 1, 0, kGreen, nullptr));
// Outside the path we should have black.
const uint8_t black[] = {0, 0, 0, 0};
EXPECT_TRUE(
GLTestHelper::CheckPixels(x + 10, y + 10, 1, 1, 0, black, nullptr));
EXPECT_TRUE(
GLTestHelper::CheckPixels(x + 80, y + 80, 1, 1, 0, black, nullptr));
}
static const GLfloat kProjectionMatrix[16];
GLint color_loc_;
};
const GLfloat CHROMIUMPathRenderingDrawTest::kProjectionMatrix[16] = {
2.0f / (CHROMIUMPathRenderingTest::kResolution - 1),
0.0f,
0.0f,
0.0f,
0.0f,
2.0f / (CHROMIUMPathRenderingTest::kResolution - 1),
0.0f,
0.0f,
0.0f,
0.0f,
-1.0f,
0.0f,
-1.0f,
-1.0f,
0.0f,
1.0f};
TEST_F(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 < base::size(kMatrixModes); ++i) {
GLfloat mf[16];
GLint mi[16];
memset(mf, 0, sizeof(mf));
memset(mi, 0, sizeof(mi));
glGetFloatv(kGetMatrixModes[i], mf);
glGetIntegerv(kGetMatrixModes[i], mi);
ExpectEqualMatrix(kIdentityMatrix, mf);
ExpectEqualMatrix(kIdentityMatrix, mi);
glMatrixLoadfCHROMIUM(kMatrixModes[i], kSeqMatrix);
memset(mf, 0, sizeof(mf));
memset(mi, 0, sizeof(mi));
glGetFloatv(kGetMatrixModes[i], mf);
glGetIntegerv(kGetMatrixModes[i], mi);
ExpectEqualMatrix(kSeqMatrix, mf);
ExpectEqualMatrix(kSeqMatrix, mi);
glMatrixLoadIdentityCHROMIUM(kMatrixModes[i]);
memset(mf, 0, sizeof(mf));
memset(mi, 0, sizeof(mi));
glGetFloatv(kGetMatrixModes[i], mf);
glGetIntegerv(kGetMatrixModes[i], mi);
ExpectEqualMatrix(kIdentityMatrix, mf);
ExpectEqualMatrix(kIdentityMatrix, mi);
EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
}
}
TEST_F(CHROMIUMPathRenderingTest, TestMatrixErrors) {
if (!IsApplicable())
return;
GLfloat mf[16];
memset(mf, 0, sizeof(mf));
glMatrixLoadfCHROMIUM(GL_PATH_MODELVIEW_CHROMIUM, mf);
EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
glMatrixLoadIdentityCHROMIUM(GL_PATH_PROJECTION_CHROMIUM);
EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
// Test that invalid matrix targets fail.
glMatrixLoadfCHROMIUM(GL_PATH_MODELVIEW_CHROMIUM - 1, mf);
EXPECT_EQ(static_cast<GLenum>(GL_INVALID_ENUM), glGetError());
// Test that invalid matrix targets fail.
glMatrixLoadIdentityCHROMIUM(GL_PATH_PROJECTION_CHROMIUM + 1);
EXPECT_EQ(static_cast<GLenum>(GL_INVALID_ENUM), glGetError());
}
TEST_F(CHROMIUMPathRenderingTest, TestSimpleCalls) {
if (!IsApplicable())
return;
// This is unspecified in NV_path_rendering.
EXPECT_EQ(0u, glGenPathsCHROMIUM(0));
GLuint path = glGenPathsCHROMIUM(1);
EXPECT_NE(path, 0u);
glDeletePathsCHROMIUM(path, 1);
EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
GLuint first_path = glGenPathsCHROMIUM(5);
EXPECT_NE(first_path, 0u);
glDeletePathsCHROMIUM(first_path, 5);
EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
// Test deleting paths that are not actually allocated:
// "unused names in /paths/ are silently ignored".
first_path = glGenPathsCHROMIUM(5);
EXPECT_NE(first_path, 0u);
glDeletePathsCHROMIUM(first_path, 6);
EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
GLsizei big_range = 0xffff;
// Setting big_range = std::numeric_limits<GLsizei>::max() should go through
// too, as far as NV_path_rendering is concerned. Current chromium side id
// allocator will use too much memory.
first_path = glGenPathsCHROMIUM(big_range);
EXPECT_NE(first_path, 0u);
glDeletePathsCHROMIUM(first_path, big_range);
EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
// Test glIsPathCHROMIUM().
path = glGenPathsCHROMIUM(1);
EXPECT_FALSE(glIsPathCHROMIUM(path));
GLubyte commands[] = {GL_MOVE_TO_CHROMIUM, GL_CLOSE_PATH_CHROMIUM};
GLfloat coords[] = {50.0f, 50.0f};
glPathCommandsCHROMIUM(path, base::size(commands), commands,
base::size(coords), GL_FLOAT, coords);
EXPECT_TRUE(glIsPathCHROMIUM(path));
glDeletePathsCHROMIUM(path, 1);
EXPECT_FALSE(glIsPathCHROMIUM(path));
}
TEST_F(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_EQ(static_cast<GLenum>(GL_INVALID_VALUE), glGetError());
first_path = glGenPathsCHROMIUM(-1);
EXPECT_EQ(first_path, 0u);
EXPECT_EQ(static_cast<GLenum>(GL_INVALID_VALUE), glGetError());
glDeletePathsCHROMIUM(1, -5);
EXPECT_EQ(static_cast<GLenum>(GL_INVALID_VALUE), glGetError());
first_path = glGenPathsCHROMIUM(-1);
EXPECT_EQ(first_path, 0u);
EXPECT_EQ(static_cast<GLenum>(GL_INVALID_VALUE), glGetError());
// Test that delete with first_id and range such that first_id + range
// overflows the GLuint. Example:
// Range is 0x7fffffff. First id is X. Last id will be X + 0x7ffffffe.
// X = 0x80000001 would succeed, where as X = 0x80000002 would fail.
// To get 0x80000002, we need to allocate first 0x7fffffff and then
// 3 (0x80000000, 0x80000001 and 0x80000002).
// While not guaranteed by the API, we expect the implementation
// hands us deterministic ids.
first_path = glGenPathsCHROMIUM(std::numeric_limits<GLsizei>::max());
EXPECT_EQ(first_path, 1u);
EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
GLuint additional_paths = glGenPathsCHROMIUM(3);
EXPECT_EQ(additional_paths,
static_cast<GLuint>(std::numeric_limits<GLsizei>::max()) + 1u);
EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
// Test that passing a range so big that it would overflow client_id
// + range - 1 check causes an error.
glDeletePathsCHROMIUM(additional_paths + 2u,
std::numeric_limits<GLsizei>::max());
EXPECT_EQ(static_cast<GLenum>(GL_INVALID_OPERATION), glGetError());
// Cleanup the above allocations. Also test that passing max value still
// works.
glDeletePathsCHROMIUM(1, std::numeric_limits<GLsizei>::max());
EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
glDeletePathsCHROMIUM(std::numeric_limits<GLsizei>::max(),
std::numeric_limits<GLsizei>::max());
EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
}
TEST_F(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_EQ(static_cast<GLenum>(GL_INVALID_VALUE), glGetError());
glPathParameterfCHROMIUM(path, GL_PATH_END_CAPS_CHROMIUM,
GL_MITER_REVERT_CHROMIUM);
EXPECT_EQ(static_cast<GLenum>(GL_INVALID_VALUE), glGetError());
// PathParameter*: Wrong floating-point value should fail.
glPathParameterfCHROMIUM(path, GL_PATH_STROKE_WIDTH_CHROMIUM, -0.1f);
EXPECT_EQ(static_cast<GLenum>(GL_INVALID_VALUE), glGetError());
glPathParameterfCHROMIUM(path, GL_PATH_MITER_LIMIT_CHROMIUM,
std::numeric_limits<float>::quiet_NaN());
EXPECT_EQ(static_cast<GLenum>(GL_INVALID_VALUE), glGetError());
glPathParameterfCHROMIUM(path, GL_PATH_MITER_LIMIT_CHROMIUM,
std::numeric_limits<float>::infinity());
EXPECT_EQ(static_cast<GLenum>(GL_INVALID_VALUE), glGetError());
// PathParameter*: Wrong pname should fail.
glPathParameteriCHROMIUM(path, GL_PATH_STROKE_WIDTH_CHROMIUM - 1, 5);
EXPECT_EQ(static_cast<GLenum>(GL_INVALID_ENUM), glGetError());
glDeletePathsCHROMIUM(path, 1);
}
TEST_F(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);
// 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_FALSE(glIsPathCHROMIUM(p));
EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
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_FALSE(glIsPathCHROMIUM(path));
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_FALSE(glIsPathCHROMIUM(path));
GLubyte commands[] = {GL_MOVE_TO_CHROMIUM, GL_CLOSE_PATH_CHROMIUM};
GLfloat coords[] = {50.0f, 50.0f};
glPathCommandsCHROMIUM(path, base::size(commands), commands,
base::size(coords), GL_FLOAT, coords);
EXPECT_TRUE(glIsPathCHROMIUM(path));
glPathCommandsCHROMIUM(path, 0, nullptr, 0, GL_FLOAT, nullptr);
EXPECT_TRUE(glIsPathCHROMIUM(path)); // The surprise.
TryAllDrawFunctions(path, GL_NO_ERROR);
glDeletePathsCHROMIUM(path, 1);
// Document a bit of an inconsistency: "clearing" a used path name causes
// path to acquire state.
path = glGenPathsCHROMIUM(1);
EXPECT_FALSE(glIsPathCHROMIUM(path));
glPathCommandsCHROMIUM(path, 0, nullptr, 0, GL_FLOAT, nullptr);
EXPECT_TRUE(glIsPathCHROMIUM(path)); // The surprise.
glDeletePathsCHROMIUM(path, 1);
// Make sure nothing got drawn by the drawing commands that should not produce
// anything.
const uint8_t black[] = {0, 0, 0, 0};
EXPECT_TRUE(GLTestHelper::CheckPixels(0, 0, kResolution, kResolution, 0,
black, nullptr));
}
TEST_F(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).
EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
GLubyte commands[] = {GL_MOVE_TO_CHROMIUM, GL_CLOSE_PATH_CHROMIUM};
GLfloat coords[] = {50.0f, 50.0f};
glPathCommandsCHROMIUM(555, base::size(commands), commands,
base::size(coords), GL_FLOAT, coords);
EXPECT_EQ(static_cast<GLenum>(GL_INVALID_OPERATION), glGetError());
// PathParameter*: Using non-existing path object produces error.
EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
glPathParameterfCHROMIUM(555, GL_PATH_STROKE_WIDTH_CHROMIUM, 5.0f);
EXPECT_EQ(static_cast<GLenum>(GL_INVALID_OPERATION), glGetError());
EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
glPathParameteriCHROMIUM(555, GL_PATH_JOIN_STYLE_CHROMIUM, GL_ROUND_CHROMIUM);
EXPECT_EQ(static_cast<GLenum>(GL_INVALID_OPERATION), glGetError());
}
TEST_F(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, base::size(commands), commands, -4, GL_FLOAT,
coords);
EXPECT_EQ(static_cast<GLenum>(GL_INVALID_VALUE), glGetError());
glPathCommandsCHROMIUM(path, -1, commands, base::size(coords), GL_FLOAT,
coords);
EXPECT_EQ(static_cast<GLenum>(GL_INVALID_VALUE), glGetError());
glPathCommandsCHROMIUM(path, base::size(commands), commands,
base::size(coords), kInvalidCoordType, coords);
EXPECT_EQ(static_cast<GLenum>(GL_INVALID_ENUM), glGetError());
// These can not distinquish between the check that should fail them.
// This should fail due to coord count * float size overflow.
glPathCommandsCHROMIUM(path, base::size(commands), commands,
std::numeric_limits<GLsizei>::max(), GL_FLOAT, coords);
// This should fail due to cmd count + coord count * short size.
glPathCommandsCHROMIUM(path, base::size(commands), commands,
std::numeric_limits<GLsizei>::max(), GL_SHORT, coords);
EXPECT_EQ(static_cast<GLenum>(GL_INVALID_OPERATION), glGetError());
glDeletePathsCHROMIUM(path, 1);
}
TEST_F(CHROMIUMPathRenderingTest, TestPathRenderingInvalidArgs) {
if (!IsApplicable())
return;
GLuint path = glGenPathsCHROMIUM(1);
glPathCommandsCHROMIUM(path, 0, nullptr, 0, GL_FLOAT, nullptr);
// Verify that normal calls work.
glStencilFillPathCHROMIUM(path, GL_COUNT_UP_CHROMIUM, 0x7F);
EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
glStencilThenCoverFillPathCHROMIUM(path, GL_COUNT_UP_CHROMIUM, 0x7F,
GL_BOUNDING_BOX_CHROMIUM);
EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
// Using invalid fill mode causes INVALID_ENUM.
glStencilFillPathCHROMIUM(path, GL_COUNT_UP_CHROMIUM - 1, 0x7F);
EXPECT_EQ(static_cast<GLenum>(GL_INVALID_ENUM), glGetError());
glStencilThenCoverFillPathCHROMIUM(path, GL_COUNT_UP_CHROMIUM - 1, 0x7F,
GL_BOUNDING_BOX_CHROMIUM);
EXPECT_EQ(static_cast<GLenum>(GL_INVALID_ENUM), glGetError());
// 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_EQ(static_cast<GLenum>(GL_INVALID_ENUM), glGetError());
// For instanced variants, we need this to error the same way
// regardless of whether # of paths == 0 would cause an early return.
for (int path_count = 0; path_count <= 1; ++path_count) {
SCOPED_TRACE(testing::Message()
<< "Invalid fillmode instanced test for path count "
<< path_count);
glStencilFillPathInstancedCHROMIUM(path_count, GL_UNSIGNED_INT, &path, 0,
GL_COUNT_UP_CHROMIUM - 1, 0x7F, GL_NONE,
nullptr);
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, nullptr);
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_EQ(static_cast<GLenum>(GL_INVALID_VALUE), glGetError());
glStencilThenCoverFillPathCHROMIUM(path, GL_COUNT_DOWN_CHROMIUM, 12,
GL_BOUNDING_BOX_CHROMIUM);
EXPECT_EQ(static_cast<GLenum>(GL_INVALID_VALUE), glGetError());
for (int path_count = 0; path_count <= 1; ++path_count) {
SCOPED_TRACE(testing::Message()
<< "Invalid mask instanced test for path count "
<< path_count);
glStencilFillPathInstancedCHROMIUM(path_count, GL_UNSIGNED_INT, &path, 0,
GL_COUNT_UP_CHROMIUM, 0x30, GL_NONE,
nullptr);
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, nullptr);
EXPECT_EQ(static_cast<GLenum>(GL_INVALID_VALUE), glGetError());
}
glDeletePathsCHROMIUM(path, 1);
}
// Tests that drawing with CHROMIUM_path_rendering functions work.
TEST_F(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(color_loc_, 1, kBlue);
glCoverFillPathCHROMIUM(path, GL_BOUNDING_BOX_CHROMIUM);
glStencilFunc(GL_EQUAL, 0x80, 0x80);
glStencilOp(GL_KEEP, GL_KEEP, GL_ZERO);
glUniform4fv(color_loc_, 1, kGreen);
glCoverStrokePathCHROMIUM(path, 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);
}
// Tests that drawing with CHROMIUM_path_rendering
// StencilThenCover{Stroke,Fill}Path functions work.
TEST_F(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(color_loc_, 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(color_loc_, 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);
}
// Tests that drawing with *Instanced functions work.
TEST_F(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 = base::size(kPaths);
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(color_loc_, 1, kBlue);
glStencilFillPathInstancedCHROMIUM(kPathCount, GL_UNSIGNED_INT, kPaths,
path - 1, GL_COUNT_UP_CHROMIUM, 0x7F,
GL_AFFINE_3D_CHROMIUM, kTransforms);
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);
glStencilFunc(GL_EQUAL, 0x80, 0x80);
glStencilOp(GL_KEEP, GL_KEEP, GL_ZERO);
glUniform4fv(color_loc_, 1, kGreen);
glCoverStrokePathInstancedCHROMIUM(kPathCount, GL_UNSIGNED_INT, kPaths,
path - 1,
GL_BOUNDING_BOX_OF_BOUNDING_BOXES_CHROMIUM,
GL_AFFINE_3D_CHROMIUM, kTransforms);
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_F(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 = base::size(kPaths);
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(color_loc_, 1, kGreen);
glStencilThenCoverStrokePathInstancedCHROMIUM(
kPathCount, GL_UNSIGNED_INT, kPaths, path - 1, 0x80, 0x80,
GL_BOUNDING_BOX_OF_BOUNDING_BOXES_CHROMIUM, GL_TRANSLATE_2D_CHROMIUM,
kTransforms);
glPathStencilFuncCHROMIUM(GL_ALWAYS, 0, 0x7F);
glStencilFunc(GL_LESS, 0, 0x7F);
glStencilOp(GL_KEEP, GL_KEEP, GL_ZERO);
glUniform4fv(color_loc_, 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);
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 CHROMIUMPathRenderingTest,
public ::testing::WithParamInterface<bool> {
protected:
void InitializeContextFeatures(GLManager::Options* options) override {
CHROMIUMPathRenderingTest::InitializeContextFeatures(options);
options->force_shader_name_hashing = GetParam();
}
/** 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 SetupProgramForTestPattern(const char* vertex_shader_source,
const char* fragment_shader_source) {
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);
GLuint vs =
GLTestHelper::LoadShader(GL_VERTEX_SHADER, vertex_shader_source);
GLuint fs =
GLTestHelper::LoadShader(GL_FRAGMENT_SHADER, fragment_shader_source);
program_ = glCreateProgram();
glBindAttribLocation(program_, kPositionLocation, "position");
glBindUniformLocationCHROMIUM(program_, kViewMatrixLocation, "view_matrix");
glBindUniformLocationCHROMIUM(program_, kColorMatrixLocation,
"color_matrix");
glBindUniformLocationCHROMIUM(program_, kModelTranslateLocation,
"model_translate");
glBindFragmentInputLocationCHROMIUM(program_, kColorFragmentInputLocation,
"color");
glAttachShader(program_, fs);
glAttachShader(program_, vs);
glDeleteShader(vs);
glDeleteShader(fs);
}
void LinkProgramForTestPattern() {
glLinkProgram(program_);
GLint linked = 0;
glGetProgramiv(program_, GL_LINK_STATUS, &linked);
EXPECT_TRUE(linked == GL_TRUE);
glUseProgram(program_);
glUniformMatrix4fv(kViewMatrixLocation, 1, GL_FALSE, kProjectionMatrix);
}
void DrawTestPattern() {
// Setup state for drawing the shape with DrawArrays.
// 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, base::size(kCommands), kCommands,
base::size(kCoords), GL_FLOAT, kCoords);
EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
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, i * kShapeWidth,
j * kShapeHeight);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
} else {
glEnable(GL_STENCIL_TEST);
path_model_translate[12] = i * kShapeWidth;
path_model_translate[13] = j * kShapeHeight;
glMatrixLoadfCHROMIUM(GL_PATH_MODELVIEW_CHROMIUM,
path_model_translate);
glStencilThenCoverFillPathCHROMIUM(path, GL_COUNT_UP_CHROMIUM, 0x7F,
GL_BOUNDING_BOX_CHROMIUM);
}
}
}
glDisableVertexAttribArray(kPositionLocation);
glDeleteBuffers(1, &vbo);
glDeletePathsCHROMIUM(path, 1);
}
void TeardownStateForTestPattern() { glDeleteProgram(program_); }
static const GLfloat kProjectionMatrix[16];
// 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;
enum {
kShapeWidth = 75,
kShapeHeight = 75,
kTestRows = kResolution / kShapeHeight,
kTestColumns = kResolution / kShapeWidth,
};
// These coordinates are inside the shape fill. This can be used to verífy
// fill color.
static const float kFillCoords[6];
GLint program_;
static const GLint kModelTranslateLocation = 3;
static const GLint kPositionLocation = 0;
static const GLint kViewMatrixLocation = 7;
};
const GLfloat CHROMIUMPathRenderingWithTexturingTest::kProjectionMatrix[16] = {
2.0f / (CHROMIUMPathRenderingWithTexturingTest::kResolution - 1),
0.0f,
0.0f,
0.0f,
0.0f,
2.0f / (CHROMIUMPathRenderingWithTexturingTest::kResolution - 1),
0.0f,
0.0f,
0.0f,
0.0f,
-1.0f,
0.0f,
-1.0f,
-1.0f,
0.0f,
1.0f};
const GLfloat CHROMIUMPathRenderingWithTexturingTest::kFillCoords[6] = {
59.0f, 50.0f, 50.0f, 28.0f, 66.0f, 63.0f};
// This test tests ProgramPathFragmentInputGenCHROMIUM and
// BindFragmentInputLocationCHROMIUM. The test draws a shape multiple times as a
// grid. Each shape is filled with a color pattern that has projection-space
// gradient of the fragment coordinates in r and g components of the color. The
// color slides as function of coordinates: x=0..kResolution --> r=0..1,
// y=0..kResolution --> g=0..1
TEST_P(CHROMIUMPathRenderingWithTexturingTest,
TestProgramPathFragmentInputGenCHROMIUM_EYE) {
if (!IsApplicable())
return;
// clang-format off
static const char* kVertexShaderSource = SHADER(
uniform mat4 view_matrix;
uniform mat4 color_matrix;
uniform vec2 model_translate;
attribute vec2 position;
varying vec3 color;
void main() {
vec4 p = vec4(model_translate + position, 1, 1);
color = (color_matrix * p).rgb;
gl_Position = view_matrix * p;
}
);
static const char* kFragmentShaderSource = SHADER(
precision mediump float;
varying vec3 color;
void main() {
gl_FragColor = vec4(color, 1.0);
}
);
// clang-format on
SetupProgramForTestPattern(kVertexShaderSource, kFragmentShaderSource);
LinkProgramForTestPattern();
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(program_, kColorFragmentInputLocation,
GL_EYE_LINEAR_CHROMIUM, 3,
kColorCoefficients);
EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
DrawTestPattern();
EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
for (int j = 0; j < kTestRows; ++j) {
for (int i = 0; i < kTestColumns; ++i) {
for (size_t k = 0; k < base::size(kFillCoords); k += 2) {
SCOPED_TRACE(testing::Message() << "Verifying fill for shape " << i
<< ", " << j << " coord " << k);
float fx = kFillCoords[k];
float fy = kFillCoords[k + 1];
float px = i * kShapeWidth;
float py = j * kShapeHeight;
uint8_t color[4];
color[0] = roundf((px + fx) / kResolution * 255.0f);
color[1] = roundf((py + fy) / kResolution * 255.0f);
color[2] = 0;
color[3] = 255;
EXPECT_TRUE(GLTestHelper::CheckPixels(px + fx, py + fy, 1, 1, 2, color,
nullptr));
}
}
}
TeardownStateForTestPattern();
EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
}
// This test tests ProgramPathFragmentInputGenCHROMIUM and
// BindFragmentInputLocationCHROMIUM, same as above test.
// Each shape is filled with a color pattern that has object-space
// gradient of the fragment coordinates in r and g components of the color. The
// color slides as function of object coordinates: x=0..kShapeWidth --> r=0..1,
// y=0..kShapeWidth --> g=0..1
TEST_P(CHROMIUMPathRenderingWithTexturingTest,
TestProgramPathFragmentInputGenCHROMIUM_OBJECT) {
if (!IsApplicable())
return;
// clang-format off
static const char* kVertexShaderSource = SHADER(
uniform mat4 view_matrix;
uniform mat4 color_matrix;
uniform vec2 model_translate;
attribute vec2 position;
varying vec3 color;
void main() {
color = (color_matrix * vec4(position, 1, 1)).rgb;
vec4 p = vec4(model_translate + position, 1, 1);
gl_Position = view_matrix * p;
}
);
static const char* kFragmentShaderSource = SHADER(
precision mediump float;
varying vec3 color;
void main() {
gl_FragColor = vec4(color.rgb, 1.0);
}
);
// clang-format on
SetupProgramForTestPattern(kVertexShaderSource, kFragmentShaderSource);
LinkProgramForTestPattern();
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(program_, kColorFragmentInputLocation,
GL_OBJECT_LINEAR_CHROMIUM, 3,
kColorCoefficients);
EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
DrawTestPattern();
EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
for (int j = 0; j < kTestRows; ++j) {
for (int i = 0; i < kTestColumns; ++i) {
for (size_t k = 0; k < base::size(kFillCoords); k += 2) {
SCOPED_TRACE(testing::Message() << "Verifying fill for shape " << i
<< ", " << j << " coord " << k);
float fx = kFillCoords[k];
float fy = kFillCoords[k + 1];
float px = i * kShapeWidth;
float py = j * kShapeHeight;
uint8_t color[4];
color[0] = roundf(fx / kShapeWidth * 255.0f);
color[1] = roundf(fy / kShapeHeight * 255.0f);
color[2] = 0;
color[3] = 255;
EXPECT_TRUE(GLTestHelper::CheckPixels(px + fx, py + fy, 1, 1, 2, color,
nullptr));
}
}
}
TeardownStateForTestPattern();
EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
}
TEST_P(CHROMIUMPathRenderingWithTexturingTest,
TestProgramPathFragmentInputGenArgs) {
if (!IsApplicable())
return;
// clang-format off
static const char* kVertexShaderSource = SHADER(
varying vec2 vec2_var; varying vec3 vec3_var; varying vec4 vec4_var;
varying float float_var; varying mat2 mat2_var; varying mat3 mat3_var;
varying mat4 mat4_var; attribute float avoid_opt; void main() {
vec2_var = vec2(1.0, 2.0 + avoid_opt);
vec3_var = vec3(1.0, 2.0, 3.0 + avoid_opt);
vec4_var = vec4(1.0, 2.0, 3.0, 4.0 + avoid_opt);
float_var = 5.0 + avoid_opt;
mat2_var = mat2(2.0 + avoid_opt);
mat3_var = mat3(3.0 + avoid_opt);
mat4_var = mat4(4.0 + avoid_opt);
gl_Position = vec4(1.0);
}
);
static const char* kFragmentShaderSource = SHADER(
precision mediump float; varying vec2 vec2_var; varying vec3 vec3_var;
varying vec4 vec4_var; varying float float_var; varying mat2 mat2_var;
varying mat3 mat3_var; varying mat4 mat4_var; void main() {
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);
}
);
// clang-format on
GLuint vs = GLTestHelper::LoadShader(GL_VERTEX_SHADER, kVertexShaderSource);
GLuint fs =
GLTestHelper::LoadShader(GL_FRAGMENT_SHADER, kFragmentShaderSource);
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},
};
GLint program = glCreateProgram();
for (size_t i = 0; i < sizeof(variables) / sizeof(variables[0]); ++i) {
glBindFragmentInputLocationCHROMIUM(program, variables[i].location,
variables[i].name);
}
glAttachShader(program, fs);
glAttachShader(program, vs);
glDeleteShader(vs);
glDeleteShader(fs);
// 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(program, -1, GL_NONE, 0, nullptr);
EXPECT_EQ(static_cast<GLenum>(GL_INVALID_OPERATION), glGetError());
glLinkProgram(program);
GLint linked = 0;
glGetProgramiv(program, GL_LINK_STATUS, &linked);
EXPECT_TRUE(linked == GL_TRUE);
glUseProgram(program);
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 ii = 0; ii < sizeof(variables) / sizeof(variables[0]); ++ii) {
for (GLint components = 0; components <= 4; ++components) {
for (size_t jj = 0; jj < base::size(kGenModes); ++jj) {
GLenum gen_mode = kGenModes[jj];
SCOPED_TRACE(testing::Message()
<< "Testing glProgramPathFragmentInputGenCHROMIUM "
<< "for fragment input '" << variables[ii].name
<< "' with " << variables[ii].components << " components "
<< " using genMode " << gen_mode << " and components "
<< components);
glProgramPathFragmentInputGenCHROMIUM(program, variables[ii].location,
gen_mode, components,
kCoefficients16);
if (components == 0 && gen_mode == GL_NONE) {
if (variables[ii].components == -1) {
// Clearing a fragment input that is not single-precision floating
// point scalar or vector is an invalid operation.
EXPECT_EQ(static_cast<GLenum>(GL_INVALID_OPERATION), glGetError());
} else {
// Clearing a valid fragment input is ok.
EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
}
} else if (components == 0 || gen_mode == GL_NONE) {
EXPECT_EQ(static_cast<GLenum>(GL_INVALID_VALUE), glGetError());
} else {
if (components == variables[ii].components) {
// Setting a generator for a single-precision floating point
// scalar or vector fragment input is ok.
EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
} else {
// Setting a generator when components do not match is an invalid
// operation.
EXPECT_EQ(static_cast<GLenum>(GL_INVALID_OPERATION), glGetError());
}
}
}
}
}
// 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.
enum {
kValidGenMode = GL_CONSTANT_CHROMIUM,
kValidComponents = 3,
kInvalidGenMode = 0xAB,
kInvalidComponents = 5,
};
glProgramPathFragmentInputGenCHROMIUM(program, -1, kValidGenMode,
kValidComponents, kCoefficients16);
EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
// 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(program, -1, kInvalidGenMode,
kValidComponents, kCoefficients16);
EXPECT_EQ(static_cast<GLenum>(GL_INVALID_ENUM), glGetError());
glProgramPathFragmentInputGenCHROMIUM(program, -1, kInvalidGenMode,
kInvalidComponents, kCoefficients16);
EXPECT_EQ(static_cast<GLenum>(GL_INVALID_ENUM), glGetError());
glProgramPathFragmentInputGenCHROMIUM(program, -1, kValidGenMode,
kInvalidComponents, kCoefficients16);
EXPECT_EQ(static_cast<GLenum>(GL_INVALID_VALUE), glGetError());
EXPECT_TRUE(glIsProgram(program));
glDeleteProgram(program);
EXPECT_FALSE(glIsProgram(program));
// Test that using invalid (deleted) program is an invalid operation.
glProgramPathFragmentInputGenCHROMIUM(program, -1, kValidGenMode,
kValidComponents, kCoefficients16);
EXPECT_EQ(static_cast<GLenum>(GL_INVALID_OPERATION), glGetError());
glProgramPathFragmentInputGenCHROMIUM(program, -1, kInvalidGenMode,
kValidComponents, kCoefficients16);
EXPECT_EQ(static_cast<GLenum>(GL_INVALID_OPERATION), glGetError());
glProgramPathFragmentInputGenCHROMIUM(program, -1, kInvalidGenMode,
kInvalidComponents, kCoefficients16);
EXPECT_EQ(static_cast<GLenum>(GL_INVALID_OPERATION), glGetError());
glProgramPathFragmentInputGenCHROMIUM(program, -1, kValidGenMode,
kInvalidComponents, kCoefficients16);
EXPECT_EQ(static_cast<GLenum>(GL_INVALID_OPERATION), glGetError());
}
// This test uses gl_FragCoord in a fragment shader. It is used to ensure
// that the internal implementation runs codepaths related to built-ins.
TEST_P(CHROMIUMPathRenderingWithTexturingTest,
TestProgramPathFragmentInputGenBuiltinInFragShader) {
if (!IsApplicable())
return;
static const int kColorLocation = 5;
static const int kFragColorLocation = 6;
// clang-format off
static const char* kVertexShaderSource = SHADER(
varying vec4 color;
void main() {
color = vec4(1.0);
gl_Position = vec4(1.0);
}
);
static const char* kFragmentShaderSource = SHADER(
precision mediump float;
varying vec4 color;
void main() {
gl_FragColor = gl_FragCoord + color;
}
);
// clang-format on
GLuint vs = GLTestHelper::LoadShader(GL_VERTEX_SHADER, kVertexShaderSource);
GLuint fs =
GLTestHelper::LoadShader(GL_FRAGMENT_SHADER, kFragmentShaderSource);
GLint program = glCreateProgram();
glBindFragmentInputLocationCHROMIUM(program, kColorLocation, "color");
EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
glBindFragmentInputLocationCHROMIUM(program, kFragColorLocation,
"gl_FragColor");
EXPECT_EQ(static_cast<GLenum>(GL_INVALID_OPERATION), glGetError());
glAttachShader(program, fs);
glAttachShader(program, vs);
glDeleteShader(vs);
glDeleteShader(fs);
glLinkProgram(program);
GLint linked = 0;
glGetProgramiv(program, GL_LINK_STATUS, &linked);
if (linked == 0) {
char buffer[1024];
GLsizei length = 0;
glGetProgramInfoLog(program, sizeof(buffer), &length, buffer);
std::string log(buffer, length);
EXPECT_EQ(1, linked) << "Error linking program: " << log;
glDeleteProgram(program);
program = 0;
}
ASSERT_EQ(GL_TRUE, linked);
glUseProgram(program);
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(
program, kColorLocation, GL_EYE_LINEAR_CHROMIUM, 4, kCoefficients16);
EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
glProgramPathFragmentInputGenCHROMIUM(
program, kFragColorLocation, GL_EYE_LINEAR_CHROMIUM, 4, kCoefficients16);
EXPECT_EQ(static_cast<GLenum>(GL_INVALID_OPERATION), glGetError());
}
TEST_P(CHROMIUMPathRenderingWithTexturingTest,
BindFragmentInputConflictsDetection) {
if (!IsApplicable())
return;
// clang-format off
static const char* kVertexShaderSource = SHADER(
attribute vec4 position;
varying vec4 colorA;
varying vec4 colorB;
void main()
{
gl_Position = position;
colorA = position + vec4(1);
colorB = position + vec4(2);
}
);
static const char* kFragmentShaderSource = SHADER(
precision mediump float;
varying vec4 colorA;
varying vec4 colorB;
void main()
{
gl_FragColor = colorA + colorB;
}
);
// clang-format on
const GLint kColorALocation = 3;
const GLint kColorBLocation = 4;
GLuint vertex_shader =
GLTestHelper::LoadShader(GL_VERTEX_SHADER, kVertexShaderSource);
GLuint fragment_shader =
GLTestHelper::LoadShader(GL_FRAGMENT_SHADER, kFragmentShaderSource);
GLuint program = glCreateProgram();
glAttachShader(program, vertex_shader);
glAttachShader(program, fragment_shader);
glBindFragmentInputLocationCHROMIUM(program, kColorALocation, "colorA");
// Bind colorB to location a, causing conflicts, link should fail.
glBindFragmentInputLocationCHROMIUM(program, kColorALocation, "colorB");
glLinkProgram(program);
GLint linked = 0;
glGetProgramiv(program, GL_LINK_STATUS, &linked);
EXPECT_EQ(0, linked);
// Bind colorB to location b, no conflicts, link should succeed.
glBindFragmentInputLocationCHROMIUM(program, kColorBLocation, "colorB");
glLinkProgram(program);
linked = 0;
glGetProgramiv(program, GL_LINK_STATUS, &linked);
EXPECT_EQ(1, linked);
GLTestHelper::CheckGLError("no errors", __LINE__);
}
// 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,
BindFragmentInputSimpleArrayHandling) {
if (!IsApplicable())
return;
// clang-format off
static const char* kVertexShaderSource = SHADER(
uniform mat4 view_matrix;
uniform mat4 color_matrix;
uniform vec2 model_translate;
attribute vec2 position;
varying vec4 color;
varying vec4 colorA[4];
void main()
{
vec4 p = vec4(model_translate + position, 1, 1);
gl_Position = view_matrix * p;
colorA[0] = vec4(0.0, 0.1, 0, 0.1);
colorA[1] = vec4(0.2);
colorA[2] = vec4(0.3);
colorA[3] = vec4(0.4);
color = vec4(10.0);
}
);
static const char* kFragmentShaderSource = SHADER(
precision mediump float;
varying vec4 color;
varying vec4 colorA[4];
void main()
{
gl_FragColor = colorA[0] * color;
}
);
// 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};
for (int pass = 0; pass < 2; ++pass) {
SetupProgramForTestPattern(kVertexShaderSource, kFragmentShaderSource);
if (pass == 0) {
glBindFragmentInputLocationCHROMIUM(program_, kUnusedLocation,
"colorA[0]");
glBindFragmentInputLocationCHROMIUM(program_, kColorA0Location, "colorA");
} else {
glBindFragmentInputLocationCHROMIUM(program_, kUnusedLocation, "colorA");
glBindFragmentInputLocationCHROMIUM(program_, kColorA0Location,
"colorA[0]");
}
LinkProgramForTestPattern();
glProgramPathFragmentInputGenCHROMIUM(program_, kColorA0Location,
GL_CONSTANT_CHROMIUM, 4, kColorA0);
glProgramPathFragmentInputGenCHROMIUM(program_, kColorFragmentInputLocation,
GL_CONSTANT_CHROMIUM, 4, kColor);
DrawTestPattern();
for (int j = 0; j < kTestRows; ++j) {
for (int i = 0; i < kTestColumns; ++i) {
for (size_t k = 0; k < base::size(kFillCoords); k += 2) {
SCOPED_TRACE(testing::Message() << "Verifying fill for shape " << i
<< ", " << j << " coord " << k);
float fx = kFillCoords[k];
float fy = kFillCoords[k + 1];
float px = i * kShapeWidth;
float py = j * kShapeHeight;
uint8_t color[4] = {0, 255, 0, 255};
EXPECT_TRUE(GLTestHelper::CheckPixels(px + fx, py + fy, 1, 1, 2,
color, nullptr));
}
}
}
TeardownStateForTestPattern();
}
EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
}
// Test binding with non-zero indices.
// Currently this is disabled, as the drivers seem to have a bug with the
// behavior.
TEST_P(CHROMIUMPathRenderingWithTexturingTest,
DISABLED_BindFragmentInputArrayHandling) {
if (!IsApplicable())
return;
// clang-format off
static const char* kVertexShaderSource = SHADER(
uniform mat4 view_matrix;
uniform mat4 color_matrix;
uniform vec2 model_translate;
attribute vec2 position;
varying vec4 color;
varying vec4 colorA[4];
void main()
{
vec4 p = vec4(model_translate + position, 1, 1);
gl_Position = view_matrix * p;
colorA[0] = vec4(0, 0.1, 0, 0.1);
colorA[1] = vec4(0, 1, 0, 1);
colorA[2] = vec4(0, 0.8, 0, 0.8);
colorA[3] = vec4(0, 0.5, 0, 0.5);
color = vec4(0.2);
}
);
static const char* kFragmentShaderSource = SHADER(
precision mediump float;
varying vec4 colorA[4];
varying vec4 color;
void main()
{
gl_FragColor = (colorA[0] * colorA[1]) +
colorA[2] + (colorA[3] * color);
}
);
// 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};
SetupProgramForTestPattern(kVertexShaderSource, kFragmentShaderSource);
glBindFragmentInputLocationCHROMIUM(program_, kUnusedLocation, "colorA[0]");
glBindFragmentInputLocationCHROMIUM(program_, kColorA1Location, "colorA[1]");
glBindFragmentInputLocationCHROMIUM(program_, kColorA2Location, "colorA[2]");
glBindFragmentInputLocationCHROMIUM(program_, kColorA3Location, "colorA[3]");
glBindFragmentInputLocationCHROMIUM(program_, kColorA0Location, "colorA");
LinkProgramForTestPattern();
glProgramPathFragmentInputGenCHROMIUM(program_, kColorA0Location,
GL_CONSTANT_CHROMIUM, 4, kColorA0);
glProgramPathFragmentInputGenCHROMIUM(program_, kColorA1Location,
GL_CONSTANT_CHROMIUM, 4, kColorA1);
glProgramPathFragmentInputGenCHROMIUM(program_, kColorA2Location,
GL_CONSTANT_CHROMIUM, 4, kColorA2);
glProgramPathFragmentInputGenCHROMIUM(program_, kColorA3Location,
GL_CONSTANT_CHROMIUM, 4, kColorA3);
glProgramPathFragmentInputGenCHROMIUM(program_, kColorFragmentInputLocation,
GL_CONSTANT_CHROMIUM, 4, kColor);
DrawTestPattern();
for (int j = 0; j < kTestRows; ++j) {
for (int i = 0; i < kTestColumns; ++i) {
for (size_t k = 0; k < base::size(kFillCoords); k += 2) {
SCOPED_TRACE(testing::Message() << "Verifying fill for shape " << i
<< ", " << j << " coord " << k);
float fx = kFillCoords[k];
float fy = kFillCoords[k + 1];
float px = i * kShapeWidth;
float py = j * kShapeHeight;
uint8_t color[4] = {0, 255, 0, 255};
EXPECT_TRUE(GLTestHelper::CheckPixels(px + fx, py + fy, 1, 1, 2, color,
nullptr));
}
}
}
EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
TeardownStateForTestPattern();
}
TEST_P(CHROMIUMPathRenderingWithTexturingTest, UnusedFragmentInputUpdate) {
if (!IsApplicable())
return;
// clang-format off
static const char* kVertexShaderString = SHADER(
attribute vec4 a_position;
void main() {
gl_Position = a_position;
}
);
static const char* kFragmentShaderString = SHADER(
precision mediump float;
uniform vec4 u_colorA;
uniform float u_colorU;
uniform vec4 u_colorC;
void main() {
gl_FragColor = u_colorA + u_colorC;
}
);
// clang-format on
const GLint kColorULocation = 1;
const GLint kNonexistingLocation = 5;
const GLint kUnboundLocation = 6;
GLuint vertex_shader =
GLTestHelper::LoadShader(GL_VERTEX_SHADER, kVertexShaderString);
GLuint fragment_shader =
GLTestHelper::LoadShader(GL_FRAGMENT_SHADER, kFragmentShaderString);
GLuint program = glCreateProgram();
glBindFragmentInputLocationCHROMIUM(program, kColorULocation, "u_colorU");
// The non-existing uniform should behave like existing, but optimized away
// uniform.
glBindFragmentInputLocationCHROMIUM(program, kNonexistingLocation,
"nonexisting");
// Let A and C be assigned automatic locations.
glAttachShader(program, vertex_shader);
glAttachShader(program, fragment_shader);
glLinkProgram(program);
GLint linked = 0;
glGetProgramiv(program, GL_LINK_STATUS, &linked);
EXPECT_EQ(1, linked);
glUseProgram(program);
GLfloat kColor[16] = {
0.0f,
};
// No errors on bound locations, since caller does not know
// if the driver optimizes them away or not.
glProgramPathFragmentInputGenCHROMIUM(program, kColorULocation,
GL_CONSTANT_CHROMIUM, 1, kColor);
EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
// No errors on bound locations of names that do not exist
// in the shader. Otherwise it would be inconsistent wrt the
// optimization case.
glProgramPathFragmentInputGenCHROMIUM(program, kNonexistingLocation,
GL_CONSTANT_CHROMIUM, 1, kColor);
EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
// The above are equal to updating -1.
glProgramPathFragmentInputGenCHROMIUM(program, -1, GL_CONSTANT_CHROMIUM, 1,
kColor);
EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
// No errors when updating with other type either.
// The type can not be known with the non-existing case.
glProgramPathFragmentInputGenCHROMIUM(program, kColorULocation,
GL_CONSTANT_CHROMIUM, 4, kColor);
EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
glProgramPathFragmentInputGenCHROMIUM(program, kNonexistingLocation,
GL_CONSTANT_CHROMIUM, 4, kColor);
EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
glProgramPathFragmentInputGenCHROMIUM(program, -1, GL_CONSTANT_CHROMIUM, 4,
kColor);
EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
// Updating an unbound, non-existing location still causes
// an error.
glProgramPathFragmentInputGenCHROMIUM(program, kUnboundLocation,
GL_CONSTANT_CHROMIUM, 4, kColor);
EXPECT_EQ(static_cast<GLenum>(GL_INVALID_OPERATION), glGetError());
}
INSTANTIATE_TEST_CASE_P(WithAndWithoutShaderNameMapping,
CHROMIUMPathRenderingWithTexturingTest,
::testing::Bool());
} // namespace gpu