// // Copyright 2015 The ANGLE Project Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // #include "test_utils/ANGLETest.h" using namespace angle; class InstancingTest : public ANGLETest { protected: InstancingTest() : mProgram(0), mVertexBuffer(0) { setWindowWidth(256); setWindowHeight(256); setConfigRedBits(8); setConfigGreenBits(8); setConfigBlueBits(8); setConfigAlphaBits(8); } ~InstancingTest() override { glDeleteBuffers(1, &mVertexBuffer); glDeleteProgram(mProgram); } void SetUp() override { ANGLETest::SetUp(); mVertexAttribDivisorANGLE = NULL; mDrawArraysInstancedANGLE = NULL; mDrawElementsInstancedANGLE = NULL; char *extensionString = (char*)glGetString(GL_EXTENSIONS); if (strstr(extensionString, "GL_ANGLE_instanced_arrays")) { mVertexAttribDivisorANGLE = (PFNGLVERTEXATTRIBDIVISORANGLEPROC)eglGetProcAddress("glVertexAttribDivisorANGLE"); mDrawArraysInstancedANGLE = (PFNGLDRAWARRAYSINSTANCEDANGLEPROC)eglGetProcAddress("glDrawArraysInstancedANGLE"); mDrawElementsInstancedANGLE = (PFNGLDRAWELEMENTSINSTANCEDANGLEPROC)eglGetProcAddress("glDrawElementsInstancedANGLE"); } ASSERT_TRUE(mVertexAttribDivisorANGLE != NULL); ASSERT_TRUE(mDrawArraysInstancedANGLE != NULL); ASSERT_TRUE(mDrawElementsInstancedANGLE != NULL); // Initialize the vertex and index vectors GLfloat qvertex1[3] = {-quadRadius, quadRadius, 0.0f}; GLfloat qvertex2[3] = {-quadRadius, -quadRadius, 0.0f}; GLfloat qvertex3[3] = { quadRadius, -quadRadius, 0.0f}; GLfloat qvertex4[3] = { quadRadius, quadRadius, 0.0f}; mQuadVertices.insert(mQuadVertices.end(), qvertex1, qvertex1 + 3); mQuadVertices.insert(mQuadVertices.end(), qvertex2, qvertex2 + 3); mQuadVertices.insert(mQuadVertices.end(), qvertex3, qvertex3 + 3); mQuadVertices.insert(mQuadVertices.end(), qvertex4, qvertex4 + 3); GLfloat coord1[2] = {0.0f, 0.0f}; GLfloat coord2[2] = {0.0f, 1.0f}; GLfloat coord3[2] = {1.0f, 1.0f}; GLfloat coord4[2] = {1.0f, 0.0f}; mTexcoords.insert(mTexcoords.end(), coord1, coord1 + 2); mTexcoords.insert(mTexcoords.end(), coord2, coord2 + 2); mTexcoords.insert(mTexcoords.end(), coord3, coord3 + 2); mTexcoords.insert(mTexcoords.end(), coord4, coord4 + 2); mIndices.push_back(0); mIndices.push_back(1); mIndices.push_back(2); mIndices.push_back(0); mIndices.push_back(2); mIndices.push_back(3); for (size_t vertexIndex = 0; vertexIndex < 6; ++vertexIndex) { mNonIndexedVertices.insert(mNonIndexedVertices.end(), mQuadVertices.begin() + mIndices[vertexIndex] * 3, mQuadVertices.begin() + mIndices[vertexIndex] * 3 + 3); } for (size_t vertexIndex = 0; vertexIndex < 6; ++vertexIndex) { mNonIndexedVertices.insert(mNonIndexedVertices.end(), mQuadVertices.begin() + mIndices[vertexIndex] * 3, mQuadVertices.begin() + mIndices[vertexIndex] * 3 + 3); } // Tile a 2x2 grid of the tiles for (float y = -1.0f + quadRadius; y < 1.0f - quadRadius; y += quadRadius * 3) { for (float x = -1.0f + quadRadius; x < 1.0f - quadRadius; x += quadRadius * 3) { GLfloat instance[3] = {x + quadRadius, y + quadRadius, 0.0f}; mInstances.insert(mInstances.end(), instance, instance + 3); } } glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glGenBuffers(1, &mVertexBuffer); ASSERT_GL_NO_ERROR(); } void setupDrawArraysTest(const std::string &vs) { const std::string fs = SHADER_SOURCE ( precision mediump float; void main() { gl_FragColor = vec4(1.0, 0, 0, 1.0); } ); mProgram = CompileProgram(vs, fs); ASSERT_NE(0u, mProgram); // Set the viewport glViewport(0, 0, getWindowWidth(), getWindowHeight()); // Clear the color buffer glClear(GL_COLOR_BUFFER_BIT); // Use the program object glUseProgram(mProgram); } void setupInstancedPointsTest() { mIndices.clear(); mIndices.push_back(0); mIndices.push_back(1); mIndices.push_back(2); mIndices.push_back(3); // clang-format off const std::string vs = SHADER_SOURCE ( attribute vec3 a_position; attribute vec3 a_instancePos; void main() { gl_Position = vec4(a_position.xyz, 1.0); gl_Position = vec4(a_instancePos.xyz, 1.0); gl_PointSize = 6.0; } ); const std::string fs = SHADER_SOURCE ( precision mediump float; void main() { gl_FragColor = vec4(1.0, 0, 0, 1.0); } ); // clang-format on mProgram = CompileProgram(vs, fs); ASSERT_NE(0u, mProgram); // Set the viewport glViewport(0, 0, getWindowWidth(), getWindowHeight()); // Clear the color buffer glClear(GL_COLOR_BUFFER_BIT); // Use the program object glUseProgram(mProgram); } void runDrawArraysTest(GLint first, GLsizei count, GLsizei instanceCount, float *offset) { glBindBuffer(GL_ARRAY_BUFFER, mVertexBuffer); glBufferData(GL_ARRAY_BUFFER, mInstances.size() * sizeof(mInstances[0]), &mInstances[0], GL_STATIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, 0); // Get the attribute locations GLint positionLoc = glGetAttribLocation(mProgram, "a_position"); GLint instancePosLoc = glGetAttribLocation(mProgram, "a_instancePos"); // Load the vertex position glVertexAttribPointer(positionLoc, 3, GL_FLOAT, GL_FALSE, 0, mNonIndexedVertices.data()); glEnableVertexAttribArray(positionLoc); // Load the instance position glBindBuffer(GL_ARRAY_BUFFER, mVertexBuffer); glVertexAttribPointer(instancePosLoc, 3, GL_FLOAT, GL_FALSE, 0, 0); glBindBuffer(GL_ARRAY_BUFFER, 0); glEnableVertexAttribArray(instancePosLoc); // Enable instancing mVertexAttribDivisorANGLE(instancePosLoc, 1); // Offset GLint uniformLoc = glGetUniformLocation(mProgram, "u_offset"); ASSERT_NE(uniformLoc, -1); glUniform3fv(uniformLoc, 1, offset); // Do the instanced draw mDrawArraysInstancedANGLE(GL_TRIANGLES, first, count, instanceCount); ASSERT_GL_NO_ERROR(); } virtual void runDrawElementsTest(std::string vs, bool shouldAttribZeroBeInstanced) { const std::string fs = SHADER_SOURCE ( precision mediump float; void main() { gl_FragColor = vec4(1.0, 0, 0, 1.0); } ); GLuint program = CompileProgram(vs, fs); ASSERT_NE(program, 0u); // Get the attribute locations GLint positionLoc = glGetAttribLocation(program, "a_position"); GLint instancePosLoc = glGetAttribLocation(program, "a_instancePos"); // If this ASSERT fails then the vertex shader code should be refactored ASSERT_EQ(shouldAttribZeroBeInstanced, (instancePosLoc == 0)); // Set the viewport glViewport(0, 0, getWindowWidth(), getWindowHeight()); // Clear the color buffer glClear(GL_COLOR_BUFFER_BIT); // Use the program object glUseProgram(program); // Load the vertex position glVertexAttribPointer(positionLoc, 3, GL_FLOAT, GL_FALSE, 0, mQuadVertices.data()); glEnableVertexAttribArray(positionLoc); // Load the instance position glVertexAttribPointer(instancePosLoc, 3, GL_FLOAT, GL_FALSE, 0, mInstances.data()); glEnableVertexAttribArray(instancePosLoc); // Enable instancing mVertexAttribDivisorANGLE(instancePosLoc, 1); // Do the instanced draw mDrawElementsInstancedANGLE(GL_TRIANGLES, static_cast(mIndices.size()), GL_UNSIGNED_SHORT, mIndices.data(), static_cast(mInstances.size()) / 3); ASSERT_GL_NO_ERROR(); checkQuads(); } void checkQuads() { // Check that various pixels are the expected color. for (unsigned int quadIndex = 0; quadIndex < 4; ++quadIndex) { unsigned int baseOffset = quadIndex * 3; int quadx = static_cast(((mInstances[baseOffset + 0]) * 0.5f + 0.5f) * getWindowWidth()); int quady = static_cast(((mInstances[baseOffset + 1]) * 0.5f + 0.5f) * getWindowHeight()); EXPECT_PIXEL_EQ(quadx, quady, 255, 0, 0, 255); } } // Loaded entry points PFNGLVERTEXATTRIBDIVISORANGLEPROC mVertexAttribDivisorANGLE; PFNGLDRAWARRAYSINSTANCEDANGLEPROC mDrawArraysInstancedANGLE; PFNGLDRAWELEMENTSINSTANCEDANGLEPROC mDrawElementsInstancedANGLE; // Vertex data std::vector mQuadVertices; std::vector mNonIndexedVertices; std::vector mTexcoords; std::vector mInstances; std::vector mIndices; const GLfloat quadRadius = 0.30f; GLuint mProgram; GLuint mVertexBuffer; }; class InstancingTestAllConfigs : public InstancingTest { protected: InstancingTestAllConfigs() {} }; class InstancingTestNo9_3 : public InstancingTest { protected: InstancingTestNo9_3() {} }; class InstancingTestPoints : public InstancingTest { protected: InstancingTestPoints() {} }; // This test uses a vertex shader with the first attribute (attribute zero) instanced. // On D3D9 and D3D11 FL9_3, this triggers a special codepath that rearranges the input layout sent to D3D, // to ensure that slot/stream zero of the input layout doesn't contain per-instance data. TEST_P(InstancingTestAllConfigs, AttributeZeroInstanced) { const std::string vs = SHADER_SOURCE ( attribute vec3 a_instancePos; attribute vec3 a_position; void main() { gl_Position = vec4(a_position.xyz + a_instancePos.xyz, 1.0); } ); runDrawElementsTest(vs, true); } // Same as AttributeZeroInstanced, but attribute zero is not instanced. // This ensures the general instancing codepath (i.e. without rearranging the input layout) works as expected. TEST_P(InstancingTestAllConfigs, AttributeZeroNotInstanced) { const std::string vs = SHADER_SOURCE ( attribute vec3 a_position; attribute vec3 a_instancePos; void main() { gl_Position = vec4(a_position.xyz + a_instancePos.xyz, 1.0); } ); runDrawElementsTest(vs, false); } // Tests that the "first" parameter to glDrawArraysInstancedANGLE is only an offset into // the non-instanced vertex attributes. TEST_P(InstancingTestNo9_3, DrawArraysWithOffset) { const std::string vs = SHADER_SOURCE ( attribute vec3 a_position; attribute vec3 a_instancePos; uniform vec3 u_offset; void main() { gl_Position = vec4(a_position.xyz + a_instancePos.xyz + u_offset, 1.0); } ); setupDrawArraysTest(vs); float offset1[3] = { 0, 0, 0 }; runDrawArraysTest(0, 6, 2, offset1); float offset2[3] = { 0.0f, 1.0f, 0 }; runDrawArraysTest(6, 6, 2, offset2); checkQuads(); } // This test verifies instancing with GL_POINTS with glDrawArraysInstanced works. // On D3D11 FL9_3, this triggers a special codepath that emulates instanced points rendering. TEST_P(InstancingTestPoints, DrawArrays) { // Disable D3D11 SDK Layers warnings checks, see ANGLE issue 667 for details // On Win7, the D3D SDK Layers emits a false warning for these tests. // This doesn't occur on Windows 10 (Version 1511) though. ignoreD3D11SDKLayersWarnings(); setupInstancedPointsTest(); glBindBuffer(GL_ARRAY_BUFFER, mVertexBuffer); glBufferData(GL_ARRAY_BUFFER, mInstances.size() * sizeof(mInstances[0]), &mInstances[0], GL_STATIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, 0); // Get the attribute locations GLint positionLoc = glGetAttribLocation(mProgram, "a_position"); GLint instancePosLoc = glGetAttribLocation(mProgram, "a_instancePos"); // Load the vertex position GLfloat pos[3] = {0, 0, 0}; glVertexAttribPointer(positionLoc, 3, GL_FLOAT, GL_FALSE, 0, pos); glEnableVertexAttribArray(positionLoc); // Load the instance position glBindBuffer(GL_ARRAY_BUFFER, mVertexBuffer); glVertexAttribPointer(instancePosLoc, 3, GL_FLOAT, GL_FALSE, 0, 0); glBindBuffer(GL_ARRAY_BUFFER, 0); glEnableVertexAttribArray(instancePosLoc); // Enable instancing mVertexAttribDivisorANGLE(instancePosLoc, 1); // Do the instanced draw mDrawArraysInstancedANGLE(GL_POINTS, 0, 1, static_cast(mInstances.size()) / 3); ASSERT_GL_NO_ERROR(); checkQuads(); } // This test verifies instancing with GL_POINTS with glDrawElementsInstanced works. // On D3D11 FL9_3, this triggers a special codepath that emulates instanced points rendering. TEST_P(InstancingTestPoints, DrawElements) { // Disable D3D11 SDK Layers warnings checks, see ANGLE issue 667 for details // On Win7, the D3D SDK Layers emits a false warning for these tests. // This doesn't occur on Windows 10 (Version 1511) though. ignoreD3D11SDKLayersWarnings(); setupInstancedPointsTest(); glBindBuffer(GL_ARRAY_BUFFER, mVertexBuffer); glBufferData(GL_ARRAY_BUFFER, mInstances.size() * sizeof(mInstances[0]), &mInstances[0], GL_STATIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, 0); // Get the attribute locations GLint positionLoc = glGetAttribLocation(mProgram, "a_position"); GLint instancePosLoc = glGetAttribLocation(mProgram, "a_instancePos"); // Load the vertex position GLfloat pos[3] = {0, 0, 0}; glVertexAttribPointer(positionLoc, 3, GL_FLOAT, GL_FALSE, 0, pos); glEnableVertexAttribArray(positionLoc); // Load the instance position glBindBuffer(GL_ARRAY_BUFFER, mVertexBuffer); glVertexAttribPointer(instancePosLoc, 3, GL_FLOAT, GL_FALSE, 0, 0); glBindBuffer(GL_ARRAY_BUFFER, 0); glEnableVertexAttribArray(instancePosLoc); // Enable instancing mVertexAttribDivisorANGLE(instancePosLoc, 1); // Do the instanced draw mDrawElementsInstancedANGLE(GL_POINTS, static_cast(mIndices.size()), GL_UNSIGNED_SHORT, mIndices.data(), static_cast(mInstances.size()) / 3); ASSERT_GL_NO_ERROR(); checkQuads(); } // Use this to select which configurations (e.g. which renderer, which GLES major version) these tests should be run against. // We test on D3D9 and D3D11 9_3 because they use special codepaths when attribute zero is instanced, unlike D3D11. ANGLE_INSTANTIATE_TEST(InstancingTestAllConfigs, ES2_D3D9(), ES2_D3D11(), ES2_D3D11_FL9_3(), ES2_OPENGL(), ES2_OPENGLES()); // TODO(jmadill): Figure out the situation with DrawInstanced on FL 9_3 ANGLE_INSTANTIATE_TEST(InstancingTestNo9_3, ES2_D3D9(), ES2_D3D11()); ANGLE_INSTANTIATE_TEST(InstancingTestPoints, ES2_D3D11(), ES2_D3D11_FL9_3());