blob: d35648238031b611b72279d1adcf8a2327f3b89b [file] [log] [blame]
//
// Copyright 2018 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.
//
// MultiDrawTest: Tests of GL_ANGLE_multi_draw
#include "test_utils/ANGLETest.h"
#include "test_utils/gl_raii.h"
using namespace angle;
namespace
{
// Create a kWidth * kHeight canvas equally split into kCountX * kCountY tiles
// each containing a quad partially coverting each tile
constexpr uint32_t kWidth = 256;
constexpr uint32_t kHeight = 256;
constexpr uint32_t kCountX = 8;
constexpr uint32_t kCountY = 8;
constexpr uint32_t kQuadCount = kCountX * kCountY;
constexpr uint32_t kTriCount = kQuadCount * 2;
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 DrawIDOption
{
NoDrawID,
UseDrawID,
};
enum class InstancingOption
{
NoInstancing,
UseInstancing,
};
using MultiDrawTestParams = std::tuple<angle::PlatformParameters, DrawIDOption, InstancingOption>;
struct PrintToStringParamName
{
std::string operator()(const ::testing::TestParamInfo<MultiDrawTestParams> &info) const
{
::std::stringstream ss;
ss << (std::get<2>(info.param) == InstancingOption::UseInstancing ? "Instanced_" : "")
<< (std::get<1>(info.param) == DrawIDOption::UseDrawID ? "DrawID_" : "")
<< std::get<0>(info.param);
return ss.str();
}
};
// These tests check correctness of the ANGLE_multi_draw extension.
// An array of quads is drawn across the screen.
// gl_DrawID is checked by using it to select the color of the draw.
// MultiDraw*Instanced entrypoints use the existing instancing APIs which are
// more fully tested in InstancingTest.cpp.
// Correct interaction with the instancing APIs is tested here by using scaling
// and then instancing the array of quads over four quadrants on the screen.
class MultiDrawTest : public ANGLETestBase, public ::testing::TestWithParam<MultiDrawTestParams>
{
protected:
MultiDrawTest()
: ANGLETestBase(std::get<0>(GetParam())),
mNonIndexedVertexBuffer(0u),
mVertexBuffer(0u),
mIndexBuffer(0u),
mInstanceBuffer(0u),
mProgram(0u)
{
setWindowWidth(kWidth);
setWindowHeight(kHeight);
setConfigRedBits(8);
setConfigGreenBits(8);
setConfigBlueBits(8);
setConfigAlphaBits(8);
}
void SetUp() override { ANGLETestBase::ANGLETestSetUp(); }
bool IsDrawIDTest() const { return std::get<1>(GetParam()) == DrawIDOption::UseDrawID; }
bool IsInstancedTest() const
{
return std::get<2>(GetParam()) == InstancingOption::UseInstancing;
}
std::string VertexShaderSource()
{
std::stringstream shader;
shader << (IsDrawIDTest() ? "#extension GL_ANGLE_multi_draw : require\n" : "")
<< (IsInstancedTest() ? "attribute float vInstance;" : "") << R"(
attribute vec2 vPosition;
varying vec4 color;
void main()
{
int id = )" << (IsDrawIDTest() ? "gl_DrawID" : "0")
<< ";"
<< R"(
float quad_id = float(id / 2);
float color_id = quad_id - (3.0 * floor(quad_id / 3.0));
if (color_id == 0.0) {
color = vec4(1, 0, 0, 1);
} else if (color_id == 1.0) {
color = vec4(0, 1, 0, 1);
} else {
color = vec4(0, 0, 1, 1);
}
mat3 transform = mat3(1.0);
)"
<< (IsInstancedTest() ? R"(
transform[0][0] = 0.5;
transform[1][1] = 0.5;
if (vInstance == 0.0) {
} else if (vInstance == 1.0) {
transform[2][0] = 0.5;
} else if (vInstance == 2.0) {
transform[2][1] = 0.5;
} else if (vInstance == 3.0) {
transform[2][0] = 0.5;
transform[2][1] = 0.5;
}
)"
: "")
<< R"(
gl_Position = vec4(transform * vec3(vPosition, 1.0) * 2.0 - 1.0, 1);
})";
return shader.str();
}
std::string FragmentShaderSource()
{
return
R"(precision mediump float;
varying vec4 color;
void main()
{
gl_FragColor = color;
})";
}
void SetupProgram()
{
mProgram = CompileProgram(VertexShaderSource().c_str(), FragmentShaderSource().c_str());
EXPECT_GL_NO_ERROR();
ASSERT_GE(mProgram, 1u);
glUseProgram(mProgram);
mPositionLoc = glGetAttribLocation(mProgram, "vPosition");
mInstanceLoc = glGetAttribLocation(mProgram, "vInstance");
}
void SetupBuffers()
{
for (uint32_t y = 0; y < kCountY; ++y)
{
for (uint32_t x = 0; x < kCountX; ++x)
{
// v3 ---- v2
// | |
// | |
// v0 ---- v1
uint32_t quadIndex = y * kCountX + x;
GLushort starting_index = static_cast<GLushort>(4 * quadIndex);
std::array<GLushort, 6> is = {0, 1, 2, 0, 2, 3};
const auto vs = getQuadVertices(x, y);
for (GLushort i : is)
{
mIndices.push_back(starting_index + i);
}
for (const auto &v : vs)
{
mVertices.insert(mVertices.end(), v.begin(), v.end());
}
for (GLushort i : is)
{
mNonIndexedVertices.insert(mNonIndexedVertices.end(), vs[i].begin(),
vs[i].end());
}
}
}
std::array<GLfloat, 4> instances{0, 1, 2, 3};
glGenBuffers(1, &mNonIndexedVertexBuffer);
glBindBuffer(GL_ARRAY_BUFFER, mNonIndexedVertexBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * mNonIndexedVertices.size(),
mNonIndexedVertices.data(), GL_STATIC_DRAW);
glGenBuffers(1, &mVertexBuffer);
glBindBuffer(GL_ARRAY_BUFFER, mVertexBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * mVertices.size(), mVertices.data(),
GL_STATIC_DRAW);
glGenBuffers(1, &mIndexBuffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLushort) * mIndices.size(), mIndices.data(),
GL_STATIC_DRAW);
glGenBuffers(1, &mInstanceBuffer);
glBindBuffer(GL_ARRAY_BUFFER, mInstanceBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * instances.size(), instances.data(),
GL_STATIC_DRAW);
ASSERT_GL_NO_ERROR();
}
void DoVertexAttribDivisor(GLint location, GLuint divisor)
{
if (getClientMajorVersion() <= 2)
{
ASSERT_TRUE(IsGLExtensionEnabled("GL_ANGLE_instanced_arrays"));
glVertexAttribDivisorANGLE(location, divisor);
}
else
{
glVertexAttribDivisor(location, divisor);
}
}
void DoDrawArrays()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glBindBuffer(GL_ARRAY_BUFFER, mNonIndexedVertexBuffer);
glEnableVertexAttribArray(mPositionLoc);
glVertexAttribPointer(mPositionLoc, 3, GL_FLOAT, GL_FALSE, 0, 0);
std::vector<GLint> firsts(kTriCount);
std::vector<GLsizei> counts(kTriCount, 3);
for (uint32_t i = 0; i < kTriCount; ++i)
firsts[i] = i * 3;
if (IsInstancedTest())
{
glBindBuffer(GL_ARRAY_BUFFER, mInstanceBuffer);
glEnableVertexAttribArray(mInstanceLoc);
glVertexAttribPointer(mInstanceLoc, 1, GL_FLOAT, GL_FALSE, 0, 0);
DoVertexAttribDivisor(mInstanceLoc, 1);
std::vector<GLsizei> instanceCounts(kTriCount, 4);
glMultiDrawArraysInstancedANGLE(GL_TRIANGLES, firsts.data(), counts.data(),
instanceCounts.data(), kTriCount);
}
else
{
glMultiDrawArraysANGLE(GL_TRIANGLES, firsts.data(), counts.data(), kTriCount);
}
}
void DoDrawElements()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBuffer);
glBindBuffer(GL_ARRAY_BUFFER, mVertexBuffer);
glEnableVertexAttribArray(mPositionLoc);
glVertexAttribPointer(mPositionLoc, 3, GL_FLOAT, GL_FALSE, 0, 0);
std::vector<GLsizei> counts(kTriCount, 3);
std::vector<const GLvoid *> indices(kTriCount);
for (uint32_t i = 0; i < kTriCount; ++i)
indices[i] = reinterpret_cast<GLvoid *>(static_cast<uintptr_t>(i * 3 * 2));
if (IsInstancedTest())
{
glBindBuffer(GL_ARRAY_BUFFER, mInstanceBuffer);
glEnableVertexAttribArray(mInstanceLoc);
glVertexAttribPointer(mInstanceLoc, 1, GL_FLOAT, GL_FALSE, 0, 0);
DoVertexAttribDivisor(mInstanceLoc, 1);
std::vector<GLsizei> instanceCounts(kTriCount, 4);
glMultiDrawElementsInstancedANGLE(GL_TRIANGLES, counts.data(), GL_UNSIGNED_SHORT,
indices.data(), instanceCounts.data(), kTriCount);
}
else
{
glMultiDrawElementsANGLE(GL_TRIANGLES, counts.data(), GL_UNSIGNED_SHORT, indices.data(),
kTriCount);
}
}
void CheckDrawResult()
{
for (uint32_t y = 0; y < kCountY; ++y)
{
for (uint32_t x = 0; x < kCountX; ++x)
{
uint32_t center_x = x * kTilePixelSize[0] + kTilePixelSize[0] / 2;
uint32_t center_y = y * kTilePixelSize[1] + kTilePixelSize[1] / 2;
uint32_t quadID = IsDrawIDTest() ? y * kCountX + x : 0;
uint32_t colorID = quadID % 3u;
std::array<GLColor, 3> colors = {GLColor(255, 0, 0, 255), GLColor(0, 255, 0, 255),
GLColor(0, 0, 255, 255)};
GLColor expected = colors[colorID];
if (IsInstancedTest())
{
EXPECT_PIXEL_RECT_EQ(center_x / 2 - kPixelCheckSize[0] / 4,
center_y / 2 - kPixelCheckSize[1] / 4,
kPixelCheckSize[0] / 2, kPixelCheckSize[1] / 2, expected);
EXPECT_PIXEL_RECT_EQ(center_x / 2 - kPixelCheckSize[0] / 4 + kWidth / 2,
center_y / 2 - kPixelCheckSize[1] / 4,
kPixelCheckSize[0] / 2, kPixelCheckSize[1] / 2, expected);
EXPECT_PIXEL_RECT_EQ(center_x / 2 - kPixelCheckSize[0] / 4,
center_y / 2 - kPixelCheckSize[1] / 4 + kHeight / 2,
kPixelCheckSize[0] / 2, kPixelCheckSize[1] / 2, expected);
EXPECT_PIXEL_RECT_EQ(center_x / 2 - kPixelCheckSize[0] / 4 + kWidth / 2,
center_y / 2 - kPixelCheckSize[1] / 4 + kHeight / 2,
kPixelCheckSize[0] / 2, kPixelCheckSize[1] / 2, expected);
}
else
{
EXPECT_PIXEL_RECT_EQ(center_x - kPixelCheckSize[0] / 2,
center_y - kPixelCheckSize[1] / 2, kPixelCheckSize[0],
kPixelCheckSize[1], expected);
}
}
}
}
void TearDown() override
{
if (mNonIndexedVertexBuffer != 0u)
{
glDeleteBuffers(1, &mNonIndexedVertexBuffer);
}
if (mVertexBuffer != 0u)
{
glDeleteBuffers(1, &mVertexBuffer);
}
if (mIndexBuffer != 0u)
{
glDeleteBuffers(1, &mIndexBuffer);
}
if (mInstanceBuffer != 0u)
{
glDeleteBuffers(1, &mInstanceBuffer);
}
if (mProgram != 0)
{
glDeleteProgram(mProgram);
}
ANGLETestBase::ANGLETestTearDown();
}
bool requestMultiDrawExtension()
{
if (IsGLExtensionRequestable("GL_ANGLE_multi_draw"))
{
glRequestExtensionANGLE("GL_ANGLE_multi_draw");
}
if (!IsGLExtensionEnabled("GL_ANGLE_multi_draw"))
{
return false;
}
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 (IsInstancedTest() && getClientMajorVersion() <= 2)
{
if (!requestInstancedExtension())
{
return false;
}
}
return requestMultiDrawExtension();
}
std::vector<GLushort> mIndices;
std::vector<GLfloat> mVertices;
std::vector<GLfloat> mNonIndexedVertices;
GLuint mNonIndexedVertexBuffer;
GLuint mVertexBuffer;
GLuint mIndexBuffer;
GLuint mInstanceBuffer;
GLuint mProgram;
GLint mPositionLoc;
GLint mInstanceLoc;
};
class MultiDrawNoInstancingSupportTest : public MultiDrawTest
{
void SetUp() override
{
ASSERT_LE(getClientMajorVersion(), 2);
ASSERT_TRUE(IsInstancedTest());
MultiDrawTest::SetUp();
}
};
// glMultiDraw*ANGLE are emulated and should always be available
TEST_P(MultiDrawTest, RequestExtension)
{
EXPECT_TRUE(requestMultiDrawExtension());
}
// Test that compile a program with the extension succeeds
TEST_P(MultiDrawTest, CanCompile)
{
ANGLE_SKIP_TEST_IF(!requestExtensions());
SetupProgram();
}
// Tests basic functionality of glMultiDrawArraysANGLE
TEST_P(MultiDrawTest, MultiDrawArrays)
{
ANGLE_SKIP_TEST_IF(!requestExtensions());
SetupBuffers();
SetupProgram();
DoDrawArrays();
EXPECT_GL_NO_ERROR();
CheckDrawResult();
}
// Tests basic functionality of glMultiDrawElementsANGLE
TEST_P(MultiDrawTest, MultiDrawElements)
{
ANGLE_SKIP_TEST_IF(!requestExtensions());
SetupBuffers();
SetupProgram();
DoDrawElements();
EXPECT_GL_NO_ERROR();
CheckDrawResult();
}
// Check that glMultiDraw*Instanced without instancing support results in GL_INVALID_OPERATION
TEST_P(MultiDrawNoInstancingSupportTest, InvalidOperation)
{
ANGLE_SKIP_TEST_IF(IsGLExtensionEnabled("GL_ANGLE_instanced_arrays"));
requestMultiDrawExtension();
SetupBuffers();
SetupProgram();
GLint first = 0;
GLsizei count = 3;
GLvoid *indices = 0;
GLsizei instances = 1;
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glBindBuffer(GL_ARRAY_BUFFER, mNonIndexedVertexBuffer);
glEnableVertexAttribArray(mPositionLoc);
glVertexAttribPointer(mPositionLoc, 3, GL_FLOAT, GL_FALSE, 0, 0);
glMultiDrawArraysInstancedANGLE(GL_TRIANGLES, &first, &count, &instances, 1);
EXPECT_GL_ERROR(GL_INVALID_OPERATION);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBuffer);
glBindBuffer(GL_ARRAY_BUFFER, mVertexBuffer);
glEnableVertexAttribArray(mPositionLoc);
glVertexAttribPointer(mPositionLoc, 3, GL_FLOAT, GL_FALSE, 0, 0);
glMultiDrawElementsInstancedANGLE(GL_TRIANGLES, &count, GL_UNSIGNED_SHORT, &indices, &instances,
1);
EXPECT_GL_ERROR(GL_INVALID_OPERATION);
}
const angle::PlatformParameters platforms[] = {
ES2_D3D9(), ES2_OPENGL(), ES2_OPENGLES(), ES2_VULKAN(),
ES3_D3D11(), ES3_OPENGL(), ES3_OPENGLES(),
};
const angle::PlatformParameters es2_platforms[] = {
ES2_D3D9(),
ES2_OPENGL(),
ES2_OPENGLES(),
ES2_VULKAN(),
};
INSTANTIATE_TEST_SUITE_P(
,
MultiDrawTest,
testing::Combine(testing::ValuesIn(::angle::FilterTestParams(platforms, ArraySize(platforms))),
testing::Values(DrawIDOption::NoDrawID, DrawIDOption::UseDrawID),
testing::Values(InstancingOption::NoInstancing,
InstancingOption::UseInstancing)),
PrintToStringParamName());
INSTANTIATE_TEST_SUITE_P(
,
MultiDrawNoInstancingSupportTest,
testing::Combine(testing::ValuesIn(::angle::FilterTestParams(es2_platforms,
ArraySize(es2_platforms))),
testing::Values(DrawIDOption::NoDrawID, DrawIDOption::UseDrawID),
testing::Values(InstancingOption::UseInstancing)),
PrintToStringParamName());
} // namespace