// // 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. // // InstancingPerf: // Performance tests for ANGLE instanced draw calls. // #include #include #include "ANGLEPerfTest.h" #include "Matrix.h" #include "random_utils.h" #include "shader_utils.h" #include "Vector.h" using namespace angle; using namespace egl_platform; namespace { float AnimationSignal(float t) { float l = t / 2.0f; float f = l - std::floor(l); return (f > 0.5f ? 1.0f - f : f) * 4.0f - 1.0f; } template size_t VectorSizeBytes(const std::vector &vec) { return sizeof(T) * vec.size(); } Vector3 RandomVector3(RNG *rng) { return Vector3(rng->randomNegativeOneToOne(), rng->randomNegativeOneToOne(), rng->randomNegativeOneToOne()); } struct InstancingPerfParams final : public RenderTestParams { // Common default options InstancingPerfParams() { majorVersion = 2; minorVersion = 0; windowWidth = 256; windowHeight = 256; iterations = 1; runTimeSeconds = 10.0; animationEnabled = false; instancingEnabled = true; } std::string suffix() const override { std::stringstream strstr; strstr << RenderTestParams::suffix(); if (!instancingEnabled) { strstr << "_billboards"; } return strstr.str(); } unsigned int iterations; double runTimeSeconds; bool animationEnabled; bool instancingEnabled; }; std::ostream &operator<<(std::ostream &os, const InstancingPerfParams ¶ms) { os << params.suffix().substr(1); return os; } class InstancingPerfBenchmark : public ANGLERenderTest, public ::testing::WithParamInterface { public: InstancingPerfBenchmark(); void initializeBenchmark() override; void destroyBenchmark() override; void drawBenchmark() override; private: GLuint mProgram; std::vector mBuffers; GLuint mNumPoints; std::vector mTranslateData; std::vector mSizeData; std::vector mColorData; angle::RNG mRNG; }; InstancingPerfBenchmark::InstancingPerfBenchmark() : ANGLERenderTest("InstancingPerf", GetParam()), mProgram(0), mNumPoints(75000) { mRunTimeSeconds = GetParam().runTimeSeconds; } void InstancingPerfBenchmark::initializeBenchmark() { const auto ¶ms = GetParam(); ASSERT_LT(0u, params.iterations); const std::string vs = "attribute vec2 aPosition;\n" "attribute vec3 aTranslate;\n" "attribute float aScale;\n" "attribute vec3 aColor;\n" "uniform mat4 uWorldMatrix;\n" "uniform mat4 uProjectionMatrix;\n" "varying vec3 vColor;\n" "void main()\n" "{\n" " vec4 position = uWorldMatrix * vec4(aTranslate, 1.0);\n" " position.xy += aPosition * aScale;\n" " gl_Position = uProjectionMatrix * position;\n" " vColor = aColor;\n" "}\n"; const std::string fs = "precision mediump float;\n" "varying vec3 vColor;\n" "void main()\n" "{\n" " gl_FragColor = vec4(vColor, 1.0);\n" "}\n"; mProgram = CompileProgram(vs, fs); ASSERT_NE(0u, mProgram); glUseProgram(mProgram); glClearColor(0.0f, 0.0f, 0.0f, 0.0f); GLuint baseIndexData[6] = {0, 1, 2, 1, 3, 2}; Vector2 basePositionData[4] = {Vector2(-1.0f, 1.0f), Vector2(1.0f, 1.0f), Vector2(-1.0f, -1.0f), Vector2(1.0f, -1.0f)}; std::vector indexData; std::vector positionData; if (!params.instancingEnabled) { GLuint pointVertexStride = 4; for (GLuint pointIndex = 0; pointIndex < mNumPoints; ++pointIndex) { for (GLuint indexIndex = 0; indexIndex < 6; ++indexIndex) { indexData.push_back(baseIndexData[indexIndex] + pointIndex * pointVertexStride); } Vector3 randVec = RandomVector3(&mRNG); for (GLuint vertexIndex = 0; vertexIndex < 4; ++vertexIndex) { positionData.push_back(basePositionData[vertexIndex]); mTranslateData.push_back(randVec); } } mSizeData.resize(mNumPoints * 4, 0.012f); mColorData.resize(mNumPoints * 4, Vector3(1.0f, 0.0f, 0.0f)); } else { for (GLuint index : baseIndexData) { indexData.push_back(index); } for (const Vector2 &position : basePositionData) { positionData.push_back(position); } for (GLuint pointIndex = 0; pointIndex < mNumPoints; ++pointIndex) { Vector3 randVec = RandomVector3(&mRNG); mTranslateData.push_back(randVec); } mSizeData.resize(mNumPoints, 0.012f); mColorData.resize(mNumPoints, Vector3(1.0f, 0.0f, 0.0f)); } mBuffers.resize(5, 0); glGenBuffers(static_cast(mBuffers.size()), &mBuffers[0]); // Index Data glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mBuffers[0]); glBufferData(GL_ELEMENT_ARRAY_BUFFER, VectorSizeBytes(indexData), &indexData[0], GL_STATIC_DRAW); // Position Data glBindBuffer(GL_ARRAY_BUFFER, mBuffers[1]); glBufferData(GL_ARRAY_BUFFER, VectorSizeBytes(positionData), &positionData[0], GL_STATIC_DRAW); GLint positionLocation = glGetAttribLocation(mProgram, "aPosition"); ASSERT_NE(-1, positionLocation); glVertexAttribPointer(positionLocation, 2, GL_FLOAT, GL_FALSE, 8, nullptr); glEnableVertexAttribArray(positionLocation); // Translate Data glBindBuffer(GL_ARRAY_BUFFER, mBuffers[2]); glBufferData(GL_ARRAY_BUFFER, VectorSizeBytes(mTranslateData), &mTranslateData[0], GL_STATIC_DRAW); GLint translateLocation = glGetAttribLocation(mProgram, "aTranslate"); ASSERT_NE(-1, translateLocation); glVertexAttribPointer(translateLocation, 3, GL_FLOAT, GL_FALSE, 12, nullptr); glEnableVertexAttribArray(translateLocation); glVertexAttribDivisorANGLE(translateLocation, 1); // Scale Data glBindBuffer(GL_ARRAY_BUFFER, mBuffers[3]); glBufferData(GL_ARRAY_BUFFER, VectorSizeBytes(mSizeData), nullptr, GL_DYNAMIC_DRAW); GLint scaleLocation = glGetAttribLocation(mProgram, "aScale"); ASSERT_NE(-1, scaleLocation); glVertexAttribPointer(scaleLocation, 1, GL_FLOAT, GL_FALSE, 4, nullptr); glEnableVertexAttribArray(scaleLocation); glVertexAttribDivisorANGLE(scaleLocation, 1); // Color Data glBindBuffer(GL_ARRAY_BUFFER, mBuffers[4]); glBufferData(GL_ARRAY_BUFFER, VectorSizeBytes(mColorData), nullptr, GL_DYNAMIC_DRAW); GLint colorLocation = glGetAttribLocation(mProgram, "aColor"); ASSERT_NE(-1, colorLocation); glVertexAttribPointer(colorLocation, 3, GL_FLOAT, GL_FALSE, 12, nullptr); glEnableVertexAttribArray(colorLocation); glVertexAttribDivisorANGLE(colorLocation, 1); // Set the viewport glViewport(0, 0, getWindow()->getWidth(), getWindow()->getHeight()); // Init matrices GLint worldMatrixLocation = glGetUniformLocation(mProgram, "uWorldMatrix"); ASSERT_NE(-1, worldMatrixLocation); Matrix4 worldMatrix = Matrix4::translate(Vector3(0, 0, -3.0f)); worldMatrix *= Matrix4::rotate(25.0f, Vector3(0.6f, 1.0f, 0.0f)); glUniformMatrix4fv(worldMatrixLocation, 1, GL_FALSE, &worldMatrix.data[0]); GLint projectionMatrixLocation = glGetUniformLocation(mProgram, "uProjectionMatrix"); ASSERT_NE(-1, projectionMatrixLocation); float fov = static_cast(getWindow()->getWidth()) / static_cast(getWindow()->getHeight()); Matrix4 projectionMatrix = Matrix4::perspective(60.0f, fov, 1.0f, 300.0f); glUniformMatrix4fv(projectionMatrixLocation, 1, GL_FALSE, &projectionMatrix.data[0]); getWindow()->setVisible(true); ASSERT_GL_NO_ERROR(); } void InstancingPerfBenchmark::destroyBenchmark() { glDeleteProgram(mProgram); if (!mBuffers.empty()) { glDeleteBuffers(static_cast(mBuffers.size()), &mBuffers[0]); mBuffers.clear(); } } void InstancingPerfBenchmark::drawBenchmark() { glClear(GL_COLOR_BUFFER_BIT); const auto ¶ms = GetParam(); // Animatino makes the test more interesting visually, but also eats up many CPU cycles. if (params.animationEnabled) { // Not implemented for billboards. ASSERT(params.instancingEnabled); float time = static_cast(mTimer->getElapsedTime()); for (size_t pointIndex = 0; pointIndex < mTranslateData.size(); ++pointIndex) { const Vector3 &translate = mTranslateData[pointIndex]; float tx = translate.x + time; float ty = translate.y + time; float tz = translate.z + time; float scale = AnimationSignal(tx) * 0.01f + 0.01f; mSizeData[pointIndex] = scale; Vector3 color; color.x = AnimationSignal(tx) * 0.5f + 0.5f; color.y = AnimationSignal(ty) * 0.5f + 0.5f; color.z = AnimationSignal(tz) * 0.5f + 0.5f; mColorData[pointIndex] = color; } } // Update scales and colors. glBindBuffer(GL_ARRAY_BUFFER, mBuffers[3]); glBufferSubData(GL_ARRAY_BUFFER, 0, VectorSizeBytes(mSizeData), &mSizeData[0]); glBindBuffer(GL_ARRAY_BUFFER, mBuffers[4]); glBufferSubData(GL_ARRAY_BUFFER, 0, VectorSizeBytes(mColorData), &mColorData[0]); // Render the instances/billboards. if (params.instancingEnabled) { for (unsigned int it = 0; it < params.iterations; it++) { glDrawElementsInstancedANGLE(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr, mNumPoints); } } else { for (unsigned int it = 0; it < params.iterations; it++) { glDrawElements(GL_TRIANGLES, 6 * mNumPoints, GL_UNSIGNED_INT, nullptr); } } ASSERT_GL_NO_ERROR(); } InstancingPerfParams InstancingPerfD3D11Params() { InstancingPerfParams params; params.eglParameters = D3D11(); return params; } InstancingPerfParams InstancingPerfD3D9Params() { InstancingPerfParams params; params.eglParameters = D3D9(); return params; } InstancingPerfParams InstancingPerfOpenGLParams() { InstancingPerfParams params; params.eglParameters = OPENGL(); return params; } TEST_P(InstancingPerfBenchmark, Run) { run(); } ANGLE_INSTANTIATE_TEST(InstancingPerfBenchmark, InstancingPerfD3D11Params(), InstancingPerfD3D9Params(), InstancingPerfOpenGLParams()); } // anonymous namespace