Consolidate parallel compilation in front-end

This cleans up the multiple compilation task implementations in the
backends and consolidates them in the front-end.  The front-end is then
able to do the compilation in an unlocked tail call instead if
desired (in a future change).

This change is in preparation for having the program link tasks directly
wait on the shader compilation tasks.  As a result, the "shader resolve"
should not be needed to access the shader compilation results; it should
be enough to wait for the compilation job.  This change therefore moves
post-processing of results to the compilation job itself as they did not
need to actually be done after compilation is done (merely after
translation is done).  As a side effect, shader substition and other
debug features should now work for the GL backend as they are now done
before back-end compilation.

Bug: angleproject:8297
Change-Id: Ib9274b1149fadca7545956a864d6635b6cba5c3a
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/4994655
Reviewed-by: Geoff Lang <geofflang@chromium.org>
Reviewed-by: Charlie Lao <cclao@google.com>
Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org>
diff --git a/include/platform/autogen/FrontendFeatures_autogen.h b/include/platform/autogen/FrontendFeatures_autogen.h
index 7e4cc5c..d8bb26b 100644
--- a/include/platform/autogen/FrontendFeatures_autogen.h
+++ b/include/platform/autogen/FrontendFeatures_autogen.h
@@ -161,6 +161,13 @@
         &members, "http://anglebug.com/8280"
     };
 
+    FeatureInfo compileJobIsThreadSafe = {
+        "compileJobIsThreadSafe",
+        FeatureCategory::FrontendFeatures,
+        "If false, parts of the compile job cannot be parallelized",
+        &members, "http://anglebug.com/8297"
+    };
+
     FeatureInfo linkJobIsThreadSafe = {
         "linkJobIsThreadSafe",
         FeatureCategory::FrontendFeatures,
diff --git a/include/platform/frontend_features.json b/include/platform/frontend_features.json
index fbee157..722f666 100644
--- a/include/platform/frontend_features.json
+++ b/include/platform/frontend_features.json
@@ -163,6 +163,14 @@
             "issue": "http://anglebug.com/8280"
         },
         {
+            "name": "compile_job_is_thread_safe",
+            "category": "Features",
+            "description": [
+                "If false, parts of the compile job cannot be parallelized"
+            ],
+            "issue": "http://anglebug.com/8297"
+        },
+        {
             "name": "link_job_is_thread_safe",
             "category": "Features",
             "description": [
diff --git a/src/libANGLE/Shader.cpp b/src/libANGLE/Shader.cpp
index 17a45a0..281500e 100644
--- a/src/libANGLE/Shader.cpp
+++ b/src/libANGLE/Shader.cpp
@@ -28,6 +28,7 @@
 #include "libANGLE/ResourceManager.h"
 #include "libANGLE/renderer/GLImplFactory.h"
 #include "libANGLE/renderer/ShaderImpl.h"
+#include "libANGLE/trace.h"
 #include "platform/autogen/FrontendFeatures_autogen.h"
 
 namespace gl
@@ -59,6 +60,229 @@
 
     return path.str();
 }
+
+class CompileTask final : public angle::Closure
+{
+  public:
+    CompileTask(const angle::FrontendFeatures &frontendFeatures,
+                ShHandle compilerHandle,
+                ShShaderOutput outputType,
+                const ShCompileOptions &options,
+                const std::string &source,
+                size_t sourceHash,
+                const SharedCompiledShaderState &compiledState,
+                size_t maxComputeWorkGroupInvocations,
+                size_t maxComputeSharedMemory,
+                std::shared_ptr<rx::ShaderTranslateTask> &&translateTask)
+        : mFrontendFeatures(frontendFeatures),
+          mMaxComputeWorkGroupInvocations(maxComputeWorkGroupInvocations),
+          mMaxComputeSharedMemory(maxComputeSharedMemory),
+          mCompilerHandle(compilerHandle),
+          mOutputType(outputType),
+          mOptions(options),
+          mSource(source),
+          mSourceHash(sourceHash),
+          mCompiledState(compiledState),
+          mTranslateTask(std::move(translateTask))
+    {}
+    ~CompileTask() override = default;
+
+    void operator()() override { mResult = compileImpl(); }
+
+    angle::Result getResult()
+    {
+        ANGLE_TRY(mResult);
+        ANGLE_TRY(mTranslateTask->getResult(mInfoLog));
+
+        return angle::Result::Continue;
+    }
+
+    bool isCompilingInternally() { return mTranslateTask->isCompilingInternally(); }
+
+    std::string &&getInfoLog() { return std::move(mInfoLog); }
+
+  private:
+    angle::Result compileImpl();
+    angle::Result postTranslate();
+
+    // Global constants that are safe to access by the worker thread
+    const angle::FrontendFeatures &mFrontendFeatures;
+    size_t mMaxComputeWorkGroupInvocations;
+    size_t mMaxComputeSharedMemory;
+
+    // Access to the compile information which are unchanged for the duration of compilation (for
+    // example shader source which cannot be changed until compilation is finished) or are kept
+    // alive (for example the compiler instance in CompilingState)
+    ShHandle mCompilerHandle;
+    ShShaderOutput mOutputType;
+    ShCompileOptions mOptions;
+    const std::string mSource;
+    size_t mSourceHash;
+    SharedCompiledShaderState mCompiledState;
+
+    std::shared_ptr<rx::ShaderTranslateTask> mTranslateTask;
+    angle::Result mResult;
+    std::string mInfoLog;
+};
+
+class CompileEvent final
+{
+  public:
+    CompileEvent(const std::shared_ptr<CompileTask> &compileTask,
+                 const std::shared_ptr<angle::WaitableEvent> &waitEvent)
+        : mCompileTask(compileTask), mWaitableEvent(waitEvent)
+    {}
+    ~CompileEvent() = default;
+
+    angle::Result wait()
+    {
+        ANGLE_TRACE_EVENT0("gpu.angle", "CompileEvent::wait");
+
+        mWaitableEvent->wait();
+
+        return mCompileTask->getResult();
+    }
+    bool isCompiling()
+    {
+        return !mWaitableEvent->isReady() || mCompileTask->isCompilingInternally();
+    }
+
+    std::string &&getInfoLog() { return std::move(mCompileTask->getInfoLog()); }
+
+  private:
+    std::shared_ptr<CompileTask> mCompileTask;
+    std::shared_ptr<angle::WaitableEvent> mWaitableEvent;
+};
+
+angle::Result CompileTask::compileImpl()
+{
+    // Call the translator and get the info log
+    bool result = mTranslateTask->translate(mCompilerHandle, mOptions, mSource);
+    mInfoLog    = sh::GetInfoLog(mCompilerHandle);
+    if (!result)
+    {
+        return angle::Result::Stop;
+    }
+
+    // Process the translation results itself; gather compilation info, substitute the shader if
+    // being overriden, etc.
+    return postTranslate();
+}
+
+angle::Result CompileTask::postTranslate()
+{
+    const bool isBinaryOutput = mOutputType == SH_SPIRV_VULKAN_OUTPUT;
+    mCompiledState->buildCompiledShaderState(mCompilerHandle, isBinaryOutput);
+
+    ASSERT(!mCompiledState->translatedSource.empty() || !mCompiledState->compiledBinary.empty());
+
+    // Validation checks for compute shaders
+    if (mCompiledState->shaderType == ShaderType::Compute && mCompiledState->localSize.isDeclared())
+    {
+        angle::CheckedNumeric<size_t> checked_local_size_product(mCompiledState->localSize[0]);
+        checked_local_size_product *= mCompiledState->localSize[1];
+        checked_local_size_product *= mCompiledState->localSize[2];
+
+        if (!checked_local_size_product.IsValid() ||
+            checked_local_size_product.ValueOrDie() > mMaxComputeWorkGroupInvocations)
+        {
+            mInfoLog +=
+                "\nThe total number of invocations within a work group exceeds "
+                "MAX_COMPUTE_WORK_GROUP_INVOCATIONS.";
+            return angle::Result::Stop;
+        }
+    }
+
+    unsigned int sharedMemSize = sh::GetShaderSharedMemorySize(mCompilerHandle);
+    if (sharedMemSize > mMaxComputeSharedMemory)
+    {
+        mInfoLog += "\nShared memory size exceeds GL_MAX_COMPUTE_SHARED_MEMORY_SIZE";
+        return angle::Result::Stop;
+    }
+
+    bool substitutedTranslatedShader = false;
+    const char *suffix               = "translated";
+    if (mFrontendFeatures.enableTranslatedShaderSubstitution.enabled)
+    {
+        // To support reading/writing compiled binaries (SPIR-V representation), need more file
+        // input/output facilities, and figure out the byte ordering of writing the 32-bit words to
+        // disk.
+        if (isBinaryOutput)
+        {
+            INFO() << "Can not substitute compiled binary (SPIR-V) shaders yet";
+        }
+        else
+        {
+            std::string substituteShaderPath = GetShaderDumpFilePath(mSourceHash, suffix);
+
+            std::string substituteShader;
+            if (angle::ReadFileToString(substituteShaderPath, &substituteShader))
+            {
+                mCompiledState->translatedSource = std::move(substituteShader);
+                substitutedTranslatedShader      = true;
+                INFO() << "Translated shader substitute found, loading from "
+                       << substituteShaderPath;
+            }
+        }
+    }
+
+    // Only dump translated shaders that have not been previously substituted. It would write the
+    // same data back to the file.
+    if (mFrontendFeatures.dumpTranslatedShaders.enabled && !substitutedTranslatedShader)
+    {
+        if (isBinaryOutput)
+        {
+            INFO() << "Can not dump compiled binary (SPIR-V) shaders yet";
+        }
+        else
+        {
+            std::string dumpFile = GetShaderDumpFilePath(mSourceHash, suffix);
+
+            const std::string &translatedSource = mCompiledState->translatedSource;
+            writeFile(dumpFile.c_str(), translatedSource.c_str(), translatedSource.length());
+            INFO() << "Dumped translated source: " << dumpFile;
+        }
+    }
+
+#if defined(ANGLE_ENABLE_ASSERTS)
+    if (!isBinaryOutput)
+    {
+        // Prefix translated shader with commented out un-translated shader.
+        // Useful in diagnostics tools which capture the shader source.
+        std::ostringstream shaderStream;
+        shaderStream << "// GLSL\n";
+        shaderStream << "//\n";
+
+        std::istringstream inputSourceStream(mSource);
+        std::string line;
+        while (std::getline(inputSourceStream, line))
+        {
+            // Remove null characters from the source line
+            line.erase(std::remove(line.begin(), line.end(), '\0'), line.end());
+
+            shaderStream << "// " << line;
+
+            // glslang complains if a comment ends with backslash
+            if (!line.empty() && line.back() == '\\')
+            {
+                shaderStream << "\\";
+            }
+
+            shaderStream << std::endl;
+        }
+        shaderStream << "\n\n";
+        shaderStream << mCompiledState->translatedSource;
+        mCompiledState->translatedSource = shaderStream.str();
+    }
+#endif  // defined(ANGLE_ENABLE_ASSERTS)
+
+    // Let the backend process the result of the compilation.  For the GL backend, this means
+    // kicking off compilation internally.  Some of the other backends fill in their internal
+    // "compiled state" at this point.
+    mTranslateTask->postTranslate(mCompilerHandle, *mCompiledState.get());
+
+    return angle::Result::Continue;
+}
 }  // anonymous namespace
 
 const char *GetShaderTypeString(ShaderType type)
@@ -111,19 +335,9 @@
     return name.str();
 }
 
-class [[nodiscard]] ScopedExit final : angle::NonCopyable
-{
-  public:
-    ScopedExit(std::function<void()> exit) : mExit(exit) {}
-    ~ScopedExit() { mExit(); }
-
-  private:
-    std::function<void()> mExit;
-};
-
 struct Shader::CompilingState
 {
-    std::shared_ptr<rx::WaitableCompileEvent> compileEvent;
+    std::unique_ptr<CompileEvent> compileEvent;
     ShCompilerInstance shCompilerInstance;
 };
 
@@ -144,9 +358,7 @@
       mHandle(handle),
       mRefCount(0),
       mDeleteStatus(false),
-      mResourceManager(manager),
-      mCurrentMaxComputeWorkGroupInvocations(0u),
-      mMaxComputeSharedMemory(0u)
+      mResourceManager(manager)
 {
     ASSERT(mImplementation);
 }
@@ -392,10 +604,6 @@
 
     mInfoLog.clear();
 
-    mCurrentMaxComputeWorkGroupInvocations =
-        static_cast<GLuint>(context->getCaps().maxComputeWorkGroupInvocations);
-    mMaxComputeSharedMemory = context->getCaps().maxComputeSharedMemorySize;
-
     ShCompileOptions options = {};
     options.objectCode       = true;
     options.emulateGLDrawID  = true;
@@ -431,6 +639,10 @@
         options.initializeUninitializedLocals = true;
     }
 
+#if defined(ANGLE_ENABLE_ASSERTS)
+    options.validateAST = true;
+#endif
+
     mBoundCompiler.set(context, context->getCompiler());
 
     ASSERT(mBoundCompiler.get());
@@ -457,10 +669,37 @@
 
     // Cache load failed, fall through normal compiling.
     mState.mCompileStatus = CompileStatus::COMPILE_REQUESTED;
-    mCompilingState.reset(new CompilingState());
+
+    // Ask the backend to prepare the translate task
+    std::shared_ptr<rx::ShaderTranslateTask> translateTask =
+        mImplementation->compile(context, &options);
+
+    // Prepare the complete compile task
+    const size_t maxComputeWorkGroupInvocations =
+        static_cast<size_t>(context->getCaps().maxComputeWorkGroupInvocations);
+    const size_t maxComputeSharedMemory = context->getCaps().maxComputeSharedMemorySize;
+
+    std::shared_ptr<CompileTask> compileTask(
+        new CompileTask(context->getFrontendFeatures(), compilerInstance.getHandle(),
+                        compilerInstance.getShaderOutputType(), options, mState.mSource,
+                        mState.mSourceHash, mState.mCompiledState, maxComputeWorkGroupInvocations,
+                        maxComputeSharedMemory, std::move(translateTask)));
+
+    // The GL backend relies on the driver's internal parallel compilation, and thus does not use a
+    // thread to compile.  A front-end feature selects whether the single-threaded pool must be
+    // used.
+    std::shared_ptr<angle::WorkerThreadPool> compileWorkerPool =
+        context->getFrontendFeatures().compileJobIsThreadSafe.enabled
+            ? context->getShaderCompileThreadPool()
+            : context->getSingleThreadPool();
+
+    // TODO: add the possibility to perform this in an unlocked tail call.  http://anglebug.com/8297
+    std::shared_ptr<angle::WaitableEvent> compileEvent =
+        compileWorkerPool->postWorkerTask(compileTask);
+
+    mCompilingState                     = std::make_unique<CompilingState>();
     mCompilingState->shCompilerInstance = std::move(compilerInstance);
-    mCompilingState->compileEvent =
-        mImplementation->compile(context, &(mCompilingState->shCompilerInstance), &options);
+    mCompilingState->compileEvent       = std::make_unique<CompileEvent>(compileTask, compileEvent);
 }
 
 void Shader::resolveCompile(const Context *context)
@@ -473,160 +712,30 @@
     ASSERT(mCompilingState.get());
     mState.mCompileStatus = CompileStatus::IS_RESOLVING;
 
-    mCompilingState->compileEvent->wait();
+    angle::Result result = mCompilingState->compileEvent->wait();
+    mInfoLog             = std::move(mCompilingState->compileEvent->getInfoLog());
 
-    mInfoLog += mCompilingState->compileEvent->getInfoLog();
-
-    ScopedExit exit([this]() {
-        mBoundCompiler->putInstance(std::move(mCompilingState->shCompilerInstance));
-        mCompilingState->compileEvent.reset();
-        mCompilingState.reset();
-    });
-
-    ShHandle compilerHandle = mCompilingState->shCompilerInstance.getHandle();
-    if (!mCompilingState->compileEvent->getResult())
-    {
-        mInfoLog += sh::GetInfoLog(compilerHandle);
-        INFO() << std::endl << mInfoLog;
-        mState.mCompileStatus = CompileStatus::NOT_COMPILED;
-        return;
-    }
-
-    const ShShaderOutput outputType = mCompilingState->shCompilerInstance.getShaderOutputType();
-    bool isBinaryOutput             = outputType == SH_SPIRV_VULKAN_OUTPUT;
-    mState.mCompiledState->buildCompiledShaderState(compilerHandle, isBinaryOutput);
-
-    const angle::FrontendFeatures &frontendFeatures = context->getFrontendFeatures();
-    bool substitutedTranslatedShader                = false;
-    const char *suffix                              = "translated";
-    if (frontendFeatures.enableTranslatedShaderSubstitution.enabled)
-    {
-        // To support reading/writing compiled binaries (SPIR-V
-        // representation), need more file input/output facilities,
-        // and figure out the byte ordering of writing the 32-bit
-        // words to disk.
-        if (isBinaryOutput)
-        {
-            INFO() << "Can not substitute compiled binary (SPIR-V) shaders yet";
-        }
-        else
-        {
-            std::string substituteShaderPath = GetShaderDumpFilePath(mState.mSourceHash, suffix);
-
-            std::string substituteShader;
-            if (angle::ReadFileToString(substituteShaderPath, &substituteShader))
-            {
-                mState.mCompiledState->translatedSource = std::move(substituteShader);
-                substitutedTranslatedShader             = true;
-                INFO() << "Trasnslated shader substitute found, loading from "
-                       << substituteShaderPath;
-            }
-        }
-    }
-
-    // Only dump translated shaders that have not been previously substituted. It would write the
-    // same data back to the file.
-    if (frontendFeatures.dumpTranslatedShaders.enabled && !substitutedTranslatedShader)
-    {
-        if (isBinaryOutput)
-        {
-            INFO() << "Can not dump compiled binary (SPIR-V) shaders yet";
-        }
-        else
-        {
-            std::string dumpFile = GetShaderDumpFilePath(mState.mSourceHash, suffix);
-
-            const std::string &translatedSource = mState.mCompiledState->translatedSource;
-            writeFile(dumpFile.c_str(), translatedSource.c_str(), translatedSource.length());
-            INFO() << "Dumped translated source: " << dumpFile;
-        }
-    }
-
-#if !defined(NDEBUG)
-    if (outputType != SH_SPIRV_VULKAN_OUTPUT)
-    {
-        // Prefix translated shader with commented out un-translated shader.
-        // Useful in diagnostics tools which capture the shader source.
-        std::ostringstream shaderStream;
-        shaderStream << "// GLSL\n";
-        shaderStream << "//\n";
-
-        std::istringstream inputSourceStream(mState.mSource);
-        std::string line;
-        while (std::getline(inputSourceStream, line))
-        {
-            // Remove null characters from the source line
-            line.erase(std::remove(line.begin(), line.end(), '\0'), line.end());
-
-            shaderStream << "// " << line;
-
-            // glslang complains if a comment ends with backslash
-            if (!line.empty() && line.back() == '\\')
-            {
-                shaderStream << "\\";
-            }
-
-            shaderStream << std::endl;
-        }
-        shaderStream << "\n\n";
-        shaderStream << mState.mCompiledState->translatedSource;
-        mState.mCompiledState->translatedSource = shaderStream.str();
-    }
-#endif  // !defined(NDEBUG)
-
-    // Validation checks for compute shaders
-    if (mState.mCompiledState->shaderType == ShaderType::Compute &&
-        mState.mCompiledState->localSize.isDeclared())
-    {
-        angle::CheckedNumeric<uint32_t> checked_local_size_product(
-            mState.mCompiledState->localSize[0]);
-        checked_local_size_product *= mState.mCompiledState->localSize[1];
-        checked_local_size_product *= mState.mCompiledState->localSize[2];
-
-        if (!checked_local_size_product.IsValid())
-        {
-            WARN() << std::endl
-                   << "Integer overflow when computing the product of local_size_x, "
-                   << "local_size_y and local_size_z.";
-            mState.mCompileStatus = CompileStatus::NOT_COMPILED;
-            return;
-        }
-        if (checked_local_size_product.ValueOrDie() > mCurrentMaxComputeWorkGroupInvocations)
-        {
-            WARN() << std::endl
-                   << "The total number of invocations within a work group exceeds "
-                   << "MAX_COMPUTE_WORK_GROUP_INVOCATIONS.";
-            mState.mCompileStatus = CompileStatus::NOT_COMPILED;
-            return;
-        }
-    }
-
-    unsigned int sharedMemSize = sh::GetShaderSharedMemorySize(compilerHandle);
-    if (sharedMemSize > mMaxComputeSharedMemory)
-    {
-        WARN() << std::endl << "Exceeded maximum shared memory size";
-        mState.mCompileStatus = CompileStatus::NOT_COMPILED;
-        return;
-    }
-
-    ASSERT(!mState.mCompiledState->translatedSource.empty() ||
-           !mState.mCompiledState->compiledBinary.empty());
-
-    bool success          = mCompilingState->compileEvent->postTranslate(&mInfoLog);
+    bool success          = result == angle::Result::Continue;
     mState.mCompileStatus = success ? CompileStatus::COMPILED : CompileStatus::NOT_COMPILED;
-
     mState.mCompiledState->successfullyCompiled = success;
 
-    MemoryShaderCache *shaderCache = context->getMemoryShaderCache();
-    if (success && shaderCache)
+    if (success)
     {
-        // Save to the shader cache.
-        if (shaderCache->putShader(context, mShaderHash, this) != angle::Result::Continue)
+        MemoryShaderCache *shaderCache = context->getMemoryShaderCache();
+        if (shaderCache != nullptr)
         {
-            ANGLE_PERF_WARNING(context->getState().getDebug(), GL_DEBUG_SEVERITY_LOW,
-                               "Failed to save compiled shader to memory shader cache.");
+            // Save to the shader cache.
+            if (shaderCache->putShader(context, mShaderHash, this) != angle::Result::Continue)
+            {
+                ANGLE_PERF_WARNING(context->getState().getDebug(), GL_DEBUG_SEVERITY_LOW,
+                                   "Failed to save compiled shader to memory shader cache.");
+            }
         }
     }
+
+    mBoundCompiler->putInstance(std::move(mCompilingState->shCompilerInstance));
+    mCompilingState->compileEvent.reset();
+    mCompilingState.reset();
 }
 
 void Shader::addRef()
@@ -667,7 +776,7 @@
 
 bool Shader::isCompleted()
 {
-    return (!mState.compilePending() || mCompilingState->compileEvent->isReady());
+    return !mState.compilePending() || !mCompilingState->compileEvent->isCompiling();
 }
 
 angle::Result Shader::serialize(const Context *context, angle::MemoryBuffer *binaryOut) const
diff --git a/src/libANGLE/Shader.h b/src/libANGLE/Shader.h
index cb64da2..2c292cc 100644
--- a/src/libANGLE/Shader.h
+++ b/src/libANGLE/Shader.h
@@ -47,7 +47,6 @@
 
 namespace gl
 {
-class CompileTask;
 class Context;
 class ShaderProgramManager;
 class State;
@@ -161,12 +160,6 @@
 
     const ShaderState &getState() const { return mState; }
 
-    GLuint getCurrentMaxComputeWorkGroupInvocations() const
-    {
-        return mCurrentMaxComputeWorkGroupInvocations;
-    }
-
-    unsigned int getMaxComputeSharedMemory() const { return mMaxComputeSharedMemory; }
     bool hasBeenDeleted() const { return mDeleteStatus; }
 
     // Block until compiling is finished and resolve it.
@@ -190,7 +183,6 @@
 
   private:
     struct CompilingState;
-
     ~Shader() override;
     static std::string joinShaderSources(GLsizei count,
                                          const char *const *string,
@@ -224,9 +216,6 @@
     egl::BlobCache::Key mShaderHash;
 
     ShaderProgramManager *mResourceManager;
-
-    GLuint mCurrentMaxComputeWorkGroupInvocations;
-    unsigned int mMaxComputeSharedMemory;
 };
 
 const char *GetShaderTypeString(ShaderType type);
diff --git a/src/libANGLE/capture/serialize.cpp b/src/libANGLE/capture/serialize.cpp
index 822cf97..68b1692 100644
--- a/src/libANGLE/capture/serialize.cpp
+++ b/src/libANGLE/capture/serialize.cpp
@@ -972,9 +972,6 @@
     // Do not serialize mType because it is already serialized in SerializeCompiledShaderState.
     json->addString("InfoLogString", shader->getInfoLogString());
     // Do not serialize compiler resources string because it can vary between test modes.
-    json->addScalar("CurrentMaxComputeWorkGroupInvocations",
-                    shader->getCurrentMaxComputeWorkGroupInvocations());
-    json->addScalar("MaxComputeSharedMemory", shader->getMaxComputeSharedMemory());
 }
 
 void SerializeVariableLocationsVector(JsonSerializer *json,
diff --git a/src/libANGLE/renderer/ShaderImpl.cpp b/src/libANGLE/renderer/ShaderImpl.cpp
index 9d774d6..fd3aa57 100644
--- a/src/libANGLE/renderer/ShaderImpl.cpp
+++ b/src/libANGLE/renderer/ShaderImpl.cpp
@@ -13,88 +13,13 @@
 
 namespace rx
 {
-
-WaitableCompileEvent::WaitableCompileEvent(std::shared_ptr<angle::WaitableEvent> waitableEvent)
-    : mWaitableEvent(waitableEvent)
-{}
-
-WaitableCompileEvent::~WaitableCompileEvent()
+bool ShaderTranslateTask::translate(ShHandle compiler,
+                                    const ShCompileOptions &options,
+                                    const std::string &source)
 {
-    mWaitableEvent.reset();
-}
-
-void WaitableCompileEvent::wait()
-{
-    mWaitableEvent->wait();
-}
-
-bool WaitableCompileEvent::isReady()
-{
-    return mWaitableEvent->isReady();
-}
-
-const std::string &WaitableCompileEvent::getInfoLog()
-{
-    return mInfoLog;
-}
-
-class TranslateTask : public angle::Closure
-{
-  public:
-    TranslateTask(ShHandle handle, const ShCompileOptions &options, const std::string &source)
-        : mHandle(handle), mOptions(options), mSource(source), mResult(false)
-    {}
-
-    void operator()() override
-    {
-        ANGLE_TRACE_EVENT1("gpu.angle", "TranslateTask::run", "source", mSource);
-        const char *source = mSource.c_str();
-        mResult            = sh::Compile(mHandle, &source, 1, mOptions);
-    }
-
-    bool getResult() { return mResult; }
-
-    ShHandle getHandle() { return mHandle; }
-
-  private:
-    ShHandle mHandle;
-    ShCompileOptions mOptions;
-    std::string mSource;
-    bool mResult;
-};
-
-class WaitableCompileEventImpl final : public WaitableCompileEvent
-{
-  public:
-    WaitableCompileEventImpl(std::shared_ptr<angle::WaitableEvent> waitableEvent,
-                             std::shared_ptr<TranslateTask> translateTask)
-        : WaitableCompileEvent(waitableEvent), mTranslateTask(translateTask)
-    {}
-
-    bool getResult() override { return mTranslateTask->getResult(); }
-
-    bool postTranslate(std::string *infoLog) override { return true; }
-
-  private:
-    std::shared_ptr<TranslateTask> mTranslateTask;
-};
-
-std::shared_ptr<WaitableCompileEvent> ShaderImpl::compileImpl(
-    const gl::Context *context,
-    gl::ShCompilerInstance *compilerInstance,
-    const std::string &source,
-    ShCompileOptions *compileOptions)
-{
-#if defined(ANGLE_ENABLE_ASSERTS)
-    compileOptions->validateAST = true;
-#endif
-
-    auto workerThreadPool = context->getShaderCompileThreadPool();
-    auto translateTask =
-        std::make_shared<TranslateTask>(compilerInstance->getHandle(), *compileOptions, source);
-
-    return std::make_shared<WaitableCompileEventImpl>(
-        workerThreadPool->postWorkerTask(translateTask), translateTask);
+    ANGLE_TRACE_EVENT1("gpu.angle", "ShaderTranslateTask::run", "source", source);
+    const char *src = source.c_str();
+    return sh::Compile(compiler, &src, 1, options);
 }
 
 angle::Result ShaderImpl::onLabelUpdate(const gl::Context *context)
diff --git a/src/libANGLE/renderer/ShaderImpl.h b/src/libANGLE/renderer/ShaderImpl.h
index 4b22a57..d3fae14 100644
--- a/src/libANGLE/renderer/ShaderImpl.h
+++ b/src/libANGLE/renderer/ShaderImpl.h
@@ -11,6 +11,7 @@
 
 #include <functional>
 
+#include "common/CompiledShaderState.h"
 #include "common/WorkerThread.h"
 #include "common/angleutils.h"
 #include "libANGLE/Shader.h"
@@ -22,27 +23,34 @@
 
 namespace rx
 {
-
-using UpdateShaderStateFunctor = std::function<void(bool compiled, ShHandle handle)>;
-class WaitableCompileEvent : public angle::WaitableEvent
+// The compile task is generally just a call to the translator.  However, different backends behave
+// differently afterwards:
+//
+// - The Vulkan backend which generates binary (i.e. SPIR-V), does nothing more
+// - The backends that generate text (HLSL and MSL), do nothing at this stage, but modify the text
+//   at link time before invoking the native compiler.  These expensive calls are handled in link
+//   sub-tasks (see LinkSubTask in ProgramImpl.h).
+// - The GL backend needs to invoke the native driver, which is problematic when done in another
+//   thread (and is avoided).
+//
+// The call to the translator can thus be done in a separate thread or without holding the share
+// group lock on all backends except GL.  On the GL backend, the translator call is done on the main
+// thread followed by a call to the native driver.  If the driver supports
+// GL_KHR_parallel_shader_compile, ANGLE still delays post-processing of the results to when
+// compilation is done (just as if it was ANGLE itself that was doing the compilation in a thread).
+class ShaderTranslateTask
 {
   public:
-    WaitableCompileEvent(std::shared_ptr<angle::WaitableEvent> waitableEvent);
-    ~WaitableCompileEvent() override;
+    virtual ~ShaderTranslateTask() = default;
+    virtual bool translate(ShHandle compiler,
+                           const ShCompileOptions &options,
+                           const std::string &source);
+    virtual void postTranslate(ShHandle compiler, const gl::CompiledShaderState &compiledState) {}
 
-    void wait() override;
-
-    bool isReady() override;
-
-    virtual bool getResult() = 0;
-
-    virtual bool postTranslate(std::string *infoLog) = 0;
-
-    const std::string &getInfoLog();
-
-  protected:
-    std::shared_ptr<angle::WaitableEvent> mWaitableEvent;
-    std::string mInfoLog;
+    // Used by the GL backend to query whether the driver is compiling in parallel internally.
+    virtual bool isCompilingInternally() { return false; }
+    // Used by the GL backend to finish internal compilation and return results.
+    virtual angle::Result getResult(std::string &infoLog) { return angle::Result::Continue; }
 };
 
 class ShaderImpl : angle::NonCopyable
@@ -53,9 +61,8 @@
 
     virtual void destroy() {}
 
-    virtual std::shared_ptr<WaitableCompileEvent> compile(const gl::Context *context,
-                                                          gl::ShCompilerInstance *compilerInstance,
-                                                          ShCompileOptions *options) = 0;
+    virtual std::shared_ptr<ShaderTranslateTask> compile(const gl::Context *context,
+                                                         ShCompileOptions *options) = 0;
 
     virtual std::string getDebugInfo() const = 0;
 
@@ -64,11 +71,6 @@
     virtual angle::Result onLabelUpdate(const gl::Context *context);
 
   protected:
-    std::shared_ptr<WaitableCompileEvent> compileImpl(const gl::Context *context,
-                                                      gl::ShCompilerInstance *compilerInstance,
-                                                      const std::string &source,
-                                                      ShCompileOptions *compileOptions);
-
     const gl::ShaderState &mState;
 };
 
diff --git a/src/libANGLE/renderer/d3d/ShaderD3D.cpp b/src/libANGLE/renderer/d3d/ShaderD3D.cpp
index d26e59d..ed0247b 100644
--- a/src/libANGLE/renderer/d3d/ShaderD3D.cpp
+++ b/src/libANGLE/renderer/d3d/ShaderD3D.cpp
@@ -22,72 +22,153 @@
 
 namespace rx
 {
+namespace
+{
+const std::map<std::string, unsigned int> &GetUniformRegisterMap(
+    const std::map<std::string, unsigned int> *uniformRegisterMap)
+{
+    ASSERT(uniformRegisterMap);
+    return *uniformRegisterMap;
+}
 
-class TranslateTaskD3D : public angle::Closure
+const std::set<std::string> &GetSlowCompilingUniformBlockSet(
+    const std::set<std::string> *slowCompilingUniformBlockSet)
+{
+    ASSERT(slowCompilingUniformBlockSet);
+    return *slowCompilingUniformBlockSet;
+}
+
+const std::set<std::string> &GetUsedImage2DFunctionNames(
+    const std::set<std::string> *usedImage2DFunctionNames)
+{
+    ASSERT(usedImage2DFunctionNames);
+    return *usedImage2DFunctionNames;
+}
+
+class ShaderTranslateTaskD3D final : public ShaderTranslateTask
 {
   public:
-    TranslateTaskD3D(ShHandle handle,
-                     const ShCompileOptions &options,
-                     const std::string &source,
-                     const std::string &sourcePath)
-        : mHandle(handle),
-          mOptions(options),
-          mSource(source),
-          mSourcePath(sourcePath),
-          mResult(false)
+    ShaderTranslateTaskD3D(const SharedCompiledShaderStateD3D &shader, std::string &&sourcePath)
+        : mSourcePath(std::move(sourcePath)), mShader(shader)
     {}
+    ~ShaderTranslateTaskD3D() override = default;
 
-    void operator()() override
+    bool translate(ShHandle compiler,
+                   const ShCompileOptions &options,
+                   const std::string &source) override
     {
-        ANGLE_TRACE_EVENT1("gpu.angle", "TranslateTask::run", "source", mSource);
-        std::vector<const char *> srcStrings;
+        ANGLE_TRACE_EVENT1("gpu.angle", "ShaderTranslateTaskD3D::run", "source", source);
+        angle::FixedVector<const char *, 2> srcStrings;
         if (!mSourcePath.empty())
         {
             srcStrings.push_back(mSourcePath.c_str());
         }
-        srcStrings.push_back(mSource.c_str());
+        srcStrings.push_back(source.c_str());
 
-        mResult = sh::Compile(mHandle, &srcStrings[0], srcStrings.size(), mOptions);
+        return sh::Compile(compiler, srcStrings.data(), srcStrings.size(), options);
     }
 
-    bool getResult() { return mResult; }
-
-  private:
-    ShHandle mHandle;
-    ShCompileOptions mOptions;
-    std::string mSource;
-    std::string mSourcePath;
-    bool mResult;
-};
-
-using PostTranslateFunctor =
-    std::function<bool(gl::ShCompilerInstance *compiler, std::string *infoLog)>;
-
-class WaitableCompileEventD3D final : public WaitableCompileEvent
-{
-  public:
-    WaitableCompileEventD3D(std::shared_ptr<angle::WaitableEvent> waitableEvent,
-                            gl::ShCompilerInstance *compilerInstance,
-                            PostTranslateFunctor &&postTranslateFunctor,
-                            std::shared_ptr<TranslateTaskD3D> translateTask)
-        : WaitableCompileEvent(waitableEvent),
-          mCompilerInstance(compilerInstance),
-          mPostTranslateFunctor(std::move(postTranslateFunctor)),
-          mTranslateTask(translateTask)
-    {}
-
-    bool getResult() override { return mTranslateTask->getResult(); }
-
-    bool postTranslate(std::string *infoLog) override
+    void postTranslate(ShHandle compiler, const gl::CompiledShaderState &compiledState) override
     {
-        return mPostTranslateFunctor(mCompilerInstance, infoLog);
+        const std::string &translatedSource = compiledState.translatedSource;
+        CompiledShaderStateD3D *state       = mShader.get();
+
+        // Note: We shouldn't need to cache this.
+        state->compilerOutputType = sh::GetShaderOutputType(compiler);
+
+        state->usesMultipleRenderTargets =
+            translatedSource.find("GL_USES_MRT") != std::string::npos;
+        state->usesFragColor = translatedSource.find("GL_USES_FRAG_COLOR") != std::string::npos;
+        state->usesFragData  = translatedSource.find("GL_USES_FRAG_DATA") != std::string::npos;
+        state->usesSecondaryColor =
+            translatedSource.find("GL_USES_SECONDARY_COLOR") != std::string::npos;
+        state->usesFragCoord   = translatedSource.find("GL_USES_FRAG_COORD") != std::string::npos;
+        state->usesFrontFacing = translatedSource.find("GL_USES_FRONT_FACING") != std::string::npos;
+        state->usesSampleID    = translatedSource.find("GL_USES_SAMPLE_ID") != std::string::npos;
+        state->usesSamplePosition =
+            translatedSource.find("GL_USES_SAMPLE_POSITION") != std::string::npos;
+        state->usesSampleMaskIn =
+            translatedSource.find("GL_USES_SAMPLE_MASK_IN") != std::string::npos;
+        state->usesSampleMask =
+            translatedSource.find("GL_USES_SAMPLE_MASK_OUT") != std::string::npos;
+        state->usesHelperInvocation =
+            translatedSource.find("GL_USES_HELPER_INVOCATION") != std::string::npos;
+        state->usesPointSize  = translatedSource.find("GL_USES_POINT_SIZE") != std::string::npos;
+        state->usesPointCoord = translatedSource.find("GL_USES_POINT_COORD") != std::string::npos;
+        state->usesDepthRange = translatedSource.find("GL_USES_DEPTH_RANGE") != std::string::npos;
+        state->hasMultiviewEnabled =
+            translatedSource.find("GL_MULTIVIEW_ENABLED") != std::string::npos;
+        state->usesVertexID = translatedSource.find("GL_USES_VERTEX_ID") != std::string::npos;
+        state->usesViewID   = translatedSource.find("GL_USES_VIEW_ID") != std::string::npos;
+        state->usesDiscardRewriting =
+            translatedSource.find("ANGLE_USES_DISCARD_REWRITING") != std::string::npos;
+        state->usesNestedBreak =
+            translatedSource.find("ANGLE_USES_NESTED_BREAK") != std::string::npos;
+        state->requiresIEEEStrictCompiling =
+            translatedSource.find("ANGLE_REQUIRES_IEEE_STRICT_COMPILING") != std::string::npos;
+
+        if (translatedSource.find("GL_USES_FRAG_DEPTH_GREATER") != std::string::npos)
+        {
+            state->fragDepthUsage = FragDepthUsage::Greater;
+        }
+        else if (translatedSource.find("GL_USES_FRAG_DEPTH_LESS") != std::string::npos)
+        {
+            state->fragDepthUsage = FragDepthUsage::Less;
+        }
+        else if (translatedSource.find("GL_USES_FRAG_DEPTH") != std::string::npos)
+        {
+            state->fragDepthUsage = FragDepthUsage::Any;
+        }
+        state->clipDistanceSize   = sh::GetClipDistanceArraySize(compiler);
+        state->cullDistanceSize   = sh::GetCullDistanceArraySize(compiler);
+        state->uniformRegisterMap = GetUniformRegisterMap(sh::GetUniformRegisterMap(compiler));
+        state->readonlyImage2DRegisterIndex = sh::GetReadonlyImage2DRegisterIndex(compiler);
+        state->image2DRegisterIndex         = sh::GetImage2DRegisterIndex(compiler);
+        state->usedImage2DFunctionNames =
+            GetUsedImage2DFunctionNames(sh::GetUsedImage2DFunctionNames(compiler));
+
+        for (const sh::InterfaceBlock &interfaceBlock : compiledState.uniformBlocks)
+        {
+            if (interfaceBlock.active)
+            {
+                unsigned int index = static_cast<unsigned int>(-1);
+                bool blockRegisterResult =
+                    sh::GetUniformBlockRegister(compiler, interfaceBlock.name, &index);
+                ASSERT(blockRegisterResult);
+                bool useStructuredBuffer =
+                    sh::ShouldUniformBlockUseStructuredBuffer(compiler, interfaceBlock.name);
+
+                state->uniformBlockRegisterMap[interfaceBlock.name] = index;
+                state->uniformBlockUseStructuredBufferMap[interfaceBlock.name] =
+                    useStructuredBuffer;
+            }
+        }
+
+        state->slowCompilingUniformBlockSet =
+            GetSlowCompilingUniformBlockSet(sh::GetSlowCompilingUniformBlockSet(compiler));
+
+        for (const sh::InterfaceBlock &interfaceBlock : compiledState.shaderStorageBlocks)
+        {
+            if (interfaceBlock.active)
+            {
+                unsigned int index = static_cast<unsigned int>(-1);
+                bool blockRegisterResult =
+                    sh::GetShaderStorageBlockRegister(compiler, interfaceBlock.name, &index);
+                ASSERT(blockRegisterResult);
+
+                state->shaderStorageBlockRegisterMap[interfaceBlock.name] = index;
+            }
+        }
+
+        state->debugInfo +=
+            "// INITIAL HLSL BEGIN\n\n" + translatedSource + "\n// INITIAL HLSL END\n\n\n";
     }
 
   private:
-    gl::ShCompilerInstance *mCompilerInstance;
-    PostTranslateFunctor mPostTranslateFunctor;
-    std::shared_ptr<TranslateTaskD3D> mTranslateTask;
+    std::string mSourcePath;
+    SharedCompiledShaderStateD3D mShader;
 };
+}  // anonymous namespace
 
 CompiledShaderStateD3D::CompiledShaderStateD3D()
     : compilerOutputType(SH_ESSL_OUTPUT),
@@ -204,30 +285,8 @@
     return slowCompilingUniformBlockSet;
 }
 
-const std::map<std::string, unsigned int> &GetUniformRegisterMap(
-    const std::map<std::string, unsigned int> *uniformRegisterMap)
-{
-    ASSERT(uniformRegisterMap);
-    return *uniformRegisterMap;
-}
-
-const std::set<std::string> &GetSlowCompilingUniformBlockSet(
-    const std::set<std::string> *slowCompilingUniformBlockSet)
-{
-    ASSERT(slowCompilingUniformBlockSet);
-    return *slowCompilingUniformBlockSet;
-}
-
-const std::set<std::string> &GetUsedImage2DFunctionNames(
-    const std::set<std::string> *usedImage2DFunctionNames)
-{
-    ASSERT(usedImage2DFunctionNames);
-    return *usedImage2DFunctionNames;
-}
-
-std::shared_ptr<WaitableCompileEvent> ShaderD3D::compile(const gl::Context *context,
-                                                         gl::ShCompilerInstance *compilerInstance,
-                                                         ShCompileOptions *options)
+std::shared_ptr<ShaderTranslateTask> ShaderD3D::compile(const gl::Context *context,
+                                                        ShCompileOptions *options)
 {
     // Create a new compiled shader state.  Currently running program link jobs will use the
     // previous state.
@@ -294,117 +353,11 @@
         options->pls = mRenderer->getNativePixelLocalStorageOptions();
     }
 
-    auto postTranslateFunctor = [this](gl::ShCompilerInstance *compiler, std::string *infoLog) {
-        const std::string &translatedSource = mState.getCompiledState()->translatedSource;
-        CompiledShaderStateD3D *state       = mCompiledState.get();
+    // The D3D translations are not currently validation-error-free
+    options->validateAST = false;
 
-        // TODO(jmadill): We shouldn't need to cache this.
-        state->compilerOutputType = compiler->getShaderOutputType();
-
-        state->usesMultipleRenderTargets =
-            translatedSource.find("GL_USES_MRT") != std::string::npos;
-        state->usesFragColor = translatedSource.find("GL_USES_FRAG_COLOR") != std::string::npos;
-        state->usesFragData  = translatedSource.find("GL_USES_FRAG_DATA") != std::string::npos;
-        state->usesSecondaryColor =
-            translatedSource.find("GL_USES_SECONDARY_COLOR") != std::string::npos;
-        state->usesFragCoord   = translatedSource.find("GL_USES_FRAG_COORD") != std::string::npos;
-        state->usesFrontFacing = translatedSource.find("GL_USES_FRONT_FACING") != std::string::npos;
-        state->usesSampleID    = translatedSource.find("GL_USES_SAMPLE_ID") != std::string::npos;
-        state->usesSamplePosition =
-            translatedSource.find("GL_USES_SAMPLE_POSITION") != std::string::npos;
-        state->usesSampleMaskIn =
-            translatedSource.find("GL_USES_SAMPLE_MASK_IN") != std::string::npos;
-        state->usesSampleMask =
-            translatedSource.find("GL_USES_SAMPLE_MASK_OUT") != std::string::npos;
-        state->usesHelperInvocation =
-            translatedSource.find("GL_USES_HELPER_INVOCATION") != std::string::npos;
-        state->usesPointSize  = translatedSource.find("GL_USES_POINT_SIZE") != std::string::npos;
-        state->usesPointCoord = translatedSource.find("GL_USES_POINT_COORD") != std::string::npos;
-        state->usesDepthRange = translatedSource.find("GL_USES_DEPTH_RANGE") != std::string::npos;
-        state->hasMultiviewEnabled =
-            translatedSource.find("GL_MULTIVIEW_ENABLED") != std::string::npos;
-        state->usesVertexID = translatedSource.find("GL_USES_VERTEX_ID") != std::string::npos;
-        state->usesViewID   = translatedSource.find("GL_USES_VIEW_ID") != std::string::npos;
-        state->usesDiscardRewriting =
-            translatedSource.find("ANGLE_USES_DISCARD_REWRITING") != std::string::npos;
-        state->usesNestedBreak =
-            translatedSource.find("ANGLE_USES_NESTED_BREAK") != std::string::npos;
-        state->requiresIEEEStrictCompiling =
-            translatedSource.find("ANGLE_REQUIRES_IEEE_STRICT_COMPILING") != std::string::npos;
-
-        ShHandle compilerHandle = compiler->getHandle();
-
-        if (translatedSource.find("GL_USES_FRAG_DEPTH_GREATER") != std::string::npos)
-        {
-            state->fragDepthUsage = FragDepthUsage::Greater;
-        }
-        else if (translatedSource.find("GL_USES_FRAG_DEPTH_LESS") != std::string::npos)
-        {
-            state->fragDepthUsage = FragDepthUsage::Less;
-        }
-        else if (translatedSource.find("GL_USES_FRAG_DEPTH") != std::string::npos)
-        {
-            state->fragDepthUsage = FragDepthUsage::Any;
-        }
-        state->clipDistanceSize = sh::GetClipDistanceArraySize(compilerHandle);
-        state->cullDistanceSize = sh::GetCullDistanceArraySize(compilerHandle);
-        state->uniformRegisterMap =
-            GetUniformRegisterMap(sh::GetUniformRegisterMap(compilerHandle));
-        state->readonlyImage2DRegisterIndex = sh::GetReadonlyImage2DRegisterIndex(compilerHandle);
-        state->image2DRegisterIndex         = sh::GetImage2DRegisterIndex(compilerHandle);
-        state->usedImage2DFunctionNames =
-            GetUsedImage2DFunctionNames(sh::GetUsedImage2DFunctionNames(compilerHandle));
-
-        for (const sh::InterfaceBlock &interfaceBlock : mState.getCompiledState()->uniformBlocks)
-        {
-            if (interfaceBlock.active)
-            {
-                unsigned int index = static_cast<unsigned int>(-1);
-                bool blockRegisterResult =
-                    sh::GetUniformBlockRegister(compilerHandle, interfaceBlock.name, &index);
-                ASSERT(blockRegisterResult);
-                bool useStructuredBuffer =
-                    sh::ShouldUniformBlockUseStructuredBuffer(compilerHandle, interfaceBlock.name);
-
-                state->uniformBlockRegisterMap[interfaceBlock.name] = index;
-                state->uniformBlockUseStructuredBufferMap[interfaceBlock.name] =
-                    useStructuredBuffer;
-            }
-        }
-
-        state->slowCompilingUniformBlockSet =
-            GetSlowCompilingUniformBlockSet(sh::GetSlowCompilingUniformBlockSet(compilerHandle));
-
-        for (const sh::InterfaceBlock &interfaceBlock :
-             mState.getCompiledState()->shaderStorageBlocks)
-        {
-            if (interfaceBlock.active)
-            {
-                unsigned int index = static_cast<unsigned int>(-1);
-                bool blockRegisterResult =
-                    sh::GetShaderStorageBlockRegister(compilerHandle, interfaceBlock.name, &index);
-                ASSERT(blockRegisterResult);
-
-                state->shaderStorageBlockRegisterMap[interfaceBlock.name] = index;
-            }
-        }
-
-        state->debugInfo += std::string("// ") + gl::GetShaderTypeString(mState.getShaderType()) +
-                            " SHADER BEGIN\n";
-        state->debugInfo += "\n// GLSL BEGIN\n\n" + mState.getSource() + "\n\n// GLSL END\n\n\n";
-        state->debugInfo +=
-            "// INITIAL HLSL BEGIN\n\n" + translatedSource + "\n// INITIAL HLSL END\n\n\n";
-        // Successive steps will append more info
-        return true;
-    };
-
-    auto workerThreadPool = context->getShaderCompileThreadPool();
-    auto translateTask = std::make_shared<TranslateTaskD3D>(compilerInstance->getHandle(), *options,
-                                                            source, sourcePath);
-
-    return std::make_shared<WaitableCompileEventD3D>(
-        workerThreadPool->postWorkerTask(translateTask), compilerInstance,
-        std::move(postTranslateFunctor), translateTask);
+    return std::shared_ptr<ShaderTranslateTask>(
+        new ShaderTranslateTaskD3D(mCompiledState, std::move(sourcePath)));
 }
 
 bool CompiledShaderStateD3D::hasUniform(const std::string &name) const
diff --git a/src/libANGLE/renderer/d3d/ShaderD3D.h b/src/libANGLE/renderer/d3d/ShaderD3D.h
index 99259eb..14adb44 100644
--- a/src/libANGLE/renderer/d3d/ShaderD3D.h
+++ b/src/libANGLE/renderer/d3d/ShaderD3D.h
@@ -114,9 +114,8 @@
     ShaderD3D(const gl::ShaderState &state, RendererD3D *renderer);
     ~ShaderD3D() override;
 
-    std::shared_ptr<WaitableCompileEvent> compile(const gl::Context *context,
-                                                  gl::ShCompilerInstance *compilerInstance,
-                                                  ShCompileOptions *options) override;
+    std::shared_ptr<ShaderTranslateTask> compile(const gl::Context *context,
+                                                 ShCompileOptions *options) override;
 
     std::string getDebugInfo() const override;
 
diff --git a/src/libANGLE/renderer/d3d/d3d11/renderer11_utils.cpp b/src/libANGLE/renderer/d3d/d3d11/renderer11_utils.cpp
index 11c19d8..f42fe54 100644
--- a/src/libANGLE/renderer/d3d/d3d11/renderer11_utils.cpp
+++ b/src/libANGLE/renderer/d3d/d3d11/renderer11_utils.cpp
@@ -2604,7 +2604,8 @@
 
     ANGLE_FEATURE_CONDITION(features, forceDepthAttachmentInitOnClear, isAMD);
 
-    // The D3D backend's handling of link is thread-safe
+    // The D3D backend's handling of compile and link is thread-safe
+    ANGLE_FEATURE_CONDITION(features, compileJobIsThreadSafe, true);
     ANGLE_FEATURE_CONDITION(features, linkJobIsThreadSafe, true);
 }
 
diff --git a/src/libANGLE/renderer/d3d/d3d9/renderer9_utils.cpp b/src/libANGLE/renderer/d3d/d3d9/renderer9_utils.cpp
index 42205c0..08abaad 100644
--- a/src/libANGLE/renderer/d3d/d3d9/renderer9_utils.cpp
+++ b/src/libANGLE/renderer/d3d/d3d9/renderer9_utils.cpp
@@ -846,7 +846,8 @@
 
 void InitializeFrontendFeatures(angle::FrontendFeatures *features, DWORD vendorID)
 {
-    // The D3D backend's handling of link is thread-safe
+    // The D3D backend's handling of compile and link is thread-safe
+    ANGLE_FEATURE_CONDITION(features, compileJobIsThreadSafe, true);
     ANGLE_FEATURE_CONDITION(features, linkJobIsThreadSafe, true);
 }
 }  // namespace d3d9
diff --git a/src/libANGLE/renderer/gl/ShaderGL.cpp b/src/libANGLE/renderer/gl/ShaderGL.cpp
index e985091..616fb56 100644
--- a/src/libANGLE/renderer/gl/ShaderGL.cpp
+++ b/src/libANGLE/renderer/gl/ShaderGL.cpp
@@ -21,57 +21,76 @@
 
 namespace rx
 {
-
-using PostTranslateFunctor  = std::function<bool(std::string *infoLog)>;
-using PeekCompletionFunctor = std::function<bool()>;
-using CheckShaderFunctor    = std::function<void()>;
-
-class WaitableCompileEventNativeParallel final : public WaitableCompileEvent
+namespace
+{
+class ShaderTranslateTaskGL final : public ShaderTranslateTask
 {
   public:
-    WaitableCompileEventNativeParallel(PostTranslateFunctor &&postTranslateFunctor,
-                                       bool result,
-                                       CheckShaderFunctor &&checkShaderFunctor,
-                                       PeekCompletionFunctor &&peekCompletionFunctor)
-        : WaitableCompileEvent(std::shared_ptr<angle::WaitableEventDone>()),
-          mPostTranslateFunctor(std::move(postTranslateFunctor)),
-          mResult(result),
-          mCheckShaderFunctor(std::move(checkShaderFunctor)),
-          mPeekCompletionFunctor(std::move(peekCompletionFunctor))
+    ShaderTranslateTaskGL(const FunctionsGL *functions,
+                          GLuint shaderID,
+                          bool hasNativeParallelCompile)
+        : mFunctions(functions),
+          mShaderID(shaderID),
+          mHasNativeParallelCompile(hasNativeParallelCompile)
     {}
+    ~ShaderTranslateTaskGL() override = default;
 
-    void wait() override { mCheckShaderFunctor(); }
+    void postTranslate(ShHandle compiler, const gl::CompiledShaderState &compiledState) override
+    {
+        const char *source = compiledState.translatedSource.c_str();
+        mFunctions->shaderSource(mShaderID, 1, &source, nullptr);
+        mFunctions->compileShader(mShaderID);
+    }
 
-    bool isReady() override { return mPeekCompletionFunctor(); }
+    bool isCompilingInternally() override
+    {
+        if (!mHasNativeParallelCompile)
+        {
+            return false;
+        }
 
-    bool getResult() override { return mResult; }
+        GLint status = GL_FALSE;
+        mFunctions->getShaderiv(mShaderID, GL_COMPLETION_STATUS, &status);
+        return status != GL_TRUE;
+    }
 
-    bool postTranslate(std::string *infoLog) override { return mPostTranslateFunctor(infoLog); }
+    angle::Result getResult(std::string &infoLog) override
+    {
+        // Check for compile errors from the native driver
+        GLint compileStatus = GL_FALSE;
+        mFunctions->getShaderiv(mShaderID, GL_COMPILE_STATUS, &compileStatus);
+        if (compileStatus != GL_FALSE)
+        {
+            return angle::Result::Continue;
+        }
+
+        // Compilation failed, put the error into the info log
+        GLint infoLogLength = 0;
+        mFunctions->getShaderiv(mShaderID, 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<char> buf(infoLogLength);
+            mFunctions->getShaderInfoLog(mShaderID, infoLogLength, nullptr, &buf[0]);
+
+            infoLog += buf.data();
+        }
+        else
+        {
+            WARN() << std::endl << "Shader compilation failed with no info log.";
+        }
+
+        return angle::Result::Stop;
+    }
 
   private:
-    PostTranslateFunctor mPostTranslateFunctor;
-    bool mResult;
-    CheckShaderFunctor mCheckShaderFunctor;
-    PeekCompletionFunctor mPeekCompletionFunctor;
+    const FunctionsGL *mFunctions;
+    GLuint mShaderID;
+    bool mHasNativeParallelCompile;
 };
-
-class WaitableCompileEventDone final : public WaitableCompileEvent
-{
-  public:
-    WaitableCompileEventDone(PostTranslateFunctor &&postTranslateFunctor, bool result)
-        : WaitableCompileEvent(std::make_shared<angle::WaitableEventDone>()),
-          mPostTranslateFunctor(std::move(postTranslateFunctor)),
-          mResult(result)
-    {}
-
-    bool getResult() override { return mResult; }
-
-    bool postTranslate(std::string *infoLog) override { return mPostTranslateFunctor(infoLog); }
-
-  private:
-    PostTranslateFunctor mPostTranslateFunctor;
-    bool mResult;
-};
+}  // anonymous namespace
 
 ShaderGL::ShaderGL(const gl::ShaderState &data,
                    GLuint shaderID,
@@ -80,8 +99,7 @@
     : ShaderImpl(data),
       mShaderID(shaderID),
       mMultiviewImplementationType(multiviewImplementationType),
-      mRenderer(renderer),
-      mCompileStatus(GL_FALSE)
+      mRenderer(renderer)
 {}
 
 ShaderGL::~ShaderGL()
@@ -95,63 +113,9 @@
     mShaderID = 0;
 }
 
-void ShaderGL::compileAndCheckShader(const char *source)
+std::shared_ptr<ShaderTranslateTask> ShaderGL::compile(const gl::Context *context,
+                                                       ShCompileOptions *options)
 {
-    compileShader(source);
-    checkShader();
-}
-
-void ShaderGL::compileShader(const char *source)
-{
-    const FunctionsGL *functions = mRenderer->getFunctions();
-    functions->shaderSource(mShaderID, 1, &source, nullptr);
-    functions->compileShader(mShaderID);
-}
-
-void ShaderGL::checkShader()
-{
-    const FunctionsGL *functions = mRenderer->getFunctions();
-
-    // Check for compile errors from the native driver
-    mCompileStatus = GL_FALSE;
-    functions->getShaderiv(mShaderID, GL_COMPILE_STATUS, &mCompileStatus);
-    if (mCompileStatus == GL_FALSE)
-    {
-        // Compilation failed, put the error into the info log
-        GLint infoLogLength = 0;
-        functions->getShaderiv(mShaderID, 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<char> buf(infoLogLength);
-            functions->getShaderInfoLog(mShaderID, infoLogLength, nullptr, &buf[0]);
-
-            mInfoLog += &buf[0];
-            WARN() << std::endl << mInfoLog;
-        }
-        else
-        {
-            WARN() << std::endl << "Shader compilation failed with no info log.";
-        }
-    }
-}
-
-bool ShaderGL::peekCompletion()
-{
-    const FunctionsGL *functions = mRenderer->getFunctions();
-    GLint status                 = GL_FALSE;
-    functions->getShaderiv(mShaderID, GL_COMPLETION_STATUS, &status);
-    return status == GL_TRUE;
-}
-
-std::shared_ptr<WaitableCompileEvent> ShaderGL::compile(const gl::Context *context,
-                                                        gl::ShCompilerInstance *compilerInstance,
-                                                        ShCompileOptions *options)
-{
-    mInfoLog.clear();
-
     options->initGLPosition = true;
 
     bool isWebGL = context->isWebGL();
@@ -308,48 +272,8 @@
         options->pls = mRenderer->getNativePixelLocalStorageOptions();
     }
 
-    auto workerThreadPool = context->getShaderCompileThreadPool();
-
-    const std::string &source = mState.getSource();
-
-    auto postTranslateFunctor = [this](std::string *infoLog) {
-        if (mCompileStatus == GL_FALSE)
-        {
-            *infoLog = mInfoLog;
-            return false;
-        }
-        return true;
-    };
-
-    ShHandle handle = compilerInstance->getHandle();
-    const char *str = source.c_str();
-    bool result     = sh::Compile(handle, &str, 1, *options);
-
-    if (mRenderer->hasNativeParallelCompile())
-    {
-        if (result)
-        {
-            compileShader(sh::GetObjectCode(handle).c_str());
-            auto checkShaderFunctor    = [this]() { checkShader(); };
-            auto peekCompletionFunctor = [this]() { return peekCompletion(); };
-            return std::make_shared<WaitableCompileEventNativeParallel>(
-                std::move(postTranslateFunctor), result, std::move(checkShaderFunctor),
-                std::move(peekCompletionFunctor));
-        }
-        else
-        {
-            return std::make_shared<WaitableCompileEventDone>([](std::string *) { return true; },
-                                                              result);
-        }
-    }
-    else
-    {
-        if (result)
-        {
-            compileAndCheckShader(sh::GetObjectCode(handle).c_str());
-        }
-        return std::make_shared<WaitableCompileEventDone>(std::move(postTranslateFunctor), result);
-    }
+    return std::shared_ptr<ShaderTranslateTask>(new ShaderTranslateTaskGL(
+        mRenderer->getFunctions(), mShaderID, mRenderer->hasNativeParallelCompile()));
 }
 
 std::string ShaderGL::getDebugInfo() const
diff --git a/src/libANGLE/renderer/gl/ShaderGL.h b/src/libANGLE/renderer/gl/ShaderGL.h
index b47c2a3..5f739fc 100644
--- a/src/libANGLE/renderer/gl/ShaderGL.h
+++ b/src/libANGLE/renderer/gl/ShaderGL.h
@@ -27,25 +27,17 @@
 
     void destroy() override;
 
-    std::shared_ptr<WaitableCompileEvent> compile(const gl::Context *context,
-                                                  gl::ShCompilerInstance *compilerInstance,
-                                                  ShCompileOptions *options) override;
+    std::shared_ptr<ShaderTranslateTask> compile(const gl::Context *context,
+                                                 ShCompileOptions *options) override;
 
     std::string getDebugInfo() const override;
 
     GLuint getShaderID() const;
 
   private:
-    void compileAndCheckShader(const char *source);
-    void compileShader(const char *source);
-    void checkShader();
-    bool peekCompletion();
-
     GLuint mShaderID;
     MultiviewImplementationTypeGL mMultiviewImplementationType;
     std::shared_ptr<RendererGL> mRenderer;
-    GLint mCompileStatus;
-    std::string mInfoLog;
 };
 
 }  // namespace rx
diff --git a/src/libANGLE/renderer/gl/renderergl_utils.cpp b/src/libANGLE/renderer/gl/renderergl_utils.cpp
index 4657094..4892c0a 100644
--- a/src/libANGLE/renderer/gl/renderergl_utils.cpp
+++ b/src/libANGLE/renderer/gl/renderergl_utils.cpp
@@ -2640,13 +2640,14 @@
     // Disable shader program cache to workaround PowerVR Rogue issues.
     ANGLE_FEATURE_CONDITION(features, disableProgramBinary, IsPowerVrRogue(functions));
 
-    // The link job needs a context, and previous experiments showed setting up temp contexts in
-    // threads for the sake of program link triggers too many driver bugs.  See
+    // The compile and link jobs need a context, and previous experiments showed setting up temp
+    // contexts in threads for the sake of program link triggers too many driver bugs.  See
     // https://chromium-review.googlesource.com/c/angle/angle/+/4774785 for context.
     //
-    // As a result, the link job is done in the same thread as the link call.  If the native driver
-    // supports parallel link, it's still done internally by the driver, and ANGLE supports delaying
-    // post-link operations until that is done.
+    // As a result, the compile and link jobs are done in the same thread as the call.  If the
+    // native driver supports parallel compile/link, it's still done internally by the driver, and
+    // ANGLE supports delaying post-compile and post-link operations until that is done.
+    ANGLE_FEATURE_CONDITION(features, compileJobIsThreadSafe, false);
     ANGLE_FEATURE_CONDITION(features, linkJobIsThreadSafe, false);
 }
 
diff --git a/src/libANGLE/renderer/metal/DisplayMtl.mm b/src/libANGLE/renderer/metal/DisplayMtl.mm
index 3fe6594..a860b6b 100644
--- a/src/libANGLE/renderer/metal/DisplayMtl.mm
+++ b/src/libANGLE/renderer/metal/DisplayMtl.mm
@@ -514,6 +514,9 @@
 
 void DisplayMtl::initializeFrontendFeatures(angle::FrontendFeatures *features) const
 {
+    // The Metal backend's handling of compile is thread-safe
+    ANGLE_FEATURE_CONDITION(features, compileJobIsThreadSafe, true);
+
     // The link job in this backend references gl::Context and ContextMtl, and thread-safety is not
     // guaranteed.  The link subtasks are safe however, they are still parallelized.
     //
diff --git a/src/libANGLE/renderer/metal/ShaderMtl.h b/src/libANGLE/renderer/metal/ShaderMtl.h
index e9f7572..a67d092 100644
--- a/src/libANGLE/renderer/metal/ShaderMtl.h
+++ b/src/libANGLE/renderer/metal/ShaderMtl.h
@@ -22,20 +22,14 @@
     ShaderMtl(const gl::ShaderState &state);
     ~ShaderMtl() override;
 
-    std::shared_ptr<WaitableCompileEvent> compile(const gl::Context *context,
-                                                  gl::ShCompilerInstance *compilerInstance,
-                                                  ShCompileOptions *options) override;
+    std::shared_ptr<ShaderTranslateTask> compile(const gl::Context *context,
+                                                 ShCompileOptions *options) override;
 
     const SharedCompiledShaderStateMtl &getCompiledState() const { return mCompiledState; }
 
     std::string getDebugInfo() const override;
 
   private:
-    std::shared_ptr<WaitableCompileEvent> compileImplMtl(const gl::Context *context,
-                                                         gl::ShCompilerInstance *compilerInstance,
-                                                         const std::string &source,
-                                                         ShCompileOptions *compileOptions);
-
     SharedCompiledShaderStateMtl mCompiledState;
 };
 
diff --git a/src/libANGLE/renderer/metal/ShaderMtl.mm b/src/libANGLE/renderer/metal/ShaderMtl.mm
index 943fb7f..c34a872 100644
--- a/src/libANGLE/renderer/metal/ShaderMtl.mm
+++ b/src/libANGLE/renderer/metal/ShaderMtl.mm
@@ -19,89 +19,37 @@
 
 namespace rx
 {
+namespace
+{
+class ShaderTranslateTaskMtl final : public ShaderTranslateTask
+{
+  public:
+    ShaderTranslateTaskMtl(const SharedCompiledShaderStateMtl &shader) : mShader(shader) {}
+    ~ShaderTranslateTaskMtl() override = default;
+
+    void postTranslate(ShHandle compiler, const gl::CompiledShaderState &compiledState) override
+    {
+        sh::TranslatorMSL *translatorMSL =
+            static_cast<sh::TShHandleBase *>(compiler)->getAsTranslatorMSL();
+        if (translatorMSL != nullptr)
+        {
+            // Copy reflection data from translation
+            mShader->translatorMetalReflection = *translatorMSL->getTranslatorMetalReflection();
+            translatorMSL->getTranslatorMetalReflection()->reset();
+        }
+    }
+
+  private:
+    SharedCompiledShaderStateMtl mShader;
+};
+}  // anonymous namespace
 
 ShaderMtl::ShaderMtl(const gl::ShaderState &state) : ShaderImpl(state) {}
 
 ShaderMtl::~ShaderMtl() {}
 
-class TranslateTask : public angle::Closure
-{
-  public:
-    TranslateTask(ShHandle handle, const ShCompileOptions &options, const std::string &source)
-        : mHandle(handle), mOptions(options), mSource(source), mResult(false)
-    {}
-
-    void operator()() override
-    {
-        ANGLE_TRACE_EVENT1("gpu.angle", "TranslateTaskMetal::run", "source", mSource);
-        const char *source = mSource.c_str();
-        mResult            = sh::Compile(mHandle, &source, 1, mOptions);
-    }
-
-    bool getResult() { return mResult; }
-
-    ShHandle getHandle() { return mHandle; }
-
-  private:
-    ShHandle mHandle;
-    ShCompileOptions mOptions;
-    std::string mSource;
-    bool mResult;
-};
-
-class MTLWaitableCompileEventImpl final : public WaitableCompileEvent
-{
-  public:
-    MTLWaitableCompileEventImpl(const SharedCompiledShaderStateMtl &shader,
-                                std::shared_ptr<angle::WaitableEvent> waitableEvent,
-                                std::shared_ptr<TranslateTask> translateTask)
-        : WaitableCompileEvent(waitableEvent), mTranslateTask(translateTask), mShader(shader)
-    {}
-
-    bool getResult() override { return mTranslateTask->getResult(); }
-
-    bool postTranslate(std::string *infoLog) override
-    {
-        sh::TShHandleBase *base    = static_cast<sh::TShHandleBase *>(mTranslateTask->getHandle());
-        auto translatorMetalDirect = base->getAsTranslatorMSL();
-        if (translatorMetalDirect != nullptr)
-        {
-            // Copy reflection from translation.
-            mShader->translatorMetalReflection =
-                *(translatorMetalDirect->getTranslatorMetalReflection());
-            translatorMetalDirect->getTranslatorMetalReflection()->reset();
-        }
-        return true;
-    }
-
-  private:
-    std::shared_ptr<TranslateTask> mTranslateTask;
-    SharedCompiledShaderStateMtl mShader;
-};
-
-std::shared_ptr<WaitableCompileEvent> ShaderMtl::compileImplMtl(
-    const gl::Context *context,
-    gl::ShCompilerInstance *compilerInstance,
-    const std::string &source,
-    ShCompileOptions *compileOptions)
-{
-// TODO(jcunningham): Remove this workaround once correct fix to move validation to the very end is
-// in place. See: https://bugs.webkit.org/show_bug.cgi?id=224991
-#if defined(ANGLE_ENABLE_ASSERTS) && 0
-    compileOptions->validateAst = true;
-#endif
-
-    auto workerThreadPool = context->getShaderCompileThreadPool();
-    auto translateTask =
-        std::make_shared<TranslateTask>(compilerInstance->getHandle(), *compileOptions, source);
-
-    return std::make_shared<MTLWaitableCompileEventImpl>(
-        mCompiledState, workerThreadPool->postWorkerTask(translateTask), translateTask);
-}
-
-std::shared_ptr<WaitableCompileEvent> ShaderMtl::compile(const gl::Context *context,
-                                                         gl::ShCompilerInstance *compilerInstance,
-                                                         ShCompileOptions *options)
+std::shared_ptr<ShaderTranslateTask> ShaderMtl::compile(const gl::Context *context,
+                                                        ShCompileOptions *options)
 {
     ContextMtl *contextMtl = mtl::GetImpl(context);
     DisplayMtl *displayMtl = contextMtl->getDisplay();
@@ -110,6 +58,10 @@
     // previous state.
     mCompiledState = std::make_shared<CompiledShaderStateMtl>();
 
+    // TODO(jcunningham): Remove this workaround once correct fix to move validation to the very end
+    // is in place. https://bugs.webkit.org/show_bug.cgi?id=224991
+    options->validateAST = false;
+
     options->initializeUninitializedLocals = true;
 
     if (context->isWebGL() && mState.getShaderType() != gl::ShaderType::Compute)
@@ -149,7 +101,7 @@
 
     options->rescopeGlobalVariables = displayMtl->getFeatures().rescopeGlobalVariables.enabled;
 
-    return compileImplMtl(context, compilerInstance, getState().getSource(), options);
+    return std::shared_ptr<ShaderTranslateTask>(new ShaderTranslateTaskMtl(mCompiledState));
 }
 
 std::string ShaderMtl::getDebugInfo() const
diff --git a/src/libANGLE/renderer/null/ShaderNULL.cpp b/src/libANGLE/renderer/null/ShaderNULL.cpp
index 98502b5..38c4a08 100644
--- a/src/libANGLE/renderer/null/ShaderNULL.cpp
+++ b/src/libANGLE/renderer/null/ShaderNULL.cpp
@@ -20,16 +20,15 @@
 
 ShaderNULL::~ShaderNULL() {}
 
-std::shared_ptr<WaitableCompileEvent> ShaderNULL::compile(const gl::Context *context,
-                                                          gl::ShCompilerInstance *compilerInstance,
-                                                          ShCompileOptions *options)
+std::shared_ptr<ShaderTranslateTask> ShaderNULL::compile(const gl::Context *context,
+                                                         ShCompileOptions *options)
 {
     const gl::Extensions &extensions = context->getImplementation()->getExtensions();
     if (extensions.shaderPixelLocalStorageANGLE)
     {
         options->pls = context->getImplementation()->getNativePixelLocalStorageOptions();
     }
-    return compileImpl(context, compilerInstance, mState.getSource(), options);
+    return std::shared_ptr<ShaderTranslateTask>(new ShaderTranslateTask);
 }
 
 std::string ShaderNULL::getDebugInfo() const
diff --git a/src/libANGLE/renderer/null/ShaderNULL.h b/src/libANGLE/renderer/null/ShaderNULL.h
index b51ef79..11f6469 100644
--- a/src/libANGLE/renderer/null/ShaderNULL.h
+++ b/src/libANGLE/renderer/null/ShaderNULL.h
@@ -21,9 +21,8 @@
     ShaderNULL(const gl::ShaderState &data);
     ~ShaderNULL() override;
 
-    std::shared_ptr<WaitableCompileEvent> compile(const gl::Context *context,
-                                                  gl::ShCompilerInstance *compilerInstance,
-                                                  ShCompileOptions *options) override;
+    std::shared_ptr<ShaderTranslateTask> compile(const gl::Context *context,
+                                                 ShCompileOptions *options) override;
 
     std::string getDebugInfo() const override;
 };
diff --git a/src/libANGLE/renderer/vulkan/RendererVk.cpp b/src/libANGLE/renderer/vulkan/RendererVk.cpp
index ddd2524..cee83f7 100644
--- a/src/libANGLE/renderer/vulkan/RendererVk.cpp
+++ b/src/libANGLE/renderer/vulkan/RendererVk.cpp
@@ -4941,7 +4941,8 @@
     // https://issuetracker.google.com/292285899
     ANGLE_FEATURE_CONDITION(features, uncurrentEglSurfaceUponSurfaceDestroy, true);
 
-    // The Vulkan backend's handling of link is thread-safe
+    // The Vulkan backend's handling of compile and link is thread-safe
+    ANGLE_FEATURE_CONDITION(features, compileJobIsThreadSafe, true);
     ANGLE_FEATURE_CONDITION(features, linkJobIsThreadSafe, true);
 }
 
diff --git a/src/libANGLE/renderer/vulkan/ShaderVk.cpp b/src/libANGLE/renderer/vulkan/ShaderVk.cpp
index 365d7eb..04f5935 100644
--- a/src/libANGLE/renderer/vulkan/ShaderVk.cpp
+++ b/src/libANGLE/renderer/vulkan/ShaderVk.cpp
@@ -16,14 +16,12 @@
 
 namespace rx
 {
-
 ShaderVk::ShaderVk(const gl::ShaderState &state) : ShaderImpl(state) {}
 
 ShaderVk::~ShaderVk() {}
 
-std::shared_ptr<WaitableCompileEvent> ShaderVk::compile(const gl::Context *context,
-                                                        gl::ShCompilerInstance *compilerInstance,
-                                                        ShCompileOptions *options)
+std::shared_ptr<ShaderTranslateTask> ShaderVk::compile(const gl::Context *context,
+                                                       ShCompileOptions *options)
 {
     ContextVk *contextVk = vk::GetImpl(context);
 
@@ -128,7 +126,8 @@
         options->pls = contextVk->getNativePixelLocalStorageOptions();
     }
 
-    return compileImpl(context, compilerInstance, mState.getSource(), options);
+    // The Vulkan backend needs no post-processing of the translated shader.
+    return std::shared_ptr<ShaderTranslateTask>(new ShaderTranslateTask);
 }
 
 std::string ShaderVk::getDebugInfo() const
diff --git a/src/libANGLE/renderer/vulkan/ShaderVk.h b/src/libANGLE/renderer/vulkan/ShaderVk.h
index 7413321..85305cb 100644
--- a/src/libANGLE/renderer/vulkan/ShaderVk.h
+++ b/src/libANGLE/renderer/vulkan/ShaderVk.h
@@ -21,9 +21,8 @@
     ShaderVk(const gl::ShaderState &state);
     ~ShaderVk() override;
 
-    std::shared_ptr<WaitableCompileEvent> compile(const gl::Context *context,
-                                                  gl::ShCompilerInstance *compilerInstance,
-                                                  ShCompileOptions *options) override;
+    std::shared_ptr<ShaderTranslateTask> compile(const gl::Context *context,
+                                                 ShCompileOptions *options) override;
 
     std::string getDebugInfo() const override;
 };
diff --git a/util/autogen/angle_features_autogen.cpp b/util/autogen/angle_features_autogen.cpp
index 5489130..6aa0442 100644
--- a/util/autogen/angle_features_autogen.cpp
+++ b/util/autogen/angle_features_autogen.cpp
@@ -64,6 +64,7 @@
     {Feature::ClampPointSize, "clampPointSize"},
     {Feature::ClearToZeroOrOneBroken, "clearToZeroOrOneBroken"},
     {Feature::ClipSrcRegionForBlitFramebuffer, "clipSrcRegionForBlitFramebuffer"},
+    {Feature::CompileJobIsThreadSafe, "compileJobIsThreadSafe"},
     {Feature::CompileMetalShaders, "compileMetalShaders"},
     {Feature::CompressVertexData, "compressVertexData"},
     {Feature::CopyIOSurfaceToNonIOSurfaceForReadOptimization, "copyIOSurfaceToNonIOSurfaceForReadOptimization"},
diff --git a/util/autogen/angle_features_autogen.h b/util/autogen/angle_features_autogen.h
index 23814e1..9b8acd0 100644
--- a/util/autogen/angle_features_autogen.h
+++ b/util/autogen/angle_features_autogen.h
@@ -64,6 +64,7 @@
     ClampPointSize,
     ClearToZeroOrOneBroken,
     ClipSrcRegionForBlitFramebuffer,
+    CompileJobIsThreadSafe,
     CompileMetalShaders,
     CompressVertexData,
     CopyIOSurfaceToNonIOSurfaceForReadOptimization,