blob: 071adbc6e71d3cdc00c5ac17a2c69d50ff8ad6ba [file] [log] [blame]
//
// Copyright 2020 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.
//
// ClipDistanceTest.cpp: Test cases for
// GL_APPLE_clip_distance / GL_EXT_clip_cull_distance / GL_ANGLE_clip_cull_distance extensions.
//
#include "test_utils/ANGLETest.h"
#include "test_utils/gl_raii.h"
#include "util/EGLWindow.h"
#include "util/test_utils.h"
using namespace angle;
class ClipDistanceAPPLETest : public ANGLETest<>
{
protected:
ClipDistanceAPPLETest()
{
setWindowWidth(64);
setWindowHeight(64);
setConfigRedBits(8);
setConfigGreenBits(8);
setConfigBlueBits(8);
setConfigAlphaBits(8);
setConfigDepthBits(24);
setExtensionsEnabled(false);
}
};
// Query max clip distances and enable, disable states of clip distances
TEST_P(ClipDistanceAPPLETest, StateQuery)
{
GLint maxClipDistances = 0;
glGetIntegerv(GL_MAX_CLIP_DISTANCES_APPLE, &maxClipDistances);
EXPECT_EQ(maxClipDistances, 0);
EXPECT_GL_ERROR(GL_INVALID_ENUM);
auto assertState = [](GLenum pname, bool valid, bool expectedState) {
EXPECT_EQ(glIsEnabled(pname), valid ? expectedState : false);
EXPECT_GL_ERROR(valid ? GL_NO_ERROR : GL_INVALID_ENUM);
GLboolean result = false;
glGetBooleanv(pname, &result);
EXPECT_EQ(result, valid ? expectedState : false);
EXPECT_GL_ERROR(valid ? GL_NO_ERROR : GL_INVALID_ENUM);
};
for (size_t i = 0; i < 8; i++)
{
assertState(GL_CLIP_DISTANCE0_APPLE + i, false, false);
glEnable(GL_CLIP_DISTANCE0_APPLE + i);
EXPECT_GL_ERROR(GL_INVALID_ENUM);
assertState(GL_CLIP_DISTANCE0_APPLE + i, false, false);
glDisable(GL_CLIP_DISTANCE0_APPLE + i);
EXPECT_GL_ERROR(GL_INVALID_ENUM);
assertState(GL_CLIP_DISTANCE0_APPLE + i, false, false);
}
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_APPLE_clip_distance"));
ASSERT_GL_NO_ERROR();
glGetIntegerv(GL_MAX_CLIP_DISTANCES_APPLE, &maxClipDistances);
EXPECT_EQ(maxClipDistances, 8);
EXPECT_GL_NO_ERROR();
for (size_t i = 0; i < 8; i++)
{
assertState(GL_CLIP_DISTANCE0_APPLE + i, true, false);
glEnable(GL_CLIP_DISTANCE0_APPLE + i);
EXPECT_GL_NO_ERROR();
assertState(GL_CLIP_DISTANCE0_APPLE + i, true, true);
glDisable(GL_CLIP_DISTANCE0_APPLE + i);
EXPECT_GL_NO_ERROR();
assertState(GL_CLIP_DISTANCE0_APPLE + i, true, false);
}
}
// Check that gl_ClipDistance is not defined for fragment shaders
TEST_P(ClipDistanceAPPLETest, FragmentShader)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_APPLE_clip_distance"));
constexpr char kVS[] = R"(
#extension GL_APPLE_clip_distance : require
void main()
{
gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
gl_ClipDistance[0] = gl_Position.w;
})";
constexpr char kFS[] = R"(
#extension GL_APPLE_clip_distance : require
void main()
{
gl_FragColor = vec4(gl_ClipDistance[0], 1.0, 1.0, 1.0);
})";
GLProgram prg;
prg.makeRaster(kVS, kFS);
EXPECT_FALSE(prg.valid());
}
// Check that gl_ClipDistance cannot be redeclared as a global
TEST_P(ClipDistanceAPPLETest, NotVarying)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_APPLE_clip_distance"));
constexpr char kVS[] = R"(
#extension GL_APPLE_clip_distance : require
highp float gl_ClipDistance[1];
void main()
{
gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
gl_ClipDistance[0] = gl_Position.w;
})";
GLProgram prg;
prg.makeRaster(kVS, essl1_shaders::fs::Red());
EXPECT_FALSE(prg.valid());
}
// Check that gl_ClipDistance size cannot be undefined
TEST_P(ClipDistanceAPPLETest, UndefinedArraySize)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_APPLE_clip_distance"));
constexpr char kVS[] = R"(
#extension GL_APPLE_clip_distance : require
void main()
{
gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
for (int i = 0; i < 8; i++)
{
gl_ClipDistance[i] = gl_Position.w;
}
})";
GLProgram prg;
prg.makeRaster(kVS, essl1_shaders::fs::Red());
EXPECT_FALSE(prg.valid());
}
// Check that gl_ClipDistance size cannot be more than maximum
TEST_P(ClipDistanceAPPLETest, OutOfRangeArraySize)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_APPLE_clip_distance"));
GLint maxClipDistances = 0;
glGetIntegerv(GL_MAX_CLIP_DISTANCES_APPLE, &maxClipDistances);
std::stringstream vsImplicit;
vsImplicit << R"(#extension GL_APPLE_clip_distance : require
void main()
{
gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
gl_ClipDistance[)"
<< maxClipDistances << R"(] = gl_Position.w;
})";
std::stringstream vsRedeclared;
vsRedeclared << R"(#extension GL_APPLE_clip_distance : require
varying highp float gl_ClipDistance[)"
<< (maxClipDistances + 1) << R"(];
void main()
{
gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
gl_ClipDistance[)"
<< (maxClipDistances - 1) << R"(] = gl_Position.w;
})";
std::stringstream vsRedeclaredInvalidIndex;
vsRedeclaredInvalidIndex << R"(#extension GL_APPLE_clip_distance : require
varying highp float gl_ClipDistance[)"
<< (maxClipDistances - 2) << R"(];
void main()
{
gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
gl_ClipDistance[)" << (maxClipDistances - 1)
<< R"(] = gl_Position.w;
})";
for (auto stream : {&vsImplicit, &vsRedeclared, &vsRedeclaredInvalidIndex})
{
GLProgram prg;
prg.makeRaster(stream->str().c_str(), essl1_shaders::fs::Red());
EXPECT_FALSE(prg.valid());
}
}
// Write to one gl_ClipDistance element
TEST_P(ClipDistanceAPPLETest, OneClipDistance)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_APPLE_clip_distance"));
constexpr char kVS[] = R"(
#extension GL_APPLE_clip_distance : require
uniform vec4 u_plane;
attribute vec2 a_position;
void main()
{
gl_Position = vec4(a_position, 0.0, 1.0);
gl_ClipDistance[0] = dot(gl_Position, u_plane);
})";
ANGLE_GL_PROGRAM(programRed, kVS, essl1_shaders::fs::Red());
glUseProgram(programRed);
ASSERT_GL_NO_ERROR();
glEnable(GL_CLIP_DISTANCE0_APPLE);
// Clear to blue
glClearColor(0, 0, 1, 1);
glClear(GL_COLOR_BUFFER_BIT);
// Draw full screen quad with color red
glUniform4f(glGetUniformLocation(programRed, "u_plane"), 1, 0, 0, 0.5);
EXPECT_GL_NO_ERROR();
drawQuad(programRed, "a_position", 0);
EXPECT_GL_NO_ERROR();
// All pixels on the left of the plane x = -0.5 must be blue
GLuint x = 0;
GLuint y = 0;
GLuint width = getWindowWidth() / 4 - 1;
GLuint height = getWindowHeight();
EXPECT_PIXEL_RECT_EQ(x, y, width, height, GLColor::blue);
// All pixels on the right of the plane x = -0.5 must be red
x = getWindowWidth() / 4 + 2;
y = 0;
width = getWindowWidth() - x;
height = getWindowHeight();
EXPECT_PIXEL_RECT_EQ(x, y, width, height, GLColor::red);
// Clear to green
glClearColor(0, 1, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
// Draw full screen quad with color red
glUniform4f(glGetUniformLocation(programRed, "u_plane"), -1, 0, 0, -0.5);
EXPECT_GL_NO_ERROR();
drawQuad(programRed, "a_position", 0);
EXPECT_GL_NO_ERROR();
// All pixels on the left of the plane x = -0.5 must be red
x = 0;
y = 0;
width = getWindowWidth() / 4 - 1;
height = getWindowHeight();
EXPECT_PIXEL_RECT_EQ(x, y, width, height, GLColor::red);
// All pixels on the right of the plane x = -0.5 must be green
x = getWindowWidth() / 4 + 2;
y = 0;
width = getWindowWidth() - x;
height = getWindowHeight();
EXPECT_PIXEL_RECT_EQ(x, y, width, height, GLColor::green);
// Disable GL_CLIP_DISTANCE
glDisable(GL_CLIP_DISTANCE0_APPLE);
drawQuad(programRed, "a_position", 0);
// All pixels must be red
x = 0;
y = 0;
width = getWindowWidth();
height = getWindowHeight();
EXPECT_PIXEL_RECT_EQ(x, y, width, height, GLColor::red);
}
// Write to each gl_ClipDistance element
TEST_P(ClipDistanceAPPLETest, EachClipDistance)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_APPLE_clip_distance"));
for (size_t i = 0; i < 8; i++)
{
std::stringstream vertexShaderStr;
vertexShaderStr << "#extension GL_APPLE_clip_distance : require\n"
<< "uniform vec4 u_plane;\n"
<< "attribute vec2 a_position;\n"
<< "void main()\n"
<< "{\n"
<< " gl_Position = vec4(a_position, 0.0, 1.0);\n"
<< " gl_ClipDistance[" << i << "] = dot(gl_Position, u_plane);\n"
<< "}";
ANGLE_GL_PROGRAM(programRed, vertexShaderStr.str().c_str(), essl1_shaders::fs::Red());
glUseProgram(programRed);
ASSERT_GL_NO_ERROR();
// Enable the current clip distance, disable all others.
for (size_t j = 0; j < 8; j++)
{
if (j == i)
glEnable(GL_CLIP_DISTANCE0_APPLE + j);
else
glDisable(GL_CLIP_DISTANCE0_APPLE + j);
}
// Clear to blue
glClearColor(0, 0, 1, 1);
glClear(GL_COLOR_BUFFER_BIT);
// Draw full screen quad with color red
glUniform4f(glGetUniformLocation(programRed, "u_plane"), 1, 0, 0, 0.5);
EXPECT_GL_NO_ERROR();
drawQuad(programRed, "a_position", 0);
EXPECT_GL_NO_ERROR();
// All pixels on the left of the plane x = -0.5 must be blue
GLuint x = 0;
GLuint y = 0;
GLuint width = getWindowWidth() / 4 - 1;
GLuint height = getWindowHeight();
EXPECT_PIXEL_RECT_EQ(x, y, width, height, GLColor::blue);
// All pixels on the right of the plane x = -0.5 must be red
x = getWindowWidth() / 4 + 2;
y = 0;
width = getWindowWidth() - x;
height = getWindowHeight();
EXPECT_PIXEL_RECT_EQ(x, y, width, height, GLColor::red);
// Clear to green
glClearColor(0, 1, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
// Draw full screen quad with color red
glUniform4f(glGetUniformLocation(programRed, "u_plane"), -1, 0, 0, -0.5);
EXPECT_GL_NO_ERROR();
drawQuad(programRed, "a_position", 0);
EXPECT_GL_NO_ERROR();
// All pixels on the left of the plane x = -0.5 must be red
x = 0;
y = 0;
width = getWindowWidth() / 4 - 1;
height = getWindowHeight();
EXPECT_PIXEL_RECT_EQ(x, y, width, height, GLColor::red);
// All pixels on the right of the plane x = -0.5 must be green
x = getWindowWidth() / 4 + 2;
y = 0;
width = getWindowWidth() - x;
height = getWindowHeight();
EXPECT_PIXEL_RECT_EQ(x, y, width, height, GLColor::green);
// Disable GL_CLIP_DISTANCE
glDisable(GL_CLIP_DISTANCE0_APPLE + i);
drawQuad(programRed, "a_position", 0);
// All pixels must be red
x = 0;
y = 0;
width = getWindowWidth();
height = getWindowHeight();
EXPECT_PIXEL_RECT_EQ(x, y, width, height, GLColor::red);
}
}
// Use 8 clip distances to draw an octagon
TEST_P(ClipDistanceAPPLETest, Octagon)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_APPLE_clip_distance"));
constexpr char kVS[] = R"(
#extension GL_APPLE_clip_distance : require
attribute vec2 a_position;
void main()
{
gl_Position = vec4(a_position, 0.0, 1.0);
gl_ClipDistance[0] = dot(gl_Position, vec4( 1, 0, 0, 0.5));
gl_ClipDistance[1] = dot(gl_Position, vec4(-1, 0, 0, 0.5));
gl_ClipDistance[2] = dot(gl_Position, vec4( 0, 1, 0, 0.5));
gl_ClipDistance[3] = dot(gl_Position, vec4( 0, -1, 0, 0.5));
gl_ClipDistance[4] = dot(gl_Position, vec4( 1, 1, 0, 0.70710678));
gl_ClipDistance[5] = dot(gl_Position, vec4( 1, -1, 0, 0.70710678));
gl_ClipDistance[6] = dot(gl_Position, vec4(-1, 1, 0, 0.70710678));
gl_ClipDistance[7] = dot(gl_Position, vec4(-1, -1, 0, 0.70710678));
})";
ANGLE_GL_PROGRAM(programRed, kVS, essl1_shaders::fs::Red());
glUseProgram(programRed);
ASSERT_GL_NO_ERROR();
glEnable(GL_CLIP_DISTANCE0_APPLE);
glEnable(GL_CLIP_DISTANCE1_APPLE);
glEnable(GL_CLIP_DISTANCE2_APPLE);
glEnable(GL_CLIP_DISTANCE3_APPLE);
glEnable(GL_CLIP_DISTANCE4_APPLE);
glEnable(GL_CLIP_DISTANCE5_APPLE);
glEnable(GL_CLIP_DISTANCE6_APPLE);
glEnable(GL_CLIP_DISTANCE7_APPLE);
// Clear to blue
glClearColor(0, 0, 1, 1);
glClear(GL_COLOR_BUFFER_BIT);
// Draw full screen quad with color red
drawQuad(programRed, "a_position", 0);
EXPECT_GL_NO_ERROR();
// Top edge
EXPECT_PIXEL_COLOR_EQ(32, 56, GLColor::blue);
EXPECT_PIXEL_COLOR_EQ(32, 40, GLColor::red);
// Top-right edge
EXPECT_PIXEL_COLOR_EQ(48, 48, GLColor::blue);
EXPECT_PIXEL_COLOR_EQ(40, 40, GLColor::red);
// Right edge
EXPECT_PIXEL_COLOR_EQ(56, 32, GLColor::blue);
EXPECT_PIXEL_COLOR_EQ(40, 32, GLColor::red);
// Bottom-right edge
EXPECT_PIXEL_COLOR_EQ(48, 16, GLColor::blue);
EXPECT_PIXEL_COLOR_EQ(40, 24, GLColor::red);
// Bottom edge
EXPECT_PIXEL_COLOR_EQ(32, 8, GLColor::blue);
EXPECT_PIXEL_COLOR_EQ(32, 24, GLColor::red);
// Bottom-left edge
EXPECT_PIXEL_COLOR_EQ(16, 16, GLColor::blue);
EXPECT_PIXEL_COLOR_EQ(24, 24, GLColor::red);
// Left edge
EXPECT_PIXEL_COLOR_EQ(8, 32, GLColor::blue);
EXPECT_PIXEL_COLOR_EQ(24, 32, GLColor::red);
// Top-left edge
EXPECT_PIXEL_COLOR_EQ(16, 48, GLColor::blue);
EXPECT_PIXEL_COLOR_EQ(24, 40, GLColor::red);
}
// Write to 3 clip distances
TEST_P(ClipDistanceAPPLETest, ThreeClipDistances)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_APPLE_clip_distance"));
constexpr char kVS[] = R"(
#extension GL_APPLE_clip_distance : require
uniform vec4 u_plane[3];
attribute vec2 a_position;
void main()
{
gl_Position = vec4(a_position, 0.0, 1.0);
gl_ClipDistance[0] = dot(gl_Position, u_plane[0]);
gl_ClipDistance[3] = dot(gl_Position, u_plane[1]);
gl_ClipDistance[7] = dot(gl_Position, u_plane[2]);
})";
ANGLE_GL_PROGRAM(programRed, kVS, essl1_shaders::fs::Red());
glUseProgram(programRed);
ASSERT_GL_NO_ERROR();
// Enable 3 clip distances
glEnable(GL_CLIP_DISTANCE0_APPLE);
glEnable(GL_CLIP_DISTANCE3_APPLE);
glEnable(GL_CLIP_DISTANCE7_APPLE);
ASSERT_GL_NO_ERROR();
// Clear to blue
glClearColor(0, 0, 1, 1);
glClear(GL_COLOR_BUFFER_BIT);
// Draw full screen quad with color red
// x = -0.5
glUniform4f(glGetUniformLocation(programRed, "u_plane[0]"), 1, 0, 0, 0.5);
// x = 0.5
glUniform4f(glGetUniformLocation(programRed, "u_plane[1]"), -1, 0, 0, 0.5);
// x + y = 1
glUniform4f(glGetUniformLocation(programRed, "u_plane[2]"), -1, -1, 0, 1);
EXPECT_GL_NO_ERROR();
drawQuad(programRed, "a_position", 0);
EXPECT_GL_NO_ERROR();
{
// All pixels on the left of the plane x = -0.5 must be blue
GLuint x = 0;
GLuint y = 0;
GLuint width = getWindowWidth() / 4 - 1;
GLuint height = getWindowHeight();
EXPECT_PIXEL_RECT_EQ(x, y, width, height, GLColor::blue);
// All pixels from the plane x = -0.5 to the plane x = 0 must be red
x = getWindowWidth() / 4 + 2;
y = 0;
width = getWindowWidth() / 2 - x;
height = getWindowHeight();
EXPECT_PIXEL_RECT_EQ(x, y, width, height, GLColor::red);
}
{
// Check pixels to the right of the plane x = 0
std::vector<GLColor> actualColors(getWindowWidth() * getWindowHeight());
glReadPixels(0, 0, getWindowWidth(), getWindowHeight(), GL_RGBA, GL_UNSIGNED_BYTE,
actualColors.data());
for (int y = 0; y < getWindowHeight(); ++y)
{
for (int x = getWindowWidth() / 2; x < getWindowWidth(); ++x)
{
const int currentPosition = y * getWindowHeight() + x;
if (x < getWindowWidth() * 3 / 2 - y - 1 && x < getWindowWidth() * 3 / 4 - 1)
{
// Bottom of the plane x + y = 1 clipped by x = 0.5 plane
EXPECT_EQ(GLColor::red, actualColors[currentPosition]);
}
else if (x > getWindowWidth() * 3 / 2 - y + 1 || x > getWindowWidth() * 3 / 4 + 1)
{
// Top of the plane x + y = 1 plus right of x = 0.5 plane
EXPECT_EQ(GLColor::blue, actualColors[currentPosition]);
}
}
}
}
// Clear to green
glClearColor(0, 1, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
// Disable gl_ClipDistance[3]
glDisable(GL_CLIP_DISTANCE3_APPLE);
// Draw full screen quad with color red
EXPECT_GL_NO_ERROR();
drawQuad(programRed, "a_position", 0);
EXPECT_GL_NO_ERROR();
{
// All pixels on the left of the plane x = -0.5 must be green
GLuint x = 0;
GLuint y = 0;
GLuint width = getWindowWidth() / 4 - 1;
GLuint height = getWindowHeight();
EXPECT_PIXEL_RECT_EQ(x, y, width, height, GLColor::green);
// All pixels from the plane x = -0.5 to the plane x = 0 must be red
x = getWindowWidth() / 4 + 2;
y = 0;
width = getWindowWidth() / 2 - x;
height = getWindowHeight();
EXPECT_PIXEL_RECT_EQ(x, y, width, height, GLColor::red);
}
// Check pixels to the right of the plane x = 0
std::vector<GLColor> actualColors(getWindowWidth() * getWindowHeight());
glReadPixels(0, 0, getWindowWidth(), getWindowHeight(), GL_RGBA, GL_UNSIGNED_BYTE,
actualColors.data());
for (int y = 0; y < getWindowHeight(); ++y)
{
for (int x = getWindowWidth() / 2; x < getWindowWidth(); ++x)
{
const int currentPosition = y * getWindowHeight() + x;
if (x < getWindowWidth() * 3 / 2 - y - 1)
{
// Bottom of the plane x + y = 1
EXPECT_EQ(GLColor::red, actualColors[currentPosition]);
}
else if (x > getWindowWidth() * 3 / 2 - y + 1)
{
// Top of the plane x + y = 1
EXPECT_EQ(GLColor::green, actualColors[currentPosition]);
}
}
}
}
// Redeclare gl_ClipDistance in shader with explicit size, also use it in a global function
// outside main()
TEST_P(ClipDistanceAPPLETest, ThreeClipDistancesRedeclared)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_APPLE_clip_distance"));
constexpr char kVS[] = R"(
#extension GL_APPLE_clip_distance : require
varying highp float gl_ClipDistance[3];
void computeClipDistances(in vec4 position, in vec4 plane[3])
{
gl_ClipDistance[0] = dot(position, plane[0]);
gl_ClipDistance[1] = dot(position, plane[1]);
gl_ClipDistance[2] = dot(position, plane[2]);
}
uniform vec4 u_plane[3];
attribute vec2 a_position;
void main()
{
gl_Position = vec4(a_position, 0.0, 1.0);
computeClipDistances(gl_Position, u_plane);
})";
ANGLE_GL_PROGRAM(programRed, kVS, essl1_shaders::fs::Red());
glUseProgram(programRed);
ASSERT_GL_NO_ERROR();
// Enable 3 clip distances
glEnable(GL_CLIP_DISTANCE0_APPLE);
glEnable(GL_CLIP_DISTANCE1_APPLE);
glEnable(GL_CLIP_DISTANCE2_APPLE);
ASSERT_GL_NO_ERROR();
// Clear to blue
glClearColor(0, 0, 1, 1);
glClear(GL_COLOR_BUFFER_BIT);
// Draw full screen quad with color red
// x = -0.5
glUniform4f(glGetUniformLocation(programRed, "u_plane[0]"), 1, 0, 0, 0.5);
// x = 0.5
glUniform4f(glGetUniformLocation(programRed, "u_plane[1]"), -1, 0, 0, 0.5);
// x + y = 1
glUniform4f(glGetUniformLocation(programRed, "u_plane[2]"), -1, -1, 0, 1);
EXPECT_GL_NO_ERROR();
drawQuad(programRed, "a_position", 0);
EXPECT_GL_NO_ERROR();
{
// All pixels on the left of the plane x = -0.5 must be blue
GLuint x = 0;
GLuint y = 0;
GLuint width = getWindowWidth() / 4 - 1;
GLuint height = getWindowHeight();
EXPECT_PIXEL_RECT_EQ(x, y, width, height, GLColor::blue);
// All pixels from the plane x = -0.5 to the plane x = 0 must be red
x = getWindowWidth() / 4 + 2;
y = 0;
width = getWindowWidth() / 2 - x;
height = getWindowHeight();
EXPECT_PIXEL_RECT_EQ(x, y, width, height, GLColor::red);
}
// Check pixels to the right of the plane x = 0
std::vector<GLColor> actualColors(getWindowWidth() * getWindowHeight());
glReadPixels(0, 0, getWindowWidth(), getWindowHeight(), GL_RGBA, GL_UNSIGNED_BYTE,
actualColors.data());
for (int y = 0; y < getWindowHeight(); ++y)
{
for (int x = getWindowWidth() / 2; x < getWindowWidth(); ++x)
{
const int currentPosition = y * getWindowHeight() + x;
if (x < getWindowWidth() * 3 / 2 - y - 1 && x < getWindowWidth() * 3 / 4 - 1)
{
// Bottom of the plane x + y = 1 clipped by x = 0.5 plane
EXPECT_EQ(GLColor::red, actualColors[currentPosition]);
}
else if (x > getWindowWidth() * 3 / 2 - y + 1 || x > getWindowWidth() * 3 / 4 + 1)
{
// Top of the plane x + y = 1 plus right of x = 0.5 plane
EXPECT_EQ(GLColor::blue, actualColors[currentPosition]);
}
}
}
}
using ClipCullDistanceTestParams = std::tuple<angle::PlatformParameters, bool>;
std::string PrintToStringParamName(const ::testing::TestParamInfo<ClipCullDistanceTestParams> &info)
{
std::stringstream ss;
ss << std::get<0>(info.param);
if (std::get<1>(info.param))
{
ss << "__EXT";
}
else
{
ss << "__ANGLE";
}
return ss.str();
}
class ClipCullDistanceTest : public ANGLETest<ClipCullDistanceTestParams>
{
protected:
const bool mCullDistanceSupportRequired;
const std::string kExtensionName;
ClipCullDistanceTest()
: mCullDistanceSupportRequired(::testing::get<1>(GetParam())),
kExtensionName(::testing::get<1>(GetParam()) ? "GL_EXT_clip_cull_distance"
: "GL_ANGLE_clip_cull_distance")
{
setWindowWidth(64);
setWindowHeight(64);
setConfigRedBits(8);
setConfigGreenBits(8);
setConfigBlueBits(8);
setConfigAlphaBits(8);
setConfigDepthBits(24);
setExtensionsEnabled(false);
}
};
// Query max clip distances and enable, disable states of clip distances
TEST_P(ClipCullDistanceTest, StateQuery)
{
GLint maxClipDistances = 0;
glGetIntegerv(GL_MAX_CLIP_DISTANCES_EXT, &maxClipDistances);
EXPECT_EQ(maxClipDistances, 0);
EXPECT_GL_ERROR(GL_INVALID_ENUM);
GLint maxCullDistances = 0;
glGetIntegerv(GL_MAX_CULL_DISTANCES_EXT, &maxCullDistances);
EXPECT_EQ(maxCullDistances, 0);
EXPECT_GL_ERROR(GL_INVALID_ENUM);
GLint maxCombinedClipAndCullDistances = 0;
glGetIntegerv(GL_MAX_COMBINED_CLIP_AND_CULL_DISTANCES_EXT, &maxCombinedClipAndCullDistances);
EXPECT_EQ(maxCombinedClipAndCullDistances, 0);
EXPECT_GL_ERROR(GL_INVALID_ENUM);
auto assertState = [](GLenum pname, bool valid, bool expectedState) {
EXPECT_EQ(glIsEnabled(pname), valid ? expectedState : false);
EXPECT_GL_ERROR(valid ? GL_NO_ERROR : GL_INVALID_ENUM);
GLboolean result = false;
glGetBooleanv(pname, &result);
EXPECT_EQ(result, valid ? expectedState : false);
EXPECT_GL_ERROR(valid ? GL_NO_ERROR : GL_INVALID_ENUM);
};
for (size_t i = 0; i < 8; i++)
{
assertState(GL_CLIP_DISTANCE0_EXT + i, false, false);
glEnable(GL_CLIP_DISTANCE0_EXT + i);
EXPECT_GL_ERROR(GL_INVALID_ENUM);
assertState(GL_CLIP_DISTANCE0_EXT + i, false, false);
glDisable(GL_CLIP_DISTANCE0_EXT + i);
EXPECT_GL_ERROR(GL_INVALID_ENUM);
assertState(GL_CLIP_DISTANCE0_EXT + i, false, false);
}
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled(kExtensionName));
ASSERT_GL_NO_ERROR();
glGetIntegerv(GL_MAX_CLIP_DISTANCES_EXT, &maxClipDistances);
EXPECT_GE(maxClipDistances, 8);
EXPECT_GL_NO_ERROR();
glGetIntegerv(GL_MAX_CULL_DISTANCES_EXT, &maxCullDistances);
if (mCullDistanceSupportRequired)
{
EXPECT_GE(maxCullDistances, 8);
}
else
{
EXPECT_TRUE(maxCullDistances == 0 || maxCullDistances >= 8);
}
EXPECT_GL_NO_ERROR();
glGetIntegerv(GL_MAX_COMBINED_CLIP_AND_CULL_DISTANCES_EXT, &maxCombinedClipAndCullDistances);
if (mCullDistanceSupportRequired)
{
EXPECT_GE(maxCombinedClipAndCullDistances, 8);
}
else
{
EXPECT_TRUE(maxCombinedClipAndCullDistances == 0 || maxCombinedClipAndCullDistances >= 8);
}
EXPECT_GL_NO_ERROR();
for (size_t i = 0; i < 8; i++)
{
assertState(GL_CLIP_DISTANCE0_EXT + i, true, false);
glEnable(GL_CLIP_DISTANCE0_EXT + i);
EXPECT_GL_NO_ERROR();
assertState(GL_CLIP_DISTANCE0_EXT + i, true, true);
glDisable(GL_CLIP_DISTANCE0_EXT + i);
EXPECT_GL_NO_ERROR();
assertState(GL_CLIP_DISTANCE0_EXT + i, true, false);
}
}
// Check that gl_ClipDistance and gl_CullDistance sizes cannot be undefined
TEST_P(ClipCullDistanceTest, UndefinedArraySize)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled(kExtensionName));
std::string kVSClip = R"(#version 300 es
#extension )" + kExtensionName +
R"( : require
void main()
{
gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
for (int i = 0; i < gl_MaxClipDistances; i++)
{
gl_ClipDistance[i] = gl_Position.w;
}
})";
std::string kVSCull = R"(#version 300 es
#extension )" + kExtensionName +
R"( : require
void main()
{
gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
for (int i = 0; i < gl_MaxCullDistances; i++)
{
gl_CullDistance[i] = gl_Position.w;
}
})";
for (auto vs : {kVSClip, kVSCull})
{
GLProgram prg;
prg.makeRaster(vs.c_str(), essl1_shaders::fs::Red());
EXPECT_FALSE(prg.valid());
}
}
// Check that shaders with invalid or missing storage qualifiers are rejected
TEST_P(ClipCullDistanceTest, StorageQualifiers)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled(kExtensionName));
std::stringstream vertexSource;
auto vs = [this, &vertexSource](std::string name, std::string qualifier) {
vertexSource.str(std::string());
vertexSource.clear();
vertexSource << "#version 300 es\n"
<< "#extension " << kExtensionName << " : require\n"
<< qualifier << " highp float " << name << "[1];\n"
<< "void main()\n"
<< "{\n"
<< " gl_Position = vec4(0.0, 0.0, 0.0, 1.0);\n"
<< " " << name << "[0] = 1.0;\n"
<< "}";
};
std::stringstream fragmentSource;
auto fs = [this, &fragmentSource](std::string name, std::string qualifier) {
fragmentSource.str(std::string());
fragmentSource.clear();
fragmentSource << "#version 300 es\n"
<< "#extension " << kExtensionName << " : require\n"
<< qualifier << " highp float " << name << "[1];\n"
<< "out highp vec4 my_FragColor;\n"
<< "void main()\n"
<< "{\n"
<< " my_FragColor = vec4(" << name << "[0], 0.0, 0.0, 1.0);\n"
<< "}";
};
auto checkProgram = [=, &vertexSource, &fragmentSource](std::string name,
std::string qualifierVertex,
std::string qualifierFragment) {
GLProgram program;
vs(name, qualifierVertex);
fs(name, qualifierFragment);
program.makeRaster(vertexSource.str().c_str(), fragmentSource.str().c_str());
return program.valid();
};
GLint maxClipDistances = 0;
glGetIntegerv(GL_MAX_CLIP_DISTANCES_EXT, &maxClipDistances);
ASSERT_GT(maxClipDistances, 0);
GLint maxCullDistances = 0;
glGetIntegerv(GL_MAX_CULL_DISTANCES_EXT, &maxCullDistances);
if (mCullDistanceSupportRequired)
{
ASSERT_GT(maxCullDistances, 0);
}
else
{
ASSERT_GE(maxCullDistances, 0);
}
std::pair<std::string, int> entries[2] = {{"gl_ClipDistance", maxClipDistances},
{"gl_CullDistance", maxCullDistances}};
for (auto entry : entries)
{
if (entry.second == 0)
continue;
EXPECT_TRUE(checkProgram(entry.first, "out", "in"));
EXPECT_FALSE(checkProgram(entry.first, "", ""));
EXPECT_FALSE(checkProgram(entry.first, "", "in"));
EXPECT_FALSE(checkProgram(entry.first, "", "out"));
EXPECT_FALSE(checkProgram(entry.first, "in", ""));
EXPECT_FALSE(checkProgram(entry.first, "in", "in"));
EXPECT_FALSE(checkProgram(entry.first, "in", "out"));
EXPECT_FALSE(checkProgram(entry.first, "out", ""));
EXPECT_FALSE(checkProgram(entry.first, "out", "out"));
}
}
// Check that array sizes cannot be more than maximum
TEST_P(ClipCullDistanceTest, OutOfRangeArraySize)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled(kExtensionName));
auto test = [this](std::string name, int maxSize) {
std::stringstream vsImplicit;
vsImplicit << R"(#version 300 es
#extension )"
<< kExtensionName << R"( : require
void main()
{
gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
)" << name << "["
<< maxSize << R"(] = gl_Position.w;
})";
std::stringstream vsRedeclared;
vsRedeclared << R"(#version 300 es
#extension )" << kExtensionName
<< R"( : require
out highp float )" << name
<< "[" << (maxSize + 1) << R"(];
void main()
{
gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
)" << name << "[" << (maxSize ? maxSize - 1 : 0)
<< R"(] = gl_Position.w;
})";
std::stringstream vsRedeclaredInvalidIndex;
vsRedeclaredInvalidIndex << R"(#version 300 es
#extension )" << kExtensionName << R"( : require
out highp float )" << name << "[" << (maxSize ? maxSize - 2 : 0)
<< R"(];
void main()
{
gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
)" << name << "[" << (maxSize ? maxSize - 1 : 0)
<< R"(] = gl_Position.w;
})";
for (auto stream : {&vsImplicit, &vsRedeclared, &vsRedeclaredInvalidIndex})
{
GLProgram prg;
prg.makeRaster(stream->str().c_str(), essl1_shaders::fs::Red());
EXPECT_FALSE(prg.valid());
}
};
GLint maxClipDistances = 0;
glGetIntegerv(GL_MAX_CLIP_DISTANCES_EXT, &maxClipDistances);
ASSERT_GT(maxClipDistances, 0);
GLint maxCullDistances = 0;
glGetIntegerv(GL_MAX_CULL_DISTANCES_EXT, &maxCullDistances);
if (mCullDistanceSupportRequired)
{
ASSERT_GT(maxCullDistances, 0);
}
else
{
ASSERT_GE(maxCullDistances, 0);
}
test("gl_ClipDistance", maxClipDistances);
test("gl_CullDistance", maxCullDistances);
}
// Check that shader validation enforces matching array sizes between shader stages
TEST_P(ClipCullDistanceTest, SizeCheck)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled(kExtensionName));
std::stringstream vertexSource;
auto vs = [this, &vertexSource](std::string name, bool declare, int size) {
vertexSource.str(std::string());
vertexSource.clear();
vertexSource << "#version 300 es\n";
vertexSource << "#extension " << kExtensionName << " : require\n";
if (declare)
{
ASSERT(size);
vertexSource << "out highp float " << name << "[" << size << "];\n";
}
vertexSource << "void main()\n";
vertexSource << "{\n";
vertexSource << " gl_Position = vec4(0.0, 0.0, 0.0, 1.0);\n";
if (size)
{
vertexSource << " " << name << "[" << (size - 1) << "] = 1.0;\n";
}
vertexSource << "}";
};
std::stringstream fragmentSource;
auto fs = [this, &fragmentSource](std::string name, bool declare, int size) {
fragmentSource.str(std::string());
fragmentSource.clear();
fragmentSource << "#version 300 es\n";
fragmentSource << "#extension " << kExtensionName << " : require\n";
if (declare)
{
ASSERT(size);
fragmentSource << "in highp float " << name << "[" << size << "];\n";
}
fragmentSource << "out highp vec4 my_FragColor;\n"
<< "void main()\n"
<< "{\n"
<< " my_FragColor = vec4(";
if (size)
{
fragmentSource << name << "[" << (size - 1) << "]";
}
else
{
fragmentSource << "1.0";
}
fragmentSource << ", 0.0, 0.0, 1.0);\n";
fragmentSource << "}\n";
};
auto checkProgram = [=, &vertexSource, &fragmentSource](std::string name, bool declareVertex,
int sizeVertex, bool declareFragment,
int sizeFragment) {
GLProgram program;
vs(name, declareVertex, sizeVertex);
fs(name, declareFragment, sizeFragment);
program.makeRaster(vertexSource.str().c_str(), fragmentSource.str().c_str());
return program.valid();
};
GLint maxClipDistances = 0;
glGetIntegerv(GL_MAX_CLIP_DISTANCES_EXT, &maxClipDistances);
ASSERT_GT(maxClipDistances, 0);
GLint maxCullDistances = 0;
glGetIntegerv(GL_MAX_CULL_DISTANCES_EXT, &maxCullDistances);
if (mCullDistanceSupportRequired)
{
ASSERT_GT(maxCullDistances, 0);
}
else
{
ASSERT_GE(maxCullDistances, 0);
}
std::pair<std::string, int> entries[2] = {{"gl_ClipDistance", maxClipDistances},
{"gl_CullDistance", maxCullDistances}};
for (auto entry : entries)
{
const std::string name = entry.first;
const int maxSize = entry.second;
// Any VS array size is valid when the value is not accessed in the fragment shader
for (int i = 1; i <= maxSize; i++)
{
EXPECT_TRUE(checkProgram(name, false, i, false, 0));
EXPECT_TRUE(checkProgram(name, true, i, false, 0));
}
// Any FS array size is invalid when the value is not written in the vertex shader
for (int i = 1; i <= maxSize; i++)
{
EXPECT_FALSE(checkProgram(name, false, 0, false, i));
EXPECT_FALSE(checkProgram(name, false, 0, true, i));
}
// Matching sizes are valid both for redeclared and implicitly sized arrays
for (int i = 1; i <= maxSize; i++)
{
EXPECT_TRUE(checkProgram(name, false, i, false, i));
EXPECT_TRUE(checkProgram(name, false, i, true, i));
EXPECT_TRUE(checkProgram(name, true, i, false, i));
EXPECT_TRUE(checkProgram(name, true, i, true, i));
}
// Non-matching sizes are invalid both for redeclared and implicitly sized arrays
for (int i = 2; i <= maxSize; i++)
{
EXPECT_FALSE(checkProgram(name, false, i - 1, false, i));
EXPECT_FALSE(checkProgram(name, false, i - 1, true, i));
EXPECT_FALSE(checkProgram(name, true, i - 1, false, i));
EXPECT_FALSE(checkProgram(name, true, i - 1, true, i));
EXPECT_FALSE(checkProgram(name, false, i, false, i - 1));
EXPECT_FALSE(checkProgram(name, false, i, true, i - 1));
EXPECT_FALSE(checkProgram(name, true, i, false, i - 1));
EXPECT_FALSE(checkProgram(name, true, i, true, i - 1));
}
// Out-of-range sizes are invalid
{
EXPECT_FALSE(checkProgram(name, false, 0, false, maxSize + 1));
EXPECT_FALSE(checkProgram(name, false, maxSize + 1, false, 0));
EXPECT_FALSE(checkProgram(name, false, maxSize + 1, false, maxSize + 1));
EXPECT_FALSE(checkProgram(name, false, 0, true, maxSize + 1));
EXPECT_FALSE(checkProgram(name, false, maxSize + 1, true, maxSize + 1));
EXPECT_FALSE(checkProgram(name, true, maxSize + 1, false, 0));
EXPECT_FALSE(checkProgram(name, true, maxSize + 1, false, maxSize + 1));
EXPECT_FALSE(checkProgram(name, true, maxSize + 1, true, maxSize + 1));
}
}
}
// Check that the sum of clip and cull distance array sizes is valid
TEST_P(ClipCullDistanceTest, SizeCheckCombined)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled(kExtensionName));
std::stringstream vertexSource;
auto vs = [this, &vertexSource](bool declareClip, int sizeClip, bool declareCull,
int sizeCull) {
vertexSource.str(std::string());
vertexSource.clear();
vertexSource << "#version 300 es\n";
vertexSource << "#extension " << kExtensionName << " : require\n";
if (declareClip)
{
ASSERT(sizeClip);
vertexSource << "out highp float gl_ClipDistance[" << sizeClip << "];\n";
}
if (declareCull)
{
ASSERT(sizeCull);
vertexSource << "out highp float gl_CullDistance[" << sizeCull << "];\n";
}
vertexSource << "void main()\n"
<< "{\n"
<< " gl_Position = vec4(0.0, 0.0, 0.0, 1.0);\n"
<< " gl_ClipDistance[" << (sizeClip - 1) << "] = 1.0;\n"
<< " gl_CullDistance[" << (sizeCull - 1) << "] = 1.0;\n"
<< "}";
};
std::stringstream fragmentSource;
auto fs = [this, &fragmentSource](bool declareClip, int sizeClip, bool declareCull,
int sizeCull) {
fragmentSource.str(std::string());
fragmentSource.clear();
fragmentSource << "#version 300 es\n";
fragmentSource << "#extension " << kExtensionName << " : require\n";
if (declareClip)
{
ASSERT(sizeClip);
fragmentSource << "in highp float gl_ClipDistance[" << sizeClip << "];\n";
}
if (declareCull)
{
ASSERT(sizeClip);
fragmentSource << "in highp float gl_CullDistance[" << sizeCull << "];\n";
}
fragmentSource << "out highp vec4 my_FragColor;\n"
<< "void main()\n"
<< "{\n"
<< " my_FragColor = vec4(\n"
<< " gl_ClipDistance[" << (sizeClip - 1) << "],\n"
<< " gl_CullDistance[" << (sizeCull - 1) << "],\n"
<< " 0.0, 1.0);\n"
<< "}\n";
};
auto checkProgram = [=, &vertexSource, &fragmentSource](
bool declareVertexClip, bool declareFragmentClip, int sizeClip,
bool declareVertexCull, bool declareFragmentCull, int sizeCull) {
GLProgram program;
vs(declareVertexClip, sizeClip, declareVertexCull, sizeCull);
fs(declareVertexClip, sizeClip, declareVertexCull, sizeCull);
program.makeRaster(vertexSource.str().c_str(), fragmentSource.str().c_str());
return program.valid();
};
GLint maxClipDistances = 0;
glGetIntegerv(GL_MAX_CLIP_DISTANCES_EXT, &maxClipDistances);
ASSERT_GT(maxClipDistances, 0);
GLint maxCullDistances = 0;
glGetIntegerv(GL_MAX_CULL_DISTANCES_EXT, &maxCullDistances);
if (mCullDistanceSupportRequired)
{
ASSERT_GT(maxCullDistances, 0);
}
else
{
ASSERT_GE(maxCullDistances, 0);
}
GLint maxCombinedClipAndCullDistances = 0;
glGetIntegerv(GL_MAX_COMBINED_CLIP_AND_CULL_DISTANCES_EXT, &maxCombinedClipAndCullDistances);
if (mCullDistanceSupportRequired)
{
ASSERT_GT(maxCombinedClipAndCullDistances, 0);
}
else
{
ASSERT_GE(maxCombinedClipAndCullDistances, 0);
}
for (int sizeClip = 1; sizeClip <= maxClipDistances; sizeClip++)
{
for (int sizeCull = 1; sizeCull <= maxCullDistances; sizeCull++)
{
// clang-format off
const bool valid = sizeClip + sizeCull <= maxCombinedClipAndCullDistances;
EXPECT_EQ(checkProgram(false, false, sizeClip, false, false, sizeCull), valid);
EXPECT_EQ(checkProgram(false, false, sizeClip, false, true, sizeCull), valid);
EXPECT_EQ(checkProgram(false, false, sizeClip, true, false, sizeCull), valid);
EXPECT_EQ(checkProgram(false, false, sizeClip, true, true, sizeCull), valid);
EXPECT_EQ(checkProgram(false, true, sizeClip, false, false, sizeCull), valid);
EXPECT_EQ(checkProgram(false, true, sizeClip, false, true, sizeCull), valid);
EXPECT_EQ(checkProgram(false, true, sizeClip, true, false, sizeCull), valid);
EXPECT_EQ(checkProgram(false, true, sizeClip, true, true, sizeCull), valid);
EXPECT_EQ(checkProgram(true, false, sizeClip, false, false, sizeCull), valid);
EXPECT_EQ(checkProgram(true, false, sizeClip, false, true, sizeCull), valid);
EXPECT_EQ(checkProgram(true, false, sizeClip, true, false, sizeCull), valid);
EXPECT_EQ(checkProgram(true, false, sizeClip, true, true, sizeCull), valid);
EXPECT_EQ(checkProgram(true, true, sizeClip, false, false, sizeCull), valid);
EXPECT_EQ(checkProgram(true, true, sizeClip, false, true, sizeCull), valid);
EXPECT_EQ(checkProgram(true, true, sizeClip, true, false, sizeCull), valid);
EXPECT_EQ(checkProgram(true, true, sizeClip, true, true, sizeCull), valid);
// clang-format on
}
}
}
// Test that declared but unused built-ins do not cause frontend failures. The internal uniform,
// which is used for passing GL state on some platforms, could be removed when built-ins are not
// accessed.
TEST_P(ClipCullDistanceTest, Unused)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled(kExtensionName));
std::stringstream vertexSource;
auto vs = [this, &vertexSource](std::string name) {
vertexSource.str(std::string());
vertexSource.clear();
vertexSource << "#version 300 es\n"
<< "#extension " << kExtensionName << " : require\n"
<< "out highp float " << name << "[8];\n"
<< "void main() { gl_Position = vec4(0.0, 0.0, 0.0, 1.0); }";
};
std::stringstream fragmentSource;
auto fs = [this, &fragmentSource](std::string name, bool declare) {
fragmentSource.str(std::string());
fragmentSource.clear();
fragmentSource << "#version 300 es\n";
fragmentSource << "#extension " << kExtensionName << " : require\n";
if (declare)
{
fragmentSource << "in highp float " << name << "[8];\n";
}
fragmentSource << "out highp vec4 my_FragColor;\n"
<< "void main() { my_FragColor = vec4(1.0, 0.0, 0.0, 1.0); }\n";
};
auto checkProgram = [=, &vertexSource, &fragmentSource](std::string name,
bool declareFragment) {
GLProgram program;
vs(name);
fs(name, declareFragment);
program.makeRaster(vertexSource.str().c_str(), fragmentSource.str().c_str());
return program.valid();
};
GLint maxClipDistances = 0;
glGetIntegerv(GL_MAX_CLIP_DISTANCES_EXT, &maxClipDistances);
ASSERT_GT(maxClipDistances, 0);
GLint maxCullDistances = 0;
glGetIntegerv(GL_MAX_CULL_DISTANCES_EXT, &maxCullDistances);
if (mCullDistanceSupportRequired)
{
ASSERT_GT(maxCullDistances, 0);
}
else
{
ASSERT_GE(maxCullDistances, 0);
}
std::pair<std::string, int> entries[2] = {{"gl_ClipDistance", maxClipDistances},
{"gl_CullDistance", maxCullDistances}};
for (auto entry : entries)
{
if (entry.second == 0)
continue;
EXPECT_TRUE(checkProgram(entry.first, false));
EXPECT_TRUE(checkProgram(entry.first, true));
}
}
// Test that unused gl_ClipDistance does not cause a translator crash
TEST_P(ClipCullDistanceTest, UnusedVertexVaryingNoCrash)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled(kExtensionName));
std::string kVS = R"(#version 300 es
#extension )" + kExtensionName +
R"( : require
precision highp float;
void main()
{
float r = gl_ClipDistance[1] + 0.5;
})";
GLProgram prg;
prg.makeRaster(kVS.c_str(), essl3_shaders::fs::Red());
EXPECT_TRUE(prg.valid());
}
// Test that unused gl_ClipDistance does not cause a translator crash
TEST_P(ClipCullDistanceTest, UnusedFragmentVaryingNoCrash)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled(kExtensionName));
std::string kFS = R"(#version 300 es
#extension )" + kExtensionName +
R"( : require
precision highp float;
out vec4 my_FragColor;
void main()
{
float r = gl_ClipDistance[1] + 0.5;
})";
GLProgram prg;
prg.makeRaster(essl3_shaders::vs::Simple(), kFS.c_str());
EXPECT_FALSE(prg.valid());
}
// Test that length() does not compile for unsized arrays
TEST_P(ClipCullDistanceTest, UnsizedArrayLength)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled(kExtensionName));
for (std::string name : {"gl_ClipDistance", "gl_CullDistance"})
{
std::stringstream vertexSource;
vertexSource << "#version 300 es\n"
<< "#extension " << kExtensionName << " : require\n"
<< "void main() { " << name << ".length(); }";
GLProgram program;
program.makeRaster(vertexSource.str().c_str(), essl3_shaders::fs::Red());
EXPECT_FALSE(program.valid()) << name;
}
}
// Test that length() returns correct values for sized arrays
TEST_P(ClipCullDistanceTest, SizedArrayLength)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled(kExtensionName));
std::stringstream vertexSource;
auto vs = [this, &vertexSource](std::string name, bool declare, int size) {
vertexSource.str(std::string());
vertexSource.clear();
vertexSource << "#version 300 es\n";
vertexSource << "#extension " << kExtensionName << " : require\n";
if (declare)
{
vertexSource << "out highp float " << name << "[" << size << "];\n";
}
vertexSource << "in vec4 a_position;\n"
<< "out float v_length;\n"
<< "void main()\n"
<< "{\n"
<< " gl_Position = a_position;\n"
<< " v_length = float(" << name << ".length()) / 16.0;\n";
// Assign all elements to avoid undefined behavior
for (int i = 0; i < size; ++i)
{
vertexSource << " " << name << "[" << i << "] = 1.0;\n";
}
vertexSource << "}";
};
std::string kFS = R"(#version 300 es
#extension )" + kExtensionName +
R"( : require
in mediump float v_length;
out mediump vec4 my_FragColor;
void main()
{
my_FragColor = vec4(v_length, 0.0, 0.0, 1.0);
})";
auto checkLength = [this, vs, kFS, &vertexSource](std::string name, bool declare, int size) {
GLProgram program;
vs(name, declare, size);
program.makeRaster(vertexSource.str().c_str(), kFS.c_str());
ASSERT_TRUE(program.valid()) << name;
glClear(GL_COLOR_BUFFER_BIT);
drawQuad(program, "a_position", 0);
EXPECT_PIXEL_NEAR(0, 0, size * 16, 0, 0, 255, 1);
};
GLint maxClipDistances = 0;
glGetIntegerv(GL_MAX_CLIP_DISTANCES_EXT, &maxClipDistances);
ASSERT_GT(maxClipDistances, 0);
GLint maxCullDistances = 0;
glGetIntegerv(GL_MAX_CULL_DISTANCES_EXT, &maxCullDistances);
if (mCullDistanceSupportRequired)
{
ASSERT_GT(maxCullDistances, 0);
}
else
{
ASSERT_GE(maxCullDistances, 0);
}
std::pair<std::string, int> entries[2] = {{"gl_ClipDistance", maxClipDistances},
{"gl_CullDistance", maxCullDistances}};
for (auto entry : entries)
{
const std::string name = entry.first;
const int maxSize = entry.second;
for (int i = 1; i <= maxSize; i++)
{
checkLength(name, false, i);
checkLength(name, true, i);
}
}
}
// Test that pruning clip/cull distance variables does not cause a translator crash
TEST_P(ClipCullDistanceTest, Pruned)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled(kExtensionName));
std::stringstream vertexSource;
auto vs = [this, &vertexSource](std::string name, bool doReturn) {
vertexSource.str(std::string());
vertexSource.clear();
vertexSource << "#version 300 es\n";
vertexSource << "#extension " << kExtensionName << " : require\n";
vertexSource << "void main()\n"
<< "{\n"
<< " " << (doReturn ? "return;\n" : "") << " " << name << "[1];\n";
vertexSource << "}";
};
std::stringstream fragmentSource;
auto fs = [this, &fragmentSource](std::string name) {
fragmentSource.str(std::string());
fragmentSource.clear();
fragmentSource << "#version 300 es\n";
fragmentSource << "#extension " << kExtensionName << " : require\n";
fragmentSource << "out mediump vec4 my_FragColor;\n"
<< "void main()\n"
<< "{\n"
<< " my_FragColor = vec4(" << name << "[1]);\n";
fragmentSource << "}";
};
auto checkPruning = [vs, fs, &vertexSource, &fragmentSource](std::string name, bool doReturn) {
GLProgram program;
vs(name, doReturn);
fs(name);
program.makeRaster(vertexSource.str().c_str(), fragmentSource.str().c_str());
ASSERT_TRUE(program.valid()) << name << (doReturn ? " after return" : "");
};
GLint maxClipDistances = 0;
glGetIntegerv(GL_MAX_CLIP_DISTANCES_EXT, &maxClipDistances);
ASSERT_GT(maxClipDistances, 0);
checkPruning("gl_ClipDistance", false);
checkPruning("gl_ClipDistance", true);
GLint maxCullDistances = 0;
glGetIntegerv(GL_MAX_CULL_DISTANCES_EXT, &maxCullDistances);
if (mCullDistanceSupportRequired)
{
ASSERT_GT(maxCullDistances, 0);
checkPruning("gl_CullDistance", false);
checkPruning("gl_CullDistance", true);
}
}
// Write to one gl_ClipDistance element
TEST_P(ClipCullDistanceTest, OneClipDistance)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled(kExtensionName));
std::string kVS = R"(#version 300 es
#extension )" + kExtensionName +
R"( : require
uniform vec4 u_plane;
in vec2 a_position;
void main()
{
gl_Position = vec4(a_position, 0.0, 1.0);
gl_ClipDistance[0] = dot(gl_Position, u_plane);
})";
ANGLE_GL_PROGRAM(programRed, kVS.c_str(), essl3_shaders::fs::Red());
glUseProgram(programRed);
ASSERT_GL_NO_ERROR();
glEnable(GL_CLIP_DISTANCE0_EXT);
// Clear to blue
glClearColor(0, 0, 1, 1);
glClear(GL_COLOR_BUFFER_BIT);
// Draw full screen quad with color red
glUniform4f(glGetUniformLocation(programRed, "u_plane"), 1, 0, 0, 0.5);
EXPECT_GL_NO_ERROR();
drawQuad(programRed, "a_position", 0);
EXPECT_GL_NO_ERROR();
// All pixels on the left of the plane x = -0.5 must be blue
GLuint x = 0;
GLuint y = 0;
GLuint width = getWindowWidth() / 4 - 1;
GLuint height = getWindowHeight();
EXPECT_PIXEL_RECT_EQ(x, y, width, height, GLColor::blue);
// All pixels on the right of the plane x = -0.5 must be red
x = getWindowWidth() / 4 + 2;
y = 0;
width = getWindowWidth() - x;
height = getWindowHeight();
EXPECT_PIXEL_RECT_EQ(x, y, width, height, GLColor::red);
// Clear to green
glClearColor(0, 1, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
// Draw full screen quad with color red
glUniform4f(glGetUniformLocation(programRed, "u_plane"), -1, 0, 0, -0.5);
EXPECT_GL_NO_ERROR();
drawQuad(programRed, "a_position", 0);
EXPECT_GL_NO_ERROR();
// All pixels on the left of the plane x = -0.5 must be red
x = 0;
y = 0;
width = getWindowWidth() / 4 - 1;
height = getWindowHeight();
EXPECT_PIXEL_RECT_EQ(x, y, width, height, GLColor::red);
// All pixels on the right of the plane x = -0.5 must be green
x = getWindowWidth() / 4 + 2;
y = 0;
width = getWindowWidth() - x;
height = getWindowHeight();
EXPECT_PIXEL_RECT_EQ(x, y, width, height, GLColor::green);
// Disable GL_CLIP_DISTANCE
glDisable(GL_CLIP_DISTANCE0_EXT);
drawQuad(programRed, "a_position", 0);
// All pixels must be red
x = 0;
y = 0;
width = getWindowWidth();
height = getWindowHeight();
EXPECT_PIXEL_RECT_EQ(x, y, width, height, GLColor::red);
}
// Write to each gl_ClipDistance element
TEST_P(ClipCullDistanceTest, EachClipDistance)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled(kExtensionName));
for (size_t i = 0; i < 8; i++)
{
std::stringstream vertexShaderStr;
vertexShaderStr << "#version 300 es\n"
<< "#extension " << kExtensionName << " : require\n"
<< "uniform vec4 u_plane;\n"
<< "in vec2 a_position;\n"
<< "void main()\n"
<< "{\n"
<< " gl_Position = vec4(a_position, 0.0, 1.0);\n"
<< " gl_ClipDistance[" << i << "] = dot(gl_Position, u_plane);\n"
<< "}";
ANGLE_GL_PROGRAM(programRed, vertexShaderStr.str().c_str(), essl3_shaders::fs::Red());
glUseProgram(programRed);
ASSERT_GL_NO_ERROR();
// Enable the current clip distance, disable all others.
for (size_t j = 0; j < 8; j++)
{
if (j == i)
glEnable(GL_CLIP_DISTANCE0_EXT + j);
else
glDisable(GL_CLIP_DISTANCE0_EXT + j);
}
// Clear to blue
glClearColor(0, 0, 1, 1);
glClear(GL_COLOR_BUFFER_BIT);
// Draw full screen quad with color red
glUniform4f(glGetUniformLocation(programRed, "u_plane"), 1, 0, 0, 0.5);
EXPECT_GL_NO_ERROR();
drawQuad(programRed, "a_position", 0);
EXPECT_GL_NO_ERROR();
// All pixels on the left of the plane x = -0.5 must be blue
GLuint x = 0;
GLuint y = 0;
GLuint width = getWindowWidth() / 4 - 1;
GLuint height = getWindowHeight();
EXPECT_PIXEL_RECT_EQ(x, y, width, height, GLColor::blue);
// All pixels on the right of the plane x = -0.5 must be red
x = getWindowWidth() / 4 + 2;
y = 0;
width = getWindowWidth() - x;
height = getWindowHeight();
EXPECT_PIXEL_RECT_EQ(x, y, width, height, GLColor::red);
// Clear to green
glClearColor(0, 1, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
// Draw full screen quad with color red
glUniform4f(glGetUniformLocation(programRed, "u_plane"), -1, 0, 0, -0.5);
EXPECT_GL_NO_ERROR();
drawQuad(programRed, "a_position", 0);
EXPECT_GL_NO_ERROR();
// All pixels on the left of the plane x = -0.5 must be red
x = 0;
y = 0;
width = getWindowWidth() / 4 - 1;
height = getWindowHeight();
EXPECT_PIXEL_RECT_EQ(x, y, width, height, GLColor::red);
// All pixels on the right of the plane x = -0.5 must be green
x = getWindowWidth() / 4 + 2;
y = 0;
width = getWindowWidth() - x;
height = getWindowHeight();
EXPECT_PIXEL_RECT_EQ(x, y, width, height, GLColor::green);
// Disable GL_CLIP_DISTANCE
glDisable(GL_CLIP_DISTANCE0_EXT + i);
drawQuad(programRed, "a_position", 0);
// All pixels must be red
x = 0;
y = 0;
width = getWindowWidth();
height = getWindowHeight();
EXPECT_PIXEL_RECT_EQ(x, y, width, height, GLColor::red);
}
}
// Use 8 clip distances to draw an octagon
TEST_P(ClipCullDistanceTest, Octagon)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled(kExtensionName));
std::string kVS = R"(#version 300 es
#extension )" + kExtensionName +
R"( : require
in vec2 a_position;
void main()
{
gl_Position = vec4(a_position, 0.0, 1.0);
gl_ClipDistance[0] = dot(gl_Position, vec4( 1, 0, 0, 0.5));
gl_ClipDistance[1] = dot(gl_Position, vec4(-1, 0, 0, 0.5));
gl_ClipDistance[2] = dot(gl_Position, vec4( 0, 1, 0, 0.5));
gl_ClipDistance[3] = dot(gl_Position, vec4( 0, -1, 0, 0.5));
gl_ClipDistance[4] = dot(gl_Position, vec4( 1, 1, 0, 0.70710678));
gl_ClipDistance[5] = dot(gl_Position, vec4( 1, -1, 0, 0.70710678));
gl_ClipDistance[6] = dot(gl_Position, vec4(-1, 1, 0, 0.70710678));
gl_ClipDistance[7] = dot(gl_Position, vec4(-1, -1, 0, 0.70710678));
})";
ANGLE_GL_PROGRAM(programRed, kVS.c_str(), essl3_shaders::fs::Red());
glUseProgram(programRed);
ASSERT_GL_NO_ERROR();
glEnable(GL_CLIP_DISTANCE0_EXT);
glEnable(GL_CLIP_DISTANCE1_EXT);
glEnable(GL_CLIP_DISTANCE2_EXT);
glEnable(GL_CLIP_DISTANCE3_EXT);
glEnable(GL_CLIP_DISTANCE4_EXT);
glEnable(GL_CLIP_DISTANCE5_EXT);
glEnable(GL_CLIP_DISTANCE6_EXT);
glEnable(GL_CLIP_DISTANCE7_EXT);
// Clear to blue
glClearColor(0, 0, 1, 1);
glClear(GL_COLOR_BUFFER_BIT);
// Draw full screen quad with color red
drawQuad(programRed, "a_position", 0);
EXPECT_GL_NO_ERROR();
// Top edge
EXPECT_PIXEL_COLOR_EQ(32, 56, GLColor::blue);
EXPECT_PIXEL_COLOR_EQ(32, 40, GLColor::red);
// Top-right edge
EXPECT_PIXEL_COLOR_EQ(48, 48, GLColor::blue);
EXPECT_PIXEL_COLOR_EQ(40, 40, GLColor::red);
// Right edge
EXPECT_PIXEL_COLOR_EQ(56, 32, GLColor::blue);
EXPECT_PIXEL_COLOR_EQ(40, 32, GLColor::red);
// Bottom-right edge
EXPECT_PIXEL_COLOR_EQ(48, 16, GLColor::blue);
EXPECT_PIXEL_COLOR_EQ(40, 24, GLColor::red);
// Bottom edge
EXPECT_PIXEL_COLOR_EQ(32, 8, GLColor::blue);
EXPECT_PIXEL_COLOR_EQ(32, 24, GLColor::red);
// Bottom-left edge
EXPECT_PIXEL_COLOR_EQ(16, 16, GLColor::blue);
EXPECT_PIXEL_COLOR_EQ(24, 24, GLColor::red);
// Left edge
EXPECT_PIXEL_COLOR_EQ(8, 32, GLColor::blue);
EXPECT_PIXEL_COLOR_EQ(24, 32, GLColor::red);
// Top-left edge
EXPECT_PIXEL_COLOR_EQ(16, 48, GLColor::blue);
EXPECT_PIXEL_COLOR_EQ(24, 40, GLColor::red);
}
// Write to 3 clip distances
TEST_P(ClipCullDistanceTest, ThreeClipDistances)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled(kExtensionName));
std::string kVS = R"(#version 300 es
#extension )" + kExtensionName +
R"( : require
uniform vec4 u_plane[3];
in vec2 a_position;
void main()
{
gl_Position = vec4(a_position, 0.0, 1.0);
gl_ClipDistance[0] = dot(gl_Position, u_plane[0]);
gl_ClipDistance[3] = dot(gl_Position, u_plane[1]);
gl_ClipDistance[7] = dot(gl_Position, u_plane[2]);
})";
ANGLE_GL_PROGRAM(programRed, kVS.c_str(), essl3_shaders::fs::Red());
glUseProgram(programRed);
ASSERT_GL_NO_ERROR();
// Enable 3 clip distances
glEnable(GL_CLIP_DISTANCE0_EXT);
glEnable(GL_CLIP_DISTANCE3_EXT);
glEnable(GL_CLIP_DISTANCE7_EXT);
ASSERT_GL_NO_ERROR();
// Clear to blue
glClearColor(0, 0, 1, 1);
glClear(GL_COLOR_BUFFER_BIT);
// Draw full screen quad with color red
// x = -0.5
glUniform4f(glGetUniformLocation(programRed, "u_plane[0]"), 1, 0, 0, 0.5);
// x = 0.5
glUniform4f(glGetUniformLocation(programRed, "u_plane[1]"), -1, 0, 0, 0.5);
// x + y = 1
glUniform4f(glGetUniformLocation(programRed, "u_plane[2]"), -1, -1, 0, 1);
EXPECT_GL_NO_ERROR();
drawQuad(programRed, "a_position", 0);
EXPECT_GL_NO_ERROR();
{
// All pixels on the left of the plane x = -0.5 must be blue
GLuint x = 0;
GLuint y = 0;
GLuint width = getWindowWidth() / 4 - 1;
GLuint height = getWindowHeight();
EXPECT_PIXEL_RECT_EQ(x, y, width, height, GLColor::blue);
// All pixels from the plane x = -0.5 to the plane x = 0 must be red
x = getWindowWidth() / 4 + 2;
y = 0;
width = getWindowWidth() / 2 - x;
height = getWindowHeight();
EXPECT_PIXEL_RECT_EQ(x, y, width, height, GLColor::red);
}
{
// Check pixels to the right of the plane x = 0
std::vector<GLColor> actualColors(getWindowWidth() * getWindowHeight());
glReadPixels(0, 0, getWindowWidth(), getWindowHeight(), GL_RGBA, GL_UNSIGNED_BYTE,
actualColors.data());
for (int y = 0; y < getWindowHeight(); ++y)
{
for (int x = getWindowWidth() / 2; x < getWindowWidth(); ++x)
{
const int currentPosition = y * getWindowHeight() + x;
if (x < getWindowWidth() * 3 / 2 - y - 1 && x < getWindowWidth() * 3 / 4 - 1)
{
// Bottom of the plane x + y = 1 clipped by x = 0.5 plane
EXPECT_EQ(GLColor::red, actualColors[currentPosition]);
}
else if (x > getWindowWidth() * 3 / 2 - y + 1 || x > getWindowWidth() * 3 / 4 + 1)
{
// Top of the plane x + y = 1 plus right of x = 0.5 plane
EXPECT_EQ(GLColor::blue, actualColors[currentPosition]);
}
}
}
}
// Clear to green
glClearColor(0, 1, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
// Disable gl_ClipDistance[3]
glDisable(GL_CLIP_DISTANCE3_EXT);
// Draw full screen quad with color red
EXPECT_GL_NO_ERROR();
drawQuad(programRed, "a_position", 0);
EXPECT_GL_NO_ERROR();
{
// All pixels on the left of the plane x = -0.5 must be green
GLuint x = 0;
GLuint y = 0;
GLuint width = getWindowWidth() / 4 - 1;
GLuint height = getWindowHeight();
EXPECT_PIXEL_RECT_EQ(x, y, width, height, GLColor::green);
// All pixels from the plane x = -0.5 to the plane x = 0 must be red
x = getWindowWidth() / 4 + 2;
y = 0;
width = getWindowWidth() / 2 - x;
height = getWindowHeight();
EXPECT_PIXEL_RECT_EQ(x, y, width, height, GLColor::red);
}
// Check pixels to the right of the plane x = 0
std::vector<GLColor> actualColors(getWindowWidth() * getWindowHeight());
glReadPixels(0, 0, getWindowWidth(), getWindowHeight(), GL_RGBA, GL_UNSIGNED_BYTE,
actualColors.data());
for (int y = 0; y < getWindowHeight(); ++y)
{
for (int x = getWindowWidth() / 2; x < getWindowWidth(); ++x)
{
const int currentPosition = y * getWindowHeight() + x;
if (x < getWindowWidth() * 3 / 2 - y - 1)
{
// Bottom of the plane x + y = 1
EXPECT_EQ(GLColor::red, actualColors[currentPosition]);
}
else if (x > getWindowWidth() * 3 / 2 - y + 1)
{
// Top of the plane x + y = 1
EXPECT_EQ(GLColor::green, actualColors[currentPosition]);
}
}
}
}
// Redeclare gl_ClipDistance in shader with explicit size, also use it in a global function
// outside main()
TEST_P(ClipCullDistanceTest, ThreeClipDistancesRedeclared)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled(kExtensionName));
std::string kVS = R"(#version 300 es
#extension )" + kExtensionName +
R"( : require
out highp float gl_ClipDistance[3];
void computeClipDistances(in vec4 position, in vec4 plane[3])
{
gl_ClipDistance[0] = dot(position, plane[0]);
gl_ClipDistance[1] = dot(position, plane[1]);
gl_ClipDistance[2] = dot(position, plane[2]);
}
uniform vec4 u_plane[3];
in vec2 a_position;
void main()
{
gl_Position = vec4(a_position, 0.0, 1.0);
computeClipDistances(gl_Position, u_plane);
})";
ANGLE_GL_PROGRAM(programRed, kVS.c_str(), essl3_shaders::fs::Red());
glUseProgram(programRed);
ASSERT_GL_NO_ERROR();
// Enable 3 clip distances
glEnable(GL_CLIP_DISTANCE0_EXT);
glEnable(GL_CLIP_DISTANCE1_EXT);
glEnable(GL_CLIP_DISTANCE2_EXT);
ASSERT_GL_NO_ERROR();
// Clear to blue
glClearColor(0, 0, 1, 1);
glClear(GL_COLOR_BUFFER_BIT);
// Draw full screen quad with color red
// x = -0.5
glUniform4f(glGetUniformLocation(programRed, "u_plane[0]"), 1, 0, 0, 0.5);
// x = 0.5
glUniform4f(glGetUniformLocation(programRed, "u_plane[1]"), -1, 0, 0, 0.5);
// x + y = 1
glUniform4f(glGetUniformLocation(programRed, "u_plane[2]"), -1, -1, 0, 1);
EXPECT_GL_NO_ERROR();
drawQuad(programRed, "a_position", 0);
EXPECT_GL_NO_ERROR();
{
// All pixels on the left of the plane x = -0.5 must be blue
GLuint x = 0;
GLuint y = 0;
GLuint width = getWindowWidth() / 4 - 1;
GLuint height = getWindowHeight();
EXPECT_PIXEL_RECT_EQ(x, y, width, height, GLColor::blue);
// All pixels from the plane x = -0.5 to the plane x = 0 must be red
x = getWindowWidth() / 4 + 2;
y = 0;
width = getWindowWidth() / 2 - x;
height = getWindowHeight();
EXPECT_PIXEL_RECT_EQ(x, y, width, height, GLColor::red);
}
// Check pixels to the right of the plane x = 0
std::vector<GLColor> actualColors(getWindowWidth() * getWindowHeight());
glReadPixels(0, 0, getWindowWidth(), getWindowHeight(), GL_RGBA, GL_UNSIGNED_BYTE,
actualColors.data());
for (int y = 0; y < getWindowHeight(); ++y)
{
for (int x = getWindowWidth() / 2; x < getWindowWidth(); ++x)
{
const int currentPosition = y * getWindowHeight() + x;
if (x < getWindowWidth() * 3 / 2 - y - 1 && x < getWindowWidth() * 3 / 4 - 1)
{
// Bottom of the plane x + y = 1 clipped by x = 0.5 plane
EXPECT_EQ(GLColor::red, actualColors[currentPosition]);
}
else if (x > getWindowWidth() * 3 / 2 - y + 1 || x > getWindowWidth() * 3 / 4 + 1)
{
// Top of the plane x + y = 1 plus right of x = 0.5 plane
EXPECT_EQ(GLColor::blue, actualColors[currentPosition]);
}
}
}
}
// Read clip distance varyings in fragment shaders
TEST_P(ClipCullDistanceTest, ClipInterpolation)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled(kExtensionName));
std::string kVS = R"(#version 300 es
#extension )" + kExtensionName +
R"( : require
in vec2 a_position;
void main()
{
gl_Position = vec4(a_position, 0.0, 1.0);
gl_ClipDistance[0] = dot(gl_Position, vec4( 1, 0, 0, 0.5));
gl_ClipDistance[1] = dot(gl_Position, vec4(-1, 0, 0, 0.5));
gl_ClipDistance[2] = dot(gl_Position, vec4( 0, 1, 0, 0.5));
gl_ClipDistance[3] = dot(gl_Position, vec4( 0, -1, 0, 0.5));
gl_ClipDistance[4] = gl_ClipDistance[0];
gl_ClipDistance[5] = gl_ClipDistance[1];
gl_ClipDistance[6] = gl_ClipDistance[2];
gl_ClipDistance[7] = gl_ClipDistance[3];
})";
std::string kFS = R"(#version 300 es
#extension )" + kExtensionName +
R"( : require
precision highp float;
out vec4 my_FragColor;
void main()
{
float r = gl_ClipDistance[0] + gl_ClipDistance[1];
float g = gl_ClipDistance[2] + gl_ClipDistance[3];
float b = gl_ClipDistance[4] + gl_ClipDistance[5];
float a = gl_ClipDistance[6] + gl_ClipDistance[7];
my_FragColor = vec4(r, g, b, a) * 0.5;
})";
ANGLE_GL_PROGRAM(programRed, kVS.c_str(), kFS.c_str());
glUseProgram(programRed);
ASSERT_GL_NO_ERROR();
glEnable(GL_CLIP_DISTANCE0_EXT);
glEnable(GL_CLIP_DISTANCE1_EXT);
glEnable(GL_CLIP_DISTANCE2_EXT);
glEnable(GL_CLIP_DISTANCE3_EXT);
glEnable(GL_CLIP_DISTANCE4_EXT);
glEnable(GL_CLIP_DISTANCE5_EXT);
glEnable(GL_CLIP_DISTANCE6_EXT);
glEnable(GL_CLIP_DISTANCE7_EXT);
// Clear to blue
glClearColor(0, 0, 1, 1);
glClear(GL_COLOR_BUFFER_BIT);
// Draw full screen quad with color red
drawQuad(programRed, "a_position", 0);
EXPECT_GL_NO_ERROR();
std::vector<GLColor> actualColors(getWindowWidth() * getWindowHeight());
glReadPixels(0, 0, getWindowWidth(), getWindowHeight(), GL_RGBA, GL_UNSIGNED_BYTE,
actualColors.data());
for (int x = 0; x < getWindowWidth(); x++)
{
for (int y = 0; y < getWindowHeight(); y++)
{
const int currentPosition = y * getWindowHeight() + x;
if (x >= getWindowWidth() / 4 && x < getWindowWidth() * 3 / 4 &&
y >= getWindowHeight() / 4 && y < getWindowHeight() * 3 / 4)
{
EXPECT_COLOR_NEAR(GLColor(127, 127, 127, 127), actualColors[currentPosition], 1);
}
else
{
EXPECT_EQ(GLColor::blue, actualColors[currentPosition]);
}
}
}
}
// Write to one gl_CullDistance element
TEST_P(ClipCullDistanceTest, OneCullDistance)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled(kExtensionName));
std::string kVS = R"(#version 300 es
#extension )" + kExtensionName +
R"( : require
uniform vec4 u_plane;
in vec2 a_position;
void main()
{
gl_Position = vec4(a_position, 0.0, 1.0);
gl_CullDistance[0] = dot(gl_Position, u_plane);
})";
GLProgram programRed;
programRed.makeRaster(kVS.c_str(), essl3_shaders::fs::Red());
if (!mCullDistanceSupportRequired)
{
GLint maxCullDistances;
glGetIntegerv(GL_MAX_CULL_DISTANCES_EXT, &maxCullDistances);
if (maxCullDistances == 0)
{
ASSERT_FALSE(programRed.valid());
return;
}
}
glUseProgram(programRed);
ASSERT_GL_NO_ERROR();
// Clear to blue
glClearColor(0, 0, 1, 1);
glClear(GL_COLOR_BUFFER_BIT);
// Draw full screen quad with color red
glUniform4f(glGetUniformLocation(programRed, "u_plane"), 1, 0, 0, 0.5);
EXPECT_GL_NO_ERROR();
drawQuad(programRed, "a_position", 0);
EXPECT_GL_NO_ERROR();
{
// All pixels must be red
GLuint x = 0;
GLuint y = 0;
GLuint width = getWindowWidth();
GLuint height = getWindowHeight();
EXPECT_PIXEL_RECT_EQ(x, y, width, height, GLColor::red);
}
// Clear to green
glClearColor(0, 1, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
// Draw full screen quad with color red
glUniform4f(glGetUniformLocation(programRed, "u_plane"), 1, 1, 0, 0);
EXPECT_GL_NO_ERROR();
drawQuad(programRed, "a_position", 0);
EXPECT_GL_NO_ERROR();
// All pixels on the plane y >= -x must be red
std::vector<GLColor> actualColors(getWindowWidth() * getWindowHeight());
glReadPixels(0, 0, getWindowWidth(), getWindowHeight(), GL_RGBA, GL_UNSIGNED_BYTE,
actualColors.data());
for (int x = 0; x < getWindowWidth(); ++x)
{
for (int y = 0; y < getWindowHeight(); ++y)
{
const int currentPosition = y * getWindowHeight() + x;
if ((x + y) >= 0)
{
EXPECT_EQ(GLColor::red, actualColors[currentPosition]);
}
else
{
EXPECT_EQ(GLColor::green, actualColors[currentPosition]);
}
}
}
}
// Read cull distance varyings in fragment shaders
TEST_P(ClipCullDistanceTest, CullInterpolation)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled(kExtensionName));
std::string kVS = R"(#version 300 es
#extension )" + kExtensionName +
R"( : require
in vec2 a_position;
void main()
{
gl_Position = vec4(a_position, 0.0, 1.0);
gl_CullDistance[0] = dot(gl_Position, vec4( 1, 0, 0, 1));
gl_CullDistance[1] = dot(gl_Position, vec4(-1, 0, 0, 1));
gl_CullDistance[2] = dot(gl_Position, vec4( 0, 1, 0, 1));
gl_CullDistance[3] = dot(gl_Position, vec4( 0, -1, 0, 1));
gl_CullDistance[4] = gl_CullDistance[0];
gl_CullDistance[5] = gl_CullDistance[1];
gl_CullDistance[6] = gl_CullDistance[2];
gl_CullDistance[7] = gl_CullDistance[3];
})";
std::string kFS = R"(#version 300 es
#extension )" + kExtensionName +
R"( : require
precision highp float;
out vec4 my_FragColor;
void main()
{
float r = gl_CullDistance[0] + gl_CullDistance[1];
float g = gl_CullDistance[2] + gl_CullDistance[3];
float b = gl_CullDistance[4] + gl_CullDistance[5];
float a = gl_CullDistance[6] + gl_CullDistance[7];
my_FragColor = vec4(r, g, b, a) * 0.25;
})";
GLProgram programRed;
programRed.makeRaster(kVS.c_str(), kFS.c_str());
if (!mCullDistanceSupportRequired)
{
GLint maxCullDistances;
glGetIntegerv(GL_MAX_CULL_DISTANCES_EXT, &maxCullDistances);
if (maxCullDistances == 0)
{
ASSERT_FALSE(programRed.valid());
return;
}
}
glUseProgram(programRed);
ASSERT_GL_NO_ERROR();
// Clear to blue
glClearColor(0, 0, 1, 1);
glClear(GL_COLOR_BUFFER_BIT);
// Draw full screen quad with color red
drawQuad(programRed, "a_position", 0, 0.5);
EXPECT_GL_NO_ERROR();
std::vector<GLColor> actualColors(getWindowWidth() * getWindowHeight());
glReadPixels(0, 0, getWindowWidth(), getWindowHeight(), GL_RGBA, GL_UNSIGNED_BYTE,
actualColors.data());
for (int x = 0; x < getWindowWidth(); x++)
{
for (int y = 0; y < getWindowHeight(); y++)
{
const int currentPosition = y * getWindowHeight() + x;
if (x >= getWindowWidth() / 4 && x < getWindowWidth() * 3 / 4 &&
y >= getWindowHeight() / 4 && y < getWindowHeight() * 3 / 4)
{
EXPECT_COLOR_NEAR(GLColor(127, 127, 127, 127), actualColors[currentPosition], 1);
}
else
{
EXPECT_EQ(GLColor::blue, actualColors[currentPosition]);
}
}
}
}
// Read both clip and cull distance varyings in fragment shaders
TEST_P(ClipCullDistanceTest, ClipCullInterpolation)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled(kExtensionName));
std::string kVS = R"(#version 300 es
#extension )" + kExtensionName +
R"( : require
in vec2 a_position;
void main()
{
gl_Position = vec4(a_position, 0.0, 1.0);
gl_ClipDistance[0] = dot(gl_Position, vec4( 1, 0, 0, 0.5));
gl_ClipDistance[1] = dot(gl_Position, vec4(-1, 0, 0, 0.5));
gl_ClipDistance[2] = dot(gl_Position, vec4( 0, 1, 0, 0.5));
gl_ClipDistance[3] = dot(gl_Position, vec4( 0, -1, 0, 0.5));
gl_CullDistance[0] = dot(gl_Position, vec4( 1, 0, 0, 1));
gl_CullDistance[1] = dot(gl_Position, vec4(-1, 0, 0, 1));
gl_CullDistance[2] = dot(gl_Position, vec4( 0, 1, 0, 1));
gl_CullDistance[3] = dot(gl_Position, vec4( 0, -1, 0, 1));
})";
std::string kFS = R"(#version 300 es
#extension )" + kExtensionName +
R"( : require
precision highp float;
out vec4 my_FragColor;
void main()
{
my_FragColor =
vec4(gl_ClipDistance[0] + gl_ClipDistance[1],
gl_ClipDistance[2] + gl_ClipDistance[3],
gl_CullDistance[0] + gl_CullDistance[1],
gl_CullDistance[2] + gl_CullDistance[3]) *
vec4(0.5, 0.5, 0.25, 0.25);
})";
GLProgram programRed;
programRed.makeRaster(kVS.c_str(), kFS.c_str());
if (!mCullDistanceSupportRequired)
{
GLint maxCullDistances;
glGetIntegerv(GL_MAX_CULL_DISTANCES_EXT, &maxCullDistances);
if (maxCullDistances == 0)
{
ASSERT_FALSE(programRed.valid());
return;
}
}
glUseProgram(programRed);
ASSERT_GL_NO_ERROR();
glEnable(GL_CLIP_DISTANCE0_EXT);
glEnable(GL_CLIP_DISTANCE1_EXT);
glEnable(GL_CLIP_DISTANCE2_EXT);
glEnable(GL_CLIP_DISTANCE3_EXT);
// Clear to blue
glClearColor(0, 0, 1, 1);
glClear(GL_COLOR_BUFFER_BIT);
// Draw full screen quad with color red
drawQuad(programRed, "a_position", 0);
EXPECT_GL_NO_ERROR();
std::vector<GLColor> actualColors(getWindowWidth() * getWindowHeight());
glReadPixels(0, 0, getWindowWidth(), getWindowHeight(), GL_RGBA, GL_UNSIGNED_BYTE,
actualColors.data());
for (int x = 0; x < getWindowWidth(); x++)
{
for (int y = 0; y < getWindowHeight(); y++)
{
const int currentPosition = y * getWindowHeight() + x;
if (x >= getWindowWidth() / 4 && x < getWindowWidth() * 3 / 4 &&
y >= getWindowHeight() / 4 && y < getWindowHeight() * 3 / 4)
{
EXPECT_COLOR_NEAR(GLColor(127, 127, 127, 127), actualColors[currentPosition], 1);
}
else
{
EXPECT_EQ(GLColor::blue, actualColors[currentPosition]);
}
}
}
}
// Write to 4 clip distances
TEST_P(ClipCullDistanceTest, FourClipDistances)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled(kExtensionName));
std::string kVS = R"(#version 300 es
#extension )" + kExtensionName +
R"( : require
in vec2 a_position;
uniform vec4 u_plane[4];
void main()
{
gl_Position = vec4(a_position, 0.0, 1.0);
gl_ClipDistance[0] = dot(gl_Position, u_plane[0]);
gl_ClipDistance[1] = dot(gl_Position, u_plane[1]);
gl_ClipDistance[2] = dot(gl_Position, u_plane[2]);
gl_ClipDistance[3] = dot(gl_Position, u_plane[3]);
})";
ANGLE_GL_PROGRAM(programRed, kVS.c_str(), essl3_shaders::fs::Red());
glUseProgram(programRed);
ASSERT_GL_NO_ERROR();
// Enable 3 clip distances
glEnable(GL_CLIP_DISTANCE0_EXT);
glEnable(GL_CLIP_DISTANCE2_EXT);
glEnable(GL_CLIP_DISTANCE3_EXT);
ASSERT_GL_NO_ERROR();
// Disable 1 clip distances
glDisable(GL_CLIP_DISTANCE1_EXT);
ASSERT_GL_NO_ERROR();
// Clear to blue
glClearColor(0, 0, 1, 1);
glClear(GL_COLOR_BUFFER_BIT);
constexpr unsigned int kNumVertices = 12;
const std::array<Vector3, kNumVertices> quadVertices = {
{Vector3(-1.0f, 1.0f, 0.0f), Vector3(0.0f, 0.0f, 0.0f), Vector3(1.0f, 1.0f, 0.0f),
Vector3(1.0f, 1.0f, 0.0f), Vector3(0.0f, 0.0f, 0.0f), Vector3(1.0f, -1.0f, 0.0f),
Vector3(1.0f, -1.0f, 0.0f), Vector3(0.0f, 0.0f, 0.0f), Vector3(-1.0f, -1.0f, 0.0f),
Vector3(-1.0f, -1.0f, 0.0f), Vector3(0.0f, 0.0f, 0.0f), Vector3(-1.0f, 1.0f, 0.0f)}};
GLBuffer vertexBuffer;
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 3 * kNumVertices, quadVertices.data(),
GL_STATIC_DRAW);
ASSERT_GL_NO_ERROR();
GLint positionLocation = glGetAttribLocation(programRed, "a_position");
glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
ASSERT_GL_NO_ERROR();
glEnableVertexAttribArray(positionLocation);
ASSERT_GL_NO_ERROR();
// Draw full screen quad and small size triangle with color red
// y <= 1.0f
glUniform4f(glGetUniformLocation(programRed, "u_plane[0]"), 0, -1, 0, 1);
// y >= 0.5f
glUniform4f(glGetUniformLocation(programRed, "u_plane[1]"), 0, 1, 0, -0.5);
// y >= 3x-0.5f
glUniform4f(glGetUniformLocation(programRed, "u_plane[2]"), -3, 1, 0, 0.5);
// y >= -3x-0.5f
glUniform4f(glGetUniformLocation(programRed, "u_plane[3]"), 3, 1, 0, 0.5);
EXPECT_GL_NO_ERROR();
glDrawArrays(GL_TRIANGLES, 0, kNumVertices);
EXPECT_GL_NO_ERROR();
const int windowWidth = getWindowWidth();
const int windowHeight = getWindowHeight();
auto checkLeftPlaneFunc = [windowWidth, windowHeight](int x, int y) -> float {
return (3 * (x - (windowWidth / 2 - 1)) - (windowHeight / 4 + 1) + y);
};
auto checkRightPlaneFunc = [windowWidth, windowHeight](int x, int y) -> float {
return (-3 * (x - (windowWidth / 2)) - (windowHeight / 4 + 1) + y);
};
// Only pixels in the triangle must be red
std::vector<GLColor> actualColors(getWindowWidth() * getWindowHeight());
glReadPixels(0, 0, getWindowWidth(), getWindowHeight(), GL_RGBA, GL_UNSIGNED_BYTE,
actualColors.data());
for (int x = 0; x < getWindowWidth(); ++x)
{
for (int y = 0; y < getWindowHeight(); ++y)
{
// The drawing method of Swiftshader and Native graphic card is different. So the
// compare function doesn't check the value on the line.
const int currentPosition = y * getWindowHeight() + x;
if (checkLeftPlaneFunc(x, y) > 0 && checkRightPlaneFunc(x, y) > 0)
{
EXPECT_EQ(GLColor::red, actualColors[currentPosition]);
}
else if (checkLeftPlaneFunc(x, y) < 0 || checkRightPlaneFunc(x, y) < 0)
{
EXPECT_EQ(GLColor::blue, actualColors[currentPosition]);
}
}
}
}
// Write to 4 cull distances
TEST_P(ClipCullDistanceTest, FourCullDistances)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled(kExtensionName));
// SwiftShader bug: http://anglebug.com/42263990
ANGLE_SKIP_TEST_IF(isSwiftshader());
std::string kVS = R"(#version 300 es
#extension )" + kExtensionName +
R"( : require
uniform vec4 u_plane[4];
in vec2 a_position;
void main()
{
gl_Position = vec4(a_position, 0.0, 1.0);
gl_CullDistance[0] = dot(gl_Position, u_plane[0]);
gl_CullDistance[1] = dot(gl_Position, u_plane[1]);
gl_CullDistance[2] = dot(gl_Position, u_plane[2]);
gl_CullDistance[3] = dot(gl_Position, u_plane[3]);
})";
GLProgram programRed;
programRed.makeRaster(kVS.c_str(), essl3_shaders::fs::Red());
if (!mCullDistanceSupportRequired)
{
GLint maxCullDistances;
glGetIntegerv(GL_MAX_CULL_DISTANCES_EXT, &maxCullDistances);
if (maxCullDistances == 0)
{
ASSERT_FALSE(programRed.valid());
return;
}
}
glUseProgram(programRed);
ASSERT_GL_NO_ERROR();
// Clear to blue
glClearColor(0, 0, 1, 1);
glClear(GL_COLOR_BUFFER_BIT);
constexpr unsigned int kNumVertices = 12;
const std::array<Vector2, kNumVertices> quadVertices = {
{Vector2(-1.0f, 1.0f), Vector2(0.0f, 0.0f), Vector2(1.0f, 1.0f), Vector2(1.0f, 1.0f),
Vector2(0.0f, 0.0f), Vector2(1.0f, -1.0f), Vector2(1.0f, -1.0f), Vector2(0.0f, 0.0f),
Vector2(-1.0f, -1.0f), Vector2(-1.0f, -1.0f), Vector2(0.0f, 0.0f), Vector2(-1.0f, 1.0f)}};
GLBuffer vertexBuffer;
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 2 * kNumVertices, quadVertices.data(),
GL_STATIC_DRAW);
ASSERT_GL_NO_ERROR();
GLint positionLocation = glGetAttribLocation(programRed, "a_position");
glVertexAttribPointer(positionLocation, 2, GL_FLOAT, GL_FALSE, 0, 0);
ASSERT_GL_NO_ERROR();
glEnableVertexAttribArray(positionLocation);
ASSERT_GL_NO_ERROR();
// Draw full screen quad and small size triangle with color red
// y <= 1.0f
glUniform4f(glGetUniformLocation(programRed, "u_plane[0]"), 0, -1, 0, 1);
// y >= 0.5f
glUniform4f(glGetUniformLocation(programRed, "u_plane[1]"), 0, 1, 0, -0.5);
// y >= 3x-0.5f
glUniform4f(glGetUniformLocation(programRed, "u_plane[2]"), -3, 1, 0, 0.5);
// y >= -3x-0.5f
glUniform4f(glGetUniformLocation(programRed, "u_plane[3]"), 3, 1, 0, 0.5);
EXPECT_GL_NO_ERROR();
glDrawArrays(GL_TRIANGLES, 0, kNumVertices);
EXPECT_GL_NO_ERROR();
// Only the bottom triangle must be culled
std::vector<GLColor> actualColors(getWindowWidth() * getWindowHeight());
glReadPixels(0, 0, getWindowWidth(), getWindowHeight(), GL_RGBA, GL_UNSIGNED_BYTE,
actualColors.data());
for (int x = 0; x < getWindowWidth(); ++x)
{
for (int y = 0; y < getWindowHeight(); ++y)
{
const int currentPosition = y * getWindowHeight() + x;
if (y > x || y >= -x + getWindowHeight() - 1)
{
EXPECT_EQ(GLColor::red, actualColors[currentPosition]);
}
else
{
EXPECT_EQ(GLColor::blue, actualColors[currentPosition]);
}
}
}
}
// Verify that EXT_clip_cull_distance works with EXT_geometry_shader
TEST_P(ClipCullDistanceTest, ClipDistanceInteractWithGeometryShader)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled(kExtensionName) ||
!EnsureGLExtensionEnabled("GL_EXT_geometry_shader"));
std::string kVS = R"(#version 310 es
#extension )" + kExtensionName +
R"( : require
#extension GL_EXT_geometry_shader : require
in vec2 a_position;
uniform vec4 u_plane[4];
void main()
{
gl_Position = vec4(a_position, 0.0, 1.0);
gl_ClipDistance[0] = dot(gl_Position, u_plane[0]);
gl_ClipDistance[1] = dot(gl_Position, u_plane[1]);
gl_ClipDistance[2] = dot(gl_Position, u_plane[2]);
gl_ClipDistance[3] = dot(gl_Position, u_plane[3]);
})";
std::string kGS = R"(#version 310 es
#extension )" + kExtensionName +
R"( : require
#extension GL_EXT_geometry_shader : require
layout (triangles) in;
layout (triangle_strip, max_vertices = 3) out;
in gl_PerVertex {
highp vec4 gl_Position;
highp float gl_ClipDistance[];
} gl_in[];
out gl_PerVertex {
highp vec4 gl_Position;
highp float gl_ClipDistance[];
};
uniform vec4 u_plane[4];
void GetNewPosition(int i)
{
gl_Position = 2.0f * gl_in[i].gl_Position;
for (int index = 0 ; index < 4 ; index++)
{
if (gl_in[i].gl_ClipDistance[index] < 0.0f)
{
gl_ClipDistance[index] = dot(gl_Position, u_plane[index]);
}
}
EmitVertex();
}
void main()
{
for (int i = 0 ; i < 3 ; i++)
{
GetNewPosition(i);
}
EndPrimitive();
})";
ANGLE_GL_PROGRAM_WITH_GS(programRed, kVS.c_str(), kGS.c_str(), essl31_shaders::fs::Red());
glUseProgram(programRed);
ASSERT_GL_NO_ERROR();
// Enable 3 clip distances
glEnable(GL_CLIP_DISTANCE0_EXT);
glEnable(GL_CLIP_DISTANCE2_EXT);
glEnable(GL_CLIP_DISTANCE3_EXT);
ASSERT_GL_NO_ERROR();
// Disable 1 clip distances
glDisable(GL_CLIP_DISTANCE1_EXT);
ASSERT_GL_NO_ERROR();
// Clear to blue
glClearColor(0, 0, 1, 1);
glClear(GL_COLOR_BUFFER_BIT);
constexpr unsigned int kNumVertices = 12;
const std::array<Vector3, kNumVertices> quadVertices = {
{Vector3(-0.5f, 0.5f, 0.0f), Vector3(0.0f, 0.0f, 0.0f), Vector3(0.5f, 0.5f, 0.0f),
Vector3(0.5f, 0.5f, 0.0f), Vector3(0.0f, 0.0f, 0.0f), Vector3(0.5f, -0.5f, 0.0f),
Vector3(0.5f, -0.5f, 0.0f), Vector3(0.0f, 0.0f, 0.0f), Vector3(-0.5f, -0.5f, 0.0f),
Vector3(-0.5f, -0.5f, 0.0f), Vector3(0.0f, 0.0f, 0.0f), Vector3(-0.5f, 0.5f, 0.0f)}};
GLBuffer vertexBuffer;
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 3 * kNumVertices, quadVertices.data(),
GL_STATIC_DRAW);
ASSERT_GL_NO_ERROR();
GLint positionLocation = glGetAttribLocation(programRed, "a_position");
glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
ASSERT_GL_NO_ERROR();
glEnableVertexAttribArray(positionLocation);
ASSERT_GL_NO_ERROR();
// Draw full screen quad and small size triangle with color red
// y <= 1.0f
glUniform4f(glGetUniformLocation(programRed, "u_plane[0]"), 0, -1, 0, 1);
// y >= 0.5f
glUniform4f(glGetUniformLocation(programRed, "u_plane[1]"), 0, 1, 0, -0.5);
// y >= 3x-0.5f
glUniform4f(glGetUniformLocation(programRed, "u_plane[2]"), -3, 1, 0, 0.5);
// y >= -3x-0.5f
glUniform4f(glGetUniformLocation(programRed, "u_plane[3]"), 3, 1, 0, 0.5);
EXPECT_GL_NO_ERROR();
glDrawArrays(GL_TRIANGLES, 0, kNumVertices);
EXPECT_GL_NO_ERROR();
const int windowWidth = getWindowWidth();
const int windowHeight = getWindowHeight();
auto checkLeftPlaneFunc = [windowWidth, windowHeight](int x, int y) -> float {
return (3 * (x - (windowWidth / 2 - 1)) - (windowHeight / 4 + 1) + y);
};
auto checkRightPlaneFunc = [windowWidth, windowHeight](int x, int y) -> float {
return (-3 * (x - (windowWidth / 2)) - (windowHeight / 4 + 1) + y);
};
// Only pixels in the triangle must be red
std::vector<GLColor> actualColors(getWindowWidth() * getWindowHeight());
glReadPixels(0, 0, getWindowWidth(), getWindowHeight(), GL_RGBA, GL_UNSIGNED_BYTE,
actualColors.data());
for (int x = 0; x < getWindowWidth(); ++x)
{
for (int y = 0; y < getWindowHeight(); ++y)
{
// The drawing method of Swiftshader and Native graphic card is different. So the
// compare function doesn't check the value on the line.
const int currentPosition = y * getWindowHeight() + x;
if (checkLeftPlaneFunc(x, y) > 0 && checkRightPlaneFunc(x, y) > 0)
{
EXPECT_EQ(GLColor::red, actualColors[currentPosition]);
}
else if (checkLeftPlaneFunc(x, y) < 0 || checkRightPlaneFunc(x, y) < 0)
{
EXPECT_EQ(GLColor::blue, actualColors[currentPosition]);
}
}
}
}
// Verify that EXT_clip_cull_distance works with EXT_geometry_shader
TEST_P(ClipCullDistanceTest, CullDistanceInteractWithGeometryShader)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled(kExtensionName) ||
!EnsureGLExtensionEnabled("GL_EXT_geometry_shader"));
std::string kVS = R"(#version 310 es
#extension )" + kExtensionName +
R"( : require
#extension GL_EXT_geometry_shader : require
in vec2 a_position;
uniform vec4 u_plane[4];
void main()
{
gl_Position = vec4(a_position, 0.0, 1.0);
gl_CullDistance[0] = dot(gl_Position, u_plane[0]);
gl_CullDistance[1] = dot(gl_Position, u_plane[1]);
gl_CullDistance[2] = dot(gl_Position, u_plane[2]);
gl_CullDistance[3] = dot(gl_Position, u_plane[3]);
})";
std::string kGS = R"(#version 310 es
#extension )" + kExtensionName +
R"( : require
#extension GL_EXT_geometry_shader : require
layout (triangles) in;
layout (triangle_strip, max_vertices = 3) out;
in gl_PerVertex {
highp vec4 gl_Position;
highp float gl_CullDistance[];
} gl_in[];
out gl_PerVertex {
highp vec4 gl_Position;
highp float gl_CullDistance[];
};
uniform vec4 u_plane[4];
void GetNewPosition(int i)
{
gl_Position = 2.0f * gl_in[i].gl_Position;
for (int index = 0 ; index < 4 ; index++)
{
if (gl_in[i].gl_CullDistance[index] < 0.0f)
{
gl_CullDistance[index] = dot(gl_Position, u_plane[index]);
}
}
EmitVertex();
}
void main()
{
for (int i = 0 ; i < 3 ; i++)
{
GetNewPosition(i);
}
EndPrimitive();
})";
GLProgram programRed;
programRed.makeRaster(kVS.c_str(), kGS.c_str(), essl3_shaders::fs::Red());
if (!mCullDistanceSupportRequired)
{
GLint maxCullDistances;
glGetIntegerv(GL_MAX_CULL_DISTANCES_EXT, &maxCullDistances);
if (maxCullDistances == 0)
{
ASSERT_FALSE(programRed.valid());
return;
}
}
glUseProgram(programRed);
ASSERT_GL_NO_ERROR();
// Clear to blue
glClearColor(0, 0, 1, 1);
glClear(GL_COLOR_BUFFER_BIT);
constexpr unsigned int kNumVertices = 12;
const std::array<Vector3, kNumVertices> quadVertices = {
{Vector3(-0.5f, 0.5f, 0.0f), Vector3(0.0f, 0.0f, 0.0f), Vector3(0.5f, 0.5f, 0.0f),
Vector3(0.5f, 0.5f, 0.0f), Vector3(0.0f, 0.0f, 0.0f), Vector3(0.5f, -0.5f, 0.0f),
Vector3(0.5f, -0.5f, 0.0f), Vector3(0.0f, 0.0f, 0.0f), Vector3(-0.5f, -0.5f, 0.0f),
Vector3(-0.5f, -0.5f, 0.0f), Vector3(0.0f, 0.0f, 0.0f), Vector3(-0.5f, 0.5f, 0.0f)}};
GLBuffer vertexBuffer;
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 3 * kNumVertices, quadVertices.data(),
GL_STATIC_DRAW);
ASSERT_GL_NO_ERROR();
GLint positionLocation = glGetAttribLocation(programRed, "a_position");
glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
ASSERT_GL_NO_ERROR();
glEnableVertexAttribArray(positionLocation);
ASSERT_GL_NO_ERROR();
// Draw full screen quad and small size triangle with color red
// y <= 1.0f
glUniform4f(glGetUniformLocation(programRed, "u_plane[0]"), 0, -1, 0, 1);
// y >= 0.5f
glUniform4f(glGetUniformLocation(programRed, "u_plane[1]"), 0, 1, 0, -0.5);
// y >= 3x-0.5f
glUniform4f(glGetUniformLocation(programRed, "u_plane[2]"), -3, 1, 0, 0.5);
// y >= -3x-0.5f
glUniform4f(glGetUniformLocation(programRed, "u_plane[3]"), 3, 1, 0, 0.5);
EXPECT_GL_NO_ERROR();
glDrawArrays(GL_TRIANGLES, 0, kNumVertices);
EXPECT_GL_NO_ERROR();
// Only pixels in the triangle must be red
std::vector<GLColor> actualColors(getWindowWidth() * getWindowHeight());
glReadPixels(0, 0, getWindowWidth(), getWindowHeight(), GL_RGBA, GL_UNSIGNED_BYTE,
actualColors.data());
for (int x = 0; x < getWindowWidth(); ++x)
{
for (int y = 0; y < getWindowHeight(); ++y)
{
const int currentPosition = y * getWindowHeight() + x;
if (y > x || y >= -x + getWindowHeight() - 1)
{
EXPECT_EQ(GLColor::red, actualColors[currentPosition]);
}
else
{
EXPECT_EQ(GLColor::blue, actualColors[currentPosition]);
}
}
}
}
ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(ClipDistanceAPPLETest);
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(ClipCullDistanceTest);
ANGLE_INSTANTIATE_TEST_COMBINE_1(ClipCullDistanceTest,
PrintToStringParamName,
testing::Bool(),
ANGLE_ALL_TEST_PLATFORMS_ES3,
ANGLE_ALL_TEST_PLATFORMS_ES31);