blob: 6078dbb341ba62fe7202710db44c791c9dd9dd3d [file] [log] [blame]
//
// Copyright 2015 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.
//
#include "anglebase/numerics/safe_conversions.h"
#include "common/mathutil.h"
#include "platform/FeaturesVk.h"
#include "test_utils/ANGLETest.h"
#include "test_utils/gl_raii.h"
using namespace angle;
namespace
{
GLsizei TypeStride(GLenum attribType)
{
switch (attribType)
{
case GL_UNSIGNED_BYTE:
case GL_BYTE:
return 1;
case GL_UNSIGNED_SHORT:
case GL_SHORT:
case GL_HALF_FLOAT:
case GL_HALF_FLOAT_OES:
return 2;
case GL_UNSIGNED_INT:
case GL_INT:
case GL_FLOAT:
case GL_UNSIGNED_INT_10_10_10_2_OES:
case GL_INT_10_10_10_2_OES:
return 4;
default:
EXPECT_TRUE(false);
return 0;
}
}
template <typename T>
GLfloat Normalize(T value)
{
static_assert(std::is_integral<T>::value, "Integer required.");
if (std::is_signed<T>::value)
{
typedef typename std::make_unsigned<T>::type unsigned_type;
return (2.0f * static_cast<GLfloat>(value) + 1.0f) /
static_cast<GLfloat>(std::numeric_limits<unsigned_type>::max());
}
else
{
return static_cast<GLfloat>(value) / static_cast<GLfloat>(std::numeric_limits<T>::max());
}
}
// Normalization for each channel of signed/unsigned 10_10_10_2 types
template <typename T>
GLfloat Normalize10(T value)
{
static_assert(std::is_integral<T>::value, "Integer required.");
GLfloat floatOutput;
if (std::is_signed<T>::value)
{
const uint32_t signMask = 0x200; // 1 set at the 9th bit
const uint32_t negativeMask = 0xFFFFFC00; // All bits from 10 to 31 set to 1
if (value & signMask)
{
int negativeNumber = value | negativeMask;
floatOutput = static_cast<GLfloat>(negativeNumber);
}
else
{
floatOutput = static_cast<GLfloat>(value);
}
const int32_t maxValue = 0x1FF; // 1 set in bits 0 through 8
const int32_t minValue = 0xFFFFFE01; // Inverse of maxValue
// A 10-bit two's complement number has the possibility of being minValue - 1 but
// OpenGL's normalization rules dictate that it should be clamped to minValue in
// this case.
if (floatOutput < minValue)
floatOutput = minValue;
const int32_t halfRange = (maxValue - minValue) >> 1;
floatOutput = ((floatOutput - minValue) / halfRange) - 1.0f;
}
else
{
const GLfloat maxValue = 1023.0f; // 1 set in bits 0 through 9
floatOutput = static_cast<GLfloat>(value) / maxValue;
}
return floatOutput;
}
template <typename T>
GLfloat Normalize2(T value)
{
static_assert(std::is_integral<T>::value, "Integer required.");
if (std::is_signed<T>::value)
{
GLfloat outputValue = static_cast<float>(value) / 1.0f;
outputValue = (outputValue >= -1.0f) ? (outputValue) : (-1.0f);
return outputValue;
}
else
{
return static_cast<float>(value) / 3.0f;
}
}
template <typename DestT, typename SrcT>
DestT Pack1010102(std::array<SrcT, 4> input)
{
static_assert(std::is_integral<SrcT>::value, "Integer required.");
static_assert(std::is_integral<DestT>::value, "Integer required.");
static_assert(std::is_unsigned<SrcT>::value == std::is_unsigned<DestT>::value,
"Signedness should be equal.");
DestT rOut, gOut, bOut, aOut;
rOut = static_cast<DestT>(input[0]);
gOut = static_cast<DestT>(input[1]);
bOut = static_cast<DestT>(input[2]);
aOut = static_cast<DestT>(input[3]);
if (std::is_unsigned<SrcT>::value)
{
return rOut << 22 | gOut << 12 | bOut << 2 | aOut;
}
else
{
// Need to apply bit mask to account for sign extension
return (0xFFC00000u & rOut << 22) | (0x003FF000u & gOut << 12) | (0x00000FFCu & bOut << 2) |
(0x00000003u & aOut);
}
}
class VertexAttributeTest : public ANGLETest
{
protected:
VertexAttributeTest()
: mProgram(0), mTestAttrib(-1), mExpectedAttrib(-1), mBuffer(0), mQuadBuffer(0)
{
setWindowWidth(128);
setWindowHeight(128);
setConfigRedBits(8);
setConfigGreenBits(8);
setConfigBlueBits(8);
setConfigAlphaBits(8);
setConfigDepthBits(24);
}
enum class Source
{
BUFFER,
IMMEDIATE,
};
struct TestData final : private angle::NonCopyable
{
TestData(GLenum typeIn,
GLboolean normalizedIn,
Source sourceIn,
const void *inputDataIn,
const GLfloat *expectedDataIn)
: type(typeIn),
normalized(normalizedIn),
bufferOffset(0),
source(sourceIn),
inputData(inputDataIn),
expectedData(expectedDataIn)
{}
GLenum type;
GLboolean normalized;
size_t bufferOffset;
Source source;
const void *inputData;
const GLfloat *expectedData;
};
void setupTest(const TestData &test, GLint typeSize)
{
if (mProgram == 0)
{
initBasicProgram();
}
if (test.source == Source::BUFFER)
{
GLsizei dataSize = kVertexCount * TypeStride(test.type);
glBindBuffer(GL_ARRAY_BUFFER, mBuffer);
glBufferData(GL_ARRAY_BUFFER, dataSize, test.inputData, GL_STATIC_DRAW);
glVertexAttribPointer(mTestAttrib, typeSize, test.type, test.normalized, 0,
reinterpret_cast<void *>(test.bufferOffset));
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
else
{
ASSERT_EQ(Source::IMMEDIATE, test.source);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glVertexAttribPointer(mTestAttrib, typeSize, test.type, test.normalized, 0,
test.inputData);
}
glVertexAttribPointer(mExpectedAttrib, typeSize, GL_FLOAT, GL_FALSE, 0, test.expectedData);
glEnableVertexAttribArray(mTestAttrib);
glEnableVertexAttribArray(mExpectedAttrib);
}
void checkPixels() { checkRGBPixels(true); }
void checkRGBPixels(bool checkAlpha)
{
GLint viewportSize[4];
glGetIntegerv(GL_VIEWPORT, viewportSize);
GLint midPixelX = (viewportSize[0] + viewportSize[2]) / 2;
GLint midPixelY = (viewportSize[1] + viewportSize[3]) / 2;
// We need to offset our checks from triangle edges to ensure we don't fall on a single tri
// Avoid making assumptions of drawQuad with four checks to check the four possible tri
// regions
if (checkAlpha)
{
EXPECT_PIXEL_EQ((midPixelX + viewportSize[0]) / 2, midPixelY, 255, 255, 255, 255);
EXPECT_PIXEL_EQ((midPixelX + viewportSize[2]) / 2, midPixelY, 255, 255, 255, 255);
EXPECT_PIXEL_EQ(midPixelX, (midPixelY + viewportSize[1]) / 2, 255, 255, 255, 255);
EXPECT_PIXEL_EQ(midPixelX, (midPixelY + viewportSize[3]) / 2, 255, 255, 255, 255);
}
else
{
EXPECT_PIXEL_RGB_EQUAL((midPixelX + viewportSize[0]) / 2, midPixelY, 255, 255, 255);
EXPECT_PIXEL_RGB_EQUAL((midPixelX + viewportSize[2]) / 2, midPixelY, 255, 255, 255);
EXPECT_PIXEL_RGB_EQUAL(midPixelX, (midPixelY + viewportSize[1]) / 2, 255, 255, 255);
EXPECT_PIXEL_RGB_EQUAL(midPixelX, (midPixelY + viewportSize[3]) / 2, 255, 255, 255);
}
}
void checkPixelsUnEqual()
{
GLint viewportSize[4];
glGetIntegerv(GL_VIEWPORT, viewportSize);
GLint midPixelX = (viewportSize[0] + viewportSize[2]) / 2;
GLint midPixelY = (viewportSize[1] + viewportSize[3]) / 2;
// We need to offset our checks from triangle edges to ensure we don't fall on a single tri
// Avoid making assumptions of drawQuad with four checks to check the four possible tri
// regions
EXPECT_PIXEL_NE((midPixelX + viewportSize[0]) / 2, midPixelY, 255, 255, 255, 255);
EXPECT_PIXEL_NE((midPixelX + viewportSize[2]) / 2, midPixelY, 255, 255, 255, 255);
EXPECT_PIXEL_NE(midPixelX, (midPixelY + viewportSize[1]) / 2, 255, 255, 255, 255);
EXPECT_PIXEL_NE(midPixelX, (midPixelY + viewportSize[3]) / 2, 255, 255, 255, 255);
}
void runTest(const TestData &test) { runTest(test, true); }
void runTest(const TestData &test, bool checkPixelEqual)
{
// TODO(geofflang): Figure out why this is broken on AMD OpenGL
ANGLE_SKIP_TEST_IF(IsAMD() && IsOpenGL());
for (GLint i = 0; i < 4; i++)
{
GLint typeSize = i + 1;
setupTest(test, typeSize);
drawQuad(mProgram, "position", 0.5f);
glDisableVertexAttribArray(mTestAttrib);
glDisableVertexAttribArray(mExpectedAttrib);
if (checkPixelEqual)
{
if ((test.type == GL_HALF_FLOAT || test.type == GL_HALF_FLOAT_OES) && IsVulkan() &&
typeSize == 3)
{ // We need a special case for RGB16F format on a Vulkan backend due to the fact
// that in such a usecase, we need to ignore the alpha channel.
checkRGBPixels(false);
}
else
{
checkPixels();
}
}
else
{
checkPixelsUnEqual();
}
}
}
void testSetUp() override
{
glClearColor(0, 0, 0, 0);
glClearDepthf(0.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glDisable(GL_DEPTH_TEST);
glGenBuffers(1, &mBuffer);
}
void testTearDown() override
{
glDeleteProgram(mProgram);
glDeleteBuffers(1, &mBuffer);
glDeleteBuffers(1, &mQuadBuffer);
}
// Override a feature to force emulation of attribute formats.
void overrideFeaturesVk(FeaturesVk *featuresVk) override
{
featuresVk->overrideFeatures({"force_fallback_format"}, true);
}
GLuint compileMultiAttribProgram(GLint attribCount)
{
std::stringstream shaderStream;
shaderStream << "attribute mediump vec4 position;" << std::endl;
for (GLint attribIndex = 0; attribIndex < attribCount; ++attribIndex)
{
shaderStream << "attribute float a" << attribIndex << ";" << std::endl;
}
shaderStream << "varying mediump float color;" << std::endl
<< "void main() {" << std::endl
<< " gl_Position = position;" << std::endl
<< " color = 0.0;" << std::endl;
for (GLint attribIndex = 0; attribIndex < attribCount; ++attribIndex)
{
shaderStream << " color += a" << attribIndex << ";" << std::endl;
}
shaderStream << "}" << std::endl;
constexpr char kFS[] =
"varying mediump float color;\n"
"void main(void)\n"
"{\n"
" gl_FragColor = vec4(color, 0.0, 0.0, 1.0);\n"
"}\n";
return CompileProgram(shaderStream.str().c_str(), kFS);
}
void setupMultiAttribs(GLuint program, GLint attribCount, GLfloat value)
{
glUseProgram(program);
for (GLint attribIndex = 0; attribIndex < attribCount; ++attribIndex)
{
std::stringstream attribStream;
attribStream << "a" << attribIndex;
GLint location = glGetAttribLocation(program, attribStream.str().c_str());
ASSERT_NE(-1, location);
glVertexAttrib1f(location, value);
glDisableVertexAttribArray(location);
}
}
void initBasicProgram()
{
constexpr char kVS[] =
"attribute mediump vec4 position;\n"
"attribute mediump vec4 test;\n"
"attribute mediump vec4 expected;\n"
"varying mediump vec4 color;\n"
"void main(void)\n"
"{\n"
" gl_Position = position;\n"
" vec4 threshold = max(abs(expected) * 0.01, 1.0 / 64.0);\n"
" color = vec4(lessThanEqual(abs(test - expected), threshold));\n"
"}\n";
constexpr char kFS[] =
"varying mediump vec4 color;\n"
"void main(void)\n"
"{\n"
" gl_FragColor = color;\n"
"}\n";
mProgram = CompileProgram(kVS, kFS);
ASSERT_NE(0u, mProgram);
mTestAttrib = glGetAttribLocation(mProgram, "test");
ASSERT_NE(-1, mTestAttrib);
mExpectedAttrib = glGetAttribLocation(mProgram, "expected");
ASSERT_NE(-1, mExpectedAttrib);
glUseProgram(mProgram);
}
static constexpr size_t kVertexCount = 24;
static void InitTestData(std::array<GLfloat, kVertexCount> &inputData,
std::array<GLfloat, kVertexCount> &expectedData)
{
for (size_t count = 0; count < kVertexCount; ++count)
{
inputData[count] = static_cast<GLfloat>(count);
expectedData[count] = inputData[count];
}
}
GLuint mProgram;
GLint mTestAttrib;
GLint mExpectedAttrib;
GLuint mBuffer;
GLuint mQuadBuffer;
};
TEST_P(VertexAttributeTest, UnsignedByteUnnormalized)
{
std::array<GLubyte, kVertexCount> inputData = {
{0, 1, 2, 3, 4, 5, 6, 7, 125, 126, 127, 128, 129, 250, 251, 252, 253, 254, 255}};
std::array<GLfloat, kVertexCount> expectedData;
for (size_t i = 0; i < kVertexCount; i++)
{
expectedData[i] = inputData[i];
}
TestData data(GL_UNSIGNED_BYTE, GL_FALSE, Source::IMMEDIATE, inputData.data(),
expectedData.data());
runTest(data);
}
TEST_P(VertexAttributeTest, UnsignedByteNormalized)
{
std::array<GLubyte, kVertexCount> inputData = {
{0, 1, 2, 3, 4, 5, 6, 7, 125, 126, 127, 128, 129, 250, 251, 252, 253, 254, 255}};
std::array<GLfloat, kVertexCount> expectedData;
for (size_t i = 0; i < kVertexCount; i++)
{
expectedData[i] = Normalize(inputData[i]);
}
TestData data(GL_UNSIGNED_BYTE, GL_TRUE, Source::IMMEDIATE, inputData.data(),
expectedData.data());
runTest(data);
}
TEST_P(VertexAttributeTest, ByteUnnormalized)
{
std::array<GLbyte, kVertexCount> inputData = {
{0, 1, 2, 3, 4, -1, -2, -3, -4, 125, 126, 127, -128, -127, -126}};
std::array<GLfloat, kVertexCount> expectedData;
for (size_t i = 0; i < kVertexCount; i++)
{
expectedData[i] = inputData[i];
}
TestData data(GL_BYTE, GL_FALSE, Source::IMMEDIATE, inputData.data(), expectedData.data());
runTest(data);
}
TEST_P(VertexAttributeTest, ByteNormalized)
{
std::array<GLbyte, kVertexCount> inputData = {
{0, 1, 2, 3, 4, -1, -2, -3, -4, 125, 126, 127, -128, -127, -126}};
std::array<GLfloat, kVertexCount> expectedData;
for (size_t i = 0; i < kVertexCount; i++)
{
expectedData[i] = Normalize(inputData[i]);
}
TestData data(GL_BYTE, GL_TRUE, Source::IMMEDIATE, inputData.data(), expectedData.data());
runTest(data);
}
TEST_P(VertexAttributeTest, UnsignedShortUnnormalized)
{
std::array<GLushort, kVertexCount> inputData = {
{0, 1, 2, 3, 254, 255, 256, 32766, 32767, 32768, 65533, 65534, 65535}};
std::array<GLfloat, kVertexCount> expectedData;
for (size_t i = 0; i < kVertexCount; i++)
{
expectedData[i] = inputData[i];
}
TestData data(GL_UNSIGNED_SHORT, GL_FALSE, Source::IMMEDIATE, inputData.data(),
expectedData.data());
runTest(data);
}
TEST_P(VertexAttributeTest, UnsignedShortNormalized)
{
std::array<GLushort, kVertexCount> inputData = {
{0, 1, 2, 3, 254, 255, 256, 32766, 32767, 32768, 65533, 65534, 65535}};
std::array<GLfloat, kVertexCount> expectedData;
for (size_t i = 0; i < kVertexCount; i++)
{
expectedData[i] = Normalize(inputData[i]);
}
TestData data(GL_UNSIGNED_SHORT, GL_TRUE, Source::IMMEDIATE, inputData.data(),
expectedData.data());
runTest(data);
}
TEST_P(VertexAttributeTest, ShortUnnormalized)
{
std::array<GLshort, kVertexCount> inputData = {
{0, 1, 2, 3, -1, -2, -3, -4, 32766, 32767, -32768, -32767, -32766}};
std::array<GLfloat, kVertexCount> expectedData;
for (size_t i = 0; i < kVertexCount; i++)
{
expectedData[i] = inputData[i];
}
TestData data(GL_SHORT, GL_FALSE, Source::IMMEDIATE, inputData.data(), expectedData.data());
runTest(data);
}
TEST_P(VertexAttributeTest, ShortNormalized)
{
std::array<GLshort, kVertexCount> inputData = {
{0, 1, 2, 3, -1, -2, -3, -4, 32766, 32767, -32768, -32767, -32766}};
std::array<GLfloat, kVertexCount> expectedData;
for (size_t i = 0; i < kVertexCount; i++)
{
expectedData[i] = Normalize(inputData[i]);
}
TestData data(GL_SHORT, GL_TRUE, Source::IMMEDIATE, inputData.data(), expectedData.data());
runTest(data);
}
// Verify that vertex data is updated correctly when using a float/half-float client memory pointer.
TEST_P(VertexAttributeTest, HalfFloatClientMemoryPointer)
{
std::array<GLhalf, kVertexCount> inputData;
std::array<GLfloat, kVertexCount> expectedData = {
{0.f, 1.5f, 2.3f, 3.2f, -1.8f, -2.2f, -3.9f, -4.f, 34.5f, 32.2f, -78.8f, -77.4f, -76.1f}};
for (size_t i = 0; i < kVertexCount; i++)
{
inputData[i] = gl::float32ToFloat16(expectedData[i]);
}
// If the extension is enabled run the test on all contexts
if (IsGLExtensionEnabled("GL_OES_vertex_half_float"))
{
TestData imediateData(GL_HALF_FLOAT_OES, GL_FALSE, Source::IMMEDIATE, inputData.data(),
expectedData.data());
runTest(imediateData);
}
// Otherwise run the test only if it is an ES3 context
else if (getClientMajorVersion() >= 3)
{
TestData imediateData(GL_HALF_FLOAT, GL_FALSE, Source::IMMEDIATE, inputData.data(),
expectedData.data());
runTest(imediateData);
}
}
// Verify that vertex data is updated correctly when using a float/half-float buffer.
TEST_P(VertexAttributeTest, HalfFloatBuffer)
{
std::array<GLhalf, kVertexCount> inputData;
std::array<GLfloat, kVertexCount> expectedData = {
{0.f, 1.5f, 2.3f, 3.2f, -1.8f, -2.2f, -3.9f, -4.f, 34.5f, 32.2f, -78.8f, -77.4f, -76.1f}};
for (size_t i = 0; i < kVertexCount; i++)
{
inputData[i] = gl::float32ToFloat16(expectedData[i]);
}
// If the extension is enabled run the test on all contexts
if (IsGLExtensionEnabled("GL_OES_vertex_half_float"))
{
TestData bufferData(GL_HALF_FLOAT_OES, GL_FALSE, Source::BUFFER, inputData.data(),
expectedData.data());
runTest(bufferData);
}
// Otherwise run the test only if it is an ES3 context
else if (getClientMajorVersion() >= 3)
{
TestData bufferData(GL_HALF_FLOAT, GL_FALSE, Source::BUFFER, inputData.data(),
expectedData.data());
runTest(bufferData);
}
}
// Verify that using the same client memory pointer in different format won't mess up the draw.
TEST_P(VertexAttributeTest, UsingDifferentFormatAndSameClientMemoryPointer)
{
std::array<GLshort, kVertexCount> inputData = {
{0, 1, 2, 3, -1, -2, -3, -4, 32766, 32767, -32768, -32767, -32766}};
std::array<GLfloat, kVertexCount> unnormalizedExpectedData;
for (size_t i = 0; i < kVertexCount; i++)
{
unnormalizedExpectedData[i] = inputData[i];
}
TestData unnormalizedData(GL_SHORT, GL_FALSE, Source::IMMEDIATE, inputData.data(),
unnormalizedExpectedData.data());
runTest(unnormalizedData);
std::array<GLfloat, kVertexCount> normalizedExpectedData;
for (size_t i = 0; i < kVertexCount; i++)
{
inputData[i] = -inputData[i];
normalizedExpectedData[i] = Normalize(inputData[i]);
}
TestData normalizedData(GL_SHORT, GL_TRUE, Source::IMMEDIATE, inputData.data(),
normalizedExpectedData.data());
runTest(normalizedData);
}
// Verify that vertex format is updated correctly when the client memory pointer is same.
TEST_P(VertexAttributeTest, NegativeUsingDifferentFormatAndSameClientMemoryPointer)
{
std::array<GLshort, kVertexCount> inputData = {
{0, 1, 2, 3, -1, -2, -3, -4, 32766, 32767, -32768, -32767, -32766}};
std::array<GLfloat, kVertexCount> unnormalizedExpectedData;
for (size_t i = 0; i < kVertexCount; i++)
{
unnormalizedExpectedData[i] = inputData[i];
}
// Use unnormalized short as the format of the data in client memory pointer in the first draw.
TestData unnormalizedData(GL_SHORT, GL_FALSE, Source::IMMEDIATE, inputData.data(),
unnormalizedExpectedData.data());
runTest(unnormalizedData);
// Use normalized short as the format of the data in client memory pointer in the second draw,
// but mExpectedAttrib is the same as the first draw.
TestData normalizedData(GL_SHORT, GL_TRUE, Source::IMMEDIATE, inputData.data(),
unnormalizedExpectedData.data());
runTest(normalizedData, false);
}
// Verify that using different vertex format and same buffer won't mess up the draw.
TEST_P(VertexAttributeTest, UsingDifferentFormatAndSameBuffer)
{
std::array<GLshort, kVertexCount> inputData = {
{0, 1, 2, 3, -1, -2, -3, -4, 32766, 32767, -32768, -32767, -32766}};
std::array<GLfloat, kVertexCount> unnormalizedExpectedData;
std::array<GLfloat, kVertexCount> normalizedExpectedData;
for (size_t i = 0; i < kVertexCount; i++)
{
unnormalizedExpectedData[i] = inputData[i];
normalizedExpectedData[i] = Normalize(inputData[i]);
}
// Use unnormalized short as the format of the data in mBuffer in the first draw.
TestData unnormalizedData(GL_SHORT, GL_FALSE, Source::BUFFER, inputData.data(),
unnormalizedExpectedData.data());
runTest(unnormalizedData);
// Use normalized short as the format of the data in mBuffer in the second draw.
TestData normalizedData(GL_SHORT, GL_TRUE, Source::BUFFER, inputData.data(),
normalizedExpectedData.data());
runTest(normalizedData);
}
// Verify that vertex format is updated correctly when the buffer is same.
TEST_P(VertexAttributeTest, NegativeUsingDifferentFormatAndSameBuffer)
{
std::array<GLshort, kVertexCount> inputData = {
{0, 1, 2, 3, -1, -2, -3, -4, 32766, 32767, -32768, -32767, -32766}};
std::array<GLfloat, kVertexCount> unnormalizedExpectedData;
for (size_t i = 0; i < kVertexCount; i++)
{
unnormalizedExpectedData[i] = inputData[i];
}
// Use unnormalized short as the format of the data in mBuffer in the first draw.
TestData unnormalizedData(GL_SHORT, GL_FALSE, Source::BUFFER, inputData.data(),
unnormalizedExpectedData.data());
runTest(unnormalizedData);
// Use normalized short as the format of the data in mBuffer in the second draw, but
// mExpectedAttrib is the same as the first draw.
TestData normalizedData(GL_SHORT, GL_TRUE, Source::BUFFER, inputData.data(),
unnormalizedExpectedData.data());
// The check should fail because the test data is changed while the expected data is the same.
runTest(normalizedData, false);
}
// Verify that mixed using buffer and client memory pointer won't mess up the draw.
TEST_P(VertexAttributeTest, MixedUsingBufferAndClientMemoryPointer)
{
std::array<GLshort, kVertexCount> inputData = {
{0, 1, 2, 3, -1, -2, -3, -4, 32766, 32767, -32768, -32767, -32766}};
std::array<GLfloat, kVertexCount> unnormalizedExpectedData;
std::array<GLfloat, kVertexCount> normalizedExpectedData;
for (size_t i = 0; i < kVertexCount; i++)
{
unnormalizedExpectedData[i] = inputData[i];
normalizedExpectedData[i] = Normalize(inputData[i]);
}
TestData unnormalizedData(GL_SHORT, GL_FALSE, Source::IMMEDIATE, inputData.data(),
unnormalizedExpectedData.data());
runTest(unnormalizedData);
TestData unnormalizedBufferData(GL_SHORT, GL_FALSE, Source::BUFFER, inputData.data(),
unnormalizedExpectedData.data());
runTest(unnormalizedBufferData);
TestData normalizedData(GL_SHORT, GL_TRUE, Source::IMMEDIATE, inputData.data(),
normalizedExpectedData.data());
runTest(normalizedData);
}
// Verify signed unnormalized INT_10_10_10_2 vertex type
TEST_P(VertexAttributeTest, SignedPacked1010102ExtensionUnnormalized)
{
std::string extensionList(reinterpret_cast<const char *>(glGetString(GL_EXTENSIONS)));
ANGLE_SKIP_TEST_IF((extensionList.find("OES_vertex_type_10_10_10_2") == std::string::npos));
// RGB channels are 10-bits, alpha is 2-bits
std::array<std::array<GLshort, 4>, kVertexCount / 4> unpackedInput = {{{0, 1, 2, 0},
{254, 255, 256, 1},
{256, 255, 254, -2},
{511, 510, 509, -1},
{-512, -511, -500, -2},
{-1, -2, -3, 1}}};
std::array<GLint, kVertexCount> packedInput;
std::array<GLfloat, kVertexCount> expectedTypeSize4;
std::array<GLfloat, kVertexCount> expectedTypeSize3;
for (size_t i = 0; i < kVertexCount / 4; i++)
{
packedInput[i] = Pack1010102<GLint, GLshort>(unpackedInput[i]);
expectedTypeSize3[i * 3 + 0] = expectedTypeSize4[i * 4 + 0] = unpackedInput[i][0];
expectedTypeSize3[i * 3 + 1] = expectedTypeSize4[i * 4 + 1] = unpackedInput[i][1];
expectedTypeSize3[i * 3 + 2] = expectedTypeSize4[i * 4 + 2] = unpackedInput[i][2];
// when the type size is 3, alpha will be 1.0f by GLES driver
expectedTypeSize4[i * 4 + 3] = unpackedInput[i][3];
}
TestData data4(GL_INT_10_10_10_2_OES, GL_FALSE, Source::IMMEDIATE, packedInput.data(),
expectedTypeSize4.data());
TestData bufferedData4(GL_INT_10_10_10_2_OES, GL_FALSE, Source::BUFFER, packedInput.data(),
expectedTypeSize4.data());
TestData data3(GL_INT_10_10_10_2_OES, GL_FALSE, Source::IMMEDIATE, packedInput.data(),
expectedTypeSize3.data());
TestData bufferedData3(GL_INT_10_10_10_2_OES, GL_FALSE, Source::BUFFER, packedInput.data(),
expectedTypeSize3.data());
std::array<std::pair<const TestData &, GLint>, 4> dataSet = {
{{data4, 4}, {bufferedData4, 4}, {data3, 3}, {bufferedData3, 3}}};
for (auto data : dataSet)
{
setupTest(data.first, data.second);
drawQuad(mProgram, "position", 0.5f);
glDisableVertexAttribArray(mTestAttrib);
glDisableVertexAttribArray(mExpectedAttrib);
checkPixels();
}
}
// Verify signed normalized INT_10_10_10_2 vertex type
TEST_P(VertexAttributeTest, SignedPacked1010102ExtensionNormalized)
{
std::string extensionList(reinterpret_cast<const char *>(glGetString(GL_EXTENSIONS)));
ANGLE_SKIP_TEST_IF((extensionList.find("OES_vertex_type_10_10_10_2") == std::string::npos));
// RGB channels are 10-bits, alpha is 2-bits
std::array<std::array<GLshort, 4>, kVertexCount / 4> unpackedInput = {{{0, 1, 2, 0},
{254, 255, 256, 1},
{256, 255, 254, -2},
{511, 510, 509, -1},
{-512, -511, -500, -2},
{-1, -2, -3, 1}}};
std::array<GLint, kVertexCount> packedInput;
std::array<GLfloat, kVertexCount> expectedNormalizedTypeSize4;
std::array<GLfloat, kVertexCount> expectedNormalizedTypeSize3;
for (size_t i = 0; i < kVertexCount / 4; i++)
{
packedInput[i] = Pack1010102<GLint, GLshort>(unpackedInput[i]);
expectedNormalizedTypeSize3[i * 3 + 0] = expectedNormalizedTypeSize4[i * 4 + 0] =
Normalize10<GLshort>(unpackedInput[i][0]);
expectedNormalizedTypeSize3[i * 3 + 1] = expectedNormalizedTypeSize4[i * 4 + 1] =
Normalize10<GLshort>(unpackedInput[i][1]);
expectedNormalizedTypeSize3[i * 3 + 2] = expectedNormalizedTypeSize4[i * 4 + 2] =
Normalize10<GLshort>(unpackedInput[i][2]);
// when the type size is 3, alpha will be 1.0f by GLES driver
expectedNormalizedTypeSize4[i * 4 + 3] = Normalize2<GLshort>(unpackedInput[i][3]);
}
TestData data4(GL_INT_10_10_10_2_OES, GL_TRUE, Source::IMMEDIATE, packedInput.data(),
expectedNormalizedTypeSize4.data());
TestData bufferedData4(GL_INT_10_10_10_2_OES, GL_TRUE, Source::BUFFER, packedInput.data(),
expectedNormalizedTypeSize4.data());
TestData data3(GL_INT_10_10_10_2_OES, GL_TRUE, Source::IMMEDIATE, packedInput.data(),
expectedNormalizedTypeSize3.data());
TestData bufferedData3(GL_INT_10_10_10_2_OES, GL_TRUE, Source::BUFFER, packedInput.data(),
expectedNormalizedTypeSize3.data());
std::array<std::pair<const TestData &, GLint>, 4> dataSet = {
{{data4, 4}, {bufferedData4, 4}, {data3, 3}, {bufferedData3, 3}}};
for (auto data : dataSet)
{
setupTest(data.first, data.second);
drawQuad(mProgram, "position", 0.5f);
glDisableVertexAttribArray(mTestAttrib);
glDisableVertexAttribArray(mExpectedAttrib);
checkPixels();
}
}
// Verify unsigned unnormalized INT_10_10_10_2 vertex type
TEST_P(VertexAttributeTest, UnsignedPacked1010102ExtensionUnnormalized)
{
std::string extensionList(reinterpret_cast<const char *>(glGetString(GL_EXTENSIONS)));
ANGLE_SKIP_TEST_IF((extensionList.find("OES_vertex_type_10_10_10_2") == std::string::npos));
// RGB channels are 10-bits, alpha is 2-bits
std::array<std::array<GLushort, 4>, kVertexCount / 4> unpackedInput = {{{0, 1, 2, 0},
{511, 512, 513, 1},
{1023, 1022, 1021, 3},
{513, 512, 511, 2},
{2, 1, 0, 3},
{1023, 1022, 1022, 0}}};
std::array<GLuint, kVertexCount> packedInput;
std::array<GLfloat, kVertexCount> expectedTypeSize3;
std::array<GLfloat, kVertexCount> expectedTypeSize4;
for (size_t i = 0; i < kVertexCount / 4; i++)
{
packedInput[i] = Pack1010102<GLuint, GLushort>(unpackedInput[i]);
expectedTypeSize3[i * 3 + 0] = expectedTypeSize4[i * 4 + 0] = unpackedInput[i][0];
expectedTypeSize3[i * 3 + 1] = expectedTypeSize4[i * 4 + 1] = unpackedInput[i][1];
expectedTypeSize3[i * 3 + 2] = expectedTypeSize4[i * 4 + 2] = unpackedInput[i][2];
// when the type size is 3, alpha will be 1.0f by GLES driver
expectedTypeSize4[i * 4 + 3] = unpackedInput[i][3];
}
TestData data4(GL_UNSIGNED_INT_10_10_10_2_OES, GL_FALSE, Source::IMMEDIATE, packedInput.data(),
expectedTypeSize4.data());
TestData bufferedData4(GL_UNSIGNED_INT_10_10_10_2_OES, GL_FALSE, Source::BUFFER,
packedInput.data(), expectedTypeSize4.data());
TestData data3(GL_UNSIGNED_INT_10_10_10_2_OES, GL_FALSE, Source::BUFFER, packedInput.data(),
expectedTypeSize3.data());
TestData bufferedData3(GL_UNSIGNED_INT_10_10_10_2_OES, GL_FALSE, Source::BUFFER,
packedInput.data(), expectedTypeSize3.data());
std::array<std::pair<const TestData &, GLint>, 4> dataSet = {
{{data4, 4}, {bufferedData4, 4}, {data3, 3}, {bufferedData3, 3}}};
for (auto data : dataSet)
{
setupTest(data.first, data.second);
drawQuad(mProgram, "position", 0.5f);
glDisableVertexAttribArray(mTestAttrib);
glDisableVertexAttribArray(mExpectedAttrib);
checkPixels();
}
}
// Verify unsigned normalized INT_10_10_10_2 vertex type
TEST_P(VertexAttributeTest, UnsignedPacked1010102ExtensionNormalized)
{
std::string extensionList(reinterpret_cast<const char *>(glGetString(GL_EXTENSIONS)));
ANGLE_SKIP_TEST_IF((extensionList.find("OES_vertex_type_10_10_10_2") == std::string::npos));
// RGB channels are 10-bits, alpha is 2-bits
std::array<std::array<GLushort, 4>, kVertexCount / 4> unpackedInput = {{{0, 1, 2, 0},
{511, 512, 513, 1},
{1023, 1022, 1021, 3},
{513, 512, 511, 2},
{2, 1, 0, 3},
{1023, 1022, 1022, 0}}};
std::array<GLuint, kVertexCount> packedInput;
std::array<GLfloat, kVertexCount> expectedTypeSize4;
std::array<GLfloat, kVertexCount> expectedTypeSize3;
for (size_t i = 0; i < kVertexCount / 4; i++)
{
packedInput[i] = Pack1010102<GLuint, GLushort>(unpackedInput[i]);
expectedTypeSize3[i * 3 + 0] = expectedTypeSize4[i * 4 + 0] =
Normalize10<GLushort>(unpackedInput[i][0]);
expectedTypeSize3[i * 3 + 1] = expectedTypeSize4[i * 4 + 1] =
Normalize10<GLushort>(unpackedInput[i][1]);
expectedTypeSize3[i * 3 + 2] = expectedTypeSize4[i * 4 + 2] =
Normalize10<GLushort>(unpackedInput[i][2]);
// when the type size is 3, alpha will be 1.0f by GLES driver
expectedTypeSize4[i * 4 + 3] = Normalize2<GLushort>(unpackedInput[i][3]);
}
TestData data4(GL_UNSIGNED_INT_10_10_10_2_OES, GL_TRUE, Source::IMMEDIATE, packedInput.data(),
expectedTypeSize4.data());
TestData bufferedData4(GL_UNSIGNED_INT_10_10_10_2_OES, GL_TRUE, Source::BUFFER,
packedInput.data(), expectedTypeSize4.data());
TestData data3(GL_UNSIGNED_INT_10_10_10_2_OES, GL_TRUE, Source::IMMEDIATE, packedInput.data(),
expectedTypeSize3.data());
TestData bufferedData3(GL_UNSIGNED_INT_10_10_10_2_OES, GL_TRUE, Source::BUFFER,
packedInput.data(), expectedTypeSize3.data());
std::array<std::pair<const TestData &, GLint>, 4> dataSet = {
{{data4, 4}, {bufferedData4, 4}, {data3, 3}, {bufferedData3, 3}}};
for (auto data : dataSet)
{
setupTest(data.first, data.second);
drawQuad(mProgram, "position", 0.5f);
glDisableVertexAttribArray(mTestAttrib);
glDisableVertexAttribArray(mExpectedAttrib);
checkPixels();
};
}
class VertexAttributeTestES3 : public VertexAttributeTest
{
protected:
VertexAttributeTestES3() {}
};
TEST_P(VertexAttributeTestES3, IntUnnormalized)
{
GLint lo = std::numeric_limits<GLint>::min();
GLint hi = std::numeric_limits<GLint>::max();
std::array<GLint, kVertexCount> inputData = {
{0, 1, 2, 3, -1, -2, -3, -4, -1, hi, hi - 1, lo, lo + 1}};
std::array<GLfloat, kVertexCount> expectedData;
for (size_t i = 0; i < kVertexCount; i++)
{
expectedData[i] = static_cast<GLfloat>(inputData[i]);
}
TestData data(GL_INT, GL_FALSE, Source::BUFFER, inputData.data(), expectedData.data());
runTest(data);
}
TEST_P(VertexAttributeTestES3, IntNormalized)
{
GLint lo = std::numeric_limits<GLint>::min();
GLint hi = std::numeric_limits<GLint>::max();
std::array<GLint, kVertexCount> inputData = {
{0, 1, 2, 3, -1, -2, -3, -4, -1, hi, hi - 1, lo, lo + 1}};
std::array<GLfloat, kVertexCount> expectedData;
for (size_t i = 0; i < kVertexCount; i++)
{
expectedData[i] = Normalize(inputData[i]);
}
TestData data(GL_INT, GL_TRUE, Source::BUFFER, inputData.data(), expectedData.data());
runTest(data);
}
TEST_P(VertexAttributeTestES3, UnsignedIntUnnormalized)
{
GLuint mid = std::numeric_limits<GLuint>::max() >> 1;
GLuint hi = std::numeric_limits<GLuint>::max();
std::array<GLuint, kVertexCount> inputData = {
{0, 1, 2, 3, 254, 255, 256, mid - 1, mid, mid + 1, hi - 2, hi - 1, hi}};
std::array<GLfloat, kVertexCount> expectedData;
for (size_t i = 0; i < kVertexCount; i++)
{
expectedData[i] = static_cast<GLfloat>(inputData[i]);
}
TestData data(GL_UNSIGNED_INT, GL_FALSE, Source::BUFFER, inputData.data(), expectedData.data());
runTest(data);
}
TEST_P(VertexAttributeTestES3, UnsignedIntNormalized)
{
GLuint mid = std::numeric_limits<GLuint>::max() >> 1;
GLuint hi = std::numeric_limits<GLuint>::max();
std::array<GLuint, kVertexCount> inputData = {
{0, 1, 2, 3, 254, 255, 256, mid - 1, mid, mid + 1, hi - 2, hi - 1, hi}};
std::array<GLfloat, kVertexCount> expectedData;
for (size_t i = 0; i < kVertexCount; i++)
{
expectedData[i] = Normalize(inputData[i]);
}
TestData data(GL_UNSIGNED_INT, GL_TRUE, Source::BUFFER, inputData.data(), expectedData.data());
runTest(data);
}
void SetupColorsForUnitQuad(GLint location, const GLColor32F &color, GLenum usage, GLBuffer *vbo)
{
glBindBuffer(GL_ARRAY_BUFFER, *vbo);
std::vector<GLColor32F> vertices(6, color);
glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(GLColor32F), vertices.data(), usage);
glEnableVertexAttribArray(location);
glVertexAttribPointer(location, 4, GL_FLOAT, GL_FALSE, 0, 0);
}
// Tests that rendering works as expected with VAOs.
TEST_P(VertexAttributeTestES3, VertexArrayObjectRendering)
{
constexpr char kVertexShader[] =
"attribute vec4 a_position;\n"
"attribute vec4 a_color;\n"
"varying vec4 v_color;\n"
"void main()\n"
"{\n"
" gl_Position = a_position;\n"
" v_color = a_color;\n"
"}";
constexpr char kFragmentShader[] =
"precision mediump float;\n"
"varying vec4 v_color;\n"
"void main()\n"
"{\n"
" gl_FragColor = v_color;\n"
"}";
ANGLE_GL_PROGRAM(program, kVertexShader, kFragmentShader);
GLint positionLoc = glGetAttribLocation(program, "a_position");
ASSERT_NE(-1, positionLoc);
GLint colorLoc = glGetAttribLocation(program, "a_color");
ASSERT_NE(-1, colorLoc);
GLVertexArray vaos[2];
GLBuffer positionBuffer;
GLBuffer colorBuffers[2];
const auto &quadVertices = GetQuadVertices();
glBindVertexArray(vaos[0]);
glBindBuffer(GL_ARRAY_BUFFER, positionBuffer);
glBufferData(GL_ARRAY_BUFFER, quadVertices.size() * sizeof(Vector3), quadVertices.data(),
GL_STATIC_DRAW);
glEnableVertexAttribArray(positionLoc);
glVertexAttribPointer(positionLoc, 3, GL_FLOAT, GL_FALSE, 0, 0);
SetupColorsForUnitQuad(colorLoc, kFloatRed, GL_STREAM_DRAW, &colorBuffers[0]);
glBindVertexArray(vaos[1]);
glBindBuffer(GL_ARRAY_BUFFER, positionBuffer);
glEnableVertexAttribArray(positionLoc);
glVertexAttribPointer(positionLoc, 3, GL_FLOAT, GL_FALSE, 0, 0);
SetupColorsForUnitQuad(colorLoc, kFloatGreen, GL_STATIC_DRAW, &colorBuffers[1]);
glUseProgram(program);
ASSERT_GL_NO_ERROR();
for (int ii = 0; ii < 2; ++ii)
{
glBindVertexArray(vaos[0]);
glDrawArrays(GL_TRIANGLES, 0, 6);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
glBindVertexArray(vaos[1]);
glDrawArrays(GL_TRIANGLES, 0, 6);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}
ASSERT_GL_NO_ERROR();
}
// Validate that we can support GL_MAX_ATTRIBS attribs
TEST_P(VertexAttributeTest, MaxAttribs)
{
// TODO(jmadill): Figure out why we get this error on AMD/OpenGL.
ANGLE_SKIP_TEST_IF(IsAMD() && IsOpenGL());
// TODO: Support this test on Vulkan. http://anglebug.com/2797
ANGLE_SKIP_TEST_IF(IsLinux() && IsVulkan() && IsIntel());
GLint maxAttribs;
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &maxAttribs);
ASSERT_GL_NO_ERROR();
// Reserve one attrib for position
GLint drawAttribs = maxAttribs - 1;
GLuint program = compileMultiAttribProgram(drawAttribs);
ASSERT_NE(0u, program);
setupMultiAttribs(program, drawAttribs, 0.5f / static_cast<float>(drawAttribs));
drawQuad(program, "position", 0.5f);
EXPECT_GL_NO_ERROR();
EXPECT_PIXEL_NEAR(0, 0, 128, 0, 0, 255, 1);
}
// Validate that we cannot support GL_MAX_ATTRIBS+1 attribs
TEST_P(VertexAttributeTest, MaxAttribsPlusOne)
{
// TODO(jmadill): Figure out why we get this error on AMD/ES2/OpenGL
ANGLE_SKIP_TEST_IF(IsAMD() && GetParam() == ES2_OPENGL());
GLint maxAttribs;
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &maxAttribs);
ASSERT_GL_NO_ERROR();
// Exceed attrib count by one (counting position)
GLint drawAttribs = maxAttribs;
GLuint program = compileMultiAttribProgram(drawAttribs);
ASSERT_EQ(0u, program);
}
// Simple test for when we use glBindAttribLocation
TEST_P(VertexAttributeTest, SimpleBindAttribLocation)
{
// Re-use the multi-attrib program, binding attribute 0
GLuint program = compileMultiAttribProgram(1);
glBindAttribLocation(program, 2, "position");
glBindAttribLocation(program, 3, "a0");
glLinkProgram(program);
// Setup and draw the quad
setupMultiAttribs(program, 1, 0.5f);
drawQuad(program, "position", 0.5f);
EXPECT_GL_NO_ERROR();
EXPECT_PIXEL_NEAR(0, 0, 128, 0, 0, 255, 1);
}
class VertexAttributeOORTest : public VertexAttributeTest
{
public:
VertexAttributeOORTest()
{
setWebGLCompatibilityEnabled(true);
setRobustAccess(false);
}
};
// Verify that drawing with a large out-of-range offset generates INVALID_OPERATION.
// Requires WebGL compatibility with robust access behaviour disabled.
TEST_P(VertexAttributeOORTest, ANGLEDrawArraysBufferTooSmall)
{
// Test skipped due to supporting GL_KHR_robust_buffer_access_behavior
ANGLE_SKIP_TEST_IF(IsGLExtensionEnabled("GL_KHR_robust_buffer_access_behavior"));
std::array<GLfloat, kVertexCount> inputData;
std::array<GLfloat, kVertexCount> expectedData;
InitTestData(inputData, expectedData);
TestData data(GL_FLOAT, GL_FALSE, Source::BUFFER, inputData.data(), expectedData.data());
data.bufferOffset = kVertexCount * TypeStride(GL_FLOAT);
setupTest(data, 1);
drawQuad(mProgram, "position", 0.5f);
EXPECT_GL_ERROR(GL_INVALID_OPERATION);
}
// Verify that index draw with an out-of-range offset generates INVALID_OPERATION.
// Requires WebGL compatibility with robust access behaviour disabled.
TEST_P(VertexAttributeOORTest, ANGLEDrawElementsBufferTooSmall)
{
// Test skipped due to supporting GL_KHR_robust_buffer_access_behavior
ANGLE_SKIP_TEST_IF(IsGLExtensionEnabled("GL_KHR_robust_buffer_access_behavior"));
std::array<GLfloat, kVertexCount> inputData;
std::array<GLfloat, kVertexCount> expectedData;
InitTestData(inputData, expectedData);
TestData data(GL_FLOAT, GL_FALSE, Source::BUFFER, inputData.data(), expectedData.data());
data.bufferOffset = (kVertexCount - 3) * TypeStride(GL_FLOAT);
setupTest(data, 1);
drawIndexedQuad(mProgram, "position", 0.5f);
EXPECT_GL_ERROR(GL_INVALID_OPERATION);
}
// Verify that DrawArarys with an out-of-range offset generates INVALID_OPERATION.
// Requires WebGL compatibility with robust access behaviour disabled.
TEST_P(VertexAttributeOORTest, ANGLEDrawArraysOutOfBoundsCases)
{
// Test skipped due to supporting GL_KHR_robust_buffer_access_behavior
ANGLE_SKIP_TEST_IF(IsGLExtensionEnabled("GL_KHR_robust_buffer_access_behavior"));
initBasicProgram();
GLfloat singleFloat = 1.0f;
GLsizei dataSize = TypeStride(GL_FLOAT);
glBindBuffer(GL_ARRAY_BUFFER, mBuffer);
glBufferData(GL_ARRAY_BUFFER, dataSize, &singleFloat, GL_STATIC_DRAW);
glVertexAttribPointer(mTestAttrib, 2, GL_FLOAT, GL_FALSE, 8, 0);
glEnableVertexAttribArray(mTestAttrib);
glBindBuffer(GL_ARRAY_BUFFER, 0);
drawIndexedQuad(mProgram, "position", 0.5f);
EXPECT_GL_ERROR(GL_INVALID_OPERATION);
}
// Verify that using a different start vertex doesn't mess up the draw.
TEST_P(VertexAttributeTest, DrawArraysWithBufferOffset)
{
// anglebug.com/4163
ANGLE_SKIP_TEST_IF(IsD3D11() && IsNVIDIA() && IsWindows7());
// TODO(jmadill): Diagnose this failure.
ANGLE_SKIP_TEST_IF(IsD3D11_FL93());
// TODO(geofflang): Figure out why this is broken on AMD OpenGL
ANGLE_SKIP_TEST_IF(IsAMD() && IsOpenGL());
// TODO(cnorthrop): Test this again on more recent drivers. http://anglebug.com/3951
ANGLE_SKIP_TEST_IF(IsLinux() && IsNVIDIA() && IsVulkan());
initBasicProgram();
glUseProgram(mProgram);
std::array<GLfloat, kVertexCount> inputData;
std::array<GLfloat, kVertexCount> expectedData;
InitTestData(inputData, expectedData);
auto quadVertices = GetQuadVertices();
GLsizei quadVerticesSize = static_cast<GLsizei>(quadVertices.size() * sizeof(quadVertices[0]));
glGenBuffers(1, &mQuadBuffer);
glBindBuffer(GL_ARRAY_BUFFER, mQuadBuffer);
glBufferData(GL_ARRAY_BUFFER, quadVerticesSize + sizeof(Vector3), nullptr, GL_STATIC_DRAW);
glBufferSubData(GL_ARRAY_BUFFER, 0, quadVerticesSize, quadVertices.data());
GLint positionLocation = glGetAttribLocation(mProgram, "position");
ASSERT_NE(-1, positionLocation);
glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
glEnableVertexAttribArray(positionLocation);
GLsizei dataSize = kVertexCount * TypeStride(GL_FLOAT);
glBindBuffer(GL_ARRAY_BUFFER, mBuffer);
glBufferData(GL_ARRAY_BUFFER, dataSize + TypeStride(GL_FLOAT), nullptr, GL_STATIC_DRAW);
glBufferSubData(GL_ARRAY_BUFFER, 0, dataSize, inputData.data());
glVertexAttribPointer(mTestAttrib, 1, GL_FLOAT, GL_FALSE, 0, nullptr);
glEnableVertexAttribArray(mTestAttrib);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glVertexAttribPointer(mExpectedAttrib, 1, GL_FLOAT, GL_FALSE, 0, expectedData.data());
glEnableVertexAttribArray(mExpectedAttrib);
// Vertex draw with no start vertex offset (second argument is zero).
glDrawArrays(GL_TRIANGLES, 0, 6);
checkPixels();
// Draw offset by one vertex.
glDrawArrays(GL_TRIANGLES, 1, 6);
checkPixels();
EXPECT_GL_NO_ERROR();
}
// Verify that when we pass a client memory pointer to a disabled attribute the draw is still
// correct.
TEST_P(VertexAttributeTest, DrawArraysWithDisabledAttribute)
{
initBasicProgram();
std::array<GLfloat, kVertexCount> inputData;
std::array<GLfloat, kVertexCount> expectedData;
InitTestData(inputData, expectedData);
auto quadVertices = GetQuadVertices();
GLsizei quadVerticesSize = static_cast<GLsizei>(quadVertices.size() * sizeof(quadVertices[0]));
glGenBuffers(1, &mQuadBuffer);
glBindBuffer(GL_ARRAY_BUFFER, mQuadBuffer);
glBufferData(GL_ARRAY_BUFFER, quadVerticesSize, quadVertices.data(), GL_STATIC_DRAW);
GLint positionLocation = glGetAttribLocation(mProgram, "position");
ASSERT_NE(-1, positionLocation);
glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
glEnableVertexAttribArray(positionLocation);
glBindBuffer(GL_ARRAY_BUFFER, mBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(inputData), inputData.data(), GL_STATIC_DRAW);
glVertexAttribPointer(mTestAttrib, 1, GL_FLOAT, GL_FALSE, 0, nullptr);
glEnableVertexAttribArray(mTestAttrib);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glVertexAttribPointer(mExpectedAttrib, 1, GL_FLOAT, GL_FALSE, 0, expectedData.data());
glEnableVertexAttribArray(mExpectedAttrib);
// mProgram2 adds an attribute 'disabled' on the basis of mProgram.
constexpr char testVertexShaderSource2[] =
"attribute mediump vec4 position;\n"
"attribute mediump vec4 test;\n"
"attribute mediump vec4 expected;\n"
"attribute mediump vec4 disabled;\n"
"varying mediump vec4 color;\n"
"void main(void)\n"
"{\n"
" gl_Position = position;\n"
" vec4 threshold = max(abs(expected + disabled) * 0.005, 1.0 / 64.0);\n"
" color = vec4(lessThanEqual(abs(test - expected), threshold));\n"
"}\n";
constexpr char testFragmentShaderSource[] =
"varying mediump vec4 color;\n"
"void main(void)\n"
"{\n"
" gl_FragColor = color;\n"
"}\n";
ANGLE_GL_PROGRAM(program, testVertexShaderSource2, testFragmentShaderSource);
GLuint mProgram2 = program.get();
ASSERT_EQ(positionLocation, glGetAttribLocation(mProgram2, "position"));
ASSERT_EQ(mTestAttrib, glGetAttribLocation(mProgram2, "test"));
ASSERT_EQ(mExpectedAttrib, glGetAttribLocation(mProgram2, "expected"));
// Pass a client memory pointer to disabledAttribute and disable it.
GLint disabledAttribute = glGetAttribLocation(mProgram2, "disabled");
ASSERT_EQ(-1, glGetAttribLocation(mProgram, "disabled"));
glVertexAttribPointer(disabledAttribute, 1, GL_FLOAT, GL_FALSE, 0, expectedData.data());
glDisableVertexAttribArray(disabledAttribute);
glUseProgram(mProgram);
glDrawArrays(GL_TRIANGLES, 0, 6);
checkPixels();
// Now enable disabledAttribute which should be used in mProgram2.
glEnableVertexAttribArray(disabledAttribute);
glUseProgram(mProgram2);
glDrawArrays(GL_TRIANGLES, 0, 6);
checkPixels();
EXPECT_GL_NO_ERROR();
}
// Test based on WebGL Test attribs/gl-disabled-vertex-attrib.html
TEST_P(VertexAttributeTest, DisabledAttribArrays)
{
// Known failure on Retina MBP: http://crbug.com/635081
ANGLE_SKIP_TEST_IF(IsOSX() && IsNVIDIA());
// TODO: Support this test on Vulkan. http://anglebug.com/2797
ANGLE_SKIP_TEST_IF(IsLinux() && IsVulkan() && IsIntel());
constexpr char kVS[] =
"attribute vec4 a_position;\n"
"attribute vec4 a_color;\n"
"varying vec4 v_color;\n"
"bool isCorrectColor(vec4 v) {\n"
" return v.x == 0.0 && v.y == 0.0 && v.z == 0.0 && v.w == 1.0;\n"
"}"
"void main() {\n"
" gl_Position = a_position;\n"
" v_color = isCorrectColor(a_color) ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);\n"
"}";
constexpr char kFS[] =
"varying mediump vec4 v_color;\n"
"void main() {\n"
" gl_FragColor = v_color;\n"
"}";
GLint maxVertexAttribs = 0;
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &maxVertexAttribs);
for (GLint colorIndex = 0; colorIndex < maxVertexAttribs; ++colorIndex)
{
GLuint program = CompileProgram(kVS, kFS, [&](GLuint program) {
glBindAttribLocation(program, colorIndex, "a_color");
});
ASSERT_NE(0u, program);
drawQuad(program, "a_position", 0.5f);
ASSERT_GL_NO_ERROR();
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
glDeleteProgram(program);
}
}
class VertexAttributeTestES31 : public VertexAttributeTestES3
{
protected:
VertexAttributeTestES31() {}
void initTest()
{
initBasicProgram();
glUseProgram(mProgram);
glGenVertexArrays(1, &mVAO);
glBindVertexArray(mVAO);
auto quadVertices = GetQuadVertices();
GLsizeiptr quadVerticesSize =
static_cast<GLsizeiptr>(quadVertices.size() * sizeof(quadVertices[0]));
glGenBuffers(1, &mQuadBuffer);
glBindBuffer(GL_ARRAY_BUFFER, mQuadBuffer);
glBufferData(GL_ARRAY_BUFFER, quadVerticesSize, quadVertices.data(), GL_STATIC_DRAW);
GLint positionLocation = glGetAttribLocation(mProgram, "position");
ASSERT_NE(-1, positionLocation);
glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
glEnableVertexAttribArray(positionLocation);
std::array<GLfloat, kVertexCount> expectedData;
for (size_t count = 0; count < kVertexCount; ++count)
{
expectedData[count] = static_cast<GLfloat>(count);
}
const GLsizei kExpectedDataSize = kVertexCount * kFloatStride;
glGenBuffers(1, &mExpectedBuffer);
glBindBuffer(GL_ARRAY_BUFFER, mExpectedBuffer);
glBufferData(GL_ARRAY_BUFFER, kExpectedDataSize, expectedData.data(), GL_STATIC_DRAW);
glVertexAttribPointer(mExpectedAttrib, 1, GL_FLOAT, GL_FALSE, 0, nullptr);
glEnableVertexAttribArray(mExpectedAttrib);
}
void testTearDown() override
{
VertexAttributeTestES3::testTearDown();
glDeleteBuffers(1, &mExpectedBuffer);
glDeleteVertexArrays(1, &mVAO);
}
void drawArraysWithStrideAndRelativeOffset(GLint stride, GLuint relativeOffset)
{
initTest();
GLint floatStride = std::max(stride / kFloatStride, 1);
GLuint floatRelativeOffset = relativeOffset / kFloatStride;
size_t floatCount = static_cast<size_t>(floatRelativeOffset) + kVertexCount * floatStride;
GLsizeiptr inputSize = static_cast<GLsizeiptr>(floatCount) * kFloatStride;
std::vector<GLfloat> inputData(floatCount);
for (size_t count = 0; count < kVertexCount; ++count)
{
inputData[floatRelativeOffset + count * floatStride] = static_cast<GLfloat>(count);
}
// Ensure inputSize, inputStride and inputOffset are multiples of TypeStride(GL_FLOAT).
GLsizei inputStride = floatStride * kFloatStride;
GLsizeiptr inputRelativeOffset = floatRelativeOffset * kFloatStride;
glBindBuffer(GL_ARRAY_BUFFER, mBuffer);
glBufferData(GL_ARRAY_BUFFER, inputSize, nullptr, GL_STATIC_DRAW);
glBufferSubData(GL_ARRAY_BUFFER, 0, inputSize, inputData.data());
glVertexAttribFormat(mTestAttrib, 1, GL_FLOAT, GL_FALSE,
base::checked_cast<GLuint>(inputRelativeOffset));
glBindVertexBuffer(mTestAttrib, mBuffer, 0, inputStride);
glEnableVertexAttribArray(mTestAttrib);
glDrawArrays(GL_TRIANGLES, 0, 6);
checkPixels();
EXPECT_GL_NO_ERROR();
}
void initOnlyUpdateBindingTest(GLint bindingToUpdate)
{
initTest();
constexpr GLuint kTestFloatOffset1 = kVertexCount;
std::array<GLfloat, kTestFloatOffset1 + kVertexCount> inputData1 = {};
for (size_t count = 0; count < kVertexCount; ++count)
{
GLfloat value = static_cast<GLfloat>(count);
inputData1[kTestFloatOffset1 + count] = value;
}
GLBuffer testBuffer1;
glBindBuffer(GL_ARRAY_BUFFER, testBuffer1);
glBufferData(GL_ARRAY_BUFFER, inputData1.size() * kFloatStride, inputData1.data(),
GL_STATIC_DRAW);
ASSERT_NE(bindingToUpdate, mTestAttrib);
ASSERT_NE(bindingToUpdate, mExpectedAttrib);
// Set mTestAttrib using the binding bindingToUpdate.
glVertexAttribFormat(mTestAttrib, 1, GL_FLOAT, GL_FALSE, 0);
glBindVertexBuffer(bindingToUpdate, testBuffer1, kTestFloatOffset1 * kFloatStride,
kFloatStride);
glVertexAttribBinding(mTestAttrib, bindingToUpdate);
glEnableVertexAttribArray(mTestAttrib);
// In the first draw the current VAO states are set to driver.
glDrawArrays(GL_TRIANGLES, 0, 6);
checkPixels();
EXPECT_GL_NO_ERROR();
// We need the second draw to ensure all VAO dirty bits are reset.
// e.g. On D3D11 back-ends, Buffer11::resize is called in the first draw, where the related
// binding is set to dirty again.
glDrawArrays(GL_TRIANGLES, 0, 6);
checkPixels();
EXPECT_GL_NO_ERROR();
}
GLuint mVAO;
GLuint mExpectedBuffer;
const GLsizei kFloatStride = TypeStride(GL_FLOAT);
// Set the maximum value for stride and relativeOffset in case they are too large.
const GLint MAX_STRIDE_FOR_TEST = 4095;
const GLint MAX_RELATIVE_OFFSET_FOR_TEST = 4095;
};
// Verify that MAX_VERTEX_ATTRIB_STRIDE is no less than the minimum required value (2048) in ES3.1.
TEST_P(VertexAttributeTestES31, MaxVertexAttribStride)
{
GLint maxStride;
glGetIntegerv(GL_MAX_VERTEX_ATTRIB_STRIDE, &maxStride);
ASSERT_GL_NO_ERROR();
EXPECT_GE(maxStride, 2048);
}
// Verify that GL_MAX_VERTEX_ATTRIB_RELATIVE_OFFSET is no less than the minimum required value
// (2047) in ES3.1.
TEST_P(VertexAttributeTestES31, MaxVertexAttribRelativeOffset)
{
GLint maxRelativeOffset;
glGetIntegerv(GL_MAX_VERTEX_ATTRIB_RELATIVE_OFFSET, &maxRelativeOffset);
ASSERT_GL_NO_ERROR();
EXPECT_GE(maxRelativeOffset, 2047);
}
// Verify using MAX_VERTEX_ATTRIB_STRIDE as stride doesn't mess up the draw.
// Use default value if the value of MAX_VERTEX_ATTRIB_STRIDE is too large for this test.
TEST_P(VertexAttributeTestES31, DrawArraysWithLargeStride)
{
GLint maxStride;
glGetIntegerv(GL_MAX_VERTEX_ATTRIB_STRIDE, &maxStride);
ASSERT_GL_NO_ERROR();
GLint largeStride = std::min(maxStride, MAX_STRIDE_FOR_TEST);
drawArraysWithStrideAndRelativeOffset(largeStride, 0);
}
// Verify using MAX_VERTEX_ATTRIB_RELATIVE_OFFSET as relativeOffset doesn't mess up the draw.
// Use default value if the value of MAX_VERTEX_ATTRIB_RELATIVE_OFFSSET is too large for this test.
TEST_P(VertexAttributeTestES31, DrawArraysWithLargeRelativeOffset)
{
GLint maxRelativeOffset;
glGetIntegerv(GL_MAX_VERTEX_ATTRIB_RELATIVE_OFFSET, &maxRelativeOffset);
ASSERT_GL_NO_ERROR();
GLint largeRelativeOffset = std::min(maxRelativeOffset, MAX_RELATIVE_OFFSET_FOR_TEST);
drawArraysWithStrideAndRelativeOffset(0, largeRelativeOffset);
}
// Test that vertex array object works correctly when render pipeline and compute pipeline are
// crossly executed.
TEST_P(VertexAttributeTestES31, MixedComputeAndRenderPipelines)
{
constexpr char kComputeShader[] =
R"(#version 310 es
layout(local_size_x=1) in;
void main()
{
})";
ANGLE_GL_COMPUTE_PROGRAM(computePogram, kComputeShader);
glViewport(0, 0, getWindowWidth(), getWindowHeight());
glClearColor(0, 0, 0, 0);
constexpr char kVertexShader[] =
R"(#version 310 es
precision mediump float;
layout(location = 0) in vec4 position;
layout(location = 2) in vec2 aOffset;
layout(location = 3) in vec4 aColor;
out vec4 vColor;
void main() {
vColor = aColor;
gl_Position = position + vec4(aOffset, 0.0, 0.0);
})";
constexpr char kFragmentShader[] =
R"(#version 310 es
precision mediump float;
in vec4 vColor;
out vec4 color;
void main() {
color = vColor;
})";
ANGLE_GL_PROGRAM(renderProgram, kVertexShader, kFragmentShader);
constexpr char kVertexShader1[] =
R"(#version 310 es
precision mediump float;
layout(location = 1) in vec4 position;
layout(location = 2) in vec2 aOffset;
layout(location = 3) in vec4 aColor;
out vec4 vColor;
void main() {
vColor = aColor;
gl_Position = position + vec4(aOffset, 0.0, 0.0);
})";
ANGLE_GL_PROGRAM(renderProgram1, kVertexShader1, kFragmentShader);
std::array<GLfloat, 8> offsets = {
-1.0, 1.0, 1.0, 1.0, -1.0, -1.0, 1.0, -1.0,
};
GLBuffer offsetBuffer;
glBindBuffer(GL_ARRAY_BUFFER, offsetBuffer);
glBufferData(GL_ARRAY_BUFFER, offsets.size() * sizeof(GLfloat), offsets.data(), GL_STATIC_DRAW);
std::array<GLfloat, 16> colors0 = {
1.0, 0.0, 0.0, 1.0, // Red
0.0, 1.0, 0.0, 1.0, // Green
0.0, 0.0, 1.0, 1.0, // Blue
1.0, 1.0, 0.0, 1.0, // Yellow
};
std::array<GLfloat, 16> colors1 = {
1.0, 1.0, 0.0, 1.0, // Yellow
0.0, 0.0, 1.0, 1.0, // Blue
0.0, 1.0, 0.0, 1.0, // Green
1.0, 0.0, 0.0, 1.0, // Red
};
GLBuffer colorBuffers[2];
glBindBuffer(GL_ARRAY_BUFFER, colorBuffers[0]);
glBufferData(GL_ARRAY_BUFFER, colors0.size() * sizeof(GLfloat), colors0.data(), GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, colorBuffers[1]);
glBufferData(GL_ARRAY_BUFFER, colors1.size() * sizeof(GLfloat), colors1.data(), GL_STATIC_DRAW);
std::array<GLfloat, 16> positions = {1.0, 1.0, -1.0, 1.0, -1.0, -1.0,
1.0, 1.0, -1.0, -1.0, 1.0, -1.0};
GLBuffer positionBuffer;
glBindBuffer(GL_ARRAY_BUFFER, positionBuffer);
glBufferData(GL_ARRAY_BUFFER, positions.size() * sizeof(GLfloat), positions.data(),
GL_STATIC_DRAW);
const int kInstanceCount = 4;
GLVertexArray vao[2];
for (size_t i = 0u; i < 2u; ++i)
{
glBindVertexArray(vao[i]);
glBindBuffer(GL_ARRAY_BUFFER, offsetBuffer);
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 2, GL_FLOAT, false, 0, 0);
glVertexAttribDivisor(2, 1);
glBindBuffer(GL_ARRAY_BUFFER, colorBuffers[i]);
glEnableVertexAttribArray(3);
glVertexAttribPointer(3, 4, GL_FLOAT, false, 0, 0);
glVertexAttribDivisor(3, 1);
glBindBuffer(GL_ARRAY_BUFFER, positionBuffer);
glEnableVertexAttribArray(i);
glVertexAttribPointer(i, 2, GL_FLOAT, false, 0, 0);
}
glClear(GL_COLOR_BUFFER_BIT);
for (int i = 0; i < 3; i++)
{
glUseProgram(renderProgram.get());
glBindVertexArray(vao[0]);
glDrawArraysInstanced(GL_TRIANGLES, 0, 6, kInstanceCount);
EXPECT_GL_NO_ERROR();
EXPECT_PIXEL_COLOR_EQ(0, getWindowHeight() / 2, GLColor::red);
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::green);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue);
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, 0, GLColor::yellow);
glBindVertexArray(vao[1]);
glUseProgram(computePogram.get());
glDispatchCompute(1, 1, 1);
glUseProgram(renderProgram1.get());
glBindVertexArray(vao[1]);
glDrawArraysInstanced(GL_TRIANGLES, 0, 6, kInstanceCount);
EXPECT_GL_NO_ERROR();
EXPECT_PIXEL_COLOR_EQ(0, getWindowHeight() / 2, GLColor::yellow);
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::blue);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, 0, GLColor::red);
}
}
TEST_P(VertexAttributeTestES31, UseComputeShaderToUpdateVertexBuffer)
{
initTest();
constexpr char kComputeShader[] =
R"(#version 310 es
layout(local_size_x=24) in;
layout(std430, binding = 0) buffer buf {
uint outData[24];
};
void main()
{
outData[gl_LocalInvocationIndex] = gl_LocalInvocationIndex;
})";
ANGLE_GL_COMPUTE_PROGRAM(computeProgram, kComputeShader);
glUseProgram(mProgram);
GLuint mid = std::numeric_limits<GLuint>::max() >> 1;
GLuint hi = std::numeric_limits<GLuint>::max();
std::array<GLuint, kVertexCount> inputData = {
{0, 1, 2, 3, 254, 255, 256, mid - 1, mid, mid + 1, hi - 2, hi - 1, hi}};
std::array<GLfloat, kVertexCount> expectedData;
for (size_t i = 0; i < kVertexCount; i++)
{
expectedData[i] = Normalize(inputData[i]);
}
// Normalized unsigned int attribute will be classified as translated static attribute.
TestData data(GL_UNSIGNED_INT, GL_TRUE, Source::BUFFER, inputData.data(), expectedData.data());
GLint typeSize = 4;
GLsizei dataSize = kVertexCount * TypeStride(data.type);
GLBuffer testBuffer;
glBindBuffer(GL_ARRAY_BUFFER, testBuffer);
glBufferData(GL_ARRAY_BUFFER, dataSize, data.inputData, GL_STATIC_DRAW);
glVertexAttribPointer(mTestAttrib, typeSize, data.type, data.normalized, 0,
reinterpret_cast<void *>(data.bufferOffset));
glEnableVertexAttribArray(mTestAttrib);
glBindBuffer(GL_ARRAY_BUFFER, mExpectedBuffer);
glBufferData(GL_ARRAY_BUFFER, dataSize, data.expectedData, GL_STATIC_DRAW);
glVertexAttribPointer(mExpectedAttrib, typeSize, GL_FLOAT, GL_FALSE, 0, nullptr);
// Draw twice to make sure that all static attributes dirty bits are synced.
glDrawArrays(GL_TRIANGLES, 0, 6);
glDrawArrays(GL_TRIANGLES, 0, 6);
checkPixels();
// Modify the testBuffer using a raw buffer
glUseProgram(computeProgram);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, testBuffer);
glDispatchCompute(1, 1, 1);
glMemoryBarrier(GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT);
// Draw again to verify that testBuffer has been changed.
glUseProgram(mProgram);
glDrawArrays(GL_TRIANGLES, 0, 6);
EXPECT_GL_NO_ERROR();
checkPixelsUnEqual();
}
// Verify that using VertexAttribBinding after VertexAttribPointer won't mess up the draw.
TEST_P(VertexAttributeTestES31, ChangeAttribBindingAfterVertexAttribPointer)
{
initTest();
constexpr GLint kInputStride = 2;
constexpr GLint kFloatOffset = 10;
std::array<GLfloat, kVertexCount + kFloatOffset> inputData1;
std::array<GLfloat, kVertexCount * kInputStride> inputData2;
for (size_t count = 0; count < kVertexCount; ++count)
{
inputData1[kFloatOffset + count] = static_cast<GLfloat>(count);
inputData2[count * kInputStride] = static_cast<GLfloat>(count);
}
GLBuffer mBuffer1;
glBindBuffer(GL_ARRAY_BUFFER, mBuffer1);
glBufferData(GL_ARRAY_BUFFER, inputData1.size() * kFloatStride, inputData1.data(),
GL_STATIC_DRAW);
// Update the format indexed mTestAttrib and the binding indexed mTestAttrib by
// VertexAttribPointer.
const GLintptr kOffset = static_cast<GLintptr>(kFloatStride * kFloatOffset);
glVertexAttribPointer(mTestAttrib, 1, GL_FLOAT, GL_FALSE, 0,
reinterpret_cast<const GLvoid *>(kOffset));
glEnableVertexAttribArray(mTestAttrib);
constexpr GLint kTestBinding = 10;
ASSERT_NE(mTestAttrib, kTestBinding);
GLBuffer mBuffer2;
glBindBuffer(GL_ARRAY_BUFFER, mBuffer2);
glBufferData(GL_ARRAY_BUFFER, inputData2.size() * kFloatStride, inputData2.data(),
GL_STATIC_DRAW);
glBindVertexBuffer(kTestBinding, mBuffer2, 0, kFloatStride * kInputStride);
// The attribute indexed mTestAttrib is using the binding indexed kTestBinding in the first
// draw.
glVertexAttribBinding(mTestAttrib, kTestBinding);
glDrawArrays(GL_TRIANGLES, 0, 6);
checkPixels();
EXPECT_GL_NO_ERROR();
// The attribute indexed mTestAttrib is using the binding indexed mTestAttrib which should be
// set after the call VertexAttribPointer before the first draw.
glVertexAttribBinding(mTestAttrib, mTestAttrib);
glDrawArrays(GL_TRIANGLES, 0, 6);
checkPixels();
EXPECT_GL_NO_ERROR();
}
// Verify that using VertexAttribFormat after VertexAttribPointer won't mess up the draw.
TEST_P(VertexAttributeTestES31, ChangeAttribFormatAfterVertexAttribPointer)
{
initTest();
constexpr GLuint kFloatOffset = 10;
std::array<GLfloat, kVertexCount + kFloatOffset> inputData;
for (size_t count = 0; count < kVertexCount; ++count)
{
inputData[kFloatOffset + count] = static_cast<GLfloat>(count);
}
glBindBuffer(GL_ARRAY_BUFFER, mBuffer);
glBufferData(GL_ARRAY_BUFFER, inputData.size() * kFloatStride, inputData.data(),
GL_STATIC_DRAW);
// Call VertexAttribPointer on mTestAttrib. Now the relativeOffset of mTestAttrib should be 0.
const GLuint kOffset = static_cast<GLuint>(kFloatStride * kFloatOffset);
glVertexAttribPointer(mTestAttrib, 1, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(mTestAttrib);
// Call VertexAttribFormat on mTestAttrib to modify the relativeOffset to kOffset.
glVertexAttribFormat(mTestAttrib, 1, GL_FLOAT, GL_FALSE, kOffset);
glDrawArrays(GL_TRIANGLES, 0, 6);
checkPixels();
EXPECT_GL_NO_ERROR();
}
// Verify that only updating a binding without updating the bound format won't mess up this draw.
TEST_P(VertexAttributeTestES31, OnlyUpdateBindingByBindVertexBuffer)
{
// Default binding index for test
constexpr GLint kTestBinding = 10;
initOnlyUpdateBindingTest(kTestBinding);
constexpr GLuint kTestFloatOffset2 = kVertexCount * 2;
std::array<GLfloat, kVertexCount> expectedData2 = {};
std::array<GLfloat, kTestFloatOffset2 + kVertexCount> inputData2 = {};
for (size_t count = 0; count < kVertexCount; ++count)
{
GLfloat value2 = static_cast<GLfloat>(count) * 2;
expectedData2[count] = value2;
inputData2[count + kTestFloatOffset2] = value2;
}
// Set another set of data for mExpectedAttrib.
GLBuffer expectedBuffer2;
glBindBuffer(GL_ARRAY_BUFFER, expectedBuffer2);
glBufferData(GL_ARRAY_BUFFER, expectedData2.size() * kFloatStride, expectedData2.data(),
GL_STATIC_DRAW);
glVertexAttribPointer(mExpectedAttrib, 1, GL_FLOAT, GL_FALSE, 0, nullptr);
GLBuffer testBuffer2;
glBindBuffer(GL_ARRAY_BUFFER, testBuffer2);
glBufferData(GL_ARRAY_BUFFER, inputData2.size() * kFloatStride, inputData2.data(),
GL_STATIC_DRAW);
// Only update the binding kTestBinding in the second draw by BindVertexBuffer.
glBindVertexBuffer(kTestBinding, testBuffer2, kTestFloatOffset2 * kFloatStride, kFloatStride);
glDrawArrays(GL_TRIANGLES, 0, 6);
checkPixels();
EXPECT_GL_NO_ERROR();
}
// Verify that only updating a binding without updating the bound format won't mess up this draw.
TEST_P(VertexAttributeTestES31, OnlyUpdateBindingByVertexAttribPointer)
{
// Default binding index for test
constexpr GLint kTestBinding = 10;
initOnlyUpdateBindingTest(kTestBinding);
constexpr GLuint kTestFloatOffset2 = kVertexCount * 3;
std::array<GLfloat, kVertexCount> expectedData2 = {};
std::array<GLfloat, kTestFloatOffset2 + kVertexCount> inputData2 = {};
for (size_t count = 0; count < kVertexCount; ++count)
{
GLfloat value2 = static_cast<GLfloat>(count) * 3;
expectedData2[count] = value2;
inputData2[count + kTestFloatOffset2] = value2;
}
// Set another set of data for mExpectedAttrib.
GLBuffer expectedBuffer2;
glBindBuffer(GL_ARRAY_BUFFER, expectedBuffer2);
glBufferData(GL_ARRAY_BUFFER, expectedData2.size() * kFloatStride, expectedData2.data(),
GL_STATIC_DRAW);
glVertexAttribPointer(mExpectedAttrib, 1, GL_FLOAT, GL_FALSE, 0, nullptr);
GLBuffer testBuffer2;
glBindBuffer(GL_ARRAY_BUFFER, testBuffer2);
glBufferData(GL_ARRAY_BUFFER, inputData2.size() * kFloatStride, inputData2.data(),
GL_STATIC_DRAW);
// Only update the binding kTestBinding in the second draw by VertexAttribPointer.
glVertexAttribPointer(kTestBinding, 1, GL_FLOAT, GL_FALSE, 0,
reinterpret_cast<const void *>(kTestFloatOffset2 * kFloatStride));
glDrawArrays(GL_TRIANGLES, 0, 6);
checkPixels();
EXPECT_GL_NO_ERROR();
}
class VertexAttributeCachingTest : public VertexAttributeTest
{
protected:
VertexAttributeCachingTest() {}
void testSetUp() override;
template <typename DestT>
static std::vector<GLfloat> GetExpectedData(const std::vector<GLubyte> &srcData,
GLenum attribType,
GLboolean normalized);
void initDoubleAttribProgram()
{
constexpr char kVS[] =
"attribute mediump vec4 position;\n"
"attribute mediump vec4 test;\n"
"attribute mediump vec4 expected;\n"
"attribute mediump vec4 test2;\n"
"attribute mediump vec4 expected2;\n"
"varying mediump vec4 color;\n"
"void main(void)\n"
"{\n"
" gl_Position = position;\n"
" vec4 threshold = max(abs(expected) * 0.01, 1.0 / 64.0);\n"
" color = vec4(lessThanEqual(abs(test - expected), threshold));\n"
" vec4 threshold2 = max(abs(expected2) * 0.01, 1.0 / 64.0);\n"
" color += vec4(lessThanEqual(abs(test2 - expected2), threshold2));\n"
"}\n";
constexpr char kFS[] =
"varying mediump vec4 color;\n"
"void main(void)\n"
"{\n"
" gl_FragColor = color;\n"
"}\n";
mProgram = CompileProgram(kVS, kFS);
ASSERT_NE(0u, mProgram);
mTestAttrib = glGetAttribLocation(mProgram, "test");
ASSERT_NE(-1, mTestAttrib);
mExpectedAttrib = glGetAttribLocation(mProgram, "expected");
ASSERT_NE(-1, mExpectedAttrib);
glUseProgram(mProgram);
}
struct AttribData
{
AttribData(GLenum typeIn, GLint sizeIn, GLboolean normalizedIn, GLsizei strideIn);
GLenum type;
GLint size;
GLboolean normalized;
GLsizei stride;
};
std::vector<AttribData> mTestData;
std::map<GLenum, std::vector<GLfloat>> mExpectedData;
std::map<GLenum, std::vector<GLfloat>> mNormExpectedData;
};
VertexAttributeCachingTest::AttribData::AttribData(GLenum typeIn,
GLint sizeIn,
GLboolean normalizedIn,
GLsizei strideIn)
: type(typeIn), size(sizeIn), normalized(normalizedIn), stride(strideIn)
{}
// static
template <typename DestT>
std::vector<GLfloat> VertexAttributeCachingTest::GetExpectedData(
const std::vector<GLubyte> &srcData,
GLenum attribType,
GLboolean normalized)
{
std::vector<GLfloat> expectedData;
const DestT *typedSrcPtr = reinterpret_cast<const DestT *>(srcData.data());
size_t iterations = srcData.size() / TypeStride(attribType);
if (normalized)
{
for (size_t index = 0; index < iterations; ++index)
{
expectedData.push_back(Normalize(typedSrcPtr[index]));
}
}
else
{
for (size_t index = 0; index < iterations; ++index)
{
expectedData.push_back(static_cast<GLfloat>(typedSrcPtr[index]));
}
}
return expectedData;
}
void VertexAttributeCachingTest::testSetUp()
{
VertexAttributeTest::testSetUp();
glBindBuffer(GL_ARRAY_BUFFER, mBuffer);
std::vector<GLubyte> srcData;
for (size_t count = 0; count < 4; ++count)
{
for (GLubyte i = 0; i < std::numeric_limits<GLubyte>::max(); ++i)
{
srcData.push_back(i);
}
}
glBufferData(GL_ARRAY_BUFFER, srcData.size(), srcData.data(), GL_STATIC_DRAW);
GLint viewportSize[4];
glGetIntegerv(GL_VIEWPORT, viewportSize);
std::vector<GLenum> attribTypes;
attribTypes.push_back(GL_BYTE);
attribTypes.push_back(GL_UNSIGNED_BYTE);
attribTypes.push_back(GL_SHORT);
attribTypes.push_back(GL_UNSIGNED_SHORT);
if (getClientMajorVersion() >= 3)
{
attribTypes.push_back(GL_INT);
attribTypes.push_back(GL_UNSIGNED_INT);
}
constexpr GLint kMaxSize = 4;
constexpr GLsizei kMaxStride = 4;
for (GLenum attribType : attribTypes)
{
for (GLint attribSize = 1; attribSize <= kMaxSize; ++attribSize)
{
for (GLsizei stride = 1; stride <= kMaxStride; ++stride)
{
mTestData.push_back(AttribData(attribType, attribSize, GL_FALSE, stride));
if (attribType != GL_FLOAT)
{
mTestData.push_back(AttribData(attribType, attribSize, GL_TRUE, stride));
}
}
}
}
mExpectedData[GL_BYTE] = GetExpectedData<GLbyte>(srcData, GL_BYTE, GL_FALSE);
mExpectedData[GL_UNSIGNED_BYTE] = GetExpectedData<GLubyte>(srcData, GL_UNSIGNED_BYTE, GL_FALSE);
mExpectedData[GL_SHORT] = GetExpectedData<GLshort>(srcData, GL_SHORT, GL_FALSE);
mExpectedData[GL_UNSIGNED_SHORT] =
GetExpectedData<GLushort>(srcData, GL_UNSIGNED_SHORT, GL_FALSE);
mExpectedData[GL_INT] = GetExpectedData<GLint>(srcData, GL_INT, GL_FALSE);
mExpectedData[GL_UNSIGNED_INT] = GetExpectedData<GLuint>(srcData, GL_UNSIGNED_INT, GL_FALSE);
mNormExpectedData[GL_BYTE] = GetExpectedData<GLbyte>(srcData, GL_BYTE, GL_TRUE);
mNormExpectedData[GL_UNSIGNED_BYTE] =
GetExpectedData<GLubyte>(srcData, GL_UNSIGNED_BYTE, GL_TRUE);
mNormExpectedData[GL_SHORT] = GetExpectedData<GLshort>(srcData, GL_SHORT, GL_TRUE);
mNormExpectedData[GL_UNSIGNED_SHORT] =
GetExpectedData<GLushort>(srcData, GL_UNSIGNED_SHORT, GL_TRUE);
mNormExpectedData[GL_INT] = GetExpectedData<GLint>(srcData, GL_INT, GL_TRUE);
mNormExpectedData[GL_UNSIGNED_INT] = GetExpectedData<GLuint>(srcData, GL_UNSIGNED_INT, GL_TRUE);
}
// In D3D11, we must sometimes translate buffer data into static attribute caches. We also use a
// cache management scheme which garbage collects old attributes after we start using too much
// cache data. This test tries to make as many attribute caches from a single buffer as possible
// to stress-test the caching code.
TEST_P(VertexAttributeCachingTest, BufferMulticaching)
{
ANGLE_SKIP_TEST_IF(IsAMD() && IsDesktopOpenGL());
initBasicProgram();
glEnableVertexAttribArray(mTestAttrib);
glEnableVertexAttribArray(mExpectedAttrib);
ASSERT_GL_NO_ERROR();
for (const AttribData &data : mTestData)
{
const auto &expected =
(data.normalized) ? mNormExpectedData[data.type] : mExpectedData[data.type];
GLsizei baseStride = static_cast<GLsizei>(data.size) * data.stride;
GLsizei stride = TypeStride(data.type) * baseStride;
glBindBuffer(GL_ARRAY_BUFFER, mBuffer);
glVertexAttribPointer(mTestAttrib, data.size, data.type, data.normalized, stride, nullptr);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glVertexAttribPointer(mExpectedAttrib, data.size, GL_FLOAT, GL_FALSE,
sizeof(GLfloat) * baseStride, expected.data());
drawQuad(mProgram, "position", 0.5f);
ASSERT_GL_NO_ERROR();
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::white);
}
}
// With D3D11 dirty bits for VertxArray11, we can leave vertex state unchanged if there aren't any
// GL calls that affect it. This test targets leaving one vertex attribute unchanged between draw
// calls while changing another vertex attribute enough that it clears the static buffer cache
// after enough iterations. It validates the unchanged attributes don't get deleted incidentally.
TEST_P(VertexAttributeCachingTest, BufferMulticachingWithOneUnchangedAttrib)
{
ANGLE_SKIP_TEST_IF(IsAMD() && IsDesktopOpenGL());
initDoubleAttribProgram();
GLint testAttrib2Location = glGetAttribLocation(mProgram, "test2");
ASSERT_NE(-1, testAttrib2Location);
GLint expectedAttrib2Location = glGetAttribLocation(mProgram, "expected2");
ASSERT_NE(-1, expectedAttrib2Location);
glEnableVertexAttribArray(mTestAttrib);
glEnableVertexAttribArray(mExpectedAttrib);
glEnableVertexAttribArray(testAttrib2Location);
glEnableVertexAttribArray(expectedAttrib2Location);
ASSERT_GL_NO_ERROR();
// Use an attribute that we know must be converted. This is a bit sensitive.
glBindBuffer(GL_ARRAY_BUFFER, mBuffer);
glVertexAttribPointer(testAttrib2Location, 3, GL_UNSIGNED_SHORT, GL_FALSE, 6, nullptr);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glVertexAttribPointer(expectedAttrib2Location, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 3,
mExpectedData[GL_UNSIGNED_SHORT].data());
for (const auto &data : mTestData)
{
const auto &expected =
(data.normalized) ? mNormExpectedData[data.type] : mExpectedData[data.type];
GLsizei baseStride = static_cast<GLsizei>(data.size) * data.stride;
GLsizei stride = TypeStride(data.type) * baseStride;
glBindBuffer(GL_ARRAY_BUFFER, mBuffer);
glVertexAttribPointer(mTestAttrib, data.size, data.type, data.normalized, stride, nullptr);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glVertexAttribPointer(mExpectedAttrib, data.size, GL_FLOAT, GL_FALSE,
sizeof(GLfloat) * baseStride, expected.data());
drawQuad(mProgram, "position", 0.5f);
ASSERT_GL_NO_ERROR();
EXPECT_PIXEL_EQ(getWindowWidth() / 2, getWindowHeight() / 2, 255, 255, 255, 255);
}
}
// Test that if there are gaps in the attribute indices, the attributes have their correct values.
TEST_P(VertexAttributeTest, UnusedVertexAttribWorks)
{
constexpr char kVertexShader[] = R"(attribute vec2 position;
attribute float actualValue;
uniform float expectedValue;
varying float result;
void main()
{
result = (actualValue == expectedValue) ? 1.0 : 0.0;
gl_Position = vec4(position, 0, 1);
})";
constexpr char kFragmentShader[] = R"(varying mediump float result;
void main()
{
gl_FragColor = result > 0.0 ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);
})";
ANGLE_GL_PROGRAM(program, kVertexShader, kFragmentShader);
// Force a gap in attributes by using location 0 and 3
GLint positionLocation = 0;
glBindAttribLocation(program, positionLocation, "position");
GLint attribLoc = 3;
glBindAttribLocation(program, attribLoc, "actualValue");
// Re-link the program to update the attribute locations
glLinkProgram(program);
ASSERT_TRUE(CheckLinkStatusAndReturnProgram(program, true));
glUseProgram(program);
GLint uniLoc = glGetUniformLocation(program, "expectedValue");
ASSERT_NE(-1, uniLoc);
glVertexAttribPointer(attribLoc, 1, GL_FLOAT, GL_FALSE, 0, nullptr);
ASSERT_NE(-1, positionLocation);
setupQuadVertexBuffer(0.5f, 1.0f);
glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(positionLocation);
std::array<GLfloat, 4> testValues = {{1, 2, 3, 4}};
for (GLfloat testValue : testValues)
{
glUniform1f(uniLoc, testValue);
glVertexAttrib1f(attribLoc, testValue);
glDrawArrays(GL_TRIANGLES, 0, 6);
ASSERT_GL_NO_ERROR();
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}
}
// Tests that repeatedly updating a disabled vertex attribute works as expected.
// This covers an ANGLE bug where dirty bits for current values were ignoring repeated updates.
TEST_P(VertexAttributeTest, DisabledAttribUpdates)
{
constexpr char kVertexShader[] = R"(attribute vec2 position;
attribute float actualValue;
uniform float expectedValue;
varying float result;
void main()
{
result = (actualValue == expectedValue) ? 1.0 : 0.0;
gl_Position = vec4(position, 0, 1);
})";
constexpr char kFragmentShader[] = R"(varying mediump float result;
void main()
{
gl_FragColor = result > 0.0 ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);
})";
ANGLE_GL_PROGRAM(program, kVertexShader, kFragmentShader);
glUseProgram(program);
GLint attribLoc = glGetAttribLocation(program, "actualValue");
ASSERT_NE(-1, attribLoc);
GLint uniLoc = glGetUniformLocation(program, "expectedValue");
ASSERT_NE(-1, uniLoc);
glVertexAttribPointer(attribLoc, 1, GL_FLOAT, GL_FALSE, 0, nullptr);
GLint positionLocation = glGetAttribLocation(program, "position");
ASSERT_NE(-1, positionLocation);
setupQuadVertexBuffer(0.5f, 1.0f);
glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(positionLocation);
std::array<GLfloat, 4> testValues = {{1, 2, 3, 4}};
for (GLfloat testValue : testValues)
{
glUniform1f(uniLoc, testValue);
glVertexAttrib1f(attribLoc, testValue);
glDrawArrays(GL_TRIANGLES, 0, 6);
ASSERT_GL_NO_ERROR();
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}
}
// Test that even inactive attributes are taken into account when checking for aliasing in case the
// shader version is >= 3.00. GLSL ES 3.00.6 section 12.46.
TEST_P(VertexAttributeTestES3, InactiveAttributeAliasing)
{
constexpr char vertexShader[] =
R"(#version 300 es
precision mediump float;
in vec4 input_active;
in vec4 input_unused;
void main()
{
gl_Position = input_active;
})";
constexpr char fragmentShader[] =
R"(#version 300 es
precision mediump float;
out vec4 color;
void main()
{
color = vec4(0.0);
})";
ANGLE_GL_PROGRAM(program, vertexShader, fragmentShader);
glBindAttribLocation(program, 0, "input_active");
glBindAttribLocation(program, 0, "input_unused");
glLinkProgram(program);
EXPECT_GL_NO_ERROR();
GLint linkStatus = 0;
glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
EXPECT_GL_FALSE(linkStatus);
}
// Test that enabling inactive attributes doesn't cause a crash
// shader version is >= 3.00
TEST_P(VertexAttributeTestES3, EnabledButInactiveAttributes)
{
// This is similar to runtest(), and the test is disabled there
ANGLE_SKIP_TEST_IF(IsAMD() && IsOpenGL());
constexpr char testVertexShaderSource[] =
R"(#version 300 es
precision mediump float;
in vec4 position;
layout(location = 1) in vec4 test;
layout(location = 2) in vec4 unused1;
layout(location = 3) in vec4 unused2;
layout(location = 4) in vec4 unused3;
layout(location = 5) in vec4 expected;
out vec4 color;
void main(void)
{
gl_Position = position;
vec4 threshold = max(abs(expected) * 0.01, 1.0 / 64.0);
color = vec4(lessThanEqual(abs(test - expected), threshold));
})";
// Same as previous one, except it uses unused1/2 instead of test/expected, leaving unused3
// unused
constexpr char testVertexShader2Source[] =
R"(#version 300 es
precision mediump float;
in vec4 position;
layout(location = 1) in vec4 test;
layout(location = 2) in vec4 unused1;
layout(location = 3) in vec4 unused2;
layout(location = 4) in vec4 unused3;
layout(location = 5) in vec4 expected;
out vec4 color;
void main(void)
{
gl_Position = position;
vec4 threshold = max(abs(unused2) * 0.01, 1.0 / 64.0);
color = vec4(lessThanEqual(abs(unused1 - unused2), threshold));
})";
constexpr char testFragmentShaderSource[] =
R"(#version 300 es
precision mediump float;
in vec4 color;
out vec4 out_color;
void main()
{
out_color = color;
})";
std::array<GLubyte, kVertexCount> inputData = {
{0, 1, 2, 3, 4, 5, 6, 7, 125, 126, 127, 128, 129, 250, 251, 252, 253, 254, 255}};
std::array<GLubyte, kVertexCount> inputData2;
std::array<GLfloat, kVertexCount> expectedData;
std::array<GLfloat, kVertexCount> expectedData2;
for (size_t i = 0; i < kVertexCount; i++)
{
expectedData[i] = inputData[i];
inputData2[i] = inputData[i] > 128 ? inputData[i] - 1 : inputData[i] + 1;
expectedData2[i] = inputData2[i];
}
// Setup the program
mProgram = CompileProgram(testVertexShaderSource, testFragmentShaderSource);
ASSERT_NE(0u, mProgram);
mTestAttrib = glGetAttribLocation(mProgram, "test");
ASSERT_EQ(1, mTestAttrib);
mExpectedAttrib = glGetAttribLocation(mProgram, "expected");
ASSERT_EQ(5, mExpectedAttrib);
GLint unused1Attrib = 2;
GLint unused2Attrib = 3;
GLint unused3Attrib = 4;
// Test enabling an unused attribute before glUseProgram
glEnableVertexAttribArray(unused3Attrib);
glUseProgram(mProgram);
// Setup the test data
TestData data(GL_UNSIGNED_BYTE, GL_FALSE, Source::IMMEDIATE, inputData.data(),
expectedData.data());
setupTest(data, 1);
// Test enabling an unused attribute after glUseProgram
glVertexAttribPointer(unused1Attrib, 1, data.type, data.normalized, 0, inputData2.data());
glEnableVertexAttribArray(unused1Attrib);
glVertexAttribPointer(unused2Attrib, 1, GL_FLOAT, GL_FALSE, 0, expectedData2.data());
glEnableVertexAttribArray(unused2Attrib);
// Run the test. This shouldn't use the unused attributes. Note that one of them is nullptr
// which can cause a crash on certain platform-driver combination.
drawQuad(mProgram, "position", 0.5f);
checkPixels();
// Now test with the same attributes enabled, but with a program with different attributes
// active
mProgram = CompileProgram(testVertexShader2Source, testFragmentShaderSource);
ASSERT_NE(0u, mProgram);
// Make sure all the attributes are in the same location
ASSERT_EQ(glGetAttribLocation(mProgram, "unused1"), unused1Attrib);
ASSERT_EQ(glGetAttribLocation(mProgram, "unused2"), unused2Attrib);
glUseProgram(mProgram);
// Run the test again. unused1/2 were disabled in the previous run (as they were inactive in
// the shader), but should be re-enabled now.
drawQuad(mProgram, "position", 0.5f);
checkPixels();
}
// Use this to select which configurations (e.g. which renderer, which GLES major version) these
// tests should be run against.
// D3D11 Feature Level 9_3 uses different D3D formats for vertex attribs compared to Feature Levels
// 10_0+, so we should test them separately.
ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(VertexAttributeTest);
ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(VertexAttributeOORTest);
ANGLE_INSTANTIATE_TEST_ES3(VertexAttributeTestES3);
ANGLE_INSTANTIATE_TEST_ES31(VertexAttributeTestES31);
ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(VertexAttributeCachingTest);
} // anonymous namespace