| // | |
| // Copyright 2018 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. | |
| // | |
| // ParallelShaderCompileTest.cpp : Tests of the GL_KHR_parallel_shader_compile extension. | |
| #include "test_utils/ANGLETest.h" | |
| #include "test_utils/gl_raii.h" | |
| #include "util/random_utils.h" | |
| #include "util/test_utils.h" | |
| using namespace angle; | |
| namespace | |
| { | |
| namespace | |
| { | |
| constexpr int kTaskCount = 32; | |
| constexpr unsigned int kPollInterval = 100; | |
| } // anonymous namespace | |
| class ParallelShaderCompileTest : public ANGLETest | |
| { | |
| protected: | |
| ParallelShaderCompileTest() | |
| { | |
| setWindowWidth(128); | |
| setWindowHeight(128); | |
| setConfigRedBits(8); | |
| setConfigGreenBits(8); | |
| setConfigBlueBits(8); | |
| setConfigAlphaBits(8); | |
| } | |
| bool ensureParallelShaderCompileExtensionAvailable() | |
| { | |
| if (IsGLExtensionRequestable("GL_KHR_parallel_shader_compile")) | |
| { | |
| glRequestExtensionANGLE("GL_KHR_parallel_shader_compile"); | |
| } | |
| if (!IsGLExtensionEnabled("GL_KHR_parallel_shader_compile")) | |
| { | |
| return false; | |
| } | |
| return true; | |
| } | |
| class Task | |
| { | |
| public: | |
| Task(int id) : mID(id) {} | |
| virtual ~Task() {} | |
| virtual bool compile() = 0; | |
| virtual bool isCompileCompleted() = 0; | |
| virtual bool link() = 0; | |
| virtual void runAndVerify(ParallelShaderCompileTest *test) = 0; | |
| bool isLinkCompleted() | |
| { | |
| GLint status; | |
| glGetProgramiv(mProgram, GL_COMPLETION_STATUS_KHR, &status); | |
| return (status == GL_TRUE); | |
| } | |
| protected: | |
| std::string insertRandomString(const std::string &source) | |
| { | |
| RNG rng; | |
| std::ostringstream ostream; | |
| ostream << source << "\n// Random string to fool program cache: " << rng.randomInt() | |
| << "\n"; | |
| return ostream.str(); | |
| } | |
| GLuint CompileShader(GLenum type, const std::string &source) | |
| { | |
| GLuint shader = glCreateShader(type); | |
| const char *sourceArray[1] = {source.c_str()}; | |
| glShaderSource(shader, 1, sourceArray, nullptr); | |
| glCompileShader(shader); | |
| return shader; | |
| } | |
| bool checkShader(GLuint shader) | |
| { | |
| GLint compileResult; | |
| glGetShaderiv(shader, GL_COMPILE_STATUS, &compileResult); | |
| if (compileResult == 0) | |
| { | |
| GLint infoLogLength; | |
| glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLogLength); | |
| // Info log length includes the null terminator, so 1 means that the info log is an | |
| // empty string. | |
| if (infoLogLength > 1) | |
| { | |
| std::vector<GLchar> infoLog(infoLogLength); | |
| glGetShaderInfoLog(shader, static_cast<GLsizei>(infoLog.size()), nullptr, | |
| &infoLog[0]); | |
| std::cerr << "shader compilation failed: " << &infoLog[0]; | |
| } | |
| else | |
| { | |
| std::cerr << "shader compilation failed. <Empty log message>"; | |
| } | |
| std::cerr << std::endl; | |
| } | |
| return (compileResult == GL_TRUE); | |
| } | |
| GLuint mProgram; | |
| int mID; | |
| }; | |
| template <typename T> | |
| class TaskRunner | |
| { | |
| public: | |
| TaskRunner() {} | |
| ~TaskRunner() {} | |
| void run(ParallelShaderCompileTest *test) | |
| { | |
| std::vector<std::unique_ptr<T>> compileTasks; | |
| for (int i = 0; i < kTaskCount; ++i) | |
| { | |
| std::unique_ptr<T> task(new T(i)); | |
| bool isCompiling = task->compile(); | |
| ASSERT_TRUE(isCompiling); | |
| compileTasks.push_back(std::move(task)); | |
| } | |
| std::vector<std::unique_ptr<T>> linkTasks; | |
| while (!compileTasks.empty()) | |
| { | |
| for (unsigned int i = 0; i < compileTasks.size();) | |
| { | |
| auto &task = compileTasks[i]; | |
| if (task->isCompileCompleted()) | |
| { | |
| bool isLinking = task->link(); | |
| ASSERT_TRUE(isLinking); | |
| linkTasks.push_back(std::move(task)); | |
| compileTasks.erase(compileTasks.begin() + i); | |
| continue; | |
| } | |
| ++i; | |
| } | |
| angle::Sleep(kPollInterval); | |
| } | |
| while (!linkTasks.empty()) | |
| { | |
| for (unsigned int i = 0; i < linkTasks.size();) | |
| { | |
| auto &task = linkTasks[i]; | |
| if (task->isLinkCompleted()) | |
| { | |
| task->runAndVerify(test); | |
| linkTasks.erase(linkTasks.begin() + i); | |
| continue; | |
| } | |
| ++i; | |
| } | |
| angle::Sleep(kPollInterval); | |
| } | |
| } | |
| }; | |
| class ClearColorWithDraw : public Task | |
| { | |
| public: | |
| ClearColorWithDraw(int taskID) : Task(taskID) | |
| { | |
| auto color = static_cast<GLubyte>(taskID * 255 / kTaskCount); | |
| mColor = {color, color, color, 255}; | |
| } | |
| bool compile() override | |
| { | |
| mVertexShader = | |
| CompileShader(GL_VERTEX_SHADER, insertRandomString(essl1_shaders::vs::Simple())); | |
| mFragmentShader = CompileShader(GL_FRAGMENT_SHADER, | |
| insertRandomString(essl1_shaders::fs::UniformColor())); | |
| return (mVertexShader != 0 && mFragmentShader != 0); | |
| } | |
| bool isCompileCompleted() override | |
| { | |
| GLint status; | |
| glGetShaderiv(mVertexShader, GL_COMPLETION_STATUS_KHR, &status); | |
| if (status == GL_TRUE) | |
| { | |
| glGetShaderiv(mFragmentShader, GL_COMPLETION_STATUS_KHR, &status); | |
| return (status == GL_TRUE); | |
| } | |
| return false; | |
| } | |
| bool link() override | |
| { | |
| mProgram = 0; | |
| if (checkShader(mVertexShader) && checkShader(mFragmentShader)) | |
| { | |
| mProgram = glCreateProgram(); | |
| glAttachShader(mProgram, mVertexShader); | |
| glAttachShader(mProgram, mFragmentShader); | |
| glLinkProgram(mProgram); | |
| } | |
| glDeleteShader(mVertexShader); | |
| glDeleteShader(mFragmentShader); | |
| return (mProgram != 0); | |
| } | |
| void runAndVerify(ParallelShaderCompileTest *test) override | |
| { | |
| glClearColor(0, 0, 0, 0); | |
| glClear(GL_COLOR_BUFFER_BIT); | |
| glDisable(GL_DEPTH_TEST); | |
| glUseProgram(mProgram); | |
| ASSERT_GL_NO_ERROR(); | |
| GLint colorUniformLocation = | |
| glGetUniformLocation(mProgram, essl1_shaders::ColorUniform()); | |
| ASSERT_NE(colorUniformLocation, -1); | |
| auto normalizeColor = mColor.toNormalizedVector(); | |
| glUniform4fv(colorUniformLocation, 1, normalizeColor.data()); | |
| test->drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.5f); | |
| EXPECT_PIXEL_COLOR_EQ(test->getWindowWidth() / 2, test->getWindowHeight() / 2, mColor); | |
| glUseProgram(0); | |
| glDeleteProgram(mProgram); | |
| ASSERT_GL_NO_ERROR(); | |
| } | |
| private: | |
| GLColor mColor; | |
| GLuint mVertexShader; | |
| GLuint mFragmentShader; | |
| }; | |
| class ImageLoadStore : public Task | |
| { | |
| public: | |
| ImageLoadStore(int taskID) : Task(taskID) {} | |
| ~ImageLoadStore() {} | |
| bool compile() override | |
| { | |
| const char kCSSource[] = R"(#version 310 es | |
| layout(local_size_x=1, local_size_y=1, local_size_z=1) in; | |
| layout(r32ui, binding = 0) readonly uniform highp uimage2D uImage_1; | |
| layout(r32ui, binding = 1) writeonly uniform highp uimage2D uImage_2; | |
| void main() | |
| { | |
| uvec4 value = imageLoad(uImage_1, ivec2(gl_LocalInvocationID.xy)); | |
| imageStore(uImage_2, ivec2(gl_LocalInvocationID.xy), value); | |
| })"; | |
| mShader = CompileShader(GL_COMPUTE_SHADER, insertRandomString(kCSSource)); | |
| return mShader != 0; | |
| } | |
| bool isCompileCompleted() override | |
| { | |
| GLint status; | |
| glGetShaderiv(mShader, GL_COMPLETION_STATUS_KHR, &status); | |
| return status == GL_TRUE; | |
| } | |
| bool link() override | |
| { | |
| mProgram = 0; | |
| if (checkShader(mShader)) | |
| { | |
| mProgram = glCreateProgram(); | |
| glAttachShader(mProgram, mShader); | |
| glLinkProgram(mProgram); | |
| } | |
| glDeleteShader(mShader); | |
| return mProgram != 0; | |
| } | |
| void runAndVerify(ParallelShaderCompileTest *test) override | |
| { | |
| // Taken from ComputeShaderTest.StoreImageThenLoad. | |
| constexpr GLuint kInputValues[3][1] = {{300}, {200}, {100}}; | |
| GLTexture texture[3]; | |
| glBindTexture(GL_TEXTURE_2D, texture[0]); | |
| glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, 1, 1); | |
| glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RED_INTEGER, GL_UNSIGNED_INT, | |
| kInputValues[0]); | |
| EXPECT_GL_NO_ERROR(); | |
| glBindTexture(GL_TEXTURE_2D, texture[1]); | |
| glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, 1, 1); | |
| glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RED_INTEGER, GL_UNSIGNED_INT, | |
| kInputValues[1]); | |
| EXPECT_GL_NO_ERROR(); | |
| glBindTexture(GL_TEXTURE_2D, texture[2]); | |
| glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, 1, 1); | |
| glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RED_INTEGER, GL_UNSIGNED_INT, | |
| kInputValues[2]); | |
| EXPECT_GL_NO_ERROR(); | |
| glUseProgram(mProgram); | |
| glBindImageTexture(0, texture[0], 0, GL_FALSE, 0, GL_READ_ONLY, GL_R32UI); | |
| glBindImageTexture(1, texture[1], 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_R32UI); | |
| glDispatchCompute(1, 1, 1); | |
| glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT); | |
| EXPECT_GL_NO_ERROR(); | |
| glBindImageTexture(0, texture[1], 0, GL_FALSE, 0, GL_READ_ONLY, GL_R32UI); | |
| glBindImageTexture(1, texture[2], 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_R32UI); | |
| glDispatchCompute(1, 1, 1); | |
| glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT); | |
| EXPECT_GL_NO_ERROR(); | |
| GLuint outputValue; | |
| GLFramebuffer framebuffer; | |
| glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer); | |
| glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, | |
| texture[2], 0); | |
| glReadPixels(0, 0, 1, 1, GL_RED_INTEGER, GL_UNSIGNED_INT, &outputValue); | |
| EXPECT_GL_NO_ERROR(); | |
| EXPECT_EQ(300u, outputValue); | |
| glUseProgram(0); | |
| glDeleteProgram(mProgram); | |
| ASSERT_GL_NO_ERROR(); | |
| } | |
| private: | |
| GLuint mShader; | |
| }; | |
| }; | |
| // Test basic functionality of GL_KHR_parallel_shader_compile | |
| TEST_P(ParallelShaderCompileTest, Basic) | |
| { | |
| ANGLE_SKIP_TEST_IF(!ensureParallelShaderCompileExtensionAvailable()); | |
| GLint count = 0; | |
| glMaxShaderCompilerThreadsKHR(8); | |
| EXPECT_GL_NO_ERROR(); | |
| glGetIntegerv(GL_MAX_SHADER_COMPILER_THREADS_KHR, &count); | |
| EXPECT_GL_NO_ERROR(); | |
| EXPECT_EQ(8, count); | |
| } | |
| // Test to compile and link many programs in parallel. | |
| TEST_P(ParallelShaderCompileTest, LinkAndDrawManyPrograms) | |
| { | |
| ANGLE_SKIP_TEST_IF(!ensureParallelShaderCompileExtensionAvailable()); | |
| TaskRunner<ClearColorWithDraw> runner; | |
| runner.run(this); | |
| } | |
| class ParallelShaderCompileTestES31 : public ParallelShaderCompileTest | |
| {}; | |
| // Test to compile and link many computing programs in parallel. | |
| TEST_P(ParallelShaderCompileTestES31, LinkAndDispatchManyPrograms) | |
| { | |
| // Flaky on Win NVIDIA D3D11. http://anglebug.com/3359 | |
| // Suspectable to the flakyness of http://anglebug.com/3349. | |
| ANGLE_SKIP_TEST_IF(IsWindows() && IsD3D11()); | |
| ANGLE_SKIP_TEST_IF(!ensureParallelShaderCompileExtensionAvailable()); | |
| TaskRunner<ImageLoadStore> runner; | |
| runner.run(this); | |
| } | |
| ANGLE_INSTANTIATE_TEST_ES2(ParallelShaderCompileTest); | |
| ANGLE_INSTANTIATE_TEST_ES31(ParallelShaderCompileTestES31); | |
| } // namespace |