Vulkan: Move default uniform init to link job

Bug: angleproject:8297
Change-Id: I5bab916f452439d92afa65b9172574000ee0b587
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/4762838
Reviewed-by: Charlie Lao <cclao@google.com>
Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org>
Reviewed-by: Yuxin Hu <yuxinhu@google.com>
This commit is contained in:
Shahbaz Youssefi
2023-08-09 16:51:13 -04:00
committed by Angle LUCI CQ
parent f4e5c327d7
commit d8cd4dcdc9
10 changed files with 350 additions and 173 deletions

View File

@@ -1122,7 +1122,7 @@ GLuint Context::createShaderProgramv(ShaderType type, GLsizei count, const GLcha
// We must wait to mark the program separable until it's successfully compiled.
programObject->setSeparable(true);
programObject->attachShader(shaderObject);
programObject->attachShader(this, shaderObject);
if (programObject->link(this) != angle::Result::Continue)
{
@@ -6152,7 +6152,7 @@ void Context::attachShader(ShaderProgramID program, ShaderProgramID shader)
Program *programObject = mState.mShaderProgramManager->getProgram(program);
Shader *shaderObject = mState.mShaderProgramManager->getShader(shader);
ASSERT(programObject && shaderObject);
programObject->attachShader(shaderObject);
programObject->attachShader(this, shaderObject);
}
void Context::copyBufferSubData(BufferBinding readTarget,

View File

@@ -672,8 +672,8 @@ angle::Result GLES1Renderer::linkProgram(Context *context,
*programOut = program;
programObject->attachShader(getShader(vertexShader));
programObject->attachShader(getShader(fragmentShader));
programObject->attachShader(context, getShader(vertexShader));
programObject->attachShader(context, getShader(fragmentShader));
for (auto it : attribLocs)
{

View File

@@ -1056,8 +1056,9 @@ const std::string &Program::getLabel() const
return mState.mLabel;
}
void Program::attachShader(Shader *shader)
void Program::attachShader(const Context *context, Shader *shader)
{
resolveLink(context);
ShaderType shaderType = shader->getType();
ASSERT(shaderType != ShaderType::InvalidEnum);

View File

@@ -452,7 +452,7 @@ class Program final : public LabeledObject, public angle::Subject
return mProgram;
}
void attachShader(Shader *shader);
void attachShader(const Context *context, Shader *shader);
void detachShader(const Context *context, Shader *shader);
int getAttachedShadersCount() const;

View File

@@ -263,6 +263,47 @@ class Shader final : angle::NonCopyable, public LabeledObject
const std::vector<sh::ShaderVariable> &getAllAttributes(const Context *context);
const std::vector<sh::ShaderVariable> &getActiveOutputVariables(const Context *context);
const std::vector<sh::ShaderVariable> &getInputVaryingsCompiled()
{
ASSERT(!mState.compilePending());
return mState.getInputVaryings();
}
const std::vector<sh::ShaderVariable> &getOutputVaryingsCompiled()
{
ASSERT(!mState.compilePending());
return mState.getOutputVaryings();
}
const std::vector<sh::ShaderVariable> &getUniformsCompiled()
{
ASSERT(!mState.compilePending());
return mState.getUniforms();
}
const std::vector<sh::InterfaceBlock> &getUniformBlocksCompiled()
{
ASSERT(!mState.compilePending());
return mState.getUniformBlocks();
}
const std::vector<sh::InterfaceBlock> &getShaderStorageBlocksCompiled()
{
ASSERT(!mState.compilePending());
return mState.getShaderStorageBlocks();
}
const std::vector<sh::ShaderVariable> &getActiveAttributesCompiled()
{
ASSERT(!mState.compilePending());
return mState.getActiveAttributes();
}
const std::vector<sh::ShaderVariable> &getAllAttributesCompiled()
{
ASSERT(!mState.compilePending());
return mState.getAllAttributes();
}
const std::vector<sh::ShaderVariable> &getActiveOutputVariablesCompiled()
{
ASSERT(!mState.compilePending());
return mState.getActiveOutputVariables();
}
// Returns mapped name of a transform feedback varying. The original name may contain array
// brackets with an index inside, which will get copied to the mapped name. The varying must be
// known to be declared in the shader.

View File

@@ -1863,7 +1863,7 @@ void ProgramExecutableVk::onProgramBind(const gl::ProgramExecutable &glExecutabl
}
angle::Result ProgramExecutableVk::resizeUniformBlockMemory(
ContextVk *contextVk,
vk::Context *context,
const gl::ProgramExecutable &glExecutable,
const gl::ShaderMap<size_t> &requiredBufferSize)
{
@@ -1874,7 +1874,7 @@ angle::Result ProgramExecutableVk::resizeUniformBlockMemory(
if (!mDefaultUniformBlocks[shaderType]->uniformData.resize(
requiredBufferSize[shaderType]))
{
ANGLE_VK_CHECK(contextVk, false, VK_ERROR_OUT_OF_HOST_MEMORY);
ANGLE_VK_CHECK(context, false, VK_ERROR_OUT_OF_HOST_MEMORY);
}
// Initialize uniform buffer memory to zero by default.

View File

@@ -277,6 +277,11 @@ class ProgramExecutableVk
const gl::Program::DirtyBits &getDirtyBits() const { return mDirtyBits; }
void resetUniformBufferDirtyBits() { mDirtyBits.reset(); }
// The following functions are for internal use of programs, including from a threaded link job:
angle::Result resizeUniformBlockMemory(vk::Context *context,
const gl::ProgramExecutable &glExecutable,
const gl::ShaderMap<size_t> &requiredBufferSize);
private:
friend class ProgramVk;
friend class ProgramPipelineVk;
@@ -368,10 +373,6 @@ class ProgramExecutableVk
const vk::GraphicsPipelineDesc **descPtrOut,
vk::PipelineHelper **pipelineOut);
angle::Result resizeUniformBlockMemory(ContextVk *contextVk,
const gl::ProgramExecutable &glExecutable,
const gl::ShaderMap<size_t> &requiredBufferSize);
angle::Result getOrAllocateDescriptorSet(vk::Context *context,
UpdateDescriptorSetsBuilder *updateBuilder,
vk::CommandBufferHelperCommon *commandBufferHelper,

View File

@@ -22,17 +22,37 @@ namespace rx
namespace
{
class LinkTask final : public vk::Context, public angle::Closure
// Identical to Std140 encoder in all aspects, except it ignores opaque uniform types.
class VulkanDefaultBlockEncoder : public sh::Std140BlockEncoder
{
public:
LinkTask(RendererVk *renderer,
const gl::ProgramState &state,
const gl::ProgramExecutable &glExecutable,
ProgramExecutableVk *executable,
gl::ScopedShaderLinkLocks *shaderLocks,
bool isGLES1,
vk::PipelineRobustness pipelineRobustness,
vk::PipelineProtectedAccess pipelineProtectedAccess)
void advanceOffset(GLenum type,
const std::vector<unsigned int> &arraySizes,
bool isRowMajorMatrix,
int arrayStride,
int matrixStride) override
{
if (gl::IsOpaqueType(type))
{
return;
}
sh::Std140BlockEncoder::advanceOffset(type, arraySizes, isRowMajorMatrix, arrayStride,
matrixStride);
}
};
class LinkTaskVk final : public vk::Context, public angle::Closure
{
public:
LinkTaskVk(RendererVk *renderer,
const gl::ProgramState &state,
const gl::ProgramExecutable &glExecutable,
ProgramExecutableVk *executable,
gl::ScopedShaderLinkLocks *shaderLocks,
bool isGLES1,
vk::PipelineRobustness pipelineRobustness,
vk::PipelineProtectedAccess pipelineProtectedAccess)
: vk::Context(renderer),
mState(state),
mGlExecutable(glExecutable),
@@ -44,7 +64,11 @@ class LinkTask final : public vk::Context, public angle::Closure
mShaderLocks.swap(*shaderLocks);
}
void operator()() override;
void operator()() override
{
angle::Result result = linkImpl();
ASSERT((result == angle::Result::Continue) == (mErrorCode == VK_SUCCESS));
}
void handleError(VkResult result,
const char *file,
@@ -81,6 +105,13 @@ class LinkTask final : public vk::Context, public angle::Closure
}
private:
angle::Result linkImpl();
angle::Result initDefaultUniformBlocks();
void generateUniformLayoutMapping(gl::ShaderMap<sh::BlockLayoutMap> *layoutMapOut,
gl::ShaderMap<size_t> *requiredBufferSizeOut);
void initDefaultUniformLayoutMapping(gl::ShaderMap<sh::BlockLayoutMap> *layoutMapOut);
// The front-end ensures that the program is not accessed while linking, so it is safe to
// direclty access the state from a potentially parallel job.
const gl::ProgramState &mState;
@@ -101,14 +132,16 @@ class LinkTask final : public vk::Context, public angle::Closure
unsigned int mErrorLine = 0;
};
void LinkTask::operator()()
angle::Result LinkTaskVk::linkImpl()
{
ANGLE_TRACE_EVENT0("gpu.angle", "ProgramVk::LinkTask::run");
ANGLE_TRACE_EVENT0("gpu.angle", "ProgramVk::LinkTaskVk::run");
// Unlock the shaders at the end of the task.
gl::ScopedShaderLinkLocks unlockAtEnd;
unlockAtEnd.swap(mShaderLocks);
ANGLE_TRY(initDefaultUniformBlocks());
// Warm up the pipeline cache by creating a few placeholder pipelines. This is not done for
// separable programs, and is deferred to when the program pipeline is finalized.
//
@@ -121,60 +154,27 @@ void LinkTask::operator()()
// - Individual GLES1 tests are long, and this adds a considerable overhead to those tests
if (!mState.isSeparable() && !mIsGLES1)
{
angle::Result result =
mExecutable->warmUpPipelineCache(this, mGlExecutable, mPipelineRobustness,
mPipelineProtectedAccess, &mCompatibleRenderPass);
ASSERT((result == angle::Result::Continue) == (mErrorCode == VK_SUCCESS));
ANGLE_TRY(mExecutable->warmUpPipelineCache(this, mGlExecutable, mPipelineRobustness,
mPipelineProtectedAccess,
&mCompatibleRenderPass));
}
return angle::Result::Continue;
}
// The event for parallelized/lockless link.
class LinkEventVulkan final : public LinkEvent
angle::Result LinkTaskVk::initDefaultUniformBlocks()
{
public:
LinkEventVulkan(std::shared_ptr<angle::WorkerThreadPool> workerPool,
std::shared_ptr<LinkTask> linkTask)
: mLinkTask(linkTask),
mWaitableEvent(
std::shared_ptr<angle::WaitableEvent>(workerPool->postWorkerTask(mLinkTask)))
{}
// Process vertex and fragment uniforms into std140 packing.
gl::ShaderMap<sh::BlockLayoutMap> layoutMap;
gl::ShaderMap<size_t> requiredBufferSize;
requiredBufferSize.fill(0);
angle::Result wait(const gl::Context *context) override
{
ANGLE_TRACE_EVENT0("gpu.angle", "ProgramVK::LinkEvent::wait");
generateUniformLayoutMapping(&layoutMap, &requiredBufferSize);
initDefaultUniformLayoutMapping(&layoutMap);
mWaitableEvent->wait();
return mLinkTask->getResult(vk::GetImpl(context));
}
bool isLinking() override { return !mWaitableEvent->isReady(); }
private:
std::shared_ptr<LinkTask> mLinkTask;
std::shared_ptr<angle::WaitableEvent> mWaitableEvent;
};
// Identical to Std140 encoder in all aspects, except it ignores opaque uniform types.
class VulkanDefaultBlockEncoder : public sh::Std140BlockEncoder
{
public:
void advanceOffset(GLenum type,
const std::vector<unsigned int> &arraySizes,
bool isRowMajorMatrix,
int arrayStride,
int matrixStride) override
{
if (gl::IsOpaqueType(type))
{
return;
}
sh::Std140BlockEncoder::advanceOffset(type, arraySizes, isRowMajorMatrix, arrayStride,
matrixStride);
}
};
// All uniform initializations are complete, now resize the buffers accordingly and return
return mExecutable->resizeUniformBlockMemory(this, mGlExecutable, requiredBufferSize);
}
void InitDefaultUniformBlock(const std::vector<sh::ShaderVariable> &uniforms,
sh::BlockLayoutMap *blockLayoutMapOut,
@@ -202,6 +202,97 @@ void InitDefaultUniformBlock(const std::vector<sh::ShaderVariable> &uniforms,
return;
}
void LinkTaskVk::generateUniformLayoutMapping(gl::ShaderMap<sh::BlockLayoutMap> *layoutMapOut,
gl::ShaderMap<size_t> *requiredBufferSizeOut)
{
for (const gl::ShaderType shaderType : mGlExecutable.getLinkedShaderStages())
{
gl::Shader *shader = mState.getAttachedShader(shaderType);
if (shader)
{
const std::vector<sh::ShaderVariable> &uniforms = shader->getUniformsCompiled();
InitDefaultUniformBlock(uniforms, &(*layoutMapOut)[shaderType],
&(*requiredBufferSizeOut)[shaderType]);
}
}
}
void LinkTaskVk::initDefaultUniformLayoutMapping(gl::ShaderMap<sh::BlockLayoutMap> *layoutMapOut)
{
// Init the default block layout info.
const auto &uniforms = mGlExecutable.getUniforms();
for (const gl::VariableLocation &location : mState.getUniformLocations())
{
gl::ShaderMap<sh::BlockMemberInfo> layoutInfo;
if (location.used() && !location.ignored)
{
const auto &uniform = uniforms[location.index];
if (uniform.isInDefaultBlock() && !uniform.isSampler() && !uniform.isImage() &&
!uniform.isFragmentInOut())
{
std::string uniformName = mGlExecutable.getUniformNameByIndex(location.index);
if (uniform.isArray())
{
// Gets the uniform name without the [0] at the end.
uniformName = gl::StripLastArrayIndex(uniformName);
ASSERT(uniformName.size() !=
mGlExecutable.getUniformNameByIndex(location.index).size());
}
bool found = false;
for (const gl::ShaderType shaderType : mGlExecutable.getLinkedShaderStages())
{
auto it = (*layoutMapOut)[shaderType].find(uniformName);
if (it != (*layoutMapOut)[shaderType].end())
{
found = true;
layoutInfo[shaderType] = it->second;
}
}
ASSERT(found);
}
}
for (const gl::ShaderType shaderType : mGlExecutable.getLinkedShaderStages())
{
mExecutable->getSharedDefaultUniformBlock(shaderType)
->uniformLayout.push_back(layoutInfo[shaderType]);
}
}
}
// The event for parallelized/lockless link.
class LinkEventVulkan final : public LinkEvent
{
public:
LinkEventVulkan(std::shared_ptr<angle::WorkerThreadPool> workerPool,
std::shared_ptr<LinkTaskVk> linkTask)
: mLinkTask(linkTask),
mWaitableEvent(
std::shared_ptr<angle::WaitableEvent>(workerPool->postWorkerTask(mLinkTask)))
{}
angle::Result wait(const gl::Context *context) override
{
ANGLE_TRACE_EVENT0("gpu.angle", "ProgramVK::LinkEvent::wait");
mWaitableEvent->wait();
return mLinkTask->getResult(vk::GetImpl(context));
}
bool isLinking() override { return !mWaitableEvent->isReady(); }
private:
std::shared_ptr<LinkTaskVk> mLinkTask;
std::shared_ptr<angle::WaitableEvent> mWaitableEvent;
};
template <typename T>
void UpdateDefaultUniformBlock(GLsizei count,
uint32_t arrayIndex,
@@ -322,6 +413,18 @@ std::unique_ptr<LinkEvent> ProgramVk::link(const gl::Context *context,
{
ANGLE_TRACE_EVENT0("gpu.angle", "ProgramVk::link");
// Make sure no compile jobs are pending.
// TODO: move this to the link job itself. http://anglebug.com/8297
const gl::ProgramExecutable &programExecutable = mState.getExecutable();
for (const gl::ShaderType shaderType : programExecutable.getLinkedShaderStages())
{
gl::Shader *shader = mState.getAttachedShader(shaderType);
if (shader)
{
shader->resolveCompile(context);
}
}
ContextVk *contextVk = vk::GetImpl(context);
// Link resources before calling GetShaderSource to make sure they are ready for the set/binding
// assignment done in that function.
@@ -343,8 +446,7 @@ std::unique_ptr<LinkEvent> ProgramVk::link(const gl::Context *context,
}
// Compile the shaders.
const gl::ProgramExecutable &programExecutable = mState.getExecutable();
angle::Result status = mExecutable.mOriginalShaderInfo.initShaders(
angle::Result status = mExecutable.mOriginalShaderInfo.initShaders(
contextVk, programExecutable.getLinkedShaderStages(), spirvBlobs,
mExecutable.mVariableInfoMap);
if (status != angle::Result::Continue)
@@ -352,21 +454,13 @@ std::unique_ptr<LinkEvent> ProgramVk::link(const gl::Context *context,
return std::make_unique<LinkEventDone>(status);
}
status = initDefaultUniformBlocks(context);
if (status != angle::Result::Continue)
{
return std::make_unique<LinkEventDone>(status);
}
// TODO(jie.a.chen@intel.com): Parallelize linking.
// http://crbug.com/849576
status = mExecutable.createPipelineLayout(contextVk, programExecutable, nullptr);
if (status != angle::Result::Continue)
{
return std::make_unique<LinkEventDone>(status);
}
std::shared_ptr<LinkTask> linkTask = std::make_shared<LinkTask>(
std::shared_ptr<LinkTaskVk> linkTask = std::make_shared<LinkTaskVk>(
contextVk->getRenderer(), mState, programExecutable, &mExecutable, shaderLocks,
context->getState().isGLES1(), contextVk->pipelineRobustness(),
contextVk->pipelineProtectedAccess());
@@ -382,91 +476,6 @@ void ProgramVk::linkResources(const gl::Context *context,
linker.linkResources(context, mState, resources);
}
angle::Result ProgramVk::initDefaultUniformBlocks(const gl::Context *glContext)
{
ContextVk *contextVk = vk::GetImpl(glContext);
// Process vertex and fragment uniforms into std140 packing.
gl::ShaderMap<sh::BlockLayoutMap> layoutMap;
gl::ShaderMap<size_t> requiredBufferSize;
requiredBufferSize.fill(0);
generateUniformLayoutMapping(glContext, layoutMap, requiredBufferSize);
initDefaultUniformLayoutMapping(layoutMap);
// All uniform initializations are complete, now resize the buffers accordingly and return
return mExecutable.resizeUniformBlockMemory(contextVk, mState.getExecutable(),
requiredBufferSize);
}
void ProgramVk::generateUniformLayoutMapping(const gl::Context *context,
gl::ShaderMap<sh::BlockLayoutMap> &layoutMap,
gl::ShaderMap<size_t> &requiredBufferSize)
{
const gl::ProgramExecutable &glExecutable = mState.getExecutable();
for (const gl::ShaderType shaderType : glExecutable.getLinkedShaderStages())
{
gl::Shader *shader = mState.getAttachedShader(shaderType);
if (shader)
{
const std::vector<sh::ShaderVariable> &uniforms = shader->getUniforms(context);
InitDefaultUniformBlock(uniforms, &layoutMap[shaderType],
&requiredBufferSize[shaderType]);
}
}
}
void ProgramVk::initDefaultUniformLayoutMapping(gl::ShaderMap<sh::BlockLayoutMap> &layoutMap)
{
// Init the default block layout info.
const auto &uniforms = mState.getUniforms();
const gl::ProgramExecutable &glExecutable = mState.getExecutable();
for (const gl::VariableLocation &location : mState.getUniformLocations())
{
gl::ShaderMap<sh::BlockMemberInfo> layoutInfo;
if (location.used() && !location.ignored)
{
const auto &uniform = uniforms[location.index];
if (uniform.isInDefaultBlock() && !uniform.isSampler() && !uniform.isImage() &&
!uniform.isFragmentInOut())
{
std::string uniformName = glExecutable.getUniformNameByIndex(location.index);
if (uniform.isArray())
{
// Gets the uniform name without the [0] at the end.
uniformName = gl::StripLastArrayIndex(uniformName);
ASSERT(uniformName.size() !=
glExecutable.getUniformNameByIndex(location.index).size());
}
bool found = false;
for (const gl::ShaderType shaderType : glExecutable.getLinkedShaderStages())
{
auto it = layoutMap[shaderType].find(uniformName);
if (it != layoutMap[shaderType].end())
{
found = true;
layoutInfo[shaderType] = it->second;
}
}
ASSERT(found);
}
}
for (const gl::ShaderType shaderType : glExecutable.getLinkedShaderStages())
{
mExecutable.mDefaultUniformBlocks[shaderType]->uniformLayout.push_back(
layoutInfo[shaderType]);
}
}
}
GLboolean ProgramVk::validate(const gl::Caps &caps, gl::InfoLog *infoLog)
{
// No-op. The spec is very vague about the behavior of validation.

View File

@@ -128,11 +128,6 @@ class ProgramVk : public ProgramImpl
const GLfloat *value);
void reset(ContextVk *contextVk);
angle::Result initDefaultUniformBlocks(const gl::Context *glContext);
void generateUniformLayoutMapping(const gl::Context *context,
gl::ShaderMap<sh::BlockLayoutMap> &layoutMap,
gl::ShaderMap<size_t> &requiredBufferSize);
void initDefaultUniformLayoutMapping(gl::ShaderMap<sh::BlockLayoutMap> &layoutMap);
template <class T>
void getUniformImpl(GLint location, T *v, GLenum entryPointType) const;

View File

@@ -27,6 +27,12 @@ class LinkAndRelinkTestES31 : public ANGLETest<>
LinkAndRelinkTestES31() {}
};
class LinkAndRelinkTestES32 : public ANGLETest<>
{
protected:
LinkAndRelinkTestES32() {}
};
// When a program link or relink fails, if you try to install the unsuccessfully
// linked program (via UseProgram) and start rendering or dispatch compute,
// We can not always report INVALID_OPERATION for rendering/compute pipeline.
@@ -445,6 +451,130 @@ void main()
EXPECT_GL_NO_ERROR();
}
// Parallel link should continue unscathed even if the attached shaders to the program are modified.
TEST_P(LinkAndRelinkTestES31, ReattachShadersWhileParallelLinking)
{
constexpr char kVS[] = R"(#version 300 es
void main()
{
vec2 position = vec2(-1, -1);
if (gl_VertexID == 1)
position = vec2(3, -1);
else if (gl_VertexID == 2)
position = vec2(-1, 3);
gl_Position = vec4(position, 0, 1);
})";
constexpr char kFSGreen[] = R"(#version 300 es
out mediump vec4 color;
void main()
{
color = vec4(0, 1, 0, 1);
})";
constexpr char kFSRed[] = R"(#version 300 es
out mediump vec4 color;
void main()
{
color = vec4(1, 0, 0, 1);
})";
GLuint program = glCreateProgram();
GLuint vs = CompileShader(GL_VERTEX_SHADER, kVS);
GLuint green = CompileShader(GL_FRAGMENT_SHADER, kFSGreen);
GLuint red = CompileShader(GL_FRAGMENT_SHADER, kFSRed);
EXPECT_NE(0u, vs);
EXPECT_NE(0u, green);
EXPECT_NE(0u, red);
glAttachShader(program, vs);
glAttachShader(program, green);
glLinkProgram(program);
ASSERT_GL_NO_ERROR();
// Immediately reattach another shader
glDetachShader(program, green);
glAttachShader(program, red);
ASSERT_GL_NO_ERROR();
// Make sure the linked program draws with green
glUseProgram(program);
ASSERT_GL_NO_ERROR();
glDrawArrays(GL_TRIANGLES, 0, 3);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
ASSERT_GL_NO_ERROR();
glDeleteShader(vs);
glDeleteShader(green);
glDeleteShader(red);
ASSERT_GL_NO_ERROR();
}
// Parallel link should continue unscathed even if new shaders are attached to the program.
TEST_P(LinkAndRelinkTestES31, AttachNewShadersWhileParallelLinking)
{
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_geometry_shader"));
constexpr char kVS[] = R"(#version 310 es
#extension GL_EXT_geometry_shader : require
void main()
{
vec2 position = vec2(-1, -1);
if (gl_VertexID == 1)
position = vec2(3, -1);
else if (gl_VertexID == 2)
position = vec2(-1, 3);
gl_Position = vec4(position, 0, 1);
})";
constexpr char kFS[] = R"(#version 310 es
#extension GL_EXT_geometry_shader : require
out mediump vec4 color;
void main()
{
color = vec4(0, 1, 0, 1);
})";
constexpr char kGS[] = R"(#version 310 es
#extension GL_EXT_geometry_shader : require
layout (invocations = 3, triangles) in;
layout (triangle_strip, max_vertices = 3) out;
void main()
{
})";
GLuint program = glCreateProgram();
GLuint vs = CompileShader(GL_VERTEX_SHADER, kVS);
GLuint fs = CompileShader(GL_FRAGMENT_SHADER, kFS);
GLuint gs = CompileShader(GL_GEOMETRY_SHADER, kGS);
EXPECT_NE(0u, vs);
EXPECT_NE(0u, fs);
EXPECT_NE(0u, gs);
glAttachShader(program, vs);
glAttachShader(program, fs);
glLinkProgram(program);
ASSERT_GL_NO_ERROR();
// Immediately attach another shader
glAttachShader(program, gs);
ASSERT_GL_NO_ERROR();
// Make sure the linked program draws with green
glUseProgram(program);
ASSERT_GL_NO_ERROR();
glDrawArrays(GL_TRIANGLES, 0, 3);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
ASSERT_GL_NO_ERROR();
glDeleteShader(vs);
glDeleteShader(fs);
glDeleteShader(gs);
ASSERT_GL_NO_ERROR();
}
ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(LinkAndRelinkTest);
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(LinkAndRelinkTestES31);