blob: 9c9ef46fb77f40a94810fbb144ba77a8156867b8 [file] [edit]
//
// 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.
//
// DrawElementsTest:
// Tests for indexed draws.
//
#include "common/unsafe_buffers.h"
#include "test_utils/ANGLETest.h"
#include "test_utils/gl_raii.h"
using namespace angle;
namespace
{
class DrawElementsTest : public ANGLETest<>
{
protected:
DrawElementsTest() : mProgram(0u)
{
setWindowWidth(64);
setWindowHeight(64);
setConfigRedBits(8);
setConfigGreenBits(8);
setConfigBlueBits(8);
setConfigAlphaBits(8);
}
~DrawElementsTest()
{
for (GLuint indexBuffer : mIndexBuffers)
{
if (indexBuffer != 0)
{
glDeleteBuffers(1, &indexBuffer);
}
}
for (GLuint vertexArray : mVertexArrays)
{
if (vertexArray != 0)
{
glDeleteVertexArrays(1, &vertexArray);
}
}
for (GLuint vertexBuffer : mVertexBuffers)
{
if (vertexBuffer != 0)
{
glDeleteBuffers(1, &vertexBuffer);
}
}
if (mProgram != 0u)
{
glDeleteProgram(mProgram);
}
}
std::vector<GLuint> mIndexBuffers;
std::vector<GLuint> mVertexArrays;
std::vector<GLuint> mVertexBuffers;
GLuint mProgram;
};
class WebGLDrawElementsTest : public DrawElementsTest
{
public:
WebGLDrawElementsTest() { setWebGLCompatibilityEnabled(true); }
};
template <typename T, typename U>
std::vector<T> convertIndexBufferWithRestarts(const std::vector<U> &input)
{
constexpr U inputRestart = std::numeric_limits<U>::max();
constexpr T outputRestart = std::numeric_limits<T>::max();
std::vector<T> output;
output.reserve(input.size());
for (auto byte : input)
{
output.push_back(byte == inputRestart ? outputRestart : static_cast<T>(byte));
}
return output;
}
enum class PrimitiveRestartMode
{
Disabled,
Enabled,
EnabledSplitContent,
};
enum class IndicesMode
{
Buffer,
BufferAndOffset,
ClientSideArray,
};
enum class RestartOption
{
None,
AtBegin,
AtEnd,
Degenerate,
};
using DrawElementsVariantsTestParams = std::tuple<angle::PlatformParameters,
GLenum, // type
bool, // useFlat
PrimitiveRestartMode, // primitiveRestartMode
bool, // drawStrip
IndicesMode, // indicesMode
RestartOption, // restartOption
bool>; // useProvokingVertexFirst
std::string DrawElementsVariantsTestPrint(
const ::testing::TestParamInfo<DrawElementsVariantsTestParams> &paramsInfo)
{
const auto &[platform, type, useFlat, primitiveRestartMode, drawStrip, indicesMode,
restartOption, useProvokingVertexFirst] = paramsInfo.param;
std::ostringstream out;
out << platform << "__"
<< (type == GL_UNSIGNED_BYTE ? "B" : (type == GL_UNSIGNED_SHORT ? "S" : "I"))
<< (useFlat ? "_Flat" : "");
switch (primitiveRestartMode)
{
case PrimitiveRestartMode::Disabled:
break;
case PrimitiveRestartMode::Enabled:
out << "_Restart";
break;
case PrimitiveRestartMode::EnabledSplitContent:
out << "_Split";
break;
}
out << (drawStrip ? "_Strip" : "_Tris");
switch (indicesMode)
{
case IndicesMode::Buffer:
break;
case IndicesMode::BufferAndOffset:
out << "_Offset";
break;
case IndicesMode::ClientSideArray:
out << "_Client";
break;
}
switch (restartOption)
{
case RestartOption::None:
break;
case RestartOption::AtBegin:
out << "_AtBegin";
break;
case RestartOption::AtEnd:
out << "_AtEnd";
break;
case RestartOption::Degenerate:
out << "_Degen";
break;
}
out << (useProvokingVertexFirst ? "_First" : "");
return out.str();
}
class DrawElementsVariantsTest : public ANGLETest<DrawElementsVariantsTestParams>
{
protected:
DrawElementsVariantsTest()
{
setWindowWidth(64);
setWindowHeight(64);
setConfigRedBits(8);
setConfigGreenBits(8);
setConfigBlueBits(8);
setConfigAlphaBits(8);
}
GLenum type() const { return std::get<1>(GetParam()); }
bool useFlat() const { return std::get<2>(GetParam()); }
PrimitiveRestartMode primitiveRestartMode() const { return std::get<3>(GetParam()); }
bool drawStrip() const { return std::get<4>(GetParam()); }
IndicesMode indicesMode() const { return std::get<5>(GetParam()); }
RestartOption restartOption() const { return std::get<6>(GetParam()); }
bool addRestartAtBegin() const { return restartOption() == RestartOption::AtBegin; }
bool addRestartAtEnd() const { return restartOption() == RestartOption::AtEnd; }
bool addRestartDegenerate() const { return restartOption() == RestartOption::Degenerate; }
bool useProvokingVertexFirst() const { return std::get<7>(GetParam()); }
bool usePrimitiveRestart() const
{
return primitiveRestartMode() != PrimitiveRestartMode::Disabled;
}
bool splitContent() const
{
return primitiveRestartMode() == PrimitiveRestartMode::EnabledSplitContent;
}
bool addOffset() const { return indicesMode() == IndicesMode::BufferAndOffset; }
bool useClientSideArrays() const { return indicesMode() == IndicesMode::ClientSideArray; }
};
// Test no error is generated when using client-side arrays, indices = nullptr and count = 0
TEST_P(DrawElementsTest, ClientSideNullptrArrayZeroCount)
{
constexpr char kVS[] =
"attribute vec3 a_pos;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(a_pos, 1.0);\n"
"}\n";
ANGLE_GL_PROGRAM(program, kVS, essl1_shaders::fs::Blue());
GLint posLocation = glGetAttribLocation(program, "a_pos");
ASSERT_NE(-1, posLocation);
glUseProgram(program);
const auto &vertices = GetQuadVertices();
GLBuffer vertexBuffer;
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices[0]) * vertices.size(), vertices.data(),
GL_STATIC_DRAW);
glVertexAttribPointer(posLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(posLocation);
ASSERT_GL_NO_ERROR();
// "If drawElements is called with a count greater than zero, and no WebGLBuffer is bound to the
// ELEMENT_ARRAY_BUFFER binding point, an INVALID_OPERATION error is generated."
glDrawElements(GL_TRIANGLES, 1, GL_UNSIGNED_BYTE, nullptr);
EXPECT_GL_ERROR(GL_INVALID_OPERATION);
// count == 0 so it's fine to have no element array buffer bound.
glDrawElements(GL_TRIANGLES, 0, GL_UNSIGNED_BYTE, nullptr);
ASSERT_GL_NO_ERROR();
}
// Test uploading part of an index buffer after deleting a vertex array
// previously used for DrawElements.
TEST_P(DrawElementsTest, DeleteVertexArrayAndUploadIndex)
{
const auto &vertices = GetIndexedQuadVertices();
const auto &indices = GetQuadIndices();
ANGLE_GL_PROGRAM(programDrawRed, essl3_shaders::vs::Simple(), essl3_shaders::fs::Red());
glUseProgram(programDrawRed);
GLint posLocation = glGetAttribLocation(programDrawRed, essl3_shaders::PositionAttrib());
ASSERT_NE(-1, posLocation);
GLuint vertexArray;
glGenVertexArrays(1, &vertexArray);
glBindVertexArray(vertexArray);
GLBuffer vertexBuffer;
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices[0]) * vertices.size(), vertices.data(),
GL_STATIC_DRAW);
glVertexAttribPointer(posLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(posLocation);
GLBuffer indexBuffer;
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices[0]) * indices.size(), indices.data(),
GL_STATIC_DRAW);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, nullptr);
glDeleteVertexArrays(1, &vertexArray);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
// Could crash here if the observer binding from the vertex array doesn't get
// removed on vertex array destruction.
glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, sizeof(indices[0]) * 3, indices.data());
ASSERT_GL_NO_ERROR();
}
// Test VAO switch is handling cached element array buffer properly along with line loop mode
// switch.
TEST_P(DrawElementsTest, LineLoopTriangles)
{
const auto &vertices = GetIndexedQuadVertices();
constexpr std::array<GLuint, 6> indices = {{0, 1, 2, 0, 2, 3}};
ANGLE_GL_PROGRAM(programDrawRed, essl3_shaders::vs::Simple(), essl3_shaders::fs::Red());
ANGLE_GL_PROGRAM(programDrawBlue, essl3_shaders::vs::Simple(), essl3_shaders::fs::Blue());
glUseProgram(programDrawRed);
GLint posLocation = glGetAttribLocation(programDrawRed, essl3_shaders::PositionAttrib());
ASSERT_NE(-1, posLocation);
GLVertexArray vertexArray[2];
GLBuffer vertexBuffer[2];
glBindVertexArray(vertexArray[0]);
GLBuffer indexBuffer;
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices[0]) * indices.size(), indices.data(),
GL_STATIC_DRAW);
for (int i = 0; i < 2; i++)
{
glBindVertexArray(ANGLE_UNSAFE_TODO(vertexArray[i]));
glBindBuffer(GL_ARRAY_BUFFER, ANGLE_UNSAFE_TODO(vertexBuffer[i]));
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices[0]) * vertices.size(), vertices.data(),
GL_STATIC_DRAW);
glVertexAttribPointer(posLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(posLocation);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
}
// First draw with VAO0 and line loop mode
glBindVertexArray(vertexArray[0]);
glDrawArrays(GL_LINE_LOOP, 0, 4);
// Switch to VAO1 and draw with triangle mode.
glBindVertexArray(vertexArray[1]);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() - 1, 0, GLColor::red);
EXPECT_PIXEL_COLOR_EQ(0, getWindowHeight() - 1, GLColor::red);
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() - 1, getWindowHeight() - 1, GLColor::red);
// Switch back to VAO0 and draw with triangle mode.
glUseProgram(programDrawBlue);
glBindVertexArray(vertexArray[0]);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue);
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() - 1, 0, GLColor::blue);
EXPECT_PIXEL_COLOR_EQ(0, getWindowHeight() - 1, GLColor::blue);
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() - 1, getWindowHeight() - 1, GLColor::blue);
ASSERT_GL_NO_ERROR();
}
// Regression test for using two VAOs, one to draw only GL_LINE_LOOPs, and
// another to draw indexed triangles.
TEST_P(DrawElementsTest, LineLoopTriangles2)
{
const auto &vertices = GetIndexedQuadVertices();
constexpr std::array<GLuint, 6> indices = {{0, 1, 2, 0, 2, 3}};
ANGLE_GL_PROGRAM(programDrawRed, essl3_shaders::vs::Simple(), essl3_shaders::fs::Red());
glUseProgram(programDrawRed);
GLint posLocation = glGetAttribLocation(programDrawRed, essl3_shaders::PositionAttrib());
ASSERT_NE(-1, posLocation);
GLVertexArray vertexArray[2];
GLBuffer vertexBuffer[2];
GLBuffer indexBuffer;
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices[0]) * indices.size(), indices.data(),
GL_STATIC_DRAW);
for (int i = 0; i < 2; i++)
{
glBindVertexArray(ANGLE_UNSAFE_TODO(vertexArray[i]));
glBindBuffer(GL_ARRAY_BUFFER, ANGLE_UNSAFE_TODO(vertexBuffer[i]));
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices[0]) * vertices.size(), vertices.data(),
GL_STATIC_DRAW);
glVertexAttribPointer(posLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(posLocation);
if (i != 0)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
}
// First draw with VAO0 and line loop mode
glBindVertexArray(vertexArray[0]);
glDrawArrays(GL_LINE_LOOP, 0, 4);
// Switch to VAO1 and draw some indexed triangles
glBindVertexArray(vertexArray[1]);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr);
// Switch back to VAO0 and do another line loop
glBindVertexArray(vertexArray[0]);
// Would crash if the index buffer dirty bit got errantly set on VAO0.
glDrawArrays(GL_LINE_LOOP, 0, 4);
ASSERT_GL_NO_ERROR();
}
// Test a state desync that can occur when using a streaming index buffer in GL in concert with
// deleting the applied index buffer.
TEST_P(DrawElementsTest, DeletingAfterStreamingIndexes)
{
// Init program
constexpr char kVS[] =
"attribute vec2 position;\n"
"attribute vec2 testFlag;\n"
"varying vec2 v_data;\n"
"void main() {\n"
" gl_Position = vec4(position, 0, 1);\n"
" v_data = testFlag;\n"
"}";
constexpr char kFS[] =
"varying highp vec2 v_data;\n"
"void main() {\n"
" gl_FragColor = vec4(v_data, 0, 1);\n"
"}";
mProgram = CompileProgram(kVS, kFS);
ASSERT_NE(0u, mProgram);
glUseProgram(mProgram);
GLint positionLocation = glGetAttribLocation(mProgram, "position");
ASSERT_NE(-1, positionLocation);
GLint testFlagLocation = glGetAttribLocation(mProgram, "testFlag");
ASSERT_NE(-1, testFlagLocation);
mIndexBuffers.resize(3u);
glGenBuffers(3, &mIndexBuffers[0]);
mVertexArrays.resize(2);
glGenVertexArrays(2, &mVertexArrays[0]);
mVertexBuffers.resize(2);
glGenBuffers(2, &mVertexBuffers[0]);
std::vector<GLuint> indexData[2];
indexData[0].push_back(0);
indexData[0].push_back(1);
indexData[0].push_back(2);
indexData[0].push_back(2);
indexData[0].push_back(3);
indexData[0].push_back(0);
indexData[1] = indexData[0];
for (GLuint &item : indexData[1])
{
item += 4u;
}
std::vector<GLfloat> positionData = {// quad verts
-1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f,
// Repeat position data
-1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f};
std::vector<GLfloat> testFlagData = {// red
1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
// green
0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f};
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBuffers[0]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLuint) * indexData[0].size(), &indexData[0][0],
GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBuffers[2]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLuint) * indexData[0].size(), &indexData[0][0],
GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBuffers[1]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLuint) * indexData[1].size(), &indexData[1][0],
GL_STATIC_DRAW);
// Initialize first vertex array with second index buffer
glBindVertexArray(mVertexArrays[0]);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBuffers[1]);
glBindBuffer(GL_ARRAY_BUFFER, mVertexBuffers[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * positionData.size(), &positionData[0],
GL_STATIC_DRAW);
glVertexAttribPointer(positionLocation, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 2, nullptr);
glEnableVertexAttribArray(positionLocation);
glBindBuffer(GL_ARRAY_BUFFER, mVertexBuffers[1]);
glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * testFlagData.size(), &testFlagData[0],
GL_STATIC_DRAW);
glVertexAttribPointer(testFlagLocation, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 2, nullptr);
glEnableVertexAttribArray(testFlagLocation);
// Initialize second vertex array with first index buffer
glBindVertexArray(mVertexArrays[1]);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBuffers[0]);
glBindBuffer(GL_ARRAY_BUFFER, mVertexBuffers[0]);
glVertexAttribPointer(positionLocation, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 2, nullptr);
glEnableVertexAttribArray(positionLocation);
glBindBuffer(GL_ARRAY_BUFFER, mVertexBuffers[1]);
glVertexAttribPointer(testFlagLocation, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 2, nullptr);
glEnableVertexAttribArray(testFlagLocation);
ASSERT_GL_NO_ERROR();
glBindVertexArray(mVertexArrays[0]);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
glBindVertexArray(mVertexArrays[1]);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
glBindVertexArray(mVertexArrays[0]);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
// Trigger the bug here.
glDeleteBuffers(1, &mIndexBuffers[2]);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
ASSERT_GL_NO_ERROR();
}
// Verify that detaching shaders after linking doesn't break draw calls
TEST_P(DrawElementsTest, DrawWithDetachedShaders)
{
const auto &vertices = GetIndexedQuadVertices();
GLBuffer vertexBuffer;
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices[0]) * vertices.size(), vertices.data(),
GL_STATIC_DRAW);
GLBuffer indexBuffer;
const auto &indices = GetQuadIndices();
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices[0]) * indices.size(), indices.data(),
GL_STATIC_DRAW);
ASSERT_GL_NO_ERROR();
GLuint vertexShader = CompileShader(GL_VERTEX_SHADER, essl3_shaders::vs::Simple());
GLuint fragmentShader = CompileShader(GL_FRAGMENT_SHADER, essl3_shaders::fs::Red());
ASSERT_NE(0u, vertexShader);
ASSERT_NE(0u, fragmentShader);
GLuint program = glCreateProgram();
glAttachShader(program, vertexShader);
glAttachShader(program, fragmentShader);
glLinkProgram(program);
GLint linkStatus;
glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
EXPECT_EQ(GL_TRUE, linkStatus);
glDetachShader(program, vertexShader);
glDetachShader(program, fragmentShader);
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
ASSERT_GL_NO_ERROR();
glUseProgram(program);
GLint posLocation = glGetAttribLocation(program, essl3_shaders::PositionAttrib());
ASSERT_NE(-1, posLocation);
GLVertexArray vertexArray;
glBindVertexArray(vertexArray);
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glVertexAttribPointer(posLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(posLocation);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, nullptr);
ASSERT_GL_NO_ERROR();
glDeleteProgram(program);
ASSERT_GL_NO_ERROR();
}
// Test drawing to part of the indices in an index buffer, and then all of them.
TEST_P(DrawElementsTest, PartOfIndexBufferThenAll)
{
// Init program
constexpr char kVS[] =
"attribute vec2 position;\n"
"attribute vec2 testFlag;\n"
"varying vec2 v_data;\n"
"void main() {\n"
" gl_Position = vec4(position, 0, 1);\n"
" v_data = testFlag;\n"
"}";
constexpr char kFS[] =
"varying highp vec2 v_data;\n"
"void main() {\n"
" gl_FragColor = vec4(v_data, 0, 1);\n"
"}";
mProgram = CompileProgram(kVS, kFS);
ASSERT_NE(0u, mProgram);
glUseProgram(mProgram);
GLint positionLocation = glGetAttribLocation(mProgram, "position");
ASSERT_NE(-1, positionLocation);
GLint testFlagLocation = glGetAttribLocation(mProgram, "testFlag");
ASSERT_NE(-1, testFlagLocation);
mIndexBuffers.resize(1);
glGenBuffers(1, &mIndexBuffers[0]);
mVertexArrays.resize(1);
glGenVertexArrays(1, &mVertexArrays[0]);
mVertexBuffers.resize(2);
glGenBuffers(2, &mVertexBuffers[0]);
std::vector<GLubyte> indexData[2];
indexData[0].push_back(0);
indexData[0].push_back(1);
indexData[0].push_back(2);
indexData[0].push_back(2);
indexData[0].push_back(3);
indexData[0].push_back(0);
indexData[0].push_back(4);
indexData[0].push_back(5);
indexData[0].push_back(6);
indexData[0].push_back(6);
indexData[0].push_back(7);
indexData[0].push_back(4);
// Make a copy:
indexData[1] = indexData[0];
std::vector<GLfloat> positionData = {// quad verts
-1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f,
// Repeat position data
-1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f};
std::vector<GLfloat> testFlagData = {// red
1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
// green
0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f};
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBuffers[0]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLubyte) * indexData[0].size(), &indexData[0][0],
GL_STATIC_DRAW);
glBindVertexArray(mVertexArrays[0]);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBuffers[0]);
glBindBuffer(GL_ARRAY_BUFFER, mVertexBuffers[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * positionData.size(), &positionData[0],
GL_STATIC_DRAW);
glVertexAttribPointer(positionLocation, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 2, nullptr);
glEnableVertexAttribArray(positionLocation);
glBindBuffer(GL_ARRAY_BUFFER, mVertexBuffers[1]);
glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * testFlagData.size(), &testFlagData[0],
GL_STATIC_DRAW);
glVertexAttribPointer(testFlagLocation, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 2, nullptr);
glEnableVertexAttribArray(testFlagLocation);
ASSERT_GL_NO_ERROR();
// Draw with just the second set of 6 items, then first 6, and then the entire index buffer
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, reinterpret_cast<const void *>(6));
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, nullptr);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
glDrawElements(GL_TRIANGLES, 12, GL_UNSIGNED_BYTE, nullptr);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
// Reload the buffer again with a copy of the same data
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLubyte) * indexData[1].size(), &indexData[1][0],
GL_STATIC_DRAW);
// Draw with just the first 6 indices, and then with the entire index buffer
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, nullptr);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
glDrawElements(GL_TRIANGLES, 12, GL_UNSIGNED_BYTE, nullptr);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
// Reload the buffer again with a copy of the same data
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLubyte) * indexData[0].size(), &indexData[0][0],
GL_STATIC_DRAW);
// This time, do not check color between draws (which causes a flush):
// Draw with just the second set of 6 items, then first 6, and then the entire index buffer
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, reinterpret_cast<const void *>(6));
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, nullptr);
glDrawElements(GL_TRIANGLES, 12, GL_UNSIGNED_BYTE, nullptr);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
ASSERT_GL_NO_ERROR();
}
// Test that glDrawElements call with different index buffer offsets work as expected
TEST_P(DrawElementsTest, DrawElementsWithDifferentIndexBufferOffsets)
{
const std::array<Vector3, 4> &vertices = GetIndexedQuadVertices();
const std::array<GLushort, 6> &indices = GetQuadIndices();
glClearColor(0.f, 0.f, 0.f, 1.f);
glClear(GL_COLOR_BUFFER_BIT);
ANGLE_GL_PROGRAM(programDrawRed, essl3_shaders::vs::Simple(), essl3_shaders::fs::Red());
ANGLE_GL_PROGRAM(programDrawGreen, essl3_shaders::vs::Simple(), essl3_shaders::fs::Green());
ANGLE_GL_PROGRAM(programDrawBlue, essl3_shaders::vs::Simple(), essl3_shaders::fs::Blue());
glUseProgram(programDrawRed);
GLuint vertexArray;
glGenVertexArrays(1, &vertexArray);
glBindVertexArray(vertexArray);
GLBuffer vertexBuffer;
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices[0]) * vertices.size(), vertices.data(),
GL_STATIC_DRAW);
GLint posLocation = glGetAttribLocation(programDrawRed, essl3_shaders::PositionAttrib());
ASSERT_NE(-1, posLocation);
glVertexAttribPointer(posLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(posLocation);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
// Draw both triangles of quad
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indices.data());
EXPECT_PIXEL_COLOR_EQ(0, 1, GLColor::red);
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() - 1, getWindowHeight() - 2, GLColor::red);
glUseProgram(programDrawGreen);
GLuint vertexArray1;
glGenVertexArrays(1, &vertexArray1);
glBindVertexArray(vertexArray1);
GLBuffer vertexBuffer1;
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer1);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices[0]) * vertices.size(), vertices.data(),
GL_STATIC_DRAW);
posLocation = glGetAttribLocation(programDrawGreen, essl3_shaders::PositionAttrib());
ASSERT_NE(-1, posLocation);
glVertexAttribPointer(posLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(posLocation);
GLBuffer indexBuffer;
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices[0]) * indices.size(), indices.data(),
GL_DYNAMIC_DRAW);
// Draw right triangle of quad
glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_SHORT, reinterpret_cast<const void *>(6));
EXPECT_PIXEL_COLOR_EQ(0, 1, GLColor::red);
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() - 1, getWindowHeight() - 2, GLColor::green);
glUseProgram(programDrawBlue);
glBindVertexArray(vertexArray);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
posLocation = glGetAttribLocation(programDrawBlue, essl3_shaders::PositionAttrib());
ASSERT_NE(-1, posLocation);
glVertexAttribPointer(posLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(posLocation);
// Draw both triangles of quad
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indices.data());
EXPECT_PIXEL_COLOR_EQ(0, 1, GLColor::blue);
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() - 1, getWindowHeight() - 2, GLColor::blue);
glDeleteVertexArrays(1, &vertexArray);
glDeleteVertexArrays(1, &vertexArray1);
ASSERT_GL_NO_ERROR();
}
// Test one element buffer bind to two vertexArrays and switch vertexArray should draw correctly
TEST_P(DrawElementsTest, TwoVertexArraysWithSameElementBuffer)
{
glClearColor(0.f, 0.f, 0.f, 1.f);
glClear(GL_COLOR_BUFFER_BIT);
ANGLE_GL_PROGRAM(programDrawRed, essl3_shaders::vs::Simple(), essl3_shaders::fs::Red());
glUseProgram(programDrawRed);
GLint posLocation = glGetAttribLocation(programDrawRed, essl3_shaders::PositionAttrib());
ASSERT_NE(-1, posLocation);
GLBuffer vertexBuffer;
const std::array<Vector3, 4> &vertices = GetIndexedQuadVertices();
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices[0]) * vertices.size(), vertices.data(),
GL_STATIC_DRAW);
GLBuffer elementBuffer;
std::array<GLubyte, 6> zeros;
size_t elementBufferSize = sizeof(zeros[0]) * zeros.size();
zeros.fill(0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementBuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, elementBufferSize, zeros.data(), GL_DYNAMIC_DRAW);
// Set up two vertex arrays using same set of buffers. Since initial element buffer all point to
// the same vertex, it should only draw one point
GLuint vertexArray[2];
glGenVertexArrays(2, vertexArray);
glBindVertexArray(vertexArray[0]);
glVertexAttribPointer(posLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(posLocation);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementBuffer);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, reinterpret_cast<const void *>(0));
glBindVertexArray(vertexArray[1]);
glVertexAttribPointer(posLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(posLocation);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementBuffer);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, reinterpret_cast<const void *>(0));
// Use vertexArray[0] and update elementBuffer and draw
const std::array<GLubyte, 6> &indices = {0, 1, 2, 0, 2, 3};
glBindVertexArray(vertexArray[0]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, elementBufferSize, indices.data(), GL_DYNAMIC_DRAW);
glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_BYTE, reinterpret_cast<const void *>(0));
// Use vertexArray[1] and and draw
glBindVertexArray(vertexArray[1]);
size_t elementBufferOffset = sizeof(indices[0]) * 3;
glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_BYTE,
reinterpret_cast<const void *>(elementBufferOffset));
// We should see both triangles
EXPECT_PIXEL_COLOR_EQ(1, 1, GLColor::red);
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() - 1, getWindowHeight() - 1, GLColor::red);
glDeleteVertexArrays(2, vertexArray);
ASSERT_GL_NO_ERROR();
}
// Test that drawing with index value a little bit less than or equal to GL_MAX_ELEMENT_INDEX
// should not have error
TEST_P(DrawElementsTest, MaxElementIndex)
{
constexpr GLuint indicesNumber = 10;
std::array<GLuint, indicesNumber> indices;
GLint64 maxIndex = 0;
GLBuffer vertexBuffer;
ANGLE_GL_PROGRAM(programDrawRed, essl3_shaders::vs::Simple(), essl3_shaders::fs::Red());
glUseProgram(programDrawRed);
GLint posLocation = glGetAttribLocation(programDrawRed, essl3_shaders::PositionAttrib());
ASSERT_NE(-1, posLocation);
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glEnableVertexAttribArray(posLocation);
glVertexAttribPointer(posLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
glVertexAttribDivisor(posLocation, 0xFFFFFFFF);
glGetInteger64v(GL_MAX_ELEMENT_INDEX, &maxIndex);
// Draw using index from maxIndex - 9 to maxIndex. Should have no error
for (GLuint i = 0; i < indicesNumber; ++i)
{
indices[i] = static_cast<GLuint>(maxIndex) - (indicesNumber - 1) + i;
}
glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(indicesNumber), GL_UNSIGNED_INT,
indices.data());
ASSERT_GL_NO_ERROR();
}
// Test that the offset in the index buffer is forced to be a multiple of the element size
TEST_P(WebGLDrawElementsTest, DrawElementsTypeAlignment)
{
constexpr char kVS[] =
"attribute vec3 a_pos;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(a_pos, 1.0);\n"
"}\n";
ANGLE_GL_PROGRAM(program, kVS, essl1_shaders::fs::Blue());
GLint posLocation = glGetAttribLocation(program, "a_pos");
ASSERT_NE(-1, posLocation);
glUseProgram(program);
const auto &vertices = GetQuadVertices();
GLBuffer vertexBuffer;
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices[0]) * vertices.size(), vertices.data(),
GL_STATIC_DRAW);
glVertexAttribPointer(posLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(posLocation);
GLBuffer indexBuffer;
const GLubyte indices1[] = {0, 0, 0, 0, 0, 0};
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices1), indices1, GL_STATIC_DRAW);
ASSERT_GL_NO_ERROR();
glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_SHORT, nullptr);
ASSERT_GL_NO_ERROR();
const GLushort indices2[] = {0, 0, 0, 0, 0, 0};
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices2), indices2, GL_STATIC_DRAW);
glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_SHORT, reinterpret_cast<const void *>(1));
EXPECT_GL_ERROR(GL_INVALID_OPERATION);
}
// Test that subsequent UNSIGNED_BYTE primitive restart draws work without problems.
// At the time of writing, Metal backend would use incorrect internal offset and
// fail the draw with validation.
TEST_P(DrawElementsTest, DrawElementsUintByteBytePrimitiveRestart)
{
glClearColor(1.f, 0.f, 0.f, 1.f);
glClear(GL_COLOR_BUFFER_BIT);
const char kVS[] = R"(#version 300 es
in vec4 a_position;
void main()
{
gl_Position = a_position;
gl_PointSize = 2.0;
})";
ANGLE_GL_PROGRAM(mProgram, kVS, essl3_shaders::fs::Green());
glUseProgram(mProgram);
GLBuffer vertexBuffer;
std::array<GLfloat, 3> vertices{-1.0f, -1.0f, 0.5f};
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
GLint posLocation = glGetAttribLocation(mProgram, essl3_shaders::PositionAttrib());
ASSERT_NE(-1, posLocation);
glEnableVertexAttribArray(posLocation);
glVertexAttribPointer(posLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices[0]) * vertices.size(), vertices.data(),
GL_STATIC_DRAW);
GLubyte indices[4] = {0xff, 0xff, 0, 0};
GLBuffer elementBuffer;
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementBuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
ASSERT_GL_NO_ERROR();
glEnable(GL_PRIMITIVE_RESTART_FIXED_INDEX);
glDrawElements(GL_POINTS, 1, GL_UNSIGNED_SHORT, nullptr);
ASSERT_GL_NO_ERROR();
glDrawElements(GL_POINTS, 4, GL_UNSIGNED_BYTE, nullptr);
ASSERT_GL_NO_ERROR();
glDrawElements(GL_POINTS, 4, GL_UNSIGNED_BYTE, nullptr);
ASSERT_GL_NO_ERROR();
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
EXPECT_PIXEL_RECT_EQ(1, 1, getWindowWidth() - 1, getWindowHeight() - 1, GLColor::red);
ASSERT_GL_NO_ERROR();
}
// Tests various draw element parameter, vertex buffer contents variants.
// Does not yet test using GL_BYTE 0xFF, GL_SHORT 0xFFFF with primitive restart off.
TEST_P(DrawElementsVariantsTest, Draw)
{
const bool hasProvokingVertexExt = IsGLExtensionEnabled("GL_ANGLE_provoking_vertex");
ANGLE_SKIP_TEST_IF(useProvokingVertexFirst() && !hasProvokingVertexExt);
glClearColor(1.f, 0.f, 0.f, 1.f);
constexpr char kFlatVS[] = R"(#version 300 es
flat out float p;
in vec4 a_position;
in float a_vertexMark;
void main()
{
p = a_vertexMark;
gl_Position = a_position;
})";
constexpr char kFlatFS[] = R"(#version 300 es
precision highp float;
flat in highp float p;
out vec4 my_FragColor;
void main()
{
if (p >= 0.5)
my_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
else
my_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
})";
GLProgram program;
if (useFlat())
{
program.makeRaster(kFlatVS, kFlatFS);
}
else
{
program.makeRaster(essl3_shaders::vs::Simple(), essl3_shaders::fs::Green());
}
ASSERT_GL_NO_ERROR();
glUseProgram(program);
GLBuffer vertexBuffer;
std::array<GLfloat, 12> vertices = {
-1.0f, -1.0f, 0.0f, 1.0f, -1.0f, 0.0f, -1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f,
};
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices[0]) * vertices.size(), vertices.data(),
GL_STATIC_DRAW);
// First vertex convention, triangle strip {0,1,2,3}: 0, 1 are provoking.
GLBuffer markFirstConventionStrip;
std::array<GLfloat, 4> markFirstStripData = {1.0f, 1.0f, 0.0f, 0.0f};
glBindBuffer(GL_ARRAY_BUFFER, markFirstConventionStrip);
glBufferData(GL_ARRAY_BUFFER, sizeof(markFirstStripData), markFirstStripData.data(),
GL_STATIC_DRAW);
// First vertex convention, triangle strip {0,1,2,0xff,2,1,3} and triangles {0,1,2,2,1,3}:
// 0, 2 are provoking.
GLBuffer markFirstConventionTriangles;
std::array<GLfloat, 4> markFirstTriData = {1.0f, 0.0f, 1.0f, 0.0f};
glBindBuffer(GL_ARRAY_BUFFER, markFirstConventionTriangles);
glBufferData(GL_ARRAY_BUFFER, sizeof(markFirstTriData), markFirstTriData.data(),
GL_STATIC_DRAW);
// Last vertex convention: triangle strip {0,1,2,3} and triangles {0,1,2,2,1,3}: 2, 3 are
// provoking.
GLBuffer markLastConvention;
std::array<GLfloat, 4> markLastData = {0.0f, 0.0f, 1.0f, 1.0f};
glBindBuffer(GL_ARRAY_BUFFER, markLastConvention);
glBufferData(GL_ARRAY_BUFFER, sizeof(markLastData), markLastData.data(), GL_STATIC_DRAW);
GLubyte contentStrip[] = {0, 1, 2, 3};
GLubyte contentTriangles[] = {0, 1, 2, 2, 1, 3};
GLubyte contentSplit[] = {0, 1, 2, 0xff, 2, 1, 3};
if (usePrimitiveRestart())
{
glEnable(GL_PRIMITIVE_RESTART_FIXED_INDEX);
}
if (useProvokingVertexFirst())
{
glProvokingVertexANGLE(GL_FIRST_VERTEX_CONVENTION_ANGLE);
}
GLint posLocation = glGetAttribLocation(program, essl3_shaders::PositionAttrib());
ASSERT_NE(-1, posLocation);
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glEnableVertexAttribArray(posLocation);
glVertexAttribPointer(posLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
if (useFlat())
{
GLint markLocation = glGetAttribLocation(program, "a_vertexMark");
ASSERT_NE(-1, markLocation);
if (!useProvokingVertexFirst())
{
glBindBuffer(GL_ARRAY_BUFFER, markLastConvention);
}
else if (drawStrip() && !splitContent())
{
glBindBuffer(GL_ARRAY_BUFFER, markFirstConventionStrip);
}
else
{
glBindBuffer(GL_ARRAY_BUFFER, markFirstConventionTriangles);
}
glEnableVertexAttribArray(markLocation);
glVertexAttribPointer(markLocation, 1, GL_FLOAT, GL_FALSE, 0, 0);
}
std::vector<GLubyte> byteIndices;
GLsizei contentStartCount = 0;
uintptr_t offset = 0;
if (addOffset())
{
byteIndices.push_back(0);
}
if (addRestartAtBegin())
{
byteIndices.push_back(0xff);
}
if (addRestartDegenerate())
{
byteIndices.push_back(0);
byteIndices.push_back(0xff);
}
contentStartCount = byteIndices.size();
if (splitContent())
{
byteIndices.insert(byteIndices.end(), std::begin(contentSplit), std::end(contentSplit));
}
else if (drawStrip())
{
byteIndices.insert(byteIndices.end(), std::begin(contentStrip), std::end(contentStrip));
}
else
{
byteIndices.insert(byteIndices.end(), std::begin(contentTriangles),
std::end(contentTriangles));
}
if (addRestartAtEnd())
{
byteIndices.push_back(0xff);
}
// Prepare typed index data for both buffer and client-side paths.
std::vector<GLushort> shortIndices;
std::vector<GLuint> intIndices;
if (type() == GL_UNSIGNED_SHORT)
{
shortIndices = convertIndexBufferWithRestarts<GLushort>(byteIndices);
}
else if (type() == GL_UNSIGNED_INT)
{
intIndices = convertIndexBufferWithRestarts<GLuint>(byteIndices);
}
GLBuffer elementBuffer;
const void *indices = nullptr;
if (useClientSideArrays())
{
ASSERT_FALSE(addOffset());
if (type() == GL_UNSIGNED_BYTE)
{
indices = byteIndices.data();
}
else if (type() == GL_UNSIGNED_SHORT)
{
indices = shortIndices.data();
}
else
{
indices = intIndices.data();
}
}
else
{
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementBuffer);
if (type() == GL_UNSIGNED_BYTE)
{
glBufferData(GL_ELEMENT_ARRAY_BUFFER, byteIndices.size(), byteIndices.data(),
GL_STATIC_DRAW);
if (addOffset())
{
offset = 1;
}
}
else if (type() == GL_UNSIGNED_SHORT)
{
glBufferData(GL_ELEMENT_ARRAY_BUFFER, 2 * shortIndices.size(), shortIndices.data(),
GL_STATIC_DRAW);
if (addOffset())
{
offset = 2;
}
}
else
{
glBufferData(GL_ELEMENT_ARRAY_BUFFER, 4 * intIndices.size(), intIndices.data(),
GL_STATIC_DRAW);
if (addOffset())
{
offset = 4;
}
}
indices = reinterpret_cast<const void *>(offset);
}
ASSERT_GL_NO_ERROR();
glClear(GL_COLOR_BUFFER_BIT);
GLenum mode = drawStrip() ? GL_TRIANGLE_STRIP : GL_TRIANGLES;
GLsizei count = byteIndices.size() - (addOffset() ? 1 : 0);
glDrawElements(mode, count, type(), indices);
ASSERT_GL_NO_ERROR();
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() - 1, 0, GLColor::green);
EXPECT_PIXEL_COLOR_EQ(0, getWindowHeight() - 1, GLColor::green);
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() - 1, getWindowHeight() - 1, GLColor::green);
ASSERT_GL_NO_ERROR();
// Test drawing again to verify caching behavior and buffer regeneration.
glClear(GL_COLOR_BUFFER_BIT);
glDrawElements(mode, count, type(), indices);
glDrawElements(mode, count, type(), indices);
ASSERT_GL_NO_ERROR();
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() - 1, 0, GLColor::green);
EXPECT_PIXEL_COLOR_EQ(0, getWindowHeight() - 1, GLColor::green);
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() - 1, getWindowHeight() - 1, GLColor::green);
ASSERT_GL_NO_ERROR();
// Test drawing again with a subrange, to check that possibly cached ranges
// are cropped correctly. Draw only one triangle.
glClear(GL_COLOR_BUFFER_BIT);
count = contentStartCount + 3 - (addOffset() ? 1 : 0);
glDrawElements(mode, count, type(), indices);
ASSERT_GL_NO_ERROR();
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() - 1, 0, GLColor::red);
EXPECT_PIXEL_COLOR_EQ(0, getWindowHeight() - 1, GLColor::red);
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() - 1, getWindowHeight() - 1, GLColor::red);
ASSERT_GL_NO_ERROR();
}
class WebGLDrawElementsTest3 : public WebGLDrawElementsTest
{};
// Test one element buffer bind to two vertexArrays and switch vertexArray should draw correctly.
// With WebGL, we will go through element buffer range validation check which will catch bugs if the
// cached index range is incorrect.
TEST_P(WebGLDrawElementsTest3, TwoVertexArraysWithSameElementBuffer)
{
glClearColor(0.f, 0.f, 0.f, 1.f);
glClear(GL_COLOR_BUFFER_BIT);
ANGLE_GL_PROGRAM(programDrawRed, essl3_shaders::vs::Simple(), essl3_shaders::fs::Red());
glUseProgram(programDrawRed);
GLint posLocation = glGetAttribLocation(programDrawRed, essl3_shaders::PositionAttrib());
ASSERT_NE(-1, posLocation);
GLBuffer vertexBuffer;
const std::array<Vector3, 4> &vertices = GetIndexedQuadVertices();
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices[0]) * vertices.size(), vertices.data(),
GL_STATIC_DRAW);
GLBuffer elementBuffer;
std::array<GLuint, 6> invalidIndexData;
size_t elementBufferSize = sizeof(invalidIndexData[0]) * invalidIndexData.size();
invalidIndexData.fill(0xffffffff);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementBuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, elementBufferSize, invalidIndexData.data(),
GL_DYNAMIC_DRAW);
// Set up two vertex arrays using same set of buffers. Since initial element buffer all point to
// 0xffffffff, which exceed max index range, it should generate GL_INVALID_OPERATION.
GLuint vertexArray[2];
glGenVertexArrays(2, vertexArray);
glBindVertexArray(vertexArray[0]);
glVertexAttribPointer(posLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(posLocation);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementBuffer);
glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, reinterpret_cast<const void *>(0));
glBindVertexArray(vertexArray[1]);
glVertexAttribPointer(posLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(posLocation);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementBuffer);
size_t elementBufferOffset1 = sizeof(invalidIndexData[0]) * 3;
glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT,
reinterpret_cast<const void *>(elementBufferOffset1));
// This should alsop clear the context error code.
ASSERT_TRUE(glGetError() == GL_INVALID_OPERATION);
// Use vertexArray[0] and update elementBuffer and draw
const std::array<GLuint, 6> &indices = {0, 1, 2, 0, 2, 3};
glBindVertexArray(vertexArray[0]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, elementBufferSize, indices.data(), GL_DYNAMIC_DRAW);
glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, reinterpret_cast<const void *>(0));
// Use vertexArray[1] and and draw
glBindVertexArray(vertexArray[1]);
glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT,
reinterpret_cast<const void *>(elementBufferOffset1));
ASSERT_TRUE(glGetError() == GL_NO_ERROR);
// We should see both triangles
EXPECT_PIXEL_COLOR_EQ(1, 1, GLColor::red);
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() - 1, getWindowHeight() - 1, GLColor::red);
glDeleteVertexArrays(2, vertexArray);
ASSERT_GL_NO_ERROR();
}
// Test that uploading a large index buffer (> 1MB) via glBufferSubData
// does not corrupt the data in the chunks after the first 1MB.
// This is a regression test for a bug in Metal backend where the offset
// was not applied to the source pointer when chunking the upload.
TEST_P(DrawElementsTest, LargeIndexBufferSubData)
{
// We want the buffer size to be slightly larger than 1MB.
// 1MB = 1024 * 1024 = 1,048,576 bytes.
// With GL_UNSIGNED_INT (4 bytes), 1MB is 262,144 indices.
// We use 263,168 indices (1,052,672 bytes).
constexpr size_t kIndexCount = 263168;
constexpr size_t kBufferSize = kIndexCount * sizeof(GLuint);
// Staging buffer chunk size is 1MB.
// First chunk: indices 0 to 262143.
// Second chunk: indices 262144 to 263167.
// Prepare indices.
// We want to draw a triangle using the last 3 indices in the buffer.
// These indices will point to green vertices.
// The indices at the start of the buffer will point to red vertices.
std::vector<GLuint> indices(kIndexCount, 0);
// Start of buffer: points to red vertices (0, 1, 2)
indices[0] = 0;
indices[1] = 1;
indices[2] = 2;
// End of buffer: points to green vertices (3, 4, 5)
indices[kIndexCount - 3] = 3;
indices[kIndexCount - 2] = 4;
indices[kIndexCount - 1] = 5;
// Vertices with position and color.
struct Vertex
{
float position[3];
GLubyte color[4];
};
const std::vector<Vertex> vertices = {
// Red triangle
{{-1.0f, -1.0f, 0.0f}, {255, 0, 0, 255}},
{{3.0f, -1.0f, 0.0f}, {255, 0, 0, 255}},
{{-1.0f, 3.0f, 0.0f}, {255, 0, 0, 255}},
// Green triangle
{{-1.0f, -1.0f, 0.0f}, {0, 255, 0, 255}},
{{3.0f, -1.0f, 0.0f}, {0, 255, 0, 255}},
{{-1.0f, 3.0f, 0.0f}, {0, 255, 0, 255}},
};
// Shader program
const char *kVS =
"attribute vec3 a_position;\n"
"attribute vec4 a_color;\n"
"varying vec4 v_color;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(a_position, 1.0);\n"
" v_color = a_color;\n"
"}\n";
const char *kFS =
"precision mediump float;\n"
"varying vec4 v_color;\n"
"void main()\n"
"{\n"
" gl_FragColor = v_color;\n"
"}\n";
ANGLE_GL_PROGRAM(program, kVS, kFS);
glUseProgram(program);
GLint posLocation = glGetAttribLocation(program, "a_position");
GLint colorLocation = glGetAttribLocation(program, "a_color");
ASSERT_NE(-1, posLocation);
ASSERT_NE(-1, colorLocation);
GLVertexArray vao;
glBindVertexArray(vao);
GLBuffer vertexBuffer;
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(Vertex), vertices.data(),
GL_STATIC_DRAW);
glVertexAttribPointer(posLocation, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex),
reinterpret_cast<const void *>(offsetof(Vertex, position)));
glEnableVertexAttribArray(posLocation);
glVertexAttribPointer(colorLocation, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(Vertex),
reinterpret_cast<const void *>(offsetof(Vertex, color)));
glEnableVertexAttribArray(colorLocation);
GLBuffer indexBuffer;
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
// Allocate
glBufferData(GL_ELEMENT_ARRAY_BUFFER, kBufferSize, nullptr, GL_STATIC_DRAW);
// Upload > 1MB
glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, kBufferSize, indices.data());
ASSERT_GL_NO_ERROR();
// Draw using the indices at the end of the buffer.
// Offset in bytes: (kIndexCount - 3) * sizeof(GLuint)
size_t drawOffset = (kIndexCount - 3) * sizeof(GLuint);
glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, reinterpret_cast<const void *>(drawOffset));
ASSERT_GL_NO_ERROR();
// If the bug is present, the indices at the end were copied from the start (which point to
// red). If fixed, they point to green.
EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::green);
}
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(DrawElementsTest);
ANGLE_INSTANTIATE_TEST_ES3(DrawElementsTest);
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(DrawElementsVariantsTest);
ANGLE_INSTANTIATE_TEST_VARIANTS_COMBINE_7(
NoRestart,
DrawElementsVariantsTest,
DrawElementsVariantsTestPrint,
testing::Values(GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT, GL_UNSIGNED_INT),
testing::Bool(), // useFlat
testing::Values(PrimitiveRestartMode::Disabled), // primitiveRestartMode
testing::Bool(), // drawStrip
testing::Values(IndicesMode::Buffer,
IndicesMode::BufferAndOffset,
IndicesMode::ClientSideArray),
testing::Values(RestartOption::None), // restartOption
testing::Bool(), // useProvokingVertexFirst
ANGLE_ALL_TEST_PLATFORMS_ES3);
ANGLE_INSTANTIATE_TEST_VARIANTS_COMBINE_7(
Restart,
DrawElementsVariantsTest,
DrawElementsVariantsTestPrint,
testing::Values(GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT, GL_UNSIGNED_INT),
testing::Bool(), // useFlat
testing::Values(PrimitiveRestartMode::Enabled, PrimitiveRestartMode::EnabledSplitContent),
testing::Bool(), // drawStrip
testing::Values(IndicesMode::Buffer,
IndicesMode::BufferAndOffset,
IndicesMode::ClientSideArray),
testing::Values(RestartOption::None,
RestartOption::AtBegin,
RestartOption::AtEnd,
RestartOption::Degenerate),
testing::Bool(), // useProvokingVertexFirst
ANGLE_ALL_TEST_PLATFORMS_ES3);
ANGLE_INSTANTIATE_TEST_ES2(WebGLDrawElementsTest);
ANGLE_INSTANTIATE_TEST_ES3(WebGLDrawElementsTest3);
} // namespace