| // | |
| // Copyright 2023 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. | |
| // | |
| // ParallelLinkProgramPerfTest: | |
| // Tests performance of compiling and linking many shaders and programs in sequence. | |
| // | |
| #include "ANGLEPerfTest.h" | |
| #include <array> | |
| #include "common/vector_utils.h" | |
| #include "util/shader_utils.h" | |
| using namespace angle; | |
| namespace | |
| { | |
| enum class CompileLinkOrder | |
| { | |
| // Most optimal behavior; all compiles are done first, then all links. | |
| AllCompilesFirst, | |
| // Less optimal, but still decent; each program's shaders are first compiled, then the program | |
| // is linked before moving on to the next program. | |
| Interleaved, | |
| // Worst behavior, which unfortunately is what the large majority of applications do; each | |
| // program's shaders are compiled, the program is linked, and the link status is immediately | |
| // queried (causing the main thread to block on the link task). | |
| InterleavedAndImmediateQuery, | |
| Unspecified, | |
| }; | |
| struct ParallelLinkProgramParams final : public RenderTestParams | |
| { | |
| ParallelLinkProgramParams(CompileLinkOrder order) | |
| { | |
| iterationsPerStep = 100; | |
| majorVersion = 3; | |
| minorVersion = 0; | |
| windowWidth = 256; | |
| windowHeight = 256; | |
| compileLinkOrder = order; | |
| } | |
| std::string story() const override | |
| { | |
| std::stringstream strstr; | |
| strstr << RenderTestParams::story(); | |
| if (compileLinkOrder == CompileLinkOrder::AllCompilesFirst) | |
| { | |
| strstr << "_all_compiles_first"; | |
| } | |
| else if (compileLinkOrder == CompileLinkOrder::Interleaved) | |
| { | |
| strstr << "_interleaved_compile_and_link"; | |
| } | |
| else if (compileLinkOrder == CompileLinkOrder::InterleavedAndImmediateQuery) | |
| { | |
| strstr << "_interleaved_compile_and_link_with_immediate_query"; | |
| } | |
| if (std::find(eglParameters.disabledFeatureOverrides.begin(), | |
| eglParameters.disabledFeatureOverrides.end(), | |
| Feature::EnableParallelCompileAndLink) != | |
| eglParameters.disabledFeatureOverrides.end()) | |
| { | |
| strstr << "_serial"; | |
| } | |
| if (eglParameters.deviceType == EGL_PLATFORM_ANGLE_DEVICE_TYPE_NULL_ANGLE) | |
| { | |
| strstr << "_null"; | |
| } | |
| return strstr.str(); | |
| } | |
| CompileLinkOrder compileLinkOrder; | |
| }; | |
| std::ostream &operator<<(std::ostream &os, const ParallelLinkProgramParams ¶ms) | |
| { | |
| os << params.backendAndStory().substr(1); | |
| return os; | |
| } | |
| class ParallelLinkProgramBenchmark : public ANGLERenderTest, | |
| public ::testing::WithParamInterface<ParallelLinkProgramParams> | |
| { | |
| public: | |
| ParallelLinkProgramBenchmark(); | |
| void initializeBenchmark() override; | |
| void destroyBenchmark() override; | |
| void drawBenchmark() override; | |
| protected: | |
| struct Program | |
| { | |
| std::string vertexShader; | |
| std::string fragmentShader; | |
| GLuint vs; | |
| GLuint fs; | |
| GLuint program; | |
| }; | |
| std::vector<Program> mPrograms; | |
| }; | |
| ParallelLinkProgramBenchmark::ParallelLinkProgramBenchmark() | |
| : ANGLERenderTest("ParallelLinkProgram", GetParam()) | |
| { | |
| if (IsWindows() && IsNVIDIA() && | |
| GetParam().eglParameters.renderer == EGL_PLATFORM_ANGLE_TYPE_OPENGL_ANGLE) | |
| { | |
| skipTest("http://anglebug.com/42266835 crashes the GL driver"); | |
| } | |
| if (IsLinux() && IsNVIDIA() && | |
| GetParam().eglParameters.renderer == EGL_PLATFORM_ANGLE_TYPE_OPENGL_ANGLE) | |
| { | |
| skipTest("http://anglebug.com/42266835 flakily crashes the GL driver"); | |
| } | |
| } | |
| void ParallelLinkProgramBenchmark::initializeBenchmark() | |
| { | |
| const ParallelLinkProgramParams ¶ms = GetParam(); | |
| // Generate N shaders and create the GL objects. DrawBenchmark would then _only_ do compilation | |
| // and link. | |
| mPrograms.resize(params.iterationsPerStep); | |
| for (uint32_t i = 0; i < params.iterationsPerStep; ++i) | |
| { | |
| std::ostringstream vs; | |
| vs << R"(#version 300 es | |
| uniform UBO | |
| { | |
| vec4 data[)" | |
| << params.iterationsPerStep << R"(]; | |
| }; | |
| uniform UBO2 | |
| { | |
| mat4 m[2]; | |
| } ubo; | |
| in mediump vec4 attr1; | |
| in mediump uvec4 attr2; | |
| out highp vec3 var1; | |
| flat out lowp uvec2 var2; | |
| void main() | |
| { | |
| vec4 a = attr1 + vec4(attr2);)"; | |
| for (uint32_t j = 0; j < i * 5; ++j) | |
| { | |
| vs << R"( | |
| a *= ubo.m[0];)"; | |
| } | |
| vs << R"( | |
| a += ubo.m[1][2]; | |
| var1 = cross(a.zxy, data[)" | |
| << i << R"(].xxw); | |
| var2 = uvec2(1, 3) * uvec2(6, 3); | |
| })"; | |
| mPrograms[i].vertexShader = vs.str(); | |
| std::ostringstream fs; | |
| fs << R"(#version 300 es | |
| uniform UBO | |
| { | |
| highp vec4 data[)" | |
| << params.iterationsPerStep << R"(]; | |
| }; | |
| in highp vec3 var1; | |
| flat in lowp uvec2 var2; | |
| uniform mediump sampler2D s; | |
| uniform mediump sampler2D s2; | |
| layout(location = 0) out mediump vec4 color1; | |
| layout(location = 2) out highp uvec2 color2; | |
| void main() | |
| { | |
| color1 = var1.xyzx + texture(s, vec2(var2) / 9.); | |
| mediump vec4 sum = vec4(0); | |
| for (int i = 0; i < 10; ++i) | |
| sum += texture(s2, vec2(float(i / 2) / 5., float(i % 5) / 5.)); | |
| uvec2 res = uvec2(sum.xz + sum.yw);)"; | |
| for (uint32_t j = 0; j < i * 5; ++j) | |
| { | |
| fs << R"( | |
| res += uvec2(data[)" | |
| << (j % 100) << R"(]);)"; | |
| } | |
| fs << R"( | |
| color2 = res; | |
| })"; | |
| mPrograms[i].fragmentShader = fs.str(); | |
| mPrograms[i].vs = glCreateShader(GL_VERTEX_SHADER); | |
| mPrograms[i].fs = glCreateShader(GL_FRAGMENT_SHADER); | |
| mPrograms[i].program = glCreateProgram(); | |
| const char *vsCStr = mPrograms[i].vertexShader.c_str(); | |
| const char *fsCStr = mPrograms[i].fragmentShader.c_str(); | |
| glShaderSource(mPrograms[i].vs, 1, &vsCStr, 0); | |
| glShaderSource(mPrograms[i].fs, 1, &fsCStr, 0); | |
| glAttachShader(mPrograms[i].program, mPrograms[i].vs); | |
| glAttachShader(mPrograms[i].program, mPrograms[i].fs); | |
| } | |
| ASSERT_GL_NO_ERROR(); | |
| } | |
| void ParallelLinkProgramBenchmark::destroyBenchmark() | |
| { | |
| const ParallelLinkProgramParams ¶ms = GetParam(); | |
| for (uint32_t i = 0; i < params.iterationsPerStep; ++i) | |
| { | |
| glDetachShader(mPrograms[i].program, mPrograms[i].vs); | |
| glDetachShader(mPrograms[i].program, mPrograms[i].fs); | |
| glDeleteShader(mPrograms[i].vs); | |
| glDeleteShader(mPrograms[i].fs); | |
| glDeleteProgram(mPrograms[i].program); | |
| } | |
| } | |
| void ParallelLinkProgramBenchmark::drawBenchmark() | |
| { | |
| const ParallelLinkProgramParams ¶ms = GetParam(); | |
| for (uint32_t i = 0; i < params.iterationsPerStep; ++i) | |
| { | |
| // Compile the shaders, and if interleaved, link the corresponding programs. | |
| glCompileShader(mPrograms[i].vs); | |
| glCompileShader(mPrograms[i].fs); | |
| if (params.compileLinkOrder != CompileLinkOrder::AllCompilesFirst) | |
| { | |
| glLinkProgram(mPrograms[i].program); | |
| if (params.compileLinkOrder == CompileLinkOrder::InterleavedAndImmediateQuery) | |
| { | |
| GLint linkStatus = GL_TRUE; | |
| glGetProgramiv(mPrograms[i].program, GL_LINK_STATUS, &linkStatus); | |
| EXPECT_TRUE(linkStatus) << i; | |
| } | |
| } | |
| } | |
| // If asked to link after all shaders are compiled, link all the programs now | |
| if (params.compileLinkOrder == CompileLinkOrder::AllCompilesFirst) | |
| { | |
| for (uint32_t i = 0; i < params.iterationsPerStep; ++i) | |
| { | |
| glLinkProgram(mPrograms[i].program); | |
| } | |
| } | |
| // Now that all the compile and link jobs have been scheduled, wait for them all to finish. | |
| for (uint32_t i = 0; i < params.iterationsPerStep; ++i) | |
| { | |
| GLint compileResult; | |
| glGetShaderiv(mPrograms[i].vs, GL_COMPILE_STATUS, &compileResult); | |
| EXPECT_NE(compileResult, 0) << i; | |
| glGetShaderiv(mPrograms[i].fs, GL_COMPILE_STATUS, &compileResult); | |
| EXPECT_NE(compileResult, 0) << i; | |
| GLint linkStatus = GL_TRUE; | |
| glGetProgramiv(mPrograms[i].program, GL_LINK_STATUS, &linkStatus); | |
| EXPECT_TRUE(linkStatus) << i; | |
| } | |
| // ANGLE supports running some optional link subtasks beyond the actual end of the link. Ensure | |
| // those are all finished by triggerring a wait on the jobs of the last program. Currently, | |
| // detaching and attaching shaders does that (among other operations). | |
| const uint32_t last = params.iterationsPerStep - 1; | |
| glDetachShader(mPrograms[last].program, mPrograms[last].vs); | |
| glAttachShader(mPrograms[last].program, mPrograms[last].vs); | |
| ASSERT_GL_NO_ERROR(); | |
| } | |
| using namespace egl_platform; | |
| ParallelLinkProgramParams ParallelLinkProgramD3D11Params(CompileLinkOrder compileLinkOrder) | |
| { | |
| ParallelLinkProgramParams params(compileLinkOrder); | |
| params.eglParameters = D3D11(); | |
| return params; | |
| } | |
| ParallelLinkProgramParams ParallelLinkProgramMetalParams(CompileLinkOrder compileLinkOrder) | |
| { | |
| ParallelLinkProgramParams params(compileLinkOrder); | |
| params.eglParameters = METAL(); | |
| return params; | |
| } | |
| ParallelLinkProgramParams ParallelLinkProgramOpenGLOrGLESParams(CompileLinkOrder compileLinkOrder) | |
| { | |
| ParallelLinkProgramParams params(compileLinkOrder); | |
| params.eglParameters = OPENGL_OR_GLES(); | |
| return params; | |
| } | |
| ParallelLinkProgramParams ParallelLinkProgramVulkanParams(CompileLinkOrder compileLinkOrder) | |
| { | |
| ParallelLinkProgramParams params(compileLinkOrder); | |
| params.eglParameters = VULKAN(); | |
| params.enable(Feature::EnableParallelCompileAndLink); | |
| return params; | |
| } | |
| ParallelLinkProgramParams SerialLinkProgramVulkanParams(CompileLinkOrder compileLinkOrder) | |
| { | |
| ParallelLinkProgramParams params(compileLinkOrder); | |
| params.eglParameters = VULKAN(); | |
| params.disable(Feature::EnableParallelCompileAndLink); | |
| return params; | |
| } | |
| // Test parallel link performance | |
| TEST_P(ParallelLinkProgramBenchmark, Run) | |
| { | |
| run(); | |
| } | |
| ANGLE_INSTANTIATE_TEST( | |
| ParallelLinkProgramBenchmark, | |
| ParallelLinkProgramD3D11Params(CompileLinkOrder::AllCompilesFirst), | |
| ParallelLinkProgramD3D11Params(CompileLinkOrder::Interleaved), | |
| ParallelLinkProgramMetalParams(CompileLinkOrder::AllCompilesFirst), | |
| ParallelLinkProgramMetalParams(CompileLinkOrder::Interleaved), | |
| ParallelLinkProgramOpenGLOrGLESParams(CompileLinkOrder::AllCompilesFirst), | |
| ParallelLinkProgramOpenGLOrGLESParams(CompileLinkOrder::Interleaved), | |
| ParallelLinkProgramVulkanParams(CompileLinkOrder::AllCompilesFirst), | |
| ParallelLinkProgramVulkanParams(CompileLinkOrder::Interleaved), | |
| ParallelLinkProgramVulkanParams(CompileLinkOrder::InterleavedAndImmediateQuery), | |
| SerialLinkProgramVulkanParams(CompileLinkOrder::AllCompilesFirst), | |
| SerialLinkProgramVulkanParams(CompileLinkOrder::Interleaved), | |
| SerialLinkProgramVulkanParams(CompileLinkOrder::InterleavedAndImmediateQuery)); | |
| } // anonymous namespace |