// // Copyright 2017 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. // // MultiviewPerfTest: // Performance tests for multiview rendering. // - MultiviewCPUBoundBenchmark issues many draw calls and state changes to stress the CPU. // - MultiviewGPUBoundBenchmark draws half a million quads with multiple attributes per vertex in // order to stress the GPU's memory system. // #include "ANGLEPerfTest.h" #include "common/vector_utils.h" #include "platform/FeaturesD3D.h" #include "test_utils/MultiviewTest.h" #include "test_utils/gl_raii.h" #include "util/shader_utils.h" #include using namespace angle; namespace { std::string GetShaderExtensionHeader(bool usesMultiview, int numViews, GLenum shaderType, ExtensionName multiviewExtension) { if (!usesMultiview) { return ""; } std::string ext; switch (multiviewExtension) { case multiview: ext = "GL_OVR_multiview"; break; case multiview2: ext = "GL_OVR_multiview2"; break; default: ext = "extension_error"; } if (shaderType == GL_VERTEX_SHADER) { return "#extension " + ext + " : require\nlayout(num_views = " + ToString(numViews) + ") in;\n"; } return "#extension " + ext + " : require\n"; } struct Vertex { Vector4 position; Vector4 colorAttributeData[6]; }; enum class MultiviewOption { NoAcceleration, InstancedMultiviewVertexShader, InstancedMultiviewGeometryShader, Unspecified }; using MultiviewPerfWorkload = std::pair; struct MultiviewPerfParams final : public RenderTestParams { MultiviewPerfParams(const EGLPlatformParameters &platformParametersIn, const MultiviewPerfWorkload &workloadIn, MultiviewOption multiviewOptionIn, ExtensionName multiviewExtensionIn) { iterationsPerStep = 1; majorVersion = 3; minorVersion = 0; eglParameters = platformParametersIn; windowWidth = workloadIn.first; windowHeight = workloadIn.second; multiviewOption = multiviewOptionIn; numViews = 2; multiviewExtension = multiviewExtensionIn; } std::string story() const override { std::string name = RenderTestParams::story(); switch (multiviewOption) { case MultiviewOption::NoAcceleration: name += "_no_acc"; break; case MultiviewOption::InstancedMultiviewVertexShader: name += "_instanced_multiview_vertex_shader"; break; case MultiviewOption::InstancedMultiviewGeometryShader: name += "_instanced_multiview_geometry_shader"; break; default: name += "_error"; break; } std::string ext; switch (multiviewExtension) { case multiview: ext = "GL_OVR_multiview"; break; case multiview2: ext = "GL_OVR_multiview2"; break; default: ext = "extension_error"; break; } name += "_" + ext; name += "_" + ToString(numViews) + "_views"; return name; } MultiviewOption multiviewOption; int numViews; angle::ExtensionName multiviewExtension; }; std::ostream &operator<<(std::ostream &os, const MultiviewPerfParams ¶ms) { os << params.backendAndStory().substr(1); return os; } class MultiviewBenchmark : public ANGLERenderTest, public ::testing::WithParamInterface { public: MultiviewBenchmark(const std::string &testName) : ANGLERenderTest(testName, GetParam()), mProgram(0) { switch (GetParam().multiviewExtension) { case multiview: addExtensionPrerequisite("GL_OVR_multiview"); break; case multiview2: addExtensionPrerequisite("GL_OVR_multiview2"); break; default: // Unknown extension. break; } } virtual ~MultiviewBenchmark() { if (mProgram != 0) { glDeleteProgram(mProgram); } } void initializeBenchmark() override; void drawBenchmark() final; void overrideWorkaroundsD3D(FeaturesD3D *features) override { features->overrideFeatures( {"select_view_in_geometry_shader"}, GetParam().multiviewOption == MultiviewOption::InstancedMultiviewGeometryShader); } protected: virtual void renderScene() = 0; void createProgram(const char *vs, const char *fs) { mProgram = CompileProgram(vs, fs); if (mProgram == 0) { FAIL() << "shader compilation failed."; } glUseProgram(mProgram); ASSERT_GL_NO_ERROR(); } GLuint mProgram; GLVertexArray mVAO; GLBuffer mVBO; private: GLFramebuffer mFramebuffer; GLTexture mColorTexture; GLTexture mDepthTexture; }; class MultiviewCPUBoundBenchmark : public MultiviewBenchmark { public: MultiviewCPUBoundBenchmark() : MultiviewBenchmark("MultiviewCPUBoundBenchmark") {} void initializeBenchmark() override; protected: void renderScene() override; }; class MultiviewGPUBoundBenchmark : public MultiviewBenchmark { public: MultiviewGPUBoundBenchmark() : MultiviewBenchmark("MultiviewGPUBoundBenchmark") {} void initializeBenchmark() override; protected: void renderScene() override; }; void MultiviewBenchmark::initializeBenchmark() { const MultiviewPerfParams *params = static_cast(&mTestParams); switch (params->multiviewOption) { case MultiviewOption::NoAcceleration: // No acceleration texture arrays glBindTexture(GL_TEXTURE_2D, mColorTexture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, params->windowWidth, params->windowHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); glBindTexture(GL_TEXTURE_2D, mDepthTexture); glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, params->windowWidth, params->windowHeight, 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr); glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mColorTexture, 0); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, mDepthTexture, 0); break; case MultiviewOption::InstancedMultiviewVertexShader: case MultiviewOption::InstancedMultiviewGeometryShader: { // Multiview texture arrays glBindTexture(GL_TEXTURE_2D_ARRAY, mColorTexture); glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, params->windowWidth, params->windowHeight, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); glBindTexture(GL_TEXTURE_2D_ARRAY, mDepthTexture); glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_DEPTH_COMPONENT32F, params->windowWidth, params->windowHeight, 2, 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr); glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer); glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, mColorTexture, 0, 0, params->numViews); glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, mDepthTexture, 0, 0, params->numViews); break; } case MultiviewOption::Unspecified: // implementation error. break; } GLenum DrawBuffers[1] = {GL_COLOR_ATTACHMENT0}; glDrawBuffers(1, DrawBuffers); ASSERT_GL_NO_ERROR(); } void MultiviewBenchmark::drawBenchmark() { const MultiviewPerfParams *params = static_cast(&mTestParams); const int viewWidth = params->windowWidth / params->numViews; const int viewHeight = params->windowHeight; switch (params->multiviewOption) { case MultiviewOption::NoAcceleration: glEnable(GL_SCISSOR_TEST); // Iterate over each view and render the scene. for (int i = 0; i < params->numViews; ++i) { glViewport(viewWidth * i, 0, viewWidth, viewHeight); glScissor(viewWidth * i, 0, viewWidth, viewHeight); renderScene(); } break; case MultiviewOption::InstancedMultiviewVertexShader: case MultiviewOption::InstancedMultiviewGeometryShader: glViewport(0, 0, viewWidth, viewHeight); glScissor(0, 0, viewWidth, viewHeight); renderScene(); break; case MultiviewOption::Unspecified: // implementation error. break; } ASSERT_GL_NO_ERROR(); } void MultiviewCPUBoundBenchmark::initializeBenchmark() { MultiviewBenchmark::initializeBenchmark(); const MultiviewPerfParams *params = static_cast(&mTestParams); const bool usesMultiview = (params->multiviewOption != MultiviewOption::NoAcceleration); const std::string vs = "#version 300 es\n" + GetShaderExtensionHeader(usesMultiview, params->numViews, GL_VERTEX_SHADER, params->multiviewExtension) + "layout(location=0) in vec4 vPosition;\n" "uniform vec2 uOffset;\n" "void main()\n" "{\n" " vec4 v = vPosition;\n" " v.xy += uOffset;\n" " gl_Position = v;\n" "}\n"; const std::string fs = "#version 300 es\n" + GetShaderExtensionHeader(usesMultiview, params->numViews, GL_FRAGMENT_SHADER, params->multiviewExtension) + "precision mediump float;\n" "out vec4 col;\n" "uniform float uColor;\n" "void main()\n" "{\n" " col = vec4(1.);\n" "}\n"; createProgram(vs.c_str(), fs.c_str()); const float viewWidth = static_cast(params->windowWidth / params->numViews); const float viewHeight = static_cast(params->windowHeight); const float quadWidth = 2.f / viewWidth; const float quadHeight = 2.f / viewHeight; Vector4 vertices[6] = {Vector4(.0f, .0f, .0f, 1.f), Vector4(quadWidth, .0f, .0f, 1.f), Vector4(quadWidth, quadHeight, 0.f, 1.f), Vector4(.0f, .0f, 0.f, 1.f), Vector4(quadWidth, quadHeight, .0f, 1.f), Vector4(.0f, quadHeight, .0f, 1.f)}; glBindVertexArray(mVAO); glBindBuffer(GL_ARRAY_BUFFER, mVBO); glBufferData(GL_ARRAY_BUFFER, 6 * sizeof(Vector4), vertices, GL_STATIC_DRAW); const GLint posLoc = glGetAttribLocation(mProgram, "vPosition"); glVertexAttribPointer(posLoc, 4, GL_FLOAT, GL_FALSE, 0, 0); glEnableVertexAttribArray(posLoc); // Render once to guarantee that the program is compiled and linked. drawBenchmark(); ASSERT_GL_NO_ERROR(); } void MultiviewCPUBoundBenchmark::renderScene() { glClearColor(0, 0, 0, 0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glUseProgram(mProgram); glBindVertexArray(mVAO); const MultiviewPerfParams *params = static_cast(&mTestParams); const int viewWidth = params->windowWidth / params->numViews; const int viewHeight = params->windowHeight; for (int w = 0; w < viewWidth; ++w) { for (int h = 0; h < viewHeight; ++h) { const float wf = static_cast(w) / viewWidth; const float wh = static_cast(h) / viewHeight; glUniform2f(glGetUniformLocation(mProgram, "uOffset"), 2.f * wf - 1.f, 2.f * wh - 1.f); glUniform1f(glGetUniformLocation(mProgram, "uColor"), wf); glDrawArrays(GL_TRIANGLES, 0, 6); } } } void MultiviewGPUBoundBenchmark::initializeBenchmark() { MultiviewBenchmark::initializeBenchmark(); const MultiviewPerfParams *params = static_cast(&mTestParams); const bool usesMultiview = (params->multiviewOption != MultiviewOption::NoAcceleration); const std::string &vs = "#version 300 es\n" + GetShaderExtensionHeader(usesMultiview, params->numViews, GL_VERTEX_SHADER, params->multiviewExtension) + "layout(location=0) in vec4 vPosition;\n" "layout(location=1) in vec4 vert_Col0;\n" "layout(location=2) in vec4 vert_Col1;\n" "layout(location=3) in vec4 vert_Col2;\n" "layout(location=4) in vec4 vert_Col3;\n" "layout(location=5) in vec4 vert_Col4;\n" "layout(location=6) in vec4 vert_Col5;\n" "out vec4 frag_Col0;\n" "out vec4 frag_Col1;\n" "out vec4 frag_Col2;\n" "out vec4 frag_Col3;\n" "out vec4 frag_Col4;\n" "out vec4 frag_Col5;\n" "void main()\n" "{\n" " frag_Col0 = vert_Col0;\n" " frag_Col1 = vert_Col1;\n" " frag_Col2 = vert_Col2;\n" " frag_Col3 = vert_Col3;\n" " frag_Col4 = vert_Col4;\n" " frag_Col5 = vert_Col5;\n" " gl_Position = vPosition;\n" "}\n"; const std::string &fs = "#version 300 es\n" + GetShaderExtensionHeader(usesMultiview, params->numViews, GL_FRAGMENT_SHADER, params->multiviewExtension) + "precision mediump float;\n" "in vec4 frag_Col0;\n" "in vec4 frag_Col1;\n" "in vec4 frag_Col2;\n" "in vec4 frag_Col3;\n" "in vec4 frag_Col4;\n" "in vec4 frag_Col5;\n" "out vec4 col;\n" "void main()\n" "{\n" " col += frag_Col0;\n" " col += frag_Col1;\n" " col += frag_Col2;\n" " col += frag_Col3;\n" " col += frag_Col4;\n" " col += frag_Col5;\n" "}\n"; createProgram(vs.c_str(), fs.c_str()); ASSERT_GL_NO_ERROR(); // Generate a vertex buffer of triangulated quads so that we have one quad per pixel. const int viewWidth = params->windowWidth / params->numViews; const int viewHeight = params->windowHeight; const float quadWidth = 2.f / static_cast(viewWidth); const float quadHeight = 2.f / static_cast(viewHeight); const int kNumQuads = viewWidth * viewHeight; const int kNumVerticesPerQuad = 6; std::vector vertexData(kNumQuads * kNumVerticesPerQuad); for (int h = 0; h < viewHeight; ++h) { for (int w = 0; w < viewWidth; ++w) { float wf = static_cast(w) / viewWidth; float hf = static_cast(h) / viewHeight; size_t index = static_cast(h * viewWidth + w) * 6u; auto &v0 = vertexData[index]; v0.position = Vector4(2.f * wf - 1.f, 2.f * hf - 1.f, .0f, 1.f); memset(v0.colorAttributeData, 0, sizeof(v0.colorAttributeData)); auto &v1 = vertexData[index + 1]; v1.position = Vector4(v0.position.x() + quadWidth, v0.position.y(), .0f, 1.f); memset(v1.colorAttributeData, 0, sizeof(v1.colorAttributeData)); auto &v2 = vertexData[index + 2]; v2.position = Vector4(v1.position.x(), v1.position.y() + quadHeight, .0f, 1.f); memset(v2.colorAttributeData, 0, sizeof(v2.colorAttributeData)); auto &v3 = vertexData[index + 3]; v3.position = v0.position; memset(v3.colorAttributeData, 0, sizeof(v3.colorAttributeData)); auto &v4 = vertexData[index + 4]; v4.position = v2.position; memset(v4.colorAttributeData, 0, sizeof(v4.colorAttributeData)); auto &v5 = vertexData[index + 5]; v5.position = Vector4(v0.position.x(), v0.position.y() + quadHeight, .0f, 1.f); memset(v5.colorAttributeData, 0, sizeof(v5.colorAttributeData)); } } glBindVertexArray(mVAO); glBindBuffer(GL_ARRAY_BUFFER, mVBO); glBufferData(GL_ARRAY_BUFFER, vertexData.size() * sizeof(Vertex), vertexData.data(), GL_STATIC_DRAW); glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), 0); glEnableVertexAttribArray(0); for (unsigned int i = 0u; i < 6u; ++i) { size_t offset = sizeof(Vector4) * (i + 1u); glVertexAttribPointer(i + 1, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast(offset)); glEnableVertexAttribArray(i + 1); } // Render once to guarantee that the program is compiled and linked. drawBenchmark(); } void MultiviewGPUBoundBenchmark::renderScene() { glClearColor(0, 0, 0, 0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glUseProgram(mProgram); glBindVertexArray(mVAO); const MultiviewPerfParams *params = static_cast(&mTestParams); const int viewWidth = params->windowWidth / params->numViews; const int viewHeight = params->windowHeight; glDrawArrays(GL_TRIANGLES, 0, viewWidth * viewHeight * 6); } namespace { MultiviewPerfWorkload SmallWorkload() { return MultiviewPerfWorkload(64, 64); } MultiviewPerfWorkload BigWorkload() { return MultiviewPerfWorkload(1024, 768); } MultiviewPerfParams NoAcceleration(const EGLPlatformParameters &eglParameters, const MultiviewPerfWorkload &workload, ExtensionName multiviewExtensionIn) { return MultiviewPerfParams(eglParameters, workload, MultiviewOption::NoAcceleration, multiviewExtensionIn); } MultiviewPerfParams SelectViewInGeometryShader(const MultiviewPerfWorkload &workload, ExtensionName multiviewExtensionIn) { return MultiviewPerfParams(egl_platform::D3D11(), workload, MultiviewOption::InstancedMultiviewGeometryShader, multiviewExtensionIn); } MultiviewPerfParams SelectViewInVertexShader(const EGLPlatformParameters &eglParameters, const MultiviewPerfWorkload &workload, ExtensionName multiviewExtensionIn) { return MultiviewPerfParams(eglParameters, workload, MultiviewOption::InstancedMultiviewVertexShader, multiviewExtensionIn); } } // namespace TEST_P(MultiviewCPUBoundBenchmark, Run) { run(); } ANGLE_INSTANTIATE_TEST( MultiviewCPUBoundBenchmark, NoAcceleration(egl_platform::OPENGL_OR_GLES(), SmallWorkload(), ExtensionName::multiview), NoAcceleration(egl_platform::D3D11(), SmallWorkload(), ExtensionName::multiview), SelectViewInGeometryShader(SmallWorkload(), ExtensionName::multiview), SelectViewInVertexShader(egl_platform::OPENGL_OR_GLES(), SmallWorkload(), ExtensionName::multiview), SelectViewInVertexShader(egl_platform::D3D11(), SmallWorkload(), ExtensionName::multiview), NoAcceleration(egl_platform::OPENGL_OR_GLES(), SmallWorkload(), ExtensionName::multiview2), NoAcceleration(egl_platform::D3D11(), SmallWorkload(), ExtensionName::multiview2), SelectViewInGeometryShader(SmallWorkload(), ExtensionName::multiview2), SelectViewInVertexShader(egl_platform::OPENGL_OR_GLES(), SmallWorkload(), ExtensionName::multiview2), SelectViewInVertexShader(egl_platform::D3D11(), SmallWorkload(), ExtensionName::multiview2)); TEST_P(MultiviewGPUBoundBenchmark, Run) { run(); } ANGLE_INSTANTIATE_TEST( MultiviewGPUBoundBenchmark, NoAcceleration(egl_platform::OPENGL_OR_GLES(), BigWorkload(), ExtensionName::multiview), NoAcceleration(egl_platform::D3D11(), BigWorkload(), ExtensionName::multiview), SelectViewInGeometryShader(BigWorkload(), ExtensionName::multiview), SelectViewInVertexShader(egl_platform::OPENGL_OR_GLES(), BigWorkload(), ExtensionName::multiview), SelectViewInVertexShader(egl_platform::D3D11(), BigWorkload(), ExtensionName::multiview), NoAcceleration(egl_platform::OPENGL_OR_GLES(), BigWorkload(), ExtensionName::multiview2), NoAcceleration(egl_platform::D3D11(), BigWorkload(), ExtensionName::multiview2), SelectViewInGeometryShader(BigWorkload(), ExtensionName::multiview2), SelectViewInVertexShader(egl_platform::OPENGL_OR_GLES(), BigWorkload(), ExtensionName::multiview2), SelectViewInVertexShader(egl_platform::D3D11(), BigWorkload(), ExtensionName::multiview2)); } // anonymous namespace