| // |
| // Copyright 2019 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. |
| // |
| |
| // DrawBaseVertexBaseInstanceTest: Tests of GL_ANGLE_base_vertex_base_instance |
| // DrawBaseInstanceTest: Tests of GL_EXT_base_instance |
| |
| #include "gpu_info_util/SystemInfo.h" |
| #include "test_utils/ANGLETest.h" |
| #include "test_utils/gl_raii.h" |
| |
| #include <numeric> |
| |
| using namespace angle; |
| |
| namespace |
| { |
| |
| // Create a kWidth * kHeight canvas equally split into kCountX * kCountY tiles |
| // each containing a quad partially covering each tile |
| constexpr uint32_t kWidth = 256; |
| constexpr uint32_t kHeight = 256; |
| constexpr uint32_t kCountX = 8; |
| constexpr uint32_t kCountY = 8; |
| constexpr std::array<GLfloat, 2> kTileSize = { |
| 1.f / static_cast<GLfloat>(kCountX), |
| 1.f / static_cast<GLfloat>(kCountY), |
| }; |
| constexpr std::array<uint32_t, 2> kTilePixelSize = {kWidth / kCountX, kHeight / kCountY}; |
| constexpr std::array<GLfloat, 2> kQuadRadius = {0.25f * kTileSize[0], 0.25f * kTileSize[1]}; |
| constexpr std::array<uint32_t, 2> kPixelCheckSize = { |
| static_cast<uint32_t>(kQuadRadius[0] * kWidth), |
| static_cast<uint32_t>(kQuadRadius[1] * kHeight)}; |
| |
| constexpr std::array<GLfloat, 2> getTileCenter(uint32_t x, uint32_t y) |
| { |
| return { |
| kTileSize[0] * (0.5f + static_cast<GLfloat>(x)), |
| kTileSize[1] * (0.5f + static_cast<GLfloat>(y)), |
| }; |
| } |
| constexpr std::array<std::array<GLfloat, 3>, 4> getQuadVertices(uint32_t x, uint32_t y) |
| { |
| const auto center = getTileCenter(x, y); |
| return { |
| std::array<GLfloat, 3>{center[0] - kQuadRadius[0], center[1] - kQuadRadius[1], 0.0f}, |
| std::array<GLfloat, 3>{center[0] + kQuadRadius[0], center[1] - kQuadRadius[1], 0.0f}, |
| std::array<GLfloat, 3>{center[0] + kQuadRadius[0], center[1] + kQuadRadius[1], 0.0f}, |
| std::array<GLfloat, 3>{center[0] - kQuadRadius[0], center[1] + kQuadRadius[1], 0.0f}, |
| }; |
| } |
| |
| enum class BaseVertexOption |
| { |
| NoBaseVertex, |
| UseBaseVertex |
| }; |
| |
| enum class BaseInstanceOption |
| { |
| NoBaseInstance, |
| UseBaseInstance |
| }; |
| |
| enum class BufferDataUsageOption |
| { |
| StaticDraw, |
| DynamicDraw |
| }; |
| |
| using DrawBaseVertexBaseInstanceTestParams = std:: |
| tuple<angle::PlatformParameters, BaseVertexOption, BaseInstanceOption, BufferDataUsageOption>; |
| |
| struct PrintToStringParamName |
| { |
| std::string operator()( |
| const ::testing::TestParamInfo<DrawBaseVertexBaseInstanceTestParams> &info) const |
| { |
| ::std::stringstream ss; |
| ss << std::get<0>(info.param) << "_" |
| << (std::get<3>(info.param) == BufferDataUsageOption::StaticDraw ? "_StaticDraw" |
| : "_DynamicDraw") |
| << (std::get<2>(info.param) == BaseInstanceOption::UseBaseInstance ? "_UseBaseInstance" |
| : "") |
| << (std::get<1>(info.param) == BaseVertexOption::UseBaseVertex ? "_UseBaseVertex" : ""); |
| return ss.str(); |
| } |
| }; |
| |
| // These tests check correctness of the ANGLE_base_vertex_base_instance extension. |
| // An array of quads is drawn across the screen. |
| // gl_VertexID, gl_InstanceID, gl_BaseVertex, and gl_BaseInstance |
| // are checked by using them to select the color of the draw. |
| class DrawBaseVertexBaseInstanceTest |
| : public ANGLETestBase, |
| public ::testing::WithParamInterface<DrawBaseVertexBaseInstanceTestParams> |
| { |
| protected: |
| DrawBaseVertexBaseInstanceTest() : ANGLETestBase(std::get<0>(GetParam())) |
| { |
| setWindowWidth(kWidth); |
| setWindowHeight(kHeight); |
| setConfigRedBits(8); |
| setConfigGreenBits(8); |
| setConfigBlueBits(8); |
| setConfigAlphaBits(8); |
| |
| // Rects in the same column are within a vertex array, testing gl_VertexID, gl_BaseVertex |
| // Rects in the same row are drawn by instancing, testing gl_InstanceID, gl_BaseInstance |
| |
| mIndices = {0, 1, 2, 0, 2, 3}; |
| |
| for (uint32_t y = 0; y < kCountY; ++y) |
| { |
| // v3 ---- v2 |
| // | | |
| // | | |
| // v0 ---- v1 |
| |
| const auto vs = getQuadVertices(0, y); |
| |
| for (const auto &v : vs) |
| { |
| mVertices.insert(mVertices.end(), v.begin(), v.end()); |
| } |
| |
| for (GLushort i : mIndices) |
| { |
| mNonIndexedVertices.insert(mNonIndexedVertices.end(), vs[i].begin(), vs[i].end()); |
| } |
| } |
| |
| mRegularIndices.resize(kCountY * mIndices.size()); |
| for (uint32_t i = 0; i < kCountY; i++) |
| { |
| uint32_t oi = 6 * i; |
| uint32_t ov = 4 * i; |
| for (uint32_t j = 0; j < 6; j++) |
| { |
| mRegularIndices[oi + j] = mIndices[j] + ov; |
| } |
| } |
| |
| std::iota(mInstancedArrayId.begin(), mInstancedArrayId.end(), 0.0f); |
| std::reverse_copy(mInstancedArrayId.begin(), mInstancedArrayId.end(), |
| mInstancedArrayColorId.begin()); |
| } |
| |
| void SetUp() override { ANGLETestBase::ANGLETestSetUp(); } |
| |
| bool useBaseVertexBuiltin() const |
| { |
| return std::get<1>(GetParam()) == BaseVertexOption::UseBaseVertex; |
| } |
| |
| bool useBaseInstanceBuiltin() const |
| { |
| return std::get<2>(GetParam()) == BaseInstanceOption::UseBaseInstance; |
| } |
| |
| GLenum getBufferDataUsage() const |
| { |
| return std::get<3>(GetParam()) == BufferDataUsageOption::StaticDraw ? GL_STATIC_DRAW |
| : GL_DYNAMIC_DRAW; |
| } |
| |
| std::string vertexShaderSource300(bool isDrawArrays, bool isMultiDraw, bool divisorTest) |
| { |
| // Each color channel is to test the value of |
| // R: gl_InstanceID and gl_BaseInstance |
| // G: gl_VertexID and gl_BaseVertex |
| // B: gl_BaseVertex |
| |
| std::stringstream shader; |
| shader << ("#version 300 es\n") |
| << (isMultiDraw ? "#extension GL_ANGLE_multi_draw : require\n" : "") |
| << ("#extension GL_ANGLE_base_vertex_base_instance_shader_builtin : require\n") |
| << "#define kCountX " << kCountX << "\n" |
| << "#define kCountY " << kCountY << "\n" |
| << R"( |
| in vec2 vPosition; |
| )" << (useBaseInstanceBuiltin() ? "" : "in float vInstanceID;\n") |
| << (!divisorTest ? "" : "in float vInstanceColorID;\n") << R"( |
| out vec4 color; |
| void main() |
| { |
| const float xStep = 1.0 / float(kCountX); |
| const float yStep = 1.0 / float(kCountY); |
| float x_id = )" |
| << (useBaseInstanceBuiltin() ? " float(gl_InstanceID + gl_BaseInstance);" |
| : "vInstanceID;") |
| << "float x_color = " |
| << (divisorTest ? "xStep * (vInstanceColorID + 1.0f);" : " 1.0 - xStep * x_id;") |
| << R"( |
| float y_id = float(gl_VertexID / )" |
| << (isDrawArrays ? "6" : "4") << R"(); |
| |
| color = vec4( |
| x_color, |
| 1.0 - yStep * y_id, |
| )" << (useBaseVertexBuiltin() ? "1.0 - yStep * float(gl_BaseVertex) / 4.0" : "1.0") |
| << R"(, |
| 1); |
| |
| mat3 transform = mat3(1.0); |
| transform[2][0] = x_id * xStep; |
| |
| gl_Position = vec4(transform * vec3(vPosition, 1.0) * 2.0 - 1.0, 1); |
| })"; |
| |
| return shader.str(); |
| } |
| |
| std::string fragmentShaderSource300() |
| { |
| return |
| R"(#version 300 es |
| precision mediump float; |
| in vec4 color; |
| out vec4 o_color; |
| void main() |
| { |
| o_color = color; |
| })"; |
| } |
| |
| void setupProgram(GLProgram &program, |
| bool isDrawArrays = true, |
| bool isMultiDraw = false, |
| bool isDivisorTest = false) |
| { |
| program.makeRaster(vertexShaderSource300(isDrawArrays, isMultiDraw, isDivisorTest).c_str(), |
| fragmentShaderSource300().c_str()); |
| EXPECT_GL_NO_ERROR(); |
| ASSERT_TRUE(program.valid()); |
| glUseProgram(program); |
| mPositionLoc = glGetAttribLocation(program, "vPosition"); |
| if (!useBaseInstanceBuiltin()) |
| { |
| mInstanceIDLoc = glGetAttribLocation(program, "vInstanceID"); |
| mInstanceColorIDLoc = glGetAttribLocation(program, "vInstanceColorID"); |
| } |
| } |
| |
| void setupNonIndexedBuffers(GLBuffer &vertexBuffer) |
| { |
| glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer); |
| glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * mNonIndexedVertices.size(), |
| mNonIndexedVertices.data(), getBufferDataUsage()); |
| |
| ASSERT_GL_NO_ERROR(); |
| } |
| |
| void setupIndexedBuffers(GLBuffer &vertexBuffer, GLBuffer &indexBuffer) |
| { |
| glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer); |
| glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * mVertices.size(), mVertices.data(), |
| getBufferDataUsage()); |
| |
| glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer); |
| glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLushort) * mIndices.size(), mIndices.data(), |
| getBufferDataUsage()); |
| |
| ASSERT_GL_NO_ERROR(); |
| } |
| |
| void setupInstanceIDBuffer(GLBuffer &instanceIDBuffer) |
| { |
| glBindBuffer(GL_ARRAY_BUFFER, instanceIDBuffer); |
| glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * mInstancedArrayId.size(), |
| mInstancedArrayId.data(), getBufferDataUsage()); |
| |
| ASSERT_GL_NO_ERROR(); |
| } |
| |
| void setupInstanceColorIDBuffer(GLBuffer &instanceIDBuffer) |
| { |
| glBindBuffer(GL_ARRAY_BUFFER, instanceIDBuffer); |
| glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * mInstancedArrayColorId.size(), |
| mInstancedArrayColorId.data(), getBufferDataUsage()); |
| |
| ASSERT_GL_NO_ERROR(); |
| } |
| |
| void setupRegularIndexedBuffer(GLBuffer &indexBuffer) |
| { |
| glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer); |
| glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLushort) * mRegularIndices.size(), |
| mRegularIndices.data(), getBufferDataUsage()); |
| |
| ASSERT_GL_NO_ERROR(); |
| } |
| |
| void setupPositionVertexAttribPointer() |
| { |
| glEnableVertexAttribArray(mPositionLoc); |
| glVertexAttribPointer(mPositionLoc, 3, GL_FLOAT, GL_FALSE, 0, 0); |
| } |
| |
| void setupInstanceIDVertexAttribPointer(GLuint instanceIDLoc) |
| { |
| glEnableVertexAttribArray(instanceIDLoc); |
| glVertexAttribPointer(instanceIDLoc, 1, GL_FLOAT, GL_FALSE, 0, 0); |
| glVertexAttribDivisor(instanceIDLoc, 1); |
| } |
| |
| void doDrawArraysInstancedBaseInstance() |
| { |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
| |
| const uint32_t countPerDraw = kCountY * 6; |
| |
| for (uint32_t i = 0; i < kCountX; i += 2) |
| { |
| glDrawArraysInstancedBaseInstanceANGLE(GL_TRIANGLES, 0, countPerDraw, 2, i); |
| } |
| } |
| |
| void doMultiDrawArraysInstancedBaseInstance() |
| { |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
| |
| const uint32_t countPerDraw = kCountY * 6; |
| |
| const GLsizei drawCount = kCountX / 2; |
| const std::vector<GLsizei> counts(drawCount, countPerDraw); |
| const std::vector<GLsizei> firsts(drawCount, 0); |
| const std::vector<GLsizei> instanceCounts(drawCount, 2); |
| std::vector<GLuint> baseInstances(drawCount); |
| for (size_t i = 0; i < drawCount; i++) |
| { |
| baseInstances[i] = i * 2; |
| } |
| glMultiDrawArraysInstancedBaseInstanceANGLE(GL_TRIANGLES, firsts.data(), counts.data(), |
| instanceCounts.data(), baseInstances.data(), |
| drawCount); |
| } |
| |
| void doDrawElementsInstancedBaseVertexBaseInstance() |
| { |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
| |
| const uint32_t countPerDraw = 6; |
| |
| for (uint32_t v = 0; v < kCountY; v++) |
| { |
| for (uint32_t i = 0; i < kCountX; i += 2) |
| { |
| glDrawElementsInstancedBaseVertexBaseInstanceANGLE( |
| GL_TRIANGLES, countPerDraw, GL_UNSIGNED_SHORT, |
| reinterpret_cast<GLvoid *>(static_cast<uintptr_t>(0)), 2, v * 4, i); |
| } |
| } |
| } |
| |
| // Call this after the *BaseVertexBaseInstance draw call to check if value of BaseVertex and |
| // BaseInstance are reset to zero |
| void doDrawArraysBaseInstanceReset() |
| { |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
| glDrawArraysInstanced(GL_TRIANGLES, 0, 6 * kCountY, 1); |
| } |
| |
| void doDrawElementsBaseVertexBaseInstanceReset() |
| { |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
| glDrawElementsInstanced(GL_TRIANGLES, 6 * kCountY, GL_UNSIGNED_SHORT, |
| reinterpret_cast<GLvoid *>(static_cast<uintptr_t>(0)), 1); |
| } |
| |
| void doMultiDrawElementsInstancedBaseVertexBaseInstance() |
| { |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
| |
| const GLsizei drawCount = kCountX * kCountY / 2; |
| const std::vector<GLsizei> counts(drawCount, 6); |
| const std::vector<GLsizei> instanceCounts(drawCount, 2); |
| const std::vector<GLvoid *> indices(drawCount, 0); |
| std::vector<GLint> baseVertices(drawCount); |
| std::vector<GLuint> baseInstances(drawCount); |
| |
| GLsizei b = 0; |
| for (uint32_t v = 0; v < kCountY; v++) |
| { |
| for (uint32_t i = 0; i < kCountX; i += 2) |
| { |
| baseVertices[b] = v * 4; |
| baseInstances[b] = i; |
| b++; |
| } |
| } |
| |
| glMultiDrawElementsInstancedBaseVertexBaseInstanceANGLE( |
| GL_TRIANGLES, counts.data(), GL_UNSIGNED_SHORT, indices.data(), instanceCounts.data(), |
| baseVertices.data(), baseInstances.data(), drawCount); |
| } |
| |
| void checkDrawResult(bool hasBaseVertex, bool oneColumn = false) |
| { |
| uint32_t numColums = oneColumn ? 1 : kCountX; |
| for (uint32_t y = 0; y < kCountY; ++y) |
| { |
| for (uint32_t x = 0; x < numColums; ++x) |
| { |
| uint32_t center_x = x * kTilePixelSize[0] + kTilePixelSize[0] / 2; |
| uint32_t center_y = y * kTilePixelSize[1] + kTilePixelSize[1] / 2; |
| |
| EXPECT_PIXEL_NEAR(center_x - kPixelCheckSize[0] / 2, |
| center_y - kPixelCheckSize[1] / 2, |
| 256.0 * (1.0 - (float)x / (float)kCountX), |
| 256.0 * (1.0 - (float)y / (float)kCountY), |
| useBaseVertexBuiltin() && hasBaseVertex && !oneColumn |
| ? 256.0 * (1.0 - (float)y / (float)kCountY) |
| : 255, |
| 255, 3); |
| } |
| } |
| } |
| |
| void TearDown() override { ANGLETestBase::ANGLETestTearDown(); } |
| |
| bool requestDrawBaseVertexBaseInstanceExtension() |
| { |
| if (IsGLExtensionRequestable("GL_ANGLE_base_vertex_base_instance")) |
| { |
| glRequestExtensionANGLE("GL_ANGLE_base_vertex_base_instance"); |
| } |
| |
| if (!IsGLExtensionEnabled("GL_ANGLE_base_vertex_base_instance")) |
| { |
| return false; |
| } |
| |
| if (IsGLExtensionRequestable("GL_ANGLE_base_vertex_base_instance_shader_builtin")) |
| { |
| glRequestExtensionANGLE("GL_ANGLE_base_vertex_base_instance_shader_builtin"); |
| } |
| |
| return true; |
| } |
| |
| bool requestInstancedExtension() |
| { |
| if (IsGLExtensionRequestable("GL_ANGLE_instanced_arrays")) |
| { |
| glRequestExtensionANGLE("GL_ANGLE_instanced_arrays"); |
| } |
| |
| if (!IsGLExtensionEnabled("GL_ANGLE_instanced_arrays")) |
| { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool requestExtensions() |
| { |
| if (getClientMajorVersion() <= 2) |
| { |
| if (!requestInstancedExtension()) |
| { |
| return false; |
| } |
| } |
| return requestDrawBaseVertexBaseInstanceExtension(); |
| } |
| |
| // Used for base vertex base instance draw calls |
| std::vector<GLushort> mIndices; |
| std::vector<GLfloat> mVertices; |
| std::vector<GLfloat> mNonIndexedVertices; |
| // Used when gl_BaseInstance is not used |
| std::array<GLfloat, kCountX> mInstancedArrayId = {}; |
| std::array<GLfloat, kCountX> mInstancedArrayColorId = {}; |
| // Used for regular draw calls without base vertex base instance |
| std::vector<GLushort> mRegularIndices; |
| GLint mPositionLoc = 0; |
| GLuint mInstanceIDLoc = 0; |
| GLuint mInstanceColorIDLoc = 0; |
| }; |
| |
| using DrawBaseInstanceTestParams = std:: |
| tuple<angle::PlatformParameters, BaseVertexOption, BaseInstanceOption, BufferDataUsageOption>; |
| |
| // The tests in DrawBaseInstanceTest check the correctness |
| // of the EXT_base_instance extension. |
| // gl_VertexID, gl_InstanceID, gl_BaseVertex, and gl_BaseInstance |
| // are checked by using them to select the color of the draw. |
| class DrawBaseInstanceTest : public ANGLETestBase, |
| public ::testing::WithParamInterface<DrawBaseInstanceTestParams> |
| { |
| protected: |
| DrawBaseInstanceTest() : ANGLETestBase(std::get<0>(GetParam())) |
| { |
| setWindowWidth(kWidth); |
| setWindowHeight(kHeight); |
| setConfigRedBits(8); |
| setConfigGreenBits(8); |
| setConfigBlueBits(8); |
| setConfigAlphaBits(8); |
| |
| // Rects in the same column are within a vertex array, testing gl_VertexID, gl_BaseVertex |
| // Rects in the same row are drawn by instancing, testing gl_InstanceID, gl_BaseInstance |
| |
| mIndices = {0, 1, 2, 0, 2, 3}; |
| |
| for (uint32_t y = 0; y < kCountY; ++y) |
| { |
| // v3 ---- v2 |
| // | | |
| // | | |
| // v0 ---- v1 |
| |
| const auto vs = getQuadVertices(0, y); |
| |
| for (const auto &v : vs) |
| { |
| mVertices.insert(mVertices.end(), v.begin(), v.end()); |
| } |
| |
| for (GLushort i : mIndices) |
| { |
| mNonIndexedVertices.insert(mNonIndexedVertices.end(), vs[i].begin(), vs[i].end()); |
| } |
| } |
| |
| mRegularIndices.resize(kCountY * mIndices.size()); |
| for (uint32_t i = 0; i < kCountY; i++) |
| { |
| uint32_t oi = 6 * i; |
| uint32_t ov = 4 * i; |
| for (uint32_t j = 0; j < 6; j++) |
| { |
| mRegularIndices[oi + j] = mIndices[j] + ov; |
| } |
| } |
| |
| std::iota(mInstancedArrayId.begin(), mInstancedArrayId.end(), 0.0f); |
| std::reverse_copy(mInstancedArrayId.begin(), mInstancedArrayId.end(), |
| mInstancedArrayColorId.begin()); |
| } |
| |
| void SetUp() override { ANGLETestBase::ANGLETestSetUp(); } |
| |
| bool useBaseVertexBuiltin() const |
| { |
| return std::get<1>(GetParam()) == BaseVertexOption::UseBaseVertex; |
| } |
| |
| bool useBaseInstanceBuiltin() const |
| { |
| return std::get<2>(GetParam()) == BaseInstanceOption::UseBaseInstance; |
| } |
| |
| GLenum getBufferDataUsage() const |
| { |
| return std::get<3>(GetParam()) == BufferDataUsageOption::StaticDraw ? GL_STATIC_DRAW |
| : GL_DYNAMIC_DRAW; |
| } |
| |
| std::string vertexShaderSource300(bool isDrawArrays, bool divisorTest) |
| { |
| // Each color channel is to test the value of |
| // R: gl_InstanceID and gl_BaseInstance |
| // G: gl_VertexID and gl_BaseVertex |
| // B: gl_BaseVertex |
| |
| std::stringstream shader; |
| shader << ("#version 300 es\n") |
| << ("#extension GL_ANGLE_base_vertex_base_instance_shader_builtin : require\n") |
| << "#define kCountX " << kCountX << "\n" |
| << "#define kCountY " << kCountY << "\n" |
| << R"( |
| in vec2 vPosition; |
| )" << (useBaseInstanceBuiltin() ? "" : "in float vInstanceID;\n") |
| << (!divisorTest ? "" : "in float vInstanceColorID;\n") << R"( |
| // Use a mediump uniform, it can get sorted before the gl_BaseVertex/Instance emulated uniforms |
| uniform mediump vec2 zero; |
| out vec4 color; |
| void main() |
| { |
| const float xStep = 1.0 / float(kCountX); |
| const float yStep = 1.0 / float(kCountY); |
| float x_id = )" |
| << (useBaseInstanceBuiltin() ? " float(gl_InstanceID + gl_BaseInstance);" |
| : "vInstanceID;") |
| << "float x_color = " |
| << (divisorTest ? "xStep * (vInstanceColorID + 1.0f);" : " 1.0 - xStep * x_id;") |
| << R"( |
| float y_id = float(gl_VertexID / )" |
| << (isDrawArrays ? "6" : "4") << R"(); |
| |
| color = vec4( |
| x_color, |
| 1.0 - yStep * y_id, |
| )" << (useBaseVertexBuiltin() ? "1.0 - yStep * float(gl_BaseVertex) / 4.0" : "1.0") |
| << R"(, |
| 1); |
| color.xy += zero; |
| |
| mat3 transform = mat3(1.0); |
| transform[2][0] = x_id * xStep; |
| |
| gl_Position = vec4(transform * vec3(vPosition, 1.0) * 2.0 - 1.0, 1); |
| })"; |
| |
| return shader.str(); |
| } |
| |
| std::string fragmentShaderSource300() |
| { |
| return |
| R"(#version 300 es |
| precision mediump float; |
| in vec4 color; |
| out vec4 o_color; |
| void main() |
| { |
| o_color = color; |
| })"; |
| } |
| |
| void setupProgram(GLProgram &program, bool isDrawArrays = true, bool isDivisorTest = false) |
| { |
| program.makeRaster(vertexShaderSource300(isDrawArrays, isDivisorTest).c_str(), |
| fragmentShaderSource300().c_str()); |
| EXPECT_GL_NO_ERROR(); |
| ASSERT_TRUE(program.valid()); |
| glUseProgram(program); |
| mPositionLoc = glGetAttribLocation(program, "vPosition"); |
| if (!useBaseInstanceBuiltin()) |
| { |
| mInstanceIDLoc = glGetAttribLocation(program, "vInstanceID"); |
| mInstanceColorIDLoc = glGetAttribLocation(program, "vInstanceColorID"); |
| } |
| } |
| |
| void setupNonIndexedBuffers(GLBuffer &vertexBuffer) |
| { |
| glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer); |
| glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * mNonIndexedVertices.size(), |
| mNonIndexedVertices.data(), getBufferDataUsage()); |
| |
| ASSERT_GL_NO_ERROR(); |
| } |
| |
| void setupIndexedBuffers(GLBuffer &vertexBuffer, GLBuffer &indexBuffer) |
| { |
| glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer); |
| glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * mVertices.size(), mVertices.data(), |
| getBufferDataUsage()); |
| |
| glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer); |
| glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLushort) * mIndices.size(), mIndices.data(), |
| getBufferDataUsage()); |
| |
| ASSERT_GL_NO_ERROR(); |
| } |
| |
| void setupInstanceIDBuffer(GLBuffer &instanceIDBuffer) |
| { |
| glBindBuffer(GL_ARRAY_BUFFER, instanceIDBuffer); |
| glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * mInstancedArrayId.size(), |
| mInstancedArrayId.data(), getBufferDataUsage()); |
| |
| ASSERT_GL_NO_ERROR(); |
| } |
| |
| void setupInstanceColorIDBuffer(GLBuffer &instanceIDBuffer) |
| { |
| glBindBuffer(GL_ARRAY_BUFFER, instanceIDBuffer); |
| glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * mInstancedArrayColorId.size(), |
| mInstancedArrayColorId.data(), getBufferDataUsage()); |
| |
| ASSERT_GL_NO_ERROR(); |
| } |
| |
| void setupRegularIndexedBuffer(GLBuffer &indexBuffer) |
| { |
| glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer); |
| glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLushort) * mRegularIndices.size(), |
| mRegularIndices.data(), getBufferDataUsage()); |
| |
| ASSERT_GL_NO_ERROR(); |
| } |
| |
| void setupPositionVertexAttribPointer() |
| { |
| glEnableVertexAttribArray(mPositionLoc); |
| glVertexAttribPointer(mPositionLoc, 3, GL_FLOAT, GL_FALSE, 0, 0); |
| } |
| |
| void setupInstanceIDVertexAttribPointer(GLuint instanceIDLoc) |
| { |
| glEnableVertexAttribArray(instanceIDLoc); |
| glVertexAttribPointer(instanceIDLoc, 1, GL_FLOAT, GL_FALSE, 0, 0); |
| glVertexAttribDivisor(instanceIDLoc, 1); |
| } |
| |
| void doDrawArraysInstancedBaseInstance() |
| { |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
| |
| const uint32_t countPerDraw = kCountY * 6; |
| |
| for (uint32_t i = 0; i < kCountX; i += 2) |
| { |
| glDrawArraysInstancedBaseInstanceEXT(GL_TRIANGLES, 0, countPerDraw, 2, i); |
| } |
| } |
| |
| void doDrawElementsInstancedBaseInstance() |
| { |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
| |
| const uint32_t countPerDraw = 6; |
| |
| for (uint32_t v = 0; v < kCountY; v++) |
| { |
| for (uint32_t i = 0; i < kCountX; i += 2) |
| { |
| glDrawElementsInstancedBaseInstanceEXT(GL_TRIANGLES, countPerDraw, |
| GL_UNSIGNED_SHORT, |
| (void *)(v * 6 * sizeof(GLushort)), 2, i); |
| } |
| } |
| } |
| |
| void doDrawElementsInstancedBaseVertexBaseInstance() |
| { |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
| |
| const uint32_t countPerDraw = 6; |
| |
| for (uint32_t v = 0; v < kCountY; v++) |
| { |
| for (uint32_t i = 0; i < kCountX; i += 2) |
| { |
| glDrawElementsInstancedBaseVertexBaseInstanceEXT( |
| GL_TRIANGLES, countPerDraw, GL_UNSIGNED_SHORT, |
| reinterpret_cast<GLvoid *>(static_cast<uintptr_t>(0)), 2, v * 4, i); |
| } |
| } |
| } |
| |
| // Call this after the *BaseInstance draw call to check |
| // if value of BaseInstance is reset to zero |
| void doDrawArraysBaseInstanceReset() |
| { |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
| glDrawArraysInstanced(GL_TRIANGLES, 0, 6 * kCountY, 1); |
| } |
| |
| void doDrawElementsInstancedBaseInstanceReset() |
| { |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
| glDrawElementsInstanced(GL_TRIANGLES, 6 * kCountY, GL_UNSIGNED_SHORT, |
| reinterpret_cast<GLvoid *>(static_cast<uintptr_t>(0)), 1); |
| } |
| |
| void doDrawElementsBaseVertexBaseInstanceReset() |
| { |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
| glDrawElementsInstanced(GL_TRIANGLES, 6 * kCountY, GL_UNSIGNED_SHORT, |
| reinterpret_cast<GLvoid *>(static_cast<uintptr_t>(0)), 1); |
| } |
| |
| void checkDrawResult(bool hasBaseVertex, bool oneColumn = false) |
| { |
| uint32_t numColums = oneColumn ? 1 : kCountX; |
| for (uint32_t y = 0; y < kCountY; ++y) |
| { |
| for (uint32_t x = 0; x < numColums; ++x) |
| { |
| uint32_t center_x = x * kTilePixelSize[0] + kTilePixelSize[0] / 2; |
| uint32_t center_y = y * kTilePixelSize[1] + kTilePixelSize[1] / 2; |
| |
| EXPECT_PIXEL_NEAR(center_x - kPixelCheckSize[0] / 2, |
| center_y - kPixelCheckSize[1] / 2, |
| 256.0 * (1.0 - (float)x / (float)kCountX), |
| 256.0 * (1.0 - (float)y / (float)kCountY), |
| useBaseVertexBuiltin() && hasBaseVertex && !oneColumn |
| ? 256.0 * (1.0 - (float)y / (float)kCountY) |
| : 255, |
| 255, 3); |
| } |
| } |
| } |
| |
| void TearDown() override { ANGLETestBase::ANGLETestTearDown(); } |
| |
| bool requestExtensions() |
| { |
| if (IsGLExtensionRequestable("GL_ANGLE_base_vertex_base_instance_shader_builtin")) |
| { |
| glRequestExtensionANGLE("GL_ANGLE_base_vertex_base_instance_shader_builtin"); |
| } |
| |
| if (!IsGLExtensionEnabled("GL_ANGLE_base_vertex_base_instance_shader_builtin")) |
| { |
| return false; |
| } |
| |
| return EnsureGLExtensionEnabled("GL_EXT_base_instance"); |
| } |
| |
| // Used for base vertex base instance draw calls |
| std::vector<GLushort> mIndices; |
| std::vector<GLfloat> mVertices; |
| std::vector<GLfloat> mNonIndexedVertices; |
| // Used when gl_BaseInstance is not used |
| std::array<GLfloat, kCountX> mInstancedArrayId = {}; |
| std::array<GLfloat, kCountX> mInstancedArrayColorId = {}; |
| // Used for regular draw calls without base vertex base instance |
| std::vector<GLushort> mRegularIndices; |
| GLint mPositionLoc = 0; |
| GLuint mInstanceIDLoc = 0; |
| GLuint mInstanceColorIDLoc = 0; |
| }; |
| |
| // Tests that compile a program with the extension succeeds |
| TEST_P(DrawBaseVertexBaseInstanceTest, CanCompile) |
| { |
| ANGLE_SKIP_TEST_IF(!requestExtensions()); |
| GLProgram p0; |
| setupProgram(p0, true, false); |
| GLProgram p1; |
| setupProgram(p1, true, true); |
| GLProgram p2; |
| setupProgram(p2, false, false); |
| GLProgram p3; |
| setupProgram(p3, false, true); |
| } |
| |
| // Tests that negative baseVertex works properly. |
| TEST_P(DrawBaseVertexBaseInstanceTest, NegativeBaseVertex) |
| { |
| ANGLE_SKIP_TEST_IF(!requestExtensions()); |
| |
| GLProgram program; |
| setupProgram(program, false, false); |
| |
| GLBuffer vertexBuffer; |
| GLBuffer indexBuffer; |
| setupIndexedBuffers(vertexBuffer, indexBuffer); |
| setupPositionVertexAttribPointer(); |
| |
| // Create a new index buffer with shifted indices: {4, 5, 6, 4, 6, 7} |
| // These indices point to the second quad (vertices 4, 5, 6, 7). |
| std::vector<GLushort> shiftedIndices = {4, 5, 6, 4, 6, 7}; |
| GLBuffer shiftedIndexBuffer; |
| glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, shiftedIndexBuffer); |
| glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLushort) * shiftedIndices.size(), |
| shiftedIndices.data(), GL_STATIC_DRAW); |
| |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
| |
| // Draw using baseVertex = -4. |
| // This should shift the indices back to {0, 1, 2, 0, 2, 3}, drawing the first quad! |
| glDrawElementsInstancedBaseVertexBaseInstanceANGLE( |
| GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, reinterpret_cast<GLvoid *>(static_cast<uintptr_t>(0)), |
| 1, -4, 0); |
| |
| ASSERT_GL_NO_ERROR(); |
| |
| // Check that the first quad was drawn as white. |
| EXPECT_PIXEL_NEAR(16, 16, 255, 255, 255, 255, 3); |
| } |
| |
| // Tests if baseInstance works properly with instanced array with non-zero divisor |
| TEST_P(DrawBaseVertexBaseInstanceTest, BaseInstanceDivisor) |
| { |
| ANGLE_SKIP_TEST_IF(!requestExtensions()); |
| ANGLE_SKIP_TEST_IF(useBaseInstanceBuiltin()); |
| |
| GLProgram program; |
| setupProgram(program, true, false, true); |
| |
| GLBuffer nonIndexedVertexBuffer; |
| setupNonIndexedBuffers(nonIndexedVertexBuffer); |
| setupPositionVertexAttribPointer(); |
| |
| GLBuffer instanceIDBuffer; |
| GLBuffer instanceColorIDBuffer; |
| |
| setupInstanceIDBuffer(instanceIDBuffer); |
| setupInstanceIDVertexAttribPointer(mInstanceIDLoc); |
| setupInstanceColorIDBuffer(instanceColorIDBuffer); |
| setupInstanceIDVertexAttribPointer(mInstanceColorIDLoc); |
| |
| doDrawArraysInstancedBaseInstance(); |
| EXPECT_GL_NO_ERROR(); |
| checkDrawResult(false); |
| |
| doDrawArraysBaseInstanceReset(); |
| EXPECT_GL_NO_ERROR(); |
| checkDrawResult(false, true); |
| |
| GLProgram programIndexed; |
| setupProgram(programIndexed, false, false, true); |
| |
| GLBuffer indexBuffer; |
| GLBuffer vertexBuffer; |
| setupIndexedBuffers(vertexBuffer, indexBuffer); |
| setupPositionVertexAttribPointer(); |
| |
| glBindBuffer(GL_ARRAY_BUFFER, instanceIDBuffer); |
| setupInstanceIDVertexAttribPointer(mInstanceIDLoc); |
| glBindBuffer(GL_ARRAY_BUFFER, instanceColorIDBuffer); |
| setupInstanceIDVertexAttribPointer(mInstanceColorIDLoc); |
| |
| doDrawElementsInstancedBaseVertexBaseInstance(); |
| EXPECT_GL_NO_ERROR(); |
| checkDrawResult(true); |
| |
| setupRegularIndexedBuffer(indexBuffer); |
| doDrawElementsBaseVertexBaseInstanceReset(); |
| EXPECT_GL_NO_ERROR(); |
| checkDrawResult(false, true); |
| } |
| |
| // Tests basic functionality of glDrawArraysInstancedBaseInstanceANGLE |
| TEST_P(DrawBaseVertexBaseInstanceTest, DrawArraysInstancedBaseInstance) |
| { |
| // TODO(shrekshao): Temporarily skip this test |
| // before we could try updating win AMD bot driver version |
| // after Lab team fixed issues with ssh into bot machines |
| // Currently this test fail on certain Win7/Win2008Server AMD GPU |
| // with driver version 23.20.185.235 when using OpenGL backend. |
| // This failure couldn't be produced on local Win10 AMD machine with latest driver installed |
| // Same for the MultiDrawArraysInstancedBaseInstance test |
| if (IsAMD() && IsWindows() && IsDesktopOpenGL()) |
| { |
| SystemInfo *systemInfo = GetTestSystemInfo(); |
| if (!systemInfo->gpus.empty()) |
| { |
| ANGLE_SKIP_TEST_IF(0x6613 == systemInfo->gpus[systemInfo->activeGPUIndex].deviceId); |
| } |
| } |
| |
| ANGLE_SKIP_TEST_IF(!requestExtensions()); |
| |
| GLProgram program; |
| setupProgram(program, true); |
| |
| GLBuffer vertexBuffer; |
| setupNonIndexedBuffers(vertexBuffer); |
| setupPositionVertexAttribPointer(); |
| |
| GLBuffer instanceIDBuffer; |
| if (!useBaseInstanceBuiltin()) |
| { |
| setupInstanceIDBuffer(instanceIDBuffer); |
| setupInstanceIDVertexAttribPointer(mInstanceIDLoc); |
| } |
| |
| doDrawArraysInstancedBaseInstance(); |
| EXPECT_GL_NO_ERROR(); |
| checkDrawResult(false); |
| |
| doDrawArraysBaseInstanceReset(); |
| EXPECT_GL_NO_ERROR(); |
| checkDrawResult(false, true); |
| } |
| |
| // Tests basic drawcount validation |
| TEST_P(DrawBaseVertexBaseInstanceTest, MultiDrawValidation) |
| { |
| ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_base_vertex_base_instance")); |
| ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_multi_draw")); |
| |
| const GLint first = 0; |
| const GLsizei count = 0; |
| const GLsizei instanceCount = 0; |
| const GLint baseVertex = 0; |
| const GLuint baseInstance = 0; |
| const GLvoid *const indices[1]{nullptr}; |
| |
| glMultiDrawArraysInstancedBaseInstanceANGLE(GL_TRIANGLES, &first, &count, &instanceCount, |
| &baseInstance, -1); |
| EXPECT_GL_ERROR(GL_INVALID_VALUE); |
| |
| glMultiDrawArraysInstancedBaseInstanceANGLE(GL_TRIANGLES, &first, &count, &instanceCount, |
| &baseInstance, 0); |
| EXPECT_GL_NO_ERROR(); |
| |
| glMultiDrawElementsInstancedBaseVertexBaseInstanceANGLE(GL_TRIANGLES, &count, GL_UNSIGNED_SHORT, |
| indices, &instanceCount, &baseVertex, |
| &baseInstance, -1); |
| EXPECT_GL_ERROR(GL_INVALID_VALUE); |
| |
| glMultiDrawElementsInstancedBaseVertexBaseInstanceANGLE(GL_TRIANGLES, &count, GL_UNSIGNED_SHORT, |
| indices, &instanceCount, &baseVertex, |
| &baseInstance, 0); |
| EXPECT_GL_NO_ERROR(); |
| } |
| |
| // Tests basic functionality of glMultiDrawArraysInstancedBaseInstance |
| TEST_P(DrawBaseVertexBaseInstanceTest, MultiDrawArraysInstancedBaseInstance) |
| { |
| if (IsAMD() && IsWindows() && IsDesktopOpenGL()) |
| { |
| SystemInfo *systemInfo = GetTestSystemInfo(); |
| if (!(systemInfo->activeGPUIndex < 0 || systemInfo->gpus.empty())) |
| { |
| ANGLE_SKIP_TEST_IF(0x6613 == systemInfo->gpus[systemInfo->activeGPUIndex].deviceId); |
| } |
| } |
| |
| ANGLE_SKIP_TEST_IF(!requestExtensions()); |
| |
| GLProgram program; |
| setupProgram(program, true, true); |
| |
| GLBuffer vertexBuffer; |
| setupNonIndexedBuffers(vertexBuffer); |
| setupPositionVertexAttribPointer(); |
| |
| GLBuffer instanceIDBuffer; |
| if (!useBaseInstanceBuiltin()) |
| { |
| setupInstanceIDBuffer(instanceIDBuffer); |
| setupInstanceIDVertexAttribPointer(mInstanceIDLoc); |
| } |
| |
| doMultiDrawArraysInstancedBaseInstance(); |
| EXPECT_GL_NO_ERROR(); |
| checkDrawResult(false); |
| |
| doDrawArraysBaseInstanceReset(); |
| EXPECT_GL_NO_ERROR(); |
| checkDrawResult(false, true); |
| } |
| |
| // Tests basic functionality of glDrawElementsInstancedBaseVertexBaseInstanceANGLE |
| TEST_P(DrawBaseVertexBaseInstanceTest, DrawElementsInstancedBaseVertexBaseInstance) |
| { |
| ANGLE_SKIP_TEST_IF(!requestExtensions()); |
| |
| GLProgram program; |
| setupProgram(program, false); |
| |
| GLBuffer indexBuffer; |
| GLBuffer vertexBuffer; |
| setupIndexedBuffers(vertexBuffer, indexBuffer); |
| setupPositionVertexAttribPointer(); |
| |
| GLBuffer instanceIDBuffer; |
| if (!useBaseInstanceBuiltin()) |
| { |
| setupInstanceIDBuffer(instanceIDBuffer); |
| setupInstanceIDVertexAttribPointer(mInstanceIDLoc); |
| } |
| |
| doDrawElementsInstancedBaseVertexBaseInstance(); |
| EXPECT_GL_NO_ERROR(); |
| checkDrawResult(true); |
| |
| setupRegularIndexedBuffer(indexBuffer); |
| doDrawElementsBaseVertexBaseInstanceReset(); |
| EXPECT_GL_NO_ERROR(); |
| checkDrawResult(true, true); |
| } |
| |
| // Tests basic functionality of glMultiDrawElementsInstancedBaseVertexBaseInstance |
| TEST_P(DrawBaseVertexBaseInstanceTest, MultiDrawElementsInstancedBaseVertexBaseInstance) |
| { |
| ANGLE_SKIP_TEST_IF(!requestExtensions()); |
| |
| GLProgram program; |
| setupProgram(program, false, true); |
| |
| GLBuffer indexBuffer; |
| GLBuffer vertexBuffer; |
| setupIndexedBuffers(vertexBuffer, indexBuffer); |
| setupPositionVertexAttribPointer(); |
| |
| GLBuffer instanceIDBuffer; |
| if (!useBaseInstanceBuiltin()) |
| { |
| setupInstanceIDBuffer(instanceIDBuffer); |
| setupInstanceIDVertexAttribPointer(mInstanceIDLoc); |
| } |
| |
| doMultiDrawElementsInstancedBaseVertexBaseInstance(); |
| EXPECT_GL_NO_ERROR(); |
| checkDrawResult(true); |
| |
| setupRegularIndexedBuffer(indexBuffer); |
| doDrawElementsBaseVertexBaseInstanceReset(); |
| EXPECT_GL_NO_ERROR(); |
| checkDrawResult(true, true); |
| } |
| |
| // Tests if baseInstance works properly with instanced array with non-zero divisor |
| TEST_P(DrawBaseInstanceTest, BaseInstanceDivisor) |
| { |
| ANGLE_SKIP_TEST_IF(!requestExtensions()); |
| ANGLE_SKIP_TEST_IF(useBaseInstanceBuiltin()); |
| |
| GLProgram program; |
| setupProgram(program, true, true); |
| |
| GLBuffer nonIndexedVertexBuffer; |
| setupNonIndexedBuffers(nonIndexedVertexBuffer); |
| setupPositionVertexAttribPointer(); |
| |
| GLBuffer instanceIDBuffer; |
| GLBuffer instanceColorIDBuffer; |
| |
| setupInstanceIDBuffer(instanceIDBuffer); |
| setupInstanceIDVertexAttribPointer(mInstanceIDLoc); |
| setupInstanceColorIDBuffer(instanceColorIDBuffer); |
| setupInstanceIDVertexAttribPointer(mInstanceColorIDLoc); |
| |
| doDrawArraysInstancedBaseInstance(); |
| EXPECT_GL_NO_ERROR(); |
| checkDrawResult(false); |
| |
| doDrawArraysBaseInstanceReset(); |
| EXPECT_GL_NO_ERROR(); |
| checkDrawResult(false, true); |
| |
| GLProgram programIndexed; |
| setupProgram(programIndexed, false, true); |
| |
| GLBuffer indexBuffer; |
| GLBuffer vertexBuffer; |
| setupIndexedBuffers(vertexBuffer, indexBuffer); |
| setupPositionVertexAttribPointer(); |
| |
| glBindBuffer(GL_ARRAY_BUFFER, instanceIDBuffer); |
| setupInstanceIDVertexAttribPointer(mInstanceIDLoc); |
| glBindBuffer(GL_ARRAY_BUFFER, instanceColorIDBuffer); |
| setupInstanceIDVertexAttribPointer(mInstanceColorIDLoc); |
| |
| doDrawElementsInstancedBaseVertexBaseInstance(); |
| EXPECT_GL_NO_ERROR(); |
| checkDrawResult(true); |
| |
| setupRegularIndexedBuffer(indexBuffer); |
| doDrawElementsBaseVertexBaseInstanceReset(); |
| EXPECT_GL_NO_ERROR(); |
| checkDrawResult(false, true); |
| } |
| |
| // Tests basic functionality of glDrawArraysInstancedBaseInstanceEXT |
| TEST_P(DrawBaseInstanceTest, DrawArraysInstancedBaseInstance) |
| { |
| // This test fails on certain Win7/Win2008Server AMD GPU |
| // with driver version 23.20.185.235 when using OpenGL backend. |
| // See comments under the ANGLE test of DrawArraysInstancedBaseInstance above. |
| if (IsAMD() && IsWindows() && IsDesktopOpenGL()) |
| { |
| SystemInfo *systemInfo = GetTestSystemInfo(); |
| if (!systemInfo->gpus.empty()) |
| { |
| ANGLE_SKIP_TEST_IF(0x6613 == systemInfo->gpus[systemInfo->activeGPUIndex].deviceId); |
| } |
| } |
| |
| ANGLE_SKIP_TEST_IF(!requestExtensions()); |
| |
| GLProgram program; |
| setupProgram(program); |
| |
| GLBuffer vertexBuffer; |
| setupNonIndexedBuffers(vertexBuffer); |
| setupPositionVertexAttribPointer(); |
| |
| GLBuffer instanceIDBuffer; |
| if (!useBaseInstanceBuiltin()) |
| { |
| setupInstanceIDBuffer(instanceIDBuffer); |
| setupInstanceIDVertexAttribPointer(mInstanceIDLoc); |
| } |
| |
| doDrawArraysInstancedBaseInstance(); |
| EXPECT_GL_NO_ERROR(); |
| checkDrawResult(false); |
| |
| doDrawArraysBaseInstanceReset(); |
| EXPECT_GL_NO_ERROR(); |
| checkDrawResult(false, true); |
| } |
| |
| // Tests basic functionality of glDrawElementsInstancedBaseInstanceEXT |
| TEST_P(DrawBaseInstanceTest, DrawElementsInstancedBaseInstance) |
| { |
| ANGLE_SKIP_TEST_IF(!requestExtensions()); |
| |
| GLProgram program; |
| setupProgram(program, false); |
| |
| GLBuffer vertexBuffer; |
| glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer); |
| glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * mVertices.size(), mVertices.data(), |
| getBufferDataUsage()); |
| EXPECT_GL_NO_ERROR(); |
| setupPositionVertexAttribPointer(); |
| |
| GLBuffer indexBuffer; |
| setupRegularIndexedBuffer(indexBuffer); |
| |
| GLBuffer instanceIDBuffer; |
| if (!useBaseInstanceBuiltin()) |
| { |
| setupInstanceIDBuffer(instanceIDBuffer); |
| setupInstanceIDVertexAttribPointer(mInstanceIDLoc); |
| } |
| |
| doDrawElementsInstancedBaseInstance(); |
| EXPECT_GL_NO_ERROR(); |
| checkDrawResult(false); |
| |
| doDrawElementsInstancedBaseInstanceReset(); |
| EXPECT_GL_NO_ERROR(); |
| checkDrawResult(false, true); |
| } |
| |
| // Tests basic functionality of glDrawElementsInstancedBaseVertexBaseInstanceEXT |
| TEST_P(DrawBaseInstanceTest, DrawElementsInstancedBaseVertexBaseInstance) |
| { |
| ANGLE_SKIP_TEST_IF(!requestExtensions()); |
| |
| GLProgram program; |
| setupProgram(program, false); |
| |
| GLBuffer indexBuffer; |
| GLBuffer vertexBuffer; |
| setupIndexedBuffers(vertexBuffer, indexBuffer); |
| setupPositionVertexAttribPointer(); |
| |
| GLBuffer instanceIDBuffer; |
| if (!useBaseInstanceBuiltin()) |
| { |
| setupInstanceIDBuffer(instanceIDBuffer); |
| setupInstanceIDVertexAttribPointer(mInstanceIDLoc); |
| } |
| |
| doDrawElementsInstancedBaseVertexBaseInstance(); |
| EXPECT_GL_NO_ERROR(); |
| checkDrawResult(true); |
| |
| setupRegularIndexedBuffer(indexBuffer); |
| doDrawElementsBaseVertexBaseInstanceReset(); |
| EXPECT_GL_NO_ERROR(); |
| checkDrawResult(true, true); |
| } |
| |
| class DrawBaseVertexBaseInstanceTest_ES3 : public ANGLETest<> |
| { |
| public: |
| DrawBaseVertexBaseInstanceTest_ES3() |
| { |
| setWindowWidth(128); |
| setWindowHeight(128); |
| setConfigRedBits(8); |
| setConfigGreenBits(8); |
| setConfigBlueBits(8); |
| setConfigAlphaBits(8); |
| } |
| |
| void runTestNonZeroDivisor(std::function<void(void)> definePositionBuffer, |
| std::function<void(void)> defineColorBuffer, |
| std::function<void(void)> draw) |
| { |
| constexpr char kVS[] = R"(#version 300 es |
| precision mediump float; |
| in vec2 position; |
| in vec2 colorIn; |
| out vec2 color; |
| void main() |
| { |
| gl_Position = vec4(position, 0, 1); |
| if ((gl_InstanceID / 2) % 2 == 1) |
| { |
| gl_Position.y += 1.0; |
| } |
| if (gl_InstanceID % 2 == 1) |
| { |
| gl_Position.x += 1.0; |
| } |
| color = colorIn; |
| })"; |
| |
| constexpr char kFS[] = R"(#version 300 es |
| precision mediump float; |
| in vec2 color; |
| out vec4 colorOut; |
| void main() |
| { |
| colorOut = vec4(color, 0, 1); |
| })"; |
| |
| ANGLE_GL_PROGRAM(program, kVS, kFS); |
| glUseProgram(program); |
| |
| const GLint posLoc = glGetAttribLocation(program, "position"); |
| const GLint colLoc = glGetAttribLocation(program, "colorIn"); |
| |
| // Draw 4 squares. With a divisor of 2 on color, 2 of them will be one color, two the |
| // other. |
| GLBuffer position; |
| glBindBuffer(GL_ARRAY_BUFFER, position); |
| definePositionBuffer(); |
| glEnableVertexAttribArray(posLoc); |
| glVertexAttribPointer(posLoc, 2, GL_FLOAT, GL_FALSE, 0, nullptr); |
| |
| GLBuffer color; |
| glBindBuffer(GL_ARRAY_BUFFER, color); |
| defineColorBuffer(); |
| glEnableVertexAttribArray(colLoc); |
| glVertexAttribPointer(colLoc, 2, GL_FLOAT, GL_FALSE, 0, nullptr); |
| glVertexAttribDivisor(colLoc, 2); |
| |
| glClearColor(0, 0, 0, 1); |
| glClear(GL_COLOR_BUFFER_BIT); |
| draw(); |
| |
| // Verify pixels. The result looks like the following: |
| // |
| // +-----------------------+ |
| // | | |
| // | +----+ +----+ | |
| // | | | | | | <-- two red squares |
| // | | | | | | |
| // | +----+ +----+ | |
| // | | |
| // | | |
| // | +----+ +----+ | |
| // | | | | | | <-- two green squares |
| // | | | | | | |
| // | +----+ +----+ | |
| // | | |
| // +-----------------------+ |
| // | |
| // V |
| // Black inside and outside the squares |
| // |
| const int w = getWindowWidth(); |
| const int h = getWindowHeight(); |
| // Don't check too close to the edges to account for precision issues. This is the margin |
| // from the edge. |
| const int m = 2; |
| const GLColor green(51, 153, 0, 255); |
| |
| // Left of squares |
| EXPECT_PIXEL_RECT_EQ(0, 0, w / 8 - m, h, GLColor::black); |
| // Between squares |
| EXPECT_PIXEL_RECT_EQ(w / 2 - w / 8 + m, 0, w / 4 - m * 2, h, GLColor::black); |
| // Right of squares |
| EXPECT_PIXEL_RECT_EQ(w - w / 8 + m, 0, w / 8 - m, h, GLColor::black); |
| |
| // Above squares |
| EXPECT_PIXEL_RECT_EQ(0, 0, w, h / 8 - m, GLColor::black); |
| // Between squares |
| EXPECT_PIXEL_RECT_EQ(0, h / 2 - h / 8 + m, w, h / 4 - m * 2, GLColor::black); |
| // Below squares |
| EXPECT_PIXEL_RECT_EQ(0, h - h / 8 + m, w, h / 8 - m, GLColor::black); |
| |
| // Squares |
| EXPECT_PIXEL_RECT_EQ(w / 8 + m, h / 8 + m, w / 4 - 4, h / 4 - 4, GLColor::red); |
| EXPECT_PIXEL_RECT_EQ(w / 2 + w / 8 + m, h / 8 + m, w / 4 - 4, h / 4 - 4, GLColor::red); |
| EXPECT_PIXEL_RECT_EQ(w / 8 + m, h / 2 + h / 8 + m, w / 4 - 4, h / 4 - 4, green); |
| EXPECT_PIXEL_RECT_EQ(w / 2 + w / 8 + m, h / 2 + h / 8 + m, w / 4 - 4, h / 4 - 4, green); |
| } |
| }; |
| |
| // gl_BaseVertex and gl_BaseInstance are translated to angle_BaseVertex and angle_BaseInstance |
| // internally. Check that a user-defined angle_BaseVertex or angle_BaseInstance is permitted |
| TEST_P(DrawBaseVertexBaseInstanceTest_ES3, AllowsUserDefinedANGLEDrawID) |
| { |
| ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_base_vertex_base_instance")); |
| ANGLE_SKIP_TEST_IF( |
| !EnsureGLExtensionEnabled("GL_ANGLE_base_vertex_base_instance_shader_builtin")); |
| |
| constexpr char kVS[] = R"(#version 300 es |
| #extension GL_ANGLE_base_vertex_base_instance_shader_builtin : require |
| in vec2 position; |
| uniform int angle_BaseVertex; |
| uniform int angle_BaseInstance; |
| out vec4 verified; |
| |
| void main() |
| { |
| // Expect gl_BaseVertex and gl_BaseInstance to be untouched when angle_BaseVertex and |
| // angle_BaseInstance are not. |
| verified = vec4(gl_BaseVertex == 2, gl_BaseInstance == 0, |
| angle_BaseVertex == 3, angle_BaseInstance == 5); |
| gl_Position = vec4(position, 0, 1); |
| })"; |
| |
| constexpr char kFS[] = R"(#version 300 es |
| precision mediump float; |
| in vec4 verified; |
| out vec4 color; |
| |
| void main() |
| { |
| color = verified; |
| })"; |
| |
| ANGLE_GL_PROGRAM(program, kVS, kFS); |
| glUseProgram(program); |
| glUniform1i(glGetUniformLocation(program, "angle_BaseVertex"), 3); |
| glUniform1i(glGetUniformLocation(program, "angle_BaseInstance"), 5); |
| |
| constexpr std::array<GLfloat, 5 * 2> kVertexData = { |
| // Vertex 0, unused |
| 10000, |
| 10000, |
| // Vertex 1, unused |
| 10000, |
| 10000, |
| // Vertices 2, 3 and 4 define the triangle because base vertex is 2. |
| -1, |
| -1, |
| 3, |
| -1, |
| -1, |
| 3, |
| }; |
| constexpr std::array<GLushort, 3> kIndexData = { |
| 0, |
| 1, |
| 2, |
| }; |
| |
| GLBuffer vertexBuffer; |
| glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer); |
| glBufferData(GL_ARRAY_BUFFER, sizeof(kVertexData), kVertexData.data(), GL_STATIC_DRAW); |
| const GLint positionLoc = glGetAttribLocation(program, "position"); |
| glEnableVertexAttribArray(positionLoc); |
| glVertexAttribPointer(positionLoc, 2, GL_FLOAT, GL_FALSE, 0, 0); |
| |
| GLBuffer indexBuffer; |
| glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer); |
| glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(kIndexData), kIndexData.data(), GL_STATIC_DRAW); |
| ASSERT_GL_NO_ERROR(); |
| |
| glDrawElementsInstancedBaseVertexBaseInstanceANGLE(GL_TRIANGLES, 3, GL_UNSIGNED_SHORT, nullptr, |
| 1, 2, 0); |
| EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::white); |
| ASSERT_GL_NO_ERROR(); |
| } |
| |
| // Test glDrawArraysInstancedBaseInstance with a non-zero divisor. |
| TEST_P(DrawBaseVertexBaseInstanceTest_ES3, NonZeroDivisorBaseInstance) |
| { |
| const bool hasEXT = IsGLExtensionEnabled("GL_EXT_base_instance"); |
| const bool hasANGLE = IsGLExtensionEnabled("GL_ANGLE_base_vertex_base_instance"); |
| ANGLE_SKIP_TEST_IF(!hasEXT && !hasANGLE); |
| |
| const int w = getWindowWidth(); |
| const int h = getWindowHeight(); |
| const float left = static_cast<float>(w / 8) / (w - 1) * 2.0 - 1.0; |
| const float right = static_cast<float>(w / 2 - w / 8) / (w - 1) * 2.0 - 1.0; |
| const float top = static_cast<float>(h / 8) / (h - 1) * 2.0 - 1.0; |
| const float bottom = static_cast<float>(h / 2 - h / 8) / (h - 1) * 2.0 - 1.0; |
| |
| const GLfloat kPositions[] = { |
| // Top left. Instances 1 and 3 shift this square to the right. Instances 2 and 3 shift it |
| // down. |
| left, top, left, bottom, right, top, right, bottom, |
| }; |
| constexpr GLfloat kColors[] = { |
| // 11 unused attributes, skipped by base instance |
| // clang-format off |
| 0.1f, 0.2f, |
| 0.3f, 0.4f, |
| 0.5f, 0.6f, |
| 0.7f, 0.8f, |
| 0.9f, 0.95f, |
| 0.85f, 0.75f, |
| 0.65f, 0.55f, |
| 0.45f, 0.35f, |
| 0.25f, 0.15f, |
| 0.05f, 0.15f, |
| 0.25f, 0.35f, |
| // Top |
| 1.0f, 0.0f, |
| // Bottom |
| 0.2f, 0.6f, |
| // clang-format on |
| }; |
| runTestNonZeroDivisor( |
| [&kPositions]() { |
| glBufferData(GL_ARRAY_BUFFER, sizeof(kPositions), kPositions, GL_STATIC_DRAW); |
| }, |
| [&kColors]() { glBufferData(GL_ARRAY_BUFFER, sizeof(kColors), kColors, GL_STATIC_DRAW); }, |
| [hasEXT]() { |
| if (hasEXT) |
| { |
| glDrawArraysInstancedBaseInstanceEXT(GL_TRIANGLE_STRIP, 0, 4, 4, 11); |
| } |
| else |
| { |
| glDrawArraysInstancedBaseInstanceANGLE(GL_TRIANGLE_STRIP, 0, 4, 4, 11); |
| } |
| }); |
| } |
| |
| // Test glDrawElementsInstancedBaseVertexBaseInstance with a non-zero divisor. |
| TEST_P(DrawBaseVertexBaseInstanceTest_ES3, NonZeroDivisorBaseVertexBaseInstance) |
| { |
| const bool hasEXT = IsGLExtensionEnabled("GL_EXT_base_instance"); |
| const bool hasANGLE = IsGLExtensionEnabled("GL_ANGLE_base_vertex_base_instance"); |
| ANGLE_SKIP_TEST_IF(!hasEXT && !hasANGLE); |
| |
| const int w = getWindowWidth(); |
| const int h = getWindowHeight(); |
| const float left = static_cast<float>(w / 8) / (w - 1) * 2.0 - 1.0; |
| const float right = static_cast<float>(w / 2 - w / 8) / (w - 1) * 2.0 - 1.0; |
| const float top = static_cast<float>(h / 8) / (h - 1) * 2.0 - 1.0; |
| const float bottom = static_cast<float>(h / 2 - h / 8) / (h - 1) * 2.0 - 1.0; |
| |
| const GLfloat kPositions[] = { |
| // 5 unused vertices, skipped by base vertex |
| // clang-format off |
| 0.1f, 0.4f, |
| 0.3f, 0.6f, |
| 0.5f, 0.8f, |
| 0.7f, 0.6f, |
| 0.9f, 0.4f, |
| // Top left. Instances 1 and 3 shift this square to the right. Instances 2 and 3 shift it |
| // down. The vertices are out of order, but are reordered by the index buffer. |
| right, bottom, left, bottom, left, top, right, top, |
| // clang-format on |
| }; |
| constexpr GLfloat kColors[] = { |
| // 7 unused attributes, skipped by base instance. |
| // clang-format off |
| 0.1f, 0.2f, |
| 0.3f, 0.4f, |
| 0.5f, 0.6f, |
| 0.7f, 0.8f, |
| 0.9f, 0.95f, |
| 0.85f, 0.75f, |
| 0.65f, 0.55f, |
| // Top |
| 1.0f, 0.0f, |
| // Bottom |
| 0.2f, 0.6f, |
| // clang-format on |
| }; |
| constexpr GLint kIndices[] = { |
| // The square |
| 2, |
| 1, |
| 3, |
| 0, |
| }; |
| |
| GLBuffer index; |
| glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index); |
| glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(kIndices), kIndices, GL_STATIC_DRAW); |
| |
| runTestNonZeroDivisor( |
| [&kPositions]() { |
| glBufferData(GL_ARRAY_BUFFER, sizeof(kPositions), kPositions, GL_STATIC_DRAW); |
| }, |
| [&kColors]() { glBufferData(GL_ARRAY_BUFFER, sizeof(kColors), kColors, GL_STATIC_DRAW); }, |
| [hasEXT]() { |
| if (hasEXT) |
| { |
| glDrawElementsInstancedBaseVertexBaseInstanceEXT(GL_TRIANGLE_STRIP, 4, |
| GL_UNSIGNED_INT, nullptr, 4, 5, 7); |
| } |
| else |
| { |
| glDrawElementsInstancedBaseVertexBaseInstanceANGLE( |
| GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_INT, nullptr, 4, 5, 7); |
| } |
| }); |
| } |
| |
| // Test that gl_InstanceID does not include base instance. |
| TEST_P(DrawBaseVertexBaseInstanceTest_ES3, InstanceIDDoesNotIncludeBaseInstance) |
| { |
| const bool hasEXT = IsGLExtensionEnabled("GL_EXT_base_instance"); |
| const bool hasANGLE = IsGLExtensionEnabled("GL_ANGLE_base_vertex_base_instance"); |
| ANGLE_SKIP_TEST_IF(!hasEXT && !hasANGLE); |
| |
| // Draw 4 triangles to cover the left half of the screen, each triangle is the result of a |
| // different instance ID. If instance ID is larger than expected (because it includes base |
| // instance), draw red to the right half of the screen. |
| constexpr char kVS[] = R"(#version 300 es |
| precision mediump float; |
| out vec2 color; |
| void main() |
| { |
| // Take the following points: |
| // |
| // P0 P1 |
| // +------+------+ |
| // | _-| | |
| // | _- | | |
| // |_- | | |
| // P2 +------+ P3 | |
| // | _-| | |
| // | _- | | |
| // |_- | | |
| // +------+------+ |
| // P4 P5 |
| // |
| // Instances generate: |
| // |
| // * Instance 0: P0, P1, P2 |
| // * Instance 1: P1, P2, P3 |
| // * Instance 2: P2, P3, P4 |
| // * Instance 3: P3, P4, P5 |
| // |
| // So effectively the output position is P[gl_VertexID + gl_InstanceID]. |
| // |
| // To make sure there are no seams, the first vertex is always offset by -(0, epsilon) and the |
| // third vertex is offset by (0, epsilon). |
| vec2 P[6] = vec2[6]( |
| vec2(-1.0, -1.0), |
| vec2( 0.0, -1.0), |
| vec2(-1.0, 0.0), |
| vec2( 0.0, 0.0), |
| vec2(-1.0, 1.0), |
| vec2( 0.0, 1.0) |
| ); |
| |
| if (gl_InstanceID < 4) |
| { |
| gl_Position = vec4(P[gl_VertexID + gl_InstanceID], 0, 1); |
| if (gl_VertexID == 0) |
| gl_Position.y -= 0.01; |
| else if (gl_VertexID == 2) |
| gl_Position.y += 0.01; |
| color = vec2(0, 1); |
| } |
| else |
| { |
| switch (gl_VertexID) { |
| case 0: gl_Position = vec4(0, -2, 0, 1); break; |
| case 1: gl_Position = vec4(2, 0, 0, 1); break; |
| case 2: gl_Position = vec4(0, 2, 0, 1); break; |
| }; |
| color = vec2(1, 0); |
| } |
| })"; |
| |
| constexpr char kFS[] = R"(#version 300 es |
| precision mediump float; |
| in vec2 color; |
| out vec4 colorOut; |
| void main() |
| { |
| colorOut = vec4(color, 0, 1); |
| })"; |
| |
| ANGLE_GL_PROGRAM(program, kVS, kFS); |
| glUseProgram(program); |
| |
| glClearColor(0, 0, 0, 1); |
| glClear(GL_COLOR_BUFFER_BIT); |
| if (hasEXT) |
| { |
| glDrawArraysInstancedBaseInstanceEXT(GL_TRIANGLE_STRIP, 0, 3, 4, 15); |
| } |
| else |
| { |
| glDrawArraysInstancedBaseInstanceANGLE(GL_TRIANGLE_STRIP, 0, 3, 4, 15); |
| } |
| |
| // Left half should be all green, right half should be all black. |
| const int w = getWindowWidth(); |
| const int h = getWindowHeight(); |
| EXPECT_PIXEL_RECT_EQ(0, 0, w / 2 - 1, h, GLColor::green); |
| EXPECT_PIXEL_RECT_EQ(w / 2 + 1, 0, w - (w / 2 + 1), h, GLColor::black); |
| } |
| |
| // Test base instance in combination with divisor |
| TEST_P(DrawBaseVertexBaseInstanceTest_ES3, BaseInstanceDivisorIndexing) |
| { |
| ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_base_instance") || |
| !EnsureGLExtensionEnabled("GL_ANGLE_base_vertex_base_instance")); |
| |
| // Vertex shader: pass per-instance integer value through a flat varying. |
| constexpr char kVS[] = R"(#version 300 es |
| #extension GL_ANGLE_base_vertex_base_instance_shader_builtin : require |
| in vec4 a_position; |
| in int a_instValue; |
| flat out int v_instValue; |
| flat out int v_instanceID; |
| flat out int v_baseInstance; |
| void main() |
| { |
| v_instValue = a_instValue; |
| v_instanceID = gl_InstanceID; |
| v_baseInstance = gl_BaseInstance; |
| gl_Position = a_position; |
| })"; |
| |
| // Fragment shader: output the instanced value as an integer. |
| constexpr char kFS[] = R"(#version 300 es |
| precision highp int; |
| flat in int v_instValue; |
| flat in int v_instanceID; |
| flat in int v_baseInstance; |
| out ivec4 o_color; |
| void main() |
| { |
| o_color = ivec4(v_instValue, v_instanceID, v_baseInstance, 1); |
| })"; |
| |
| ANGLE_GL_PROGRAM(program, kVS, kFS); |
| glUseProgram(program); |
| |
| glClearColor(0, 0, 0, 1); |
| glClear(GL_COLOR_BUFFER_BIT); |
| GLint posLoc = glGetAttribLocation(program, "a_position"); |
| GLint instLoc = glGetAttribLocation(program, "a_instValue"); |
| ASSERT_NE(-1, posLoc); |
| ASSERT_NE(-1, instLoc); |
| |
| // Render to a GL_RGBA32I texture via FBO for exact integer readback. |
| GLTexture intTex; |
| glBindTexture(GL_TEXTURE_2D, intTex); |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32I, getWindowWidth(), getWindowHeight(), 0, |
| GL_RGBA_INTEGER, GL_INT, nullptr); |
| |
| GLFramebuffer fbo; |
| glBindFramebuffer(GL_FRAMEBUFFER, fbo); |
| glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, intTex, 0); |
| ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER)); |
| |
| // Position: a full-viewport triangle strip (4 vertices). |
| const float positions[] = { |
| -1.0f, -1.0f, 0.0f, 1.0f, 1.0f, -1.0f, 0.0f, 1.0f, |
| -1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, |
| }; |
| GLBuffer posBuffer; |
| glBindBuffer(GL_ARRAY_BUFFER, posBuffer); |
| glBufferData(GL_ARRAY_BUFFER, sizeof(positions), positions, GL_STATIC_DRAW); |
| glEnableVertexAttribArray(posLoc); |
| glVertexAttribPointer(posLoc, 4, GL_FLOAT, GL_FALSE, 0, nullptr); |
| |
| // Per-instance attribute: 8 distinct integer values, divisor = 3. |
| // Each value is a unique identifier for its buffer index. |
| const GLint instValues[] = {0, 1, 2, 3, 4, 5, 6, 7}; |
| GLBuffer instBuffer; |
| glBindBuffer(GL_ARRAY_BUFFER, instBuffer); |
| glBufferData(GL_ARRAY_BUFFER, sizeof(instValues), instValues, GL_STATIC_DRAW); |
| glEnableVertexAttribArray(instLoc); |
| glVertexAttribIPointer(instLoc, 1, GL_INT, 0, nullptr); |
| glVertexAttribDivisor(instLoc, 3); |
| ASSERT_GL_NO_ERROR(); |
| |
| glViewport(0, 0, getWindowWidth(), getWindowHeight()); |
| struct Subcase |
| { |
| GLsizei instanceCount; |
| GLuint baseInstance; |
| } subcases[]{ |
| {.instanceCount = 1, .baseInstance = 0}, |
| {.instanceCount = 1, .baseInstance = 4}, |
| {.instanceCount = 1, .baseInstance = 7}, |
| {.instanceCount = 6, .baseInstance = 3}, |
| }; |
| |
| for (auto const &subcase : subcases) |
| { |
| SCOPED_TRACE(testing::Message() |
| << "Subcase subcase{.instanceCount = " << subcase.instanceCount |
| << ", .baseInstance = " << subcase.baseInstance << "}"); |
| const GLint clearValue[] = {0, 0, 0, 0}; |
| glClearBufferiv(GL_COLOR, 0, clearValue); |
| glDrawArraysInstancedBaseInstanceANGLE(GL_TRIANGLE_STRIP, 0, 4, subcase.instanceCount, |
| subcase.baseInstance); |
| ASSERT_GL_NO_ERROR(); |
| GLint lastInstanceAttrIndex = subcase.baseInstance + (subcase.instanceCount - 1) / 3; |
| // SAFETY: Values chosen in the test cases, max value within 0..7. |
| GLint lastInstValue = ANGLE_UNSAFE_BUFFERS(instValues[lastInstanceAttrIndex]); |
| GLint lastInstanceID = subcase.instanceCount - 1; |
| // R == instValue (per-instance attribute), G == gl_InstanceID, B == gl_BaseInstance. |
| GLColor32I expected(lastInstValue, lastInstanceID, static_cast<GLint>(subcase.baseInstance), |
| 1); |
| EXPECT_PIXEL_32I_COLOR(0, 0, expected); |
| } |
| } |
| |
| // Test base instance with divisor >= 256 |
| TEST_P(DrawBaseVertexBaseInstanceTest_ES3, BaseInstanceLargeDivisor) |
| { |
| const bool hasEXT = IsGLExtensionEnabled("GL_EXT_base_instance"); |
| const bool hasANGLE = IsGLExtensionEnabled("GL_ANGLE_base_vertex_base_instance"); |
| ANGLE_SKIP_TEST_IF(!hasEXT && !hasANGLE); |
| |
| constexpr uint32_t kBaseInstance = 3; |
| constexpr uint32_t kRegionCount = 4; |
| constexpr uint32_t kDivisor = 256; |
| |
| // Create an attribute that will be instanced. |
| const std::array<GLColor, kBaseInstance + kRegionCount> kData = { |
| GLColor(10, 20, 30, 40), GLColor(11, 21, 31, 41), GLColor(12, 22, 32, 42), GLColor::red, |
| GLColor::green, GLColor::blue, GLColor::yellow, |
| }; |
| |
| GLBuffer instancedData; |
| glBindBuffer(GL_ARRAY_BUFFER, instancedData); |
| glBufferData(GL_ARRAY_BUFFER, sizeof(kData), kData.data(), GL_STATIC_DRAW); |
| |
| glEnableVertexAttribArray(0); |
| glVertexAttribPointer(0, 4, GL_UNSIGNED_BYTE, true, 4, nullptr); |
| glVertexAttribDivisor(0, kDivisor); |
| |
| // The divisor of the data should be high (256) to trigger a specific path in the Vulkan |
| // backend. The test renders 256*4 instances with a base instance of 3. To simplify |
| // validation, the output is divided into four corners, where every 256 instances overdraw the |
| // same corner. |
| // |
| // P0 |
| // + |
| // / | \ |
| // / | \ |
| // / | \ |
| // / | \ |
| // +--------+--------+ |
| // / | | | \ |
| // / | | | \ |
| // / | | O | \ |
| // P1 +------+--------+--------+------+ P3 |
| // \ | | | / |
| // \ | | | / |
| // \ | | | / |
| // +--------+--------+ |
| // \ | / |
| // \ | / |
| // \ | / |
| // \ | / |
| // + |
| // P2 |
| // |
| // Instances generate: |
| // |
| // * Instances [0 * divisor, 1 * divisor): O, P0, P1 |
| // * Instances [1 * divisor, 2 * divisor): O, P1, P2 |
| // * Instances [2 * divisor, 3 * divisor): O, P2, P3 |
| // * Instances [3 * divisor, 4 * divisor): O, P3, P0 |
| // |
| // So the vertices are always as follows, given i = gl_InstanceID / divisor: |
| // |
| // * gl_VertexID 0: P[0] |
| // * gl_VertexID 1: P[i] |
| // * gl_VertexID 2: P[(i + 1) % 4] |
| // |
| constexpr char kVS[] = R"(#version 300 es |
| precision mediump float; |
| in vec4 instanceData; |
| out vec4 color; |
| void main() |
| { |
| vec2 P[4] = vec2[4]( |
| vec2( 0.0, -2.0), |
| vec2(-2.0, 0.0), |
| vec2( 0.0, 2.0), |
| vec2( 2.0, 0.0) |
| ); |
| |
| int index = gl_InstanceID / 256; |
| switch (gl_VertexID % 3) |
| { |
| case 0: |
| gl_Position = vec4(0, 0, 0, 1); |
| break; |
| case 1: |
| gl_Position = vec4(P[index], 0, 1); |
| break; |
| default: |
| gl_Position = vec4(P[(index + 1) % 4], 0, 1); |
| break; |
| } |
| |
| color = instanceData; |
| })"; |
| |
| constexpr char kFS[] = R"(#version 300 es |
| precision mediump float; |
| in vec4 color; |
| out vec4 colorOut; |
| void main() |
| { |
| colorOut = color; |
| })"; |
| |
| ANGLE_GL_PROGRAM(program, kVS, kFS); |
| glUseProgram(program); |
| |
| glClearColor(0, 0, 0, 1); |
| glClear(GL_COLOR_BUFFER_BIT); |
| if (hasEXT) |
| { |
| glDrawArraysInstancedBaseInstanceEXT(GL_TRIANGLE_STRIP, 0, 3, kDivisor * kRegionCount, |
| kBaseInstance); |
| } |
| else |
| { |
| glDrawArraysInstancedBaseInstanceANGLE(GL_TRIANGLE_STRIP, 0, 3, kDivisor * kRegionCount, |
| kBaseInstance); |
| } |
| |
| const int w = getWindowWidth(); |
| const int h = getWindowHeight(); |
| EXPECT_PIXEL_RECT_EQ(0, 0, w / 2 - 1, h / 2 - 1, kData[kBaseInstance]); |
| EXPECT_PIXEL_RECT_EQ(0, h / 2 + 1, w / 2 - 1, h - (h / 2 + 1), kData[kBaseInstance + 1]); |
| EXPECT_PIXEL_RECT_EQ(w / 2 + 1, h / 2 + 1, w - (w / 2 + 1), h - (h / 2 + 1), |
| kData[kBaseInstance + 2]); |
| EXPECT_PIXEL_RECT_EQ(w / 2 + 1, 0, w - (w / 2 + 1), h / 2, kData[kBaseInstance + 3]); |
| } |
| |
| // Test base instance with divisor < 256 where vertex attribute data is sourced from client memory. |
| TEST_P(DrawBaseVertexBaseInstanceTest_ES3, BaseInstanceSmallDivisorClientMemory) |
| { |
| const bool hasEXT = IsGLExtensionEnabled("GL_EXT_base_instance"); |
| const bool hasANGLE = IsGLExtensionEnabled("GL_ANGLE_base_vertex_base_instance"); |
| ANGLE_SKIP_TEST_IF(!hasEXT && !hasANGLE); |
| |
| constexpr uint32_t kBaseInstance = 3; |
| constexpr uint32_t kRegionCount = 4; |
| constexpr uint32_t kDivisor = 11; |
| |
| // Create an attribute that will be instanced. |
| const std::array<GLColor, kBaseInstance + kRegionCount> kData = { |
| GLColor(10, 20, 30, 40), GLColor(11, 21, 31, 41), GLColor(12, 22, 32, 42), GLColor::red, |
| GLColor::green, GLColor::blue, GLColor::yellow, |
| }; |
| |
| // Data needs to be sourced from a client buffer to trigger a specific path in the Vulkan |
| // backend. |
| glEnableVertexAttribArray(0); |
| glVertexAttribPointer(0, 4, GL_UNSIGNED_BYTE, true, 4, kData.data()); |
| glVertexAttribDivisor(0, kDivisor); |
| |
| // The divisor of the data should be low (<256) to trigger a specific path in the Vulkan |
| // backend. The test renders 11*4 instances with a base instance of 3. To simplify |
| // validation, the output is divided as such: |
| // |
| // P0 |
| // + |
| // / | \ |
| // / | \ |
| // / | \ |
| // / | \ |
| // +--------+--------+ |
| // / | | | \ |
| // / | | | \ |
| // / | | O | \ |
| // P1 +------+--------+--------+------+ P3 |
| // \ | | | / |
| // \ | | | / |
| // \ | | | / |
| // +--------+--------+ |
| // \ | / |
| // \ | / |
| // \ | / |
| // \ | / |
| // + |
| // P2 |
| // |
| // Instances generate: |
| // |
| // * Instances [0 * divisor, 1 * divisor): O, P0, P1 |
| // * Instances [1 * divisor, 2 * divisor): O, P1, P2 |
| // * Instances [2 * divisor, 3 * divisor): O, P2, P3 |
| // * Instances [3 * divisor, 4 * divisor): O, P3, P0 |
| // |
| // So the vertices are always as follows, given i = gl_InstanceID / divisor: |
| // |
| // * gl_VertexID 0: P[0] |
| // * gl_VertexID 1: P[i] |
| // * gl_VertexID 2: P[(i + 1) % 4] |
| // |
| constexpr char kVS[] = R"(#version 300 es |
| precision mediump float; |
| in vec4 instanceData; |
| out vec4 color; |
| void main() |
| { |
| vec2 P[4] = vec2[4]( |
| vec2( 0.0, -2.0), |
| vec2(-2.0, 0.0), |
| vec2( 0.0, 2.0), |
| vec2( 2.0, 0.0) |
| ); |
| |
| int index = gl_InstanceID / 11; |
| switch (gl_VertexID % 3) |
| { |
| case 0: |
| gl_Position = vec4(0, 0, 0, 1); |
| break; |
| case 1: |
| gl_Position = vec4(P[index], 0, 1); |
| break; |
| default: |
| gl_Position = vec4(P[(index + 1) % 4], 0, 1); |
| break; |
| } |
| |
| color = instanceData; |
| })"; |
| |
| constexpr char kFS[] = R"(#version 300 es |
| precision mediump float; |
| in vec4 color; |
| out vec4 colorOut; |
| void main() |
| { |
| colorOut = color; |
| })"; |
| |
| ANGLE_GL_PROGRAM(program, kVS, kFS); |
| glUseProgram(program); |
| |
| glClearColor(0, 0, 0, 1); |
| glClear(GL_COLOR_BUFFER_BIT); |
| if (hasEXT) |
| { |
| glDrawArraysInstancedBaseInstanceEXT(GL_TRIANGLE_STRIP, 0, 3, kDivisor * kRegionCount, |
| kBaseInstance); |
| } |
| else |
| { |
| glDrawArraysInstancedBaseInstanceANGLE(GL_TRIANGLE_STRIP, 0, 3, kDivisor * kRegionCount, |
| kBaseInstance); |
| } |
| |
| const int w = getWindowWidth(); |
| const int h = getWindowHeight(); |
| EXPECT_PIXEL_RECT_EQ(0, 0, w / 2 - 1, h / 2 - 1, kData[kBaseInstance]); |
| EXPECT_PIXEL_RECT_EQ(0, h / 2 + 1, w / 2 - 1, h - (h / 2 + 1), kData[kBaseInstance + 1]); |
| EXPECT_PIXEL_RECT_EQ(w / 2 + 1, h / 2 + 1, w - (w / 2 + 1), h - (h / 2 + 1), |
| kData[kBaseInstance + 2]); |
| EXPECT_PIXEL_RECT_EQ(w / 2 + 1, 0, w - (w / 2 + 1), h / 2, kData[kBaseInstance + 3]); |
| } |
| |
| GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(DrawBaseVertexBaseInstanceTest); |
| |
| #define ANGLE_ALL_BASEVERTEXBASEINTANCE_TEST_PLATFORMS_ES3 \ |
| ES3_D3D11().enable(Feature::AlwaysEnableEmulatedMultidrawExtensions), \ |
| ES3_OPENGL().enable(Feature::AlwaysEnableEmulatedMultidrawExtensions), \ |
| ES3_OPENGLES().enable(Feature::AlwaysEnableEmulatedMultidrawExtensions), \ |
| ES3_VULKAN().enable(Feature::AlwaysEnableEmulatedMultidrawExtensions), \ |
| ES3_VULKAN().disable(Feature::SupportsVertexInputDynamicState), \ |
| ES3_VULKAN_SWIFTSHADER().enable(Feature::AlwaysEnableEmulatedMultidrawExtensions), \ |
| ES3_METAL().enable(Feature::AlwaysEnableEmulatedMultidrawExtensions) |
| |
| ANGLE_INSTANTIATE_TEST_COMBINE_3( |
| DrawBaseVertexBaseInstanceTest, |
| PrintToStringParamName(), |
| testing::Values(BaseVertexOption::NoBaseVertex, BaseVertexOption::UseBaseVertex), |
| testing::Values(BaseInstanceOption::NoBaseInstance, BaseInstanceOption::UseBaseInstance), |
| testing::Values(BufferDataUsageOption::StaticDraw, BufferDataUsageOption::DynamicDraw), |
| ANGLE_ALL_BASEVERTEXBASEINTANCE_TEST_PLATFORMS_ES3); |
| |
| ANGLE_INSTANTIATE_TEST_COMBINE_3( |
| DrawBaseInstanceTest, |
| PrintToStringParamName(), |
| testing::Values(BaseVertexOption::NoBaseVertex, BaseVertexOption::UseBaseVertex), |
| testing::Values(BaseInstanceOption::NoBaseInstance, BaseInstanceOption::UseBaseInstance), |
| testing::Values(BufferDataUsageOption::StaticDraw, BufferDataUsageOption::DynamicDraw), |
| ANGLE_ALL_BASEVERTEXBASEINTANCE_TEST_PLATFORMS_ES3); |
| |
| GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(DrawBaseVertexBaseInstanceTest_ES3); |
| ANGLE_INSTANTIATE_TEST(DrawBaseVertexBaseInstanceTest_ES3, |
| ANGLE_ALL_BASEVERTEXBASEINTANCE_TEST_PLATFORMS_ES3); |
| |
| } // namespace |