diff --git a/src/libANGLE/Context.cpp b/src/libANGLE/Context.cpp index 800061d5a..b76f67e6d 100644 --- a/src/libANGLE/Context.cpp +++ b/src/libANGLE/Context.cpp @@ -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, diff --git a/src/libANGLE/GLES1Renderer.cpp b/src/libANGLE/GLES1Renderer.cpp index 3f46313ec..b51e0d5a5 100644 --- a/src/libANGLE/GLES1Renderer.cpp +++ b/src/libANGLE/GLES1Renderer.cpp @@ -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) { diff --git a/src/libANGLE/Program.cpp b/src/libANGLE/Program.cpp index 1ef902a6c..18337ea89 100644 --- a/src/libANGLE/Program.cpp +++ b/src/libANGLE/Program.cpp @@ -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); diff --git a/src/libANGLE/Program.h b/src/libANGLE/Program.h index 21a26406d..150899261 100644 --- a/src/libANGLE/Program.h +++ b/src/libANGLE/Program.h @@ -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; diff --git a/src/libANGLE/Shader.h b/src/libANGLE/Shader.h index 2ee3e7e6e..0923ea75b 100644 --- a/src/libANGLE/Shader.h +++ b/src/libANGLE/Shader.h @@ -263,6 +263,47 @@ class Shader final : angle::NonCopyable, public LabeledObject const std::vector &getAllAttributes(const Context *context); const std::vector &getActiveOutputVariables(const Context *context); + const std::vector &getInputVaryingsCompiled() + { + ASSERT(!mState.compilePending()); + return mState.getInputVaryings(); + } + const std::vector &getOutputVaryingsCompiled() + { + ASSERT(!mState.compilePending()); + return mState.getOutputVaryings(); + } + const std::vector &getUniformsCompiled() + { + ASSERT(!mState.compilePending()); + return mState.getUniforms(); + } + const std::vector &getUniformBlocksCompiled() + { + ASSERT(!mState.compilePending()); + return mState.getUniformBlocks(); + } + const std::vector &getShaderStorageBlocksCompiled() + { + ASSERT(!mState.compilePending()); + return mState.getShaderStorageBlocks(); + } + const std::vector &getActiveAttributesCompiled() + { + ASSERT(!mState.compilePending()); + return mState.getActiveAttributes(); + } + const std::vector &getAllAttributesCompiled() + { + ASSERT(!mState.compilePending()); + return mState.getAllAttributes(); + } + const std::vector &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. diff --git a/src/libANGLE/renderer/vulkan/ProgramExecutableVk.cpp b/src/libANGLE/renderer/vulkan/ProgramExecutableVk.cpp index 595809b2f..9e6626add 100644 --- a/src/libANGLE/renderer/vulkan/ProgramExecutableVk.cpp +++ b/src/libANGLE/renderer/vulkan/ProgramExecutableVk.cpp @@ -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 &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. diff --git a/src/libANGLE/renderer/vulkan/ProgramExecutableVk.h b/src/libANGLE/renderer/vulkan/ProgramExecutableVk.h index ea610ea63..a907ad30b 100644 --- a/src/libANGLE/renderer/vulkan/ProgramExecutableVk.h +++ b/src/libANGLE/renderer/vulkan/ProgramExecutableVk.h @@ -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 &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 &requiredBufferSize); - angle::Result getOrAllocateDescriptorSet(vk::Context *context, UpdateDescriptorSetsBuilder *updateBuilder, vk::CommandBufferHelperCommon *commandBufferHelper, diff --git a/src/libANGLE/renderer/vulkan/ProgramVk.cpp b/src/libANGLE/renderer/vulkan/ProgramVk.cpp index 52d07469e..af42296ca 100644 --- a/src/libANGLE/renderer/vulkan/ProgramVk.cpp +++ b/src/libANGLE/renderer/vulkan/ProgramVk.cpp @@ -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 &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 *layoutMapOut, + gl::ShaderMap *requiredBufferSizeOut); + void initDefaultUniformLayoutMapping(gl::ShaderMap *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 workerPool, - std::shared_ptr linkTask) - : mLinkTask(linkTask), - mWaitableEvent( - std::shared_ptr(workerPool->postWorkerTask(mLinkTask))) - {} + // Process vertex and fragment uniforms into std140 packing. + gl::ShaderMap layoutMap; + gl::ShaderMap 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 mLinkTask; - std::shared_ptr 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 &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 &uniforms, sh::BlockLayoutMap *blockLayoutMapOut, @@ -202,6 +202,97 @@ void InitDefaultUniformBlock(const std::vector &uniforms, return; } +void LinkTaskVk::generateUniformLayoutMapping(gl::ShaderMap *layoutMapOut, + gl::ShaderMap *requiredBufferSizeOut) +{ + for (const gl::ShaderType shaderType : mGlExecutable.getLinkedShaderStages()) + { + gl::Shader *shader = mState.getAttachedShader(shaderType); + + if (shader) + { + const std::vector &uniforms = shader->getUniformsCompiled(); + InitDefaultUniformBlock(uniforms, &(*layoutMapOut)[shaderType], + &(*requiredBufferSizeOut)[shaderType]); + } + } +} + +void LinkTaskVk::initDefaultUniformLayoutMapping(gl::ShaderMap *layoutMapOut) +{ + // Init the default block layout info. + const auto &uniforms = mGlExecutable.getUniforms(); + + for (const gl::VariableLocation &location : mState.getUniformLocations()) + { + gl::ShaderMap 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 workerPool, + std::shared_ptr linkTask) + : mLinkTask(linkTask), + mWaitableEvent( + std::shared_ptr(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 mLinkTask; + std::shared_ptr mWaitableEvent; +}; + template void UpdateDefaultUniformBlock(GLsizei count, uint32_t arrayIndex, @@ -322,6 +413,18 @@ std::unique_ptr 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 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 ProgramVk::link(const gl::Context *context, return std::make_unique(status); } - status = initDefaultUniformBlocks(context); - if (status != angle::Result::Continue) - { - return std::make_unique(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(status); } - std::shared_ptr linkTask = std::make_shared( + std::shared_ptr linkTask = std::make_shared( 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 layoutMap; - gl::ShaderMap 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 &layoutMap, - gl::ShaderMap &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 &uniforms = shader->getUniforms(context); - InitDefaultUniformBlock(uniforms, &layoutMap[shaderType], - &requiredBufferSize[shaderType]); - } - } -} - -void ProgramVk::initDefaultUniformLayoutMapping(gl::ShaderMap &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 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. diff --git a/src/libANGLE/renderer/vulkan/ProgramVk.h b/src/libANGLE/renderer/vulkan/ProgramVk.h index 25906770a..ec096ac63 100644 --- a/src/libANGLE/renderer/vulkan/ProgramVk.h +++ b/src/libANGLE/renderer/vulkan/ProgramVk.h @@ -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 &layoutMap, - gl::ShaderMap &requiredBufferSize); - void initDefaultUniformLayoutMapping(gl::ShaderMap &layoutMap); template void getUniformImpl(GLint location, T *v, GLenum entryPointType) const; diff --git a/src/tests/gl_tests/LinkAndRelinkTest.cpp b/src/tests/gl_tests/LinkAndRelinkTest.cpp index 4fa5a6ca7..1d2c3e60c 100644 --- a/src/tests/gl_tests/LinkAndRelinkTest.cpp +++ b/src/tests/gl_tests/LinkAndRelinkTest.cpp @@ -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);