Commit 89899748 by jchen10 Committed by Commit Bot

ParallelCompile: D3D compute

This parallelizes the compiling and linking for compute shaders on the D3D backend. Bug: chromium:849576 Change-Id: Idd6b418cb9c2448209c15eab2756599f8ff7af4c Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/1415725Reviewed-by: 's avatarJamie Madill <jmadill@chromium.org> Reviewed-by: 's avatarGeoff Lang <geofflang@chromium.org> Commit-Queue: Jie A Chen <jie.a.chen@intel.com>
parent 2889dff6
...@@ -1758,6 +1758,21 @@ class ProgramD3D::GetGeometryExecutableTask : public ProgramD3D::GetExecutableTa ...@@ -1758,6 +1758,21 @@ class ProgramD3D::GetGeometryExecutableTask : public ProgramD3D::GetExecutableTa
const gl::State &mState; const gl::State &mState;
}; };
class ProgramD3D::GetComputeExecutableTask : public ProgramD3D::GetExecutableTask
{
public:
GetComputeExecutableTask(ProgramD3D *program) : GetExecutableTask(program) {}
angle::Result run() override
{
mProgram->updateCachedImage2DBindLayoutFromComputeShader();
ShaderExecutableD3D *computeExecutable = nullptr;
ANGLE_TRY(mProgram->getComputeExecutableForImage2DBindLayout(this, &computeExecutable,
&mInfoLog));
return computeExecutable ? angle::Result::Continue : angle::Result::Incomplete;
}
};
// The LinkEvent implementation for linking a rendering(VS, FS, GS) program. // The LinkEvent implementation for linking a rendering(VS, FS, GS) program.
class ProgramD3D::GraphicsProgramLinkEvent final : public LinkEvent class ProgramD3D::GraphicsProgramLinkEvent final : public LinkEvent
{ {
...@@ -1793,9 +1808,9 @@ class ProgramD3D::GraphicsProgramLinkEvent final : public LinkEvent ...@@ -1793,9 +1808,9 @@ class ProgramD3D::GraphicsProgramLinkEvent final : public LinkEvent
ANGLE_TRY(checkTask(context, mPixelTask.get())); ANGLE_TRY(checkTask(context, mPixelTask.get()));
ANGLE_TRY(checkTask(context, mGeometryTask.get())); ANGLE_TRY(checkTask(context, mGeometryTask.get()));
if (mVertexTask.get()->getResult() == angle::Result::Incomplete || if (mVertexTask->getResult() == angle::Result::Incomplete ||
mPixelTask.get()->getResult() == angle::Result::Incomplete || mPixelTask->getResult() == angle::Result::Incomplete ||
mGeometryTask.get()->getResult() == angle::Result::Incomplete) mGeometryTask->getResult() == angle::Result::Incomplete)
{ {
return angle::Result::Incomplete; return angle::Result::Incomplete;
} }
...@@ -1873,6 +1888,36 @@ class ProgramD3D::GraphicsProgramLinkEvent final : public LinkEvent ...@@ -1873,6 +1888,36 @@ class ProgramD3D::GraphicsProgramLinkEvent final : public LinkEvent
const ShaderD3D *mFragmentShader; const ShaderD3D *mFragmentShader;
}; };
// The LinkEvent implementation for linking a computing program.
class ProgramD3D::ComputeProgramLinkEvent final : public LinkEvent
{
public:
ComputeProgramLinkEvent(gl::InfoLog &infoLog,
std::shared_ptr<ProgramD3D::GetComputeExecutableTask> computeTask,
std::shared_ptr<WaitableEvent> event)
: mInfoLog(infoLog), mComputeTask(computeTask), mWaitEvent(event)
{}
bool isLinking() override { return !mWaitEvent->isReady(); }
angle::Result wait(const gl::Context *context) override
{
mWaitEvent->wait();
angle::Result result = mComputeTask->getResult();
if (result != angle::Result::Continue)
{
mInfoLog << "Failed to create D3D compute shader.";
}
return result;
}
private:
gl::InfoLog &mInfoLog;
std::shared_ptr<ProgramD3D::GetComputeExecutableTask> mComputeTask;
std::shared_ptr<WaitableEvent> mWaitEvent;
};
std::unique_ptr<LinkEvent> ProgramD3D::compileProgramExecutables(const gl::Context *context, std::unique_ptr<LinkEvent> ProgramD3D::compileProgramExecutables(const gl::Context *context,
gl::InfoLog &infoLog) gl::InfoLog &infoLog)
{ {
...@@ -1897,6 +1942,21 @@ std::unique_ptr<LinkEvent> ProgramD3D::compileProgramExecutables(const gl::Conte ...@@ -1897,6 +1942,21 @@ std::unique_ptr<LinkEvent> ProgramD3D::compileProgramExecutables(const gl::Conte
vertexShaderD3D, fragmentShaderD3D); vertexShaderD3D, fragmentShaderD3D);
} }
std::unique_ptr<LinkEvent> ProgramD3D::compileComputeExecutable(const gl::Context *context,
gl::InfoLog &infoLog)
{
// Ensure the compiler is initialized to avoid race conditions.
angle::Result result = mRenderer->ensureHLSLCompilerInitialized(GetImplAs<ContextD3D>(context));
if (result != angle::Result::Continue)
{
return std::make_unique<LinkEventDone>(result);
}
auto computeTask = std::make_shared<GetComputeExecutableTask>(this);
return std::make_unique<ComputeProgramLinkEvent>(
infoLog, computeTask,
WorkerThreadPool::PostWorkerTask(context->getWorkerThreadPool(), computeTask));
}
angle::Result ProgramD3D::getComputeExecutableForImage2DBindLayout( angle::Result ProgramD3D::getComputeExecutableForImage2DBindLayout(
d3d::Context *context, d3d::Context *context,
ShaderExecutableD3D **outExecutable, ShaderExecutableD3D **outExecutable,
...@@ -1939,19 +1999,6 @@ angle::Result ProgramD3D::getComputeExecutableForImage2DBindLayout( ...@@ -1939,19 +1999,6 @@ angle::Result ProgramD3D::getComputeExecutableForImage2DBindLayout(
return angle::Result::Continue; return angle::Result::Continue;
} }
angle::Result ProgramD3D::compileComputeExecutable(d3d::Context *context, gl::InfoLog &infoLog)
{
// Ensure the compiler is initialized to avoid race conditions.
ANGLE_TRY(mRenderer->ensureHLSLCompilerInitialized(context));
updateCachedImage2DBindLayoutFromComputeShader();
ShaderExecutableD3D *computeExecutable = nullptr;
ANGLE_TRY(getComputeExecutableForImage2DBindLayout(context, &computeExecutable, &infoLog));
return computeExecutable ? angle::Result::Continue : angle::Result::Incomplete;
}
std::unique_ptr<LinkEvent> ProgramD3D::link(const gl::Context *context, std::unique_ptr<LinkEvent> ProgramD3D::link(const gl::Context *context,
const gl::ProgramLinkedResources &resources, const gl::ProgramLinkedResources &resources,
gl::InfoLog &infoLog) gl::InfoLog &infoLog)
...@@ -1982,12 +2029,7 @@ std::unique_ptr<LinkEvent> ProgramD3D::link(const gl::Context *context, ...@@ -1982,12 +2029,7 @@ std::unique_ptr<LinkEvent> ProgramD3D::link(const gl::Context *context,
defineUniformsAndAssignRegisters(); defineUniformsAndAssignRegisters();
angle::Result result = compileComputeExecutable(GetImplAs<ContextD3D>(context), infoLog); return compileComputeExecutable(context, infoLog);
if (result != angle::Result::Continue)
{
infoLog << "Failed to create D3D compute shader.";
}
return std::make_unique<LinkEventDone>(result);
} }
else else
{ {
......
...@@ -333,7 +333,9 @@ class ProgramD3D : public ProgramImpl ...@@ -333,7 +333,9 @@ class ProgramD3D : public ProgramImpl
class GetVertexExecutableTask; class GetVertexExecutableTask;
class GetPixelExecutableTask; class GetPixelExecutableTask;
class GetGeometryExecutableTask; class GetGeometryExecutableTask;
class GetComputeExecutableTask;
class GraphicsProgramLinkEvent; class GraphicsProgramLinkEvent;
class ComputeProgramLinkEvent;
class LoadBinaryTask; class LoadBinaryTask;
class LoadBinaryLinkEvent; class LoadBinaryLinkEvent;
...@@ -475,7 +477,8 @@ class ProgramD3D : public ProgramImpl ...@@ -475,7 +477,8 @@ class ProgramD3D : public ProgramImpl
std::unique_ptr<LinkEvent> compileProgramExecutables(const gl::Context *context, std::unique_ptr<LinkEvent> compileProgramExecutables(const gl::Context *context,
gl::InfoLog &infoLog); gl::InfoLog &infoLog);
angle::Result compileComputeExecutable(d3d::Context *context, gl::InfoLog &infoLog); std::unique_ptr<LinkEvent> compileComputeExecutable(const gl::Context *context,
gl::InfoLog &infoLog);
angle::Result loadBinaryShaderExecutables(const gl::Context *context, angle::Result loadBinaryShaderExecutables(const gl::Context *context,
gl::BinaryInputStream *stream, gl::BinaryInputStream *stream,
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
// ParallelShaderCompileTest.cpp : Tests of the GL_KHR_parallel_shader_compile extension. // ParallelShaderCompileTest.cpp : Tests of the GL_KHR_parallel_shader_compile extension.
#include "test_utils/ANGLETest.h" #include "test_utils/ANGLETest.h"
#include "test_utils/gl_raii.h"
#include "util/random_utils.h" #include "util/random_utils.h"
...@@ -15,6 +16,14 @@ using namespace angle; ...@@ -15,6 +16,14 @@ using namespace angle;
namespace namespace
{ {
namespace
{
constexpr int kTaskCount = 32;
constexpr unsigned int kPollInterval = 100;
} // anonymous namespace
class ParallelShaderCompileTest : public ANGLETest class ParallelShaderCompileTest : public ANGLETest
{ {
protected: protected:
...@@ -46,46 +55,16 @@ class ParallelShaderCompileTest : public ANGLETest ...@@ -46,46 +55,16 @@ class ParallelShaderCompileTest : public ANGLETest
return true; return true;
} }
class ClearColorWithDraw class Task
{ {
public: public:
ClearColorWithDraw(GLubyte color) : mColor(color, color, color, 255) {} Task(int id) : mID(id) {}
virtual ~Task() {}
bool compile()
{
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() virtual bool compile() = 0;
{ virtual bool isCompileCompleted() = 0;
GLint status; virtual bool link() = 0;
glGetShaderiv(mVertexShader, GL_COMPLETION_STATUS_KHR, &status); virtual void runAndVerify(ParallelShaderCompileTest *test) = 0;
if (status == GL_TRUE)
{
glGetShaderiv(mFragmentShader, GL_COMPLETION_STATUS_KHR, &status);
return (status == GL_TRUE);
}
return false;
}
bool link()
{
mProgram = 0;
if (checkShader(mVertexShader) && checkShader(mFragmentShader))
{
mProgram = glCreateProgram();
glAttachShader(mProgram, mVertexShader);
glAttachShader(mProgram, mFragmentShader);
glLinkProgram(mProgram);
}
glDeleteShader(mVertexShader);
glDeleteShader(mFragmentShader);
return (mProgram != 0);
}
bool isLinkCompleted() bool isLinkCompleted()
{ {
...@@ -94,32 +73,13 @@ class ParallelShaderCompileTest : public ANGLETest ...@@ -94,32 +73,13 @@ class ParallelShaderCompileTest : public ANGLETest
return (status == GL_TRUE); return (status == GL_TRUE);
} }
void drawAndVerify(ParallelShaderCompileTest *test) protected:
{
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:
std::string insertRandomString(const std::string &source) std::string insertRandomString(const std::string &source)
{ {
RNG rng; RNG rng;
std::ostringstream ostream; std::ostringstream ostream;
ostream << "// Random string to fool program cache: " << rng.randomInt() << "\n" ostream << source << "\n// Random string to fool program cache: " << rng.randomInt()
<< source; << "\n";
return ostream.str(); return ostream.str();
} }
...@@ -161,45 +121,30 @@ class ParallelShaderCompileTest : public ANGLETest ...@@ -161,45 +121,30 @@ class ParallelShaderCompileTest : public ANGLETest
return (compileResult == GL_TRUE); return (compileResult == GL_TRUE);
} }
GLColor mColor;
GLuint mVertexShader;
GLuint mFragmentShader;
GLuint mProgram; GLuint mProgram;
int mID;
}; };
};
// Test basic functionality of GL_KHR_parallel_shader_compile
TEST_P(ParallelShaderCompileTest, Basic)
{
ANGLE_SKIP_TEST_IF(!ensureParallelShaderCompileExtensionAvailable());
GLint count = 0; template <typename T>
glMaxShaderCompilerThreadsKHR(8); class TaskRunner
EXPECT_GL_NO_ERROR(); {
glGetIntegerv(GL_MAX_SHADER_COMPILER_THREADS_KHR, &count); public:
EXPECT_GL_NO_ERROR(); TaskRunner() {}
EXPECT_EQ(8, count); ~TaskRunner() {}
}
// Test to compile and link many programs in parallel. void run(ParallelShaderCompileTest *test)
TEST_P(ParallelShaderCompileTest, LinkAndDrawManyPrograms) {
{
ANGLE_SKIP_TEST_IF(!ensureParallelShaderCompileExtensionAvailable());
std::vector<std::unique_ptr<ClearColorWithDraw>> compileTasks; std::vector<std::unique_ptr<T>> compileTasks;
constexpr int kTaskCount = 32;
for (int i = 0; i < kTaskCount; ++i) for (int i = 0; i < kTaskCount; ++i)
{ {
std::unique_ptr<ClearColorWithDraw> task( std::unique_ptr<T> task(new T(i));
new ClearColorWithDraw(static_cast<GLubyte>(i * 255 / kTaskCount)));
bool isCompiling = task->compile(); bool isCompiling = task->compile();
ASSERT_TRUE(isCompiling); ASSERT_TRUE(isCompiling);
compileTasks.push_back(std::move(task)); compileTasks.push_back(std::move(task));
} }
constexpr unsigned int kPollInterval = 100; std::vector<std::unique_ptr<T>> linkTasks;
std::vector<std::unique_ptr<ClearColorWithDraw>> linkTasks;
while (!compileTasks.empty()) while (!compileTasks.empty())
{ {
for (unsigned int i = 0; i < compileTasks.size();) for (unsigned int i = 0; i < compileTasks.size();)
...@@ -227,7 +172,7 @@ TEST_P(ParallelShaderCompileTest, LinkAndDrawManyPrograms) ...@@ -227,7 +172,7 @@ TEST_P(ParallelShaderCompileTest, LinkAndDrawManyPrograms)
if (task->isLinkCompleted()) if (task->isLinkCompleted())
{ {
task->drawAndVerify(this); task->runAndVerify(test);
linkTasks.erase(linkTasks.begin() + i); linkTasks.erase(linkTasks.begin() + i);
continue; continue;
} }
...@@ -235,6 +180,212 @@ TEST_P(ParallelShaderCompileTest, LinkAndDrawManyPrograms) ...@@ -235,6 +180,212 @@ TEST_P(ParallelShaderCompileTest, LinkAndDrawManyPrograms)
} }
Sleep(kPollInterval); 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_TEXTURE_UPDATE_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_TEXTURE_UPDATE_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)
{
ANGLE_SKIP_TEST_IF(!ensureParallelShaderCompileExtensionAvailable());
TaskRunner<ImageLoadStore> runner;
runner.run(this);
} }
ANGLE_INSTANTIATE_TEST(ParallelShaderCompileTest, ANGLE_INSTANTIATE_TEST(ParallelShaderCompileTest,
...@@ -245,4 +396,6 @@ ANGLE_INSTANTIATE_TEST(ParallelShaderCompileTest, ...@@ -245,4 +396,6 @@ ANGLE_INSTANTIATE_TEST(ParallelShaderCompileTest,
ES2_OPENGLES(), ES2_OPENGLES(),
ES2_VULKAN()); ES2_VULKAN());
ANGLE_INSTANTIATE_TEST(ParallelShaderCompileTestES31, ES31_OPENGL(), ES31_OPENGLES(), ES31_D3D11());
} // namespace } // namespace
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment