| // | |
| // 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 "ANGLEPerfTest.h" | |
| #include "common/unsafe_buffers.h" | |
| #include <array> | |
| #include <cmath> | |
| #include <sstream> | |
| #include "util/Matrix.h" | |
| #include "util/random_utils.h" | |
| #include "util/shader_utils.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 <typename T> | |
| size_t VectorSizeBytes(const std::vector<T> &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; | |
| iterationsPerStep = 1; | |
| runTimeSeconds = 10.0; | |
| animationEnabled = false; | |
| instancingEnabled = true; | |
| } | |
| std::string story() const override | |
| { | |
| std::stringstream strstr; | |
| strstr << RenderTestParams::story(); | |
| if (!instancingEnabled) | |
| { | |
| strstr << "_billboards"; | |
| } | |
| return strstr.str(); | |
| } | |
| double runTimeSeconds; | |
| bool animationEnabled; | |
| bool instancingEnabled; | |
| }; | |
| std::ostream &operator<<(std::ostream &os, const InstancingPerfParams ¶ms) | |
| { | |
| os << params.backendAndStory().substr(1); | |
| return os; | |
| } | |
| class InstancingPerfBenchmark : public ANGLERenderTest, | |
| public ::testing::WithParamInterface<InstancingPerfParams> | |
| { | |
| public: | |
| InstancingPerfBenchmark(); | |
| void initializeBenchmark() override; | |
| void destroyBenchmark() override; | |
| void drawBenchmark() override; | |
| private: | |
| GLuint mProgram; | |
| std::vector<GLuint> mBuffers; | |
| GLuint mNumPoints; | |
| std::vector<Vector3> mTranslateData; | |
| std::vector<float> mSizeData; | |
| std::vector<Vector3> mColorData; | |
| angle::RNG mRNG; | |
| }; | |
| InstancingPerfBenchmark::InstancingPerfBenchmark() | |
| : ANGLERenderTest("InstancingPerf", GetParam()), mProgram(0), mNumPoints(75000) | |
| {} | |
| void InstancingPerfBenchmark::initializeBenchmark() | |
| { | |
| const auto ¶ms = GetParam(); | |
| const char kVS[] = | |
| "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"; | |
| constexpr char kFS[] = | |
| "precision mediump float;\n" | |
| "varying vec3 vColor;\n" | |
| "void main()\n" | |
| "{\n" | |
| " gl_FragColor = vec4(vColor, 1.0);\n" | |
| "}\n"; | |
| mProgram = CompileProgram(kVS, kFS); | |
| ASSERT_NE(0u, mProgram); | |
| glUseProgram(mProgram); | |
| glClearColor(0.0f, 0.0f, 0.0f, 0.0f); | |
| static constexpr std::array<GLuint, 6> baseIndexData = {0, 1, 2, 1, 3, 2}; | |
| std::array<Vector2, 4> basePositionData = {Vector2(-1.0f, 1.0f), Vector2(1.0f, 1.0f), | |
| Vector2(-1.0f, -1.0f), Vector2(1.0f, -1.0f)}; | |
| std::vector<GLuint> indexData; | |
| std::vector<Vector2> 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<GLsizei>(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<float>(getWindow()->getWidth()) / static_cast<float>(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<GLsizei>(mBuffers.size()), &mBuffers[0]); | |
| mBuffers.clear(); | |
| } | |
| } | |
| void InstancingPerfBenchmark::drawBenchmark() | |
| { | |
| glClear(GL_COLOR_BUFFER_BIT); | |
| const auto ¶ms = GetParam(); | |
| // Animation makes the test more interesting visually, but also eats up many CPU cycles. | |
| if (params.animationEnabled) | |
| { | |
| float time = static_cast<float>(mTrialTimer.getElapsedWallClockTime()); | |
| 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 = | |
| Vector3(AnimationSignal(tx), AnimationSignal(ty), AnimationSignal(tz)) * 0.5f + | |
| Vector3(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.iterationsPerStep; it++) | |
| { | |
| glDrawElementsInstancedANGLE(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr, mNumPoints); | |
| } | |
| } | |
| else | |
| { | |
| for (unsigned int it = 0; it < params.iterationsPerStep; it++) | |
| { | |
| glDrawElements(GL_TRIANGLES, 6 * mNumPoints, GL_UNSIGNED_INT, nullptr); | |
| } | |
| } | |
| ASSERT_GL_NO_ERROR(); | |
| } | |
| InstancingPerfParams InstancingPerfD3D11Params() | |
| { | |
| InstancingPerfParams params; | |
| params.eglParameters = D3D11(); | |
| return params; | |
| } | |
| InstancingPerfParams InstancingPerfMetalParams() | |
| { | |
| InstancingPerfParams params; | |
| params.eglParameters = METAL(); | |
| return params; | |
| } | |
| InstancingPerfParams InstancingPerfOpenGLOrGLESParams() | |
| { | |
| InstancingPerfParams params; | |
| params.eglParameters = OPENGL_OR_GLES(); | |
| return params; | |
| } | |
| TEST_P(InstancingPerfBenchmark, Run) | |
| { | |
| run(); | |
| } | |
| ANGLE_INSTANTIATE_TEST(InstancingPerfBenchmark, | |
| InstancingPerfD3D11Params(), | |
| InstancingPerfMetalParams(), | |
| InstancingPerfOpenGLOrGLESParams()); | |
| } // anonymous namespace |