// // 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 infoLog(infoLogLength); glGetShaderInfoLog(shader, static_cast(infoLog.size()), nullptr, &infoLog[0]); std::cerr << "shader compilation failed: " << &infoLog[0]; } else { std::cerr << "shader compilation failed. "; } std::cerr << std::endl; } return (compileResult == GL_TRUE); } GLuint mProgram; int mID; }; template class TaskRunner { public: TaskRunner() {} ~TaskRunner() {} void run(ParallelShaderCompileTest *test) { std::vector> compileTasks; for (int i = 0; i < kTaskCount; ++i) { std::unique_ptr task(new T(i)); bool isCompiling = task->compile(); ASSERT_TRUE(isCompiling); compileTasks.push_back(std::move(task)); } std::vector> 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(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 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()); // TODO(http://anglebug.com/5656): Fails on Linux+Intel+OpenGL ANGLE_SKIP_TEST_IF(IsLinux() && IsIntel() && IsOpenGL()); ANGLE_SKIP_TEST_IF(!ensureParallelShaderCompileExtensionAvailable()); TaskRunner runner; runner.run(this); } ANGLE_INSTANTIATE_TEST_ES2(ParallelShaderCompileTest); GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(ParallelShaderCompileTestES31); ANGLE_INSTANTIATE_TEST_ES31(ParallelShaderCompileTestES31); } // namespace