blob: bcd88ef01308759085c8244ad2058efe68686f2b [file] [log] [blame]
//
// 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());
// TODO(http://anglebug.com/5656): Fails on Linux+Intel+OpenGL
ANGLE_SKIP_TEST_IF(IsLinux() && IsIntel() && IsOpenGL());
ANGLE_SKIP_TEST_IF(!ensureParallelShaderCompileExtensionAvailable());
TaskRunner<ImageLoadStore> runner;
runner.run(this);
}
ANGLE_INSTANTIATE_TEST_ES2(ParallelShaderCompileTest);
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(ParallelShaderCompileTestES31);
ANGLE_INSTANTIATE_TEST_ES31(ParallelShaderCompileTestES31);
} // namespace