D3D11: Update cached dynamically recompiled programs.

This change makes it so that when we need to recompile a program on a
draw call, we also update the cache. It also streamlines the internal
queries of the dynamic vertex and fragment shaders such that we only
update the input and output signatures a single time per draw. This
should also facilitate dirty bit implementations for the D3D11 back-
end.

BUG=angleproject:2116

Change-Id: Iccb0501b700bc894f40a8c68d7f297ff0c8f46bd
Reviewed-on: https://chromium-review.googlesource.com/531798
Reviewed-by: Geoff Lang <geofflang@chromium.org>
Commit-Queue: Jamie Madill <jmadill@chromium.org>
diff --git a/src/libANGLE/Context.cpp b/src/libANGLE/Context.cpp
index 98efef9..2e7ec29 100644
--- a/src/libANGLE/Context.cpp
+++ b/src/libANGLE/Context.cpp
@@ -1837,14 +1837,14 @@
 
 void Context::drawArrays(GLenum mode, GLint first, GLsizei count)
 {
-    ANGLE_CONTEXT_TRY(prepareForDraw());
+    ANGLE_CONTEXT_TRY(prepareForDraw(mode));
     ANGLE_CONTEXT_TRY(mImplementation->drawArrays(this, mode, first, count));
     MarkTransformFeedbackBufferUsage(mGLState.getCurrentTransformFeedback());
 }
 
 void Context::drawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei instanceCount)
 {
-    ANGLE_CONTEXT_TRY(prepareForDraw());
+    ANGLE_CONTEXT_TRY(prepareForDraw(mode));
     ANGLE_CONTEXT_TRY(
         mImplementation->drawArraysInstanced(this, mode, first, count, instanceCount));
     MarkTransformFeedbackBufferUsage(mGLState.getCurrentTransformFeedback());
@@ -1852,7 +1852,7 @@
 
 void Context::drawElements(GLenum mode, GLsizei count, GLenum type, const void *indices)
 {
-    ANGLE_CONTEXT_TRY(prepareForDraw());
+    ANGLE_CONTEXT_TRY(prepareForDraw(mode));
     ANGLE_CONTEXT_TRY(mImplementation->drawElements(this, mode, count, type, indices));
 }
 
@@ -1862,7 +1862,7 @@
                                     const void *indices,
                                     GLsizei instances)
 {
-    ANGLE_CONTEXT_TRY(prepareForDraw());
+    ANGLE_CONTEXT_TRY(prepareForDraw(mode));
     ANGLE_CONTEXT_TRY(
         mImplementation->drawElementsInstanced(this, mode, count, type, indices, instances));
 }
@@ -1874,20 +1874,20 @@
                                 GLenum type,
                                 const void *indices)
 {
-    ANGLE_CONTEXT_TRY(prepareForDraw());
+    ANGLE_CONTEXT_TRY(prepareForDraw(mode));
     ANGLE_CONTEXT_TRY(
         mImplementation->drawRangeElements(this, mode, start, end, count, type, indices));
 }
 
 void Context::drawArraysIndirect(GLenum mode, const void *indirect)
 {
-    ANGLE_CONTEXT_TRY(prepareForDraw());
+    ANGLE_CONTEXT_TRY(prepareForDraw(mode));
     ANGLE_CONTEXT_TRY(mImplementation->drawArraysIndirect(this, mode, indirect));
 }
 
 void Context::drawElementsIndirect(GLenum mode, GLenum type, const void *indirect)
 {
-    ANGLE_CONTEXT_TRY(prepareForDraw());
+    ANGLE_CONTEXT_TRY(prepareForDraw(mode));
     ANGLE_CONTEXT_TRY(mImplementation->drawElementsIndirect(this, mode, type, indirect));
 }
 
@@ -2170,7 +2170,7 @@
     return QueryProgramResourceLocation(programObject, programInterface, name);
 }
 
-void Context::handleError(const Error &error)
+Error Context::handleError(const Error &error)
 {
     if (error.isError())
     {
@@ -2188,6 +2188,8 @@
                                  GL_DEBUG_SEVERITY_HIGH, error.getMessage());
         }
     }
+
+    return error;
 }
 
 // Get one of the recorded errors and clear its flag, if any.
@@ -2820,10 +2822,18 @@
     mWorkarounds.loseContextOnOutOfMemory = (mResetStrategy == GL_LOSE_CONTEXT_ON_RESET_EXT);
 }
 
-Error Context::prepareForDraw()
+Error Context::prepareForDraw(GLenum drawMode)
 {
     syncRendererState();
-    return NoError();
+
+    InfoLog infoLog;
+    Error err = mImplementation->triggerDrawCallProgramRecompilation(this, &infoLog,
+                                                                     mMemoryProgramCache, drawMode);
+    if (err.isError())
+    {
+        WARN() << "Dynamic recompilation error log: " << infoLog.str();
+    }
+    return err;
 }
 
 void Context::syncRendererState()
diff --git a/src/libANGLE/Context.h b/src/libANGLE/Context.h
index a4f32cd..4daf1ee 100644
--- a/src/libANGLE/Context.h
+++ b/src/libANGLE/Context.h
@@ -779,7 +779,8 @@
                           void *binary);
     void programBinary(GLuint program, GLenum binaryFormat, const void *binary, GLsizei length);
 
-    void handleError(const Error &error) override;
+    // Returns the error.
+    Error handleError(const Error &error) override;
 
     GLenum getError();
     void markContextLost();
@@ -832,7 +833,7 @@
     egl::Surface *getCurrentReadSurface() const { return mCurrentSurface; }
 
   private:
-    Error prepareForDraw();
+    Error prepareForDraw(GLenum drawMode);
     void syncRendererState();
     void syncRendererState(const State::DirtyBits &bitMask, const State::DirtyObjects &objectMask);
     void syncStateForReadPixels();
diff --git a/src/libANGLE/ContextState.h b/src/libANGLE/ContextState.h
index 6f6a167..fb95d67 100644
--- a/src/libANGLE/ContextState.h
+++ b/src/libANGLE/ContextState.h
@@ -100,7 +100,7 @@
                       bool skipValidation);
     virtual ~ValidationContext() {}
 
-    virtual void handleError(const Error &error) = 0;
+    virtual Error handleError(const Error &error) = 0;
 
     const ContextState &getContextState() const { return mState; }
     GLint getClientMajorVersion() const { return mState.getClientMajorVersion(); }
diff --git a/src/libANGLE/MemoryProgramCache.cpp b/src/libANGLE/MemoryProgramCache.cpp
index 1a93cd8..e34a18a 100644
--- a/src/libANGLE/MemoryProgramCache.cpp
+++ b/src/libANGLE/MemoryProgramCache.cpp
@@ -585,6 +585,13 @@
     put(programHash, std::move(binaryProgram));
 }
 
+void MemoryProgramCache::updateProgram(const Context *context, const Program *program)
+{
+    gl::ProgramHash programHash;
+    ComputeHash(context, program, &programHash);
+    putProgram(programHash, context, program);
+}
+
 void MemoryProgramCache::putBinary(const ProgramHash &programHash,
                                    const uint8_t *binary,
                                    size_t length)
diff --git a/src/libANGLE/MemoryProgramCache.h b/src/libANGLE/MemoryProgramCache.h
index 039561b..e1166e8 100644
--- a/src/libANGLE/MemoryProgramCache.h
+++ b/src/libANGLE/MemoryProgramCache.h
@@ -82,6 +82,9 @@
     // Helper method that serializes a program.
     void putProgram(const ProgramHash &programHash, const Context *context, const Program *program);
 
+    // Same as putProgram but computes the hash.
+    void updateProgram(const Context *context, const Program *program);
+
     // Store a binary directly.
     void putBinary(const ProgramHash &programHash, const uint8_t *binary, size_t length);
 
diff --git a/src/libANGLE/renderer/ContextImpl.h b/src/libANGLE/renderer/ContextImpl.h
index b6233c4..406796e 100644
--- a/src/libANGLE/renderer/ContextImpl.h
+++ b/src/libANGLE/renderer/ContextImpl.h
@@ -18,6 +18,7 @@
 
 namespace gl
 {
+class MemoryProgramCache;
 class Path;
 struct Workarounds;
 }
@@ -155,6 +156,18 @@
                                       GLuint numGroupsY,
                                       GLuint numGroupsZ) = 0;
 
+    // This does not correspond to a GL API, but matches a common GL driver behaviour where
+    // draw call states can trigger dynamic shader recompilation. We pass the Program cache
+    // handle as a mutable pointer to this Impl method to both trigger dynamic recompilations
+    // and to allow the back-end to store the refreshed shaders in the cache.
+    virtual gl::Error triggerDrawCallProgramRecompilation(const gl::Context *context,
+                                                          gl::InfoLog *infoLog,
+                                                          gl::MemoryProgramCache *memoryCache,
+                                                          GLenum drawMode)
+    {
+        return gl::NoError();
+    }
+
     const gl::ContextState &getContextState() { return mState; }
     int getClientMajorVersion() const { return mState.getClientMajorVersion(); }
     int getClientMinorVersion() const { return mState.getClientMinorVersion(); }
diff --git a/src/libANGLE/renderer/d3d/ProgramD3D.cpp b/src/libANGLE/renderer/d3d/ProgramD3D.cpp
index 38f7cf9..b7bb3d1 100644
--- a/src/libANGLE/renderer/d3d/ProgramD3D.cpp
+++ b/src/libANGLE/renderer/d3d/ProgramD3D.cpp
@@ -34,10 +34,12 @@
 namespace
 {
 
-gl::InputLayout GetDefaultInputLayoutFromShader(const gl::Context *context,
-                                                gl::Shader *vertexShader)
+void GetDefaultInputLayoutFromShader(const gl::Context *context,
+                                     gl::Shader *vertexShader,
+                                     gl::InputLayout *inputLayoutOut)
 {
-    gl::InputLayout defaultLayout;
+    inputLayoutOut->clear();
+
     for (const sh::Attribute &shaderAttr : vertexShader->getActiveAttributes(context))
     {
         if (shaderAttr.type != GL_NONE)
@@ -53,26 +55,48 @@
                 gl::VertexFormatType defaultType =
                     gl::GetVertexFormatType(componentType, GL_FALSE, components, pureInt);
 
-                defaultLayout.push_back(defaultType);
+                inputLayoutOut->push_back(defaultType);
             }
         }
     }
-
-    return defaultLayout;
 }
 
-std::vector<GLenum> GetDefaultOutputLayoutFromShader(
-    const std::vector<PixelShaderOutputVariable> &shaderOutputVars)
+void GetDefaultOutputLayoutFromShader(
+    const std::vector<PixelShaderOutputVariable> &shaderOutputVars,
+    std::vector<GLenum> *outputLayoutOut)
 {
-    std::vector<GLenum> defaultPixelOutput;
+    outputLayoutOut->clear();
 
     if (!shaderOutputVars.empty())
     {
-        defaultPixelOutput.push_back(GL_COLOR_ATTACHMENT0 +
-                                     static_cast<unsigned int>(shaderOutputVars[0].outputIndex));
+        outputLayoutOut->push_back(GL_COLOR_ATTACHMENT0 +
+                                   static_cast<unsigned int>(shaderOutputVars[0].outputIndex));
     }
+}
 
-    return defaultPixelOutput;
+void GetPixelOutputLayoutFromFramebuffer(const gl::Context *context,
+                                         const gl::Framebuffer *framebuffer,
+                                         std::vector<GLenum> *signature)
+{
+    signature->clear();
+
+    FramebufferD3D *fboD3D   = GetImplAs<FramebufferD3D>(framebuffer);
+    const auto &colorbuffers = fboD3D->getColorAttachmentsForRender(context);
+
+    for (size_t colorAttachment = 0; colorAttachment < colorbuffers.size(); ++colorAttachment)
+    {
+        const gl::FramebufferAttachment *colorbuffer = colorbuffers[colorAttachment];
+
+        if (colorbuffer)
+        {
+            signature->push_back(colorbuffer->getBinding() == GL_BACK ? GL_COLOR_ATTACHMENT0
+                                                                      : colorbuffer->getBinding());
+        }
+        else
+        {
+            signature->push_back(GL_NONE);
+        }
+    }
 }
 
 bool IsRowMajorLayout(const sh::InterfaceBlockField &var)
@@ -1133,49 +1157,20 @@
 {
 }
 
-gl::Error ProgramD3D::getPixelExecutableForFramebuffer(const gl::Context *context,
-                                                       const gl::Framebuffer *fbo,
-                                                       ShaderExecutableD3D **outExecutable)
-{
-    mPixelShaderOutputFormatCache.clear();
-
-    FramebufferD3D *fboD3D                 = GetImplAs<FramebufferD3D>(fbo);
-    const gl::AttachmentList &colorbuffers = fboD3D->getColorAttachmentsForRender(context);
-
-    for (size_t colorAttachment = 0; colorAttachment < colorbuffers.size(); ++colorAttachment)
-    {
-        const gl::FramebufferAttachment *colorbuffer = colorbuffers[colorAttachment];
-
-        if (colorbuffer)
-        {
-            mPixelShaderOutputFormatCache.push_back(colorbuffer->getBinding() == GL_BACK
-                                                        ? GL_COLOR_ATTACHMENT0
-                                                        : colorbuffer->getBinding());
-        }
-        else
-        {
-            mPixelShaderOutputFormatCache.push_back(GL_NONE);
-        }
-    }
-
-    return getPixelExecutableForOutputLayout(mPixelShaderOutputFormatCache, outExecutable, nullptr);
-}
-
-gl::Error ProgramD3D::getPixelExecutableForOutputLayout(const std::vector<GLenum> &outputSignature,
-                                                        ShaderExecutableD3D **outExectuable,
-                                                        gl::InfoLog *infoLog)
+gl::Error ProgramD3D::getPixelExecutableForCachedOutputLayout(ShaderExecutableD3D **outExecutable,
+                                                              gl::InfoLog *infoLog)
 {
     for (size_t executableIndex = 0; executableIndex < mPixelExecutables.size(); executableIndex++)
     {
-        if (mPixelExecutables[executableIndex]->matchesSignature(outputSignature))
+        if (mPixelExecutables[executableIndex]->matchesSignature(mPixelShaderOutputLayoutCache))
         {
-            *outExectuable = mPixelExecutables[executableIndex]->shaderExecutable();
+            *outExecutable = mPixelExecutables[executableIndex]->shaderExecutable();
             return gl::NoError();
         }
     }
 
     std::string finalPixelHLSL = mDynamicHLSL->generatePixelShaderForOutputSignature(
-        mPixelHLSL, mPixelShaderKey, mUsesFragDepth, outputSignature);
+        mPixelHLSL, mPixelShaderKey, mUsesFragDepth, mPixelShaderOutputLayoutCache);
 
     // Generate new pixel executable
     ShaderExecutableD3D *pixelExecutable = nullptr;
@@ -1191,7 +1186,7 @@
     if (pixelExecutable)
     {
         mPixelExecutables.push_back(std::unique_ptr<PixelExecutable>(
-            new PixelExecutable(outputSignature, pixelExecutable)));
+            new PixelExecutable(mPixelShaderOutputLayoutCache, pixelExecutable)));
     }
     else if (!infoLog)
     {
@@ -1199,16 +1194,13 @@
               << tempInfoLog.str() << std::endl;
     }
 
-    *outExectuable = pixelExecutable;
+    *outExecutable = pixelExecutable;
     return gl::NoError();
 }
 
-gl::Error ProgramD3D::getVertexExecutableForInputLayout(const gl::InputLayout &inputLayout,
-                                                        ShaderExecutableD3D **outExectuable,
-                                                        gl::InfoLog *infoLog)
+gl::Error ProgramD3D::getVertexExecutableForCachedInputLayout(ShaderExecutableD3D **outExectuable,
+                                                              gl::InfoLog *infoLog)
 {
-    VertexExecutable::getSignature(mRenderer, inputLayout, &mCachedVertexSignature);
-
     for (size_t executableIndex = 0; executableIndex < mVertexExecutables.size(); executableIndex++)
     {
         if (mVertexExecutables[executableIndex]->matchesSignature(mCachedVertexSignature))
@@ -1220,7 +1212,7 @@
 
     // Generate new dynamic layout with attribute conversions
     std::string finalVertexHLSL = mDynamicHLSL->generateVertexShaderForInputLayout(
-        mVertexHLSL, inputLayout, mState.getAttributes());
+        mVertexHLSL, mCachedInputLayout, mState.getAttributes());
 
     // Generate new vertex executable
     ShaderExecutableD3D *vertexExecutable = nullptr;
@@ -1236,7 +1228,7 @@
     if (vertexExecutable)
     {
         mVertexExecutables.push_back(std::unique_ptr<VertexExecutable>(
-            new VertexExecutable(inputLayout, mCachedVertexSignature, vertexExecutable)));
+            new VertexExecutable(mCachedInputLayout, mCachedVertexSignature, vertexExecutable)));
     }
     else if (!infoLog)
     {
@@ -1338,11 +1330,9 @@
     }
     gl::Error run() override
     {
-        const auto &defaultInputLayout =
-            GetDefaultInputLayoutFromShader(mContext, mProgram->mState.getAttachedVertexShader());
+        mProgram->updateCachedInputLayoutFromShader(mContext);
 
-        ANGLE_TRY(
-            mProgram->getVertexExecutableForInputLayout(defaultInputLayout, &mResult, &mInfoLog));
+        ANGLE_TRY(mProgram->getVertexExecutableForCachedInputLayout(&mResult, &mInfoLog));
 
         return gl::NoError();
     }
@@ -1351,22 +1341,31 @@
     const gl::Context *mContext;
 };
 
+void ProgramD3D::updateCachedInputLayoutFromShader(const gl::Context *context)
+{
+    GetDefaultInputLayoutFromShader(context, mState.getAttachedVertexShader(), &mCachedInputLayout);
+    VertexExecutable::getSignature(mRenderer, mCachedInputLayout, &mCachedVertexSignature);
+}
+
 class ProgramD3D::GetPixelExecutableTask : public ProgramD3D::GetExecutableTask
 {
   public:
     GetPixelExecutableTask(ProgramD3D *program) : GetExecutableTask(program) {}
     gl::Error run() override
     {
-        const auto &defaultPixelOutput =
-            GetDefaultOutputLayoutFromShader(mProgram->getPixelShaderKey());
+        mProgram->updateCachedOutputLayoutFromShader();
 
-        ANGLE_TRY(
-            mProgram->getPixelExecutableForOutputLayout(defaultPixelOutput, &mResult, &mInfoLog));
+        ANGLE_TRY(mProgram->getPixelExecutableForCachedOutputLayout(&mResult, &mInfoLog));
 
         return gl::NoError();
     }
 };
 
+void ProgramD3D::updateCachedOutputLayoutFromShader()
+{
+    GetDefaultOutputLayoutFromShader(mPixelShaderKey, &mPixelShaderOutputLayoutCache);
+}
+
 class ProgramD3D::GetGeometryExecutableTask : public ProgramD3D::GetExecutableTask
 {
   public:
@@ -2415,6 +2414,14 @@
                                     state.getVertexAttribCurrentValue(locationIndex).Type);
         }
     }
+
+    VertexExecutable::getSignature(mRenderer, mCachedInputLayout, &mCachedVertexSignature);
+}
+
+void ProgramD3D::updateCachedOutputLayout(const gl::Context *context,
+                                          const gl::Framebuffer *framebuffer)
+{
+    GetPixelOutputLayoutFromFramebuffer(context, framebuffer, &mPixelShaderOutputLayoutCache);
 }
 
 void ProgramD3D::gatherTransformFeedbackVaryings(const gl::VaryingPacking &varyingPacking,
@@ -2527,4 +2534,44 @@
     UNREACHABLE();
 }
 
+bool ProgramD3D::hasVertexExecutableForCachedInputLayout()
+{
+    VertexExecutable::getSignature(mRenderer, mCachedInputLayout, &mCachedVertexSignature);
+
+    for (size_t executableIndex = 0; executableIndex < mVertexExecutables.size(); executableIndex++)
+    {
+        if (mVertexExecutables[executableIndex]->matchesSignature(mCachedVertexSignature))
+        {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+bool ProgramD3D::hasGeometryExecutableForPrimitiveType(GLenum drawMode)
+{
+    if (!usesGeometryShader(drawMode))
+    {
+        // No shader necessary mean we have the required (null) executable.
+        return true;
+    }
+
+    gl::PrimitiveType geometryShaderType = GetGeometryShaderTypeFromDrawMode(drawMode);
+    return mGeometryExecutables[geometryShaderType].get() != nullptr;
+}
+
+bool ProgramD3D::hasPixelExecutableForCachedOutputLayout()
+{
+    for (size_t executableIndex = 0; executableIndex < mPixelExecutables.size(); executableIndex++)
+    {
+        if (mPixelExecutables[executableIndex]->matchesSignature(mPixelShaderOutputLayoutCache))
+        {
+            return true;
+        }
+    }
+
+    return false;
+}
+
 }  // namespace rx
diff --git a/src/libANGLE/renderer/d3d/ProgramD3D.h b/src/libANGLE/renderer/d3d/ProgramD3D.h
index d638d37..a570f8e 100644
--- a/src/libANGLE/renderer/d3d/ProgramD3D.h
+++ b/src/libANGLE/renderer/d3d/ProgramD3D.h
@@ -165,19 +165,14 @@
     void setBinaryRetrievableHint(bool retrievable) override;
     void setSeparable(bool separable) override;
 
-    gl::Error getPixelExecutableForFramebuffer(const gl::Context *context,
-                                               const gl::Framebuffer *fbo,
-                                               ShaderExecutableD3D **outExectuable);
-    gl::Error getPixelExecutableForOutputLayout(const std::vector<GLenum> &outputLayout,
-                                                ShaderExecutableD3D **outExectuable,
-                                                gl::InfoLog *infoLog);
-    gl::Error getVertexExecutableForInputLayout(const gl::InputLayout &inputLayout,
-                                                ShaderExecutableD3D **outExectuable,
-                                                gl::InfoLog *infoLog);
+    gl::Error getVertexExecutableForCachedInputLayout(ShaderExecutableD3D **outExectuable,
+                                                      gl::InfoLog *infoLog);
     gl::Error getGeometryExecutableForPrimitiveType(const gl::ContextState &data,
                                                     GLenum drawMode,
                                                     ShaderExecutableD3D **outExecutable,
                                                     gl::InfoLog *infoLog);
+    gl::Error getPixelExecutableForCachedOutputLayout(ShaderExecutableD3D **outExectuable,
+                                                      gl::InfoLog *infoLog);
     gl::Error getComputeExecutable(ShaderExecutableD3D **outExecutable);
     gl::LinkResult link(const gl::Context *context,
                         const gl::VaryingPacking &packing,
@@ -261,10 +256,15 @@
     }
 
     void updateCachedInputLayout(Serial associatedSerial, const gl::State &state);
-    const gl::InputLayout &getCachedInputLayout() const { return mCachedInputLayout; }
+    void updateCachedOutputLayout(const gl::Context *context, const gl::Framebuffer *framebuffer);
 
     bool isSamplerMappingDirty() { return mDirtySamplerMapping; }
 
+    // Checks if we need to recompile certain shaders.
+    bool hasVertexExecutableForCachedInputLayout();
+    bool hasGeometryExecutableForPrimitiveType(GLenum drawMode);
+    bool hasPixelExecutableForCachedOutputLayout();
+
   private:
     // These forward-declared tasks are used for multi-thread shader compiles.
     class GetExecutableTask;
@@ -381,6 +381,9 @@
     void initUniformBlockInfo(const gl::Context *context, gl::Shader *shader);
     size_t getUniformBlockInfo(const sh::InterfaceBlock &interfaceBlock);
 
+    void updateCachedInputLayoutFromShader(const gl::Context *context);
+    void updateCachedOutputLayoutFromShader();
+
     RendererD3D *mRenderer;
     DynamicHLSL *mDynamicHLSL;
 
@@ -417,8 +420,8 @@
     GLuint mUsedComputeSamplerRange;
     bool mDirtySamplerMapping;
 
-    // Cache for getPixelExecutableForFramebuffer
-    std::vector<GLenum> mPixelShaderOutputFormatCache;
+    // Cache for pixel shader output layout to save reallocations.
+    std::vector<GLenum> mPixelShaderOutputLayoutCache;
 
     AttribIndexArray mAttribLocationToD3DSemantic;
 
@@ -441,6 +444,6 @@
 
     Serial mCurrentVertexArrayStateSerial;
 };
-}
+}  // namespace rx
 
 #endif  // LIBANGLE_RENDERER_D3D_PROGRAMD3D_H_
diff --git a/src/libANGLE/renderer/d3d/d3d11/Context11.cpp b/src/libANGLE/renderer/d3d/d3d11/Context11.cpp
index fc2fcb9..b726c6c 100644
--- a/src/libANGLE/renderer/d3d/d3d11/Context11.cpp
+++ b/src/libANGLE/renderer/d3d/d3d11/Context11.cpp
@@ -10,6 +10,8 @@
 #include "libANGLE/renderer/d3d/d3d11/Context11.h"
 
 #include "common/string_utils.h"
+#include "libANGLE/Context.h"
+#include "libANGLE/MemoryProgramCache.h"
 #include "libANGLE/renderer/d3d/CompilerD3D.h"
 #include "libANGLE/renderer/d3d/ProgramD3D.h"
 #include "libANGLE/renderer/d3d/RenderbufferD3D.h"
@@ -294,4 +296,58 @@
     return mRenderer->dispatchCompute(context, numGroupsX, numGroupsY, numGroupsZ);
 }
 
+gl::Error Context11::triggerDrawCallProgramRecompilation(const gl::Context *context,
+                                                         gl::InfoLog *infoLog,
+                                                         gl::MemoryProgramCache *memoryCache,
+                                                         GLenum drawMode)
+{
+    const auto &glState    = context->getGLState();
+    const auto *va11       = GetImplAs<VertexArray11>(glState.getVertexArray());
+    const auto *drawFBO    = glState.getDrawFramebuffer();
+    gl::Program *program   = glState.getProgram();
+    ProgramD3D *programD3D = GetImplAs<ProgramD3D>(program);
+
+    programD3D->updateCachedInputLayout(va11->getCurrentStateSerial(), glState);
+    programD3D->updateCachedOutputLayout(context, drawFBO);
+
+    bool recompileVS = !programD3D->hasVertexExecutableForCachedInputLayout();
+    bool recompileGS = !programD3D->hasGeometryExecutableForPrimitiveType(drawMode);
+    bool recompilePS = !programD3D->hasPixelExecutableForCachedOutputLayout();
+
+    if (!recompileVS && !recompileGS && !recompilePS)
+    {
+        return gl::NoError();
+    }
+
+    // Load the compiler if necessary and recompile the programs.
+    ANGLE_TRY(mRenderer->ensureHLSLCompilerInitialized());
+
+    if (recompileVS)
+    {
+        ShaderExecutableD3D *vertexExe = nullptr;
+        ANGLE_TRY(programD3D->getVertexExecutableForCachedInputLayout(&vertexExe, infoLog));
+    }
+
+    if (recompileGS)
+    {
+        ShaderExecutableD3D *geometryExe = nullptr;
+        ANGLE_TRY(programD3D->getGeometryExecutableForPrimitiveType(
+            context->getContextState(), drawMode, &geometryExe, infoLog));
+    }
+
+    if (recompilePS)
+    {
+        ShaderExecutableD3D *pixelExe = nullptr;
+        ANGLE_TRY(programD3D->getPixelExecutableForCachedOutputLayout(&pixelExe, infoLog));
+    }
+
+    // Refresh the program cache entry.
+    if (memoryCache)
+    {
+        memoryCache->updateProgram(context, program);
+    }
+
+    return gl::NoError();
+}
+
 }  // namespace rx
diff --git a/src/libANGLE/renderer/d3d/d3d11/Context11.h b/src/libANGLE/renderer/d3d/d3d11/Context11.h
index 3e70486..110f5cc 100644
--- a/src/libANGLE/renderer/d3d/d3d11/Context11.h
+++ b/src/libANGLE/renderer/d3d/d3d11/Context11.h
@@ -135,6 +135,11 @@
                               GLuint numGroupsY,
                               GLuint numGroupsZ) override;
 
+    gl::Error triggerDrawCallProgramRecompilation(const gl::Context *context,
+                                                  gl::InfoLog *infoLog,
+                                                  gl::MemoryProgramCache *memoryCache,
+                                                  GLenum drawMode) override;
+
   private:
     Renderer11 *mRenderer;
 };
diff --git a/src/libANGLE/renderer/d3d/d3d11/InputLayoutCache.cpp b/src/libANGLE/renderer/d3d/d3d11/InputLayoutCache.cpp
index 1064f86..64d8382 100644
--- a/src/libANGLE/renderer/d3d/d3d11/InputLayoutCache.cpp
+++ b/src/libANGLE/renderer/d3d/d3d11/InputLayoutCache.cpp
@@ -35,19 +35,6 @@
     return usesPointSpriteEmulation ? 1 : 0;
 }
 
-gl::InputLayout GetInputLayout(const std::vector<const TranslatedAttribute *> &translatedAttributes)
-{
-    gl::InputLayout inputLayout(translatedAttributes.size(), gl::VERTEX_FORMAT_INVALID);
-
-    for (size_t attributeIndex = 0; attributeIndex < translatedAttributes.size(); ++attributeIndex)
-    {
-        const TranslatedAttribute *translatedAttribute = translatedAttributes[attributeIndex];
-        inputLayout[attributeIndex]                    = gl::GetVertexFormatType(
-            *translatedAttribute->attribute, translatedAttribute->currentValueType);
-    }
-    return inputLayout;
-}
-
 GLenum GetGLSLAttributeType(const std::vector<sh::Attribute> &shaderAttributes, size_t index)
 {
     // Count matrices differently
@@ -547,10 +534,8 @@
         inputElementCount++;
     }
 
-    const gl::InputLayout &shaderInputLayout = GetInputLayout(mCurrentAttributes);
-
     ShaderExecutableD3D *shader = nullptr;
-    ANGLE_TRY(programD3D->getVertexExecutableForInputLayout(shaderInputLayout, &shader, nullptr));
+    ANGLE_TRY(programD3D->getVertexExecutableForCachedInputLayout(&shader, nullptr));
 
     ShaderExecutableD3D *shader11 = GetAs<ShaderExecutable11>(shader);
 
diff --git a/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp b/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp
index 30367e2..48a3d12 100644
--- a/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp
+++ b/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp
@@ -1709,8 +1709,7 @@
         }
 
         rx::ShaderExecutableD3D *pixelExe = nullptr;
-        ANGLE_TRY(programD3D->getPixelExecutableForFramebuffer(
-            context, glState.getDrawFramebuffer(), &pixelExe));
+        ANGLE_TRY(programD3D->getPixelExecutableForCachedOutputLayout(&pixelExe, nullptr));
 
         // Skip the draw call if rasterizer discard is enabled (or no fragment shader).
         if (!pixelExe || glState.getRasterizerState().rasterizerDiscard)
diff --git a/src/libANGLE/renderer/d3d/d3d11/StateManager11.cpp b/src/libANGLE/renderer/d3d/d3d11/StateManager11.cpp
index 02ca154..51c9832 100644
--- a/src/libANGLE/renderer/d3d/d3d11/StateManager11.cpp
+++ b/src/libANGLE/renderer/d3d/d3d11/StateManager11.cpp
@@ -1737,23 +1737,22 @@
 
 gl::Error StateManager11::syncProgram(const gl::Context *context, GLenum drawMode)
 {
-    // This method is called single-threaded.
-    ANGLE_TRY(mRenderer->ensureHLSLCompilerInitialized());
-
     const auto &glState = context->getGLState();
-    const auto *va11 = GetImplAs<VertexArray11>(glState.getVertexArray());
+    const auto *va11    = GetImplAs<VertexArray11>(glState.getVertexArray());
+    auto *programD3D    = GetImplAs<ProgramD3D>(glState.getProgram());
 
-    ProgramD3D *programD3D = GetImplAs<ProgramD3D>(glState.getProgram());
     programD3D->updateCachedInputLayout(va11->getCurrentStateSerial(), glState);
 
-    const auto &inputLayout = programD3D->getCachedInputLayout();
+    // Binaries must be compiled before the sync.
+    ASSERT(programD3D->hasVertexExecutableForCachedInputLayout());
+    ASSERT(programD3D->hasGeometryExecutableForPrimitiveType(drawMode));
+    ASSERT(programD3D->hasPixelExecutableForCachedOutputLayout());
 
     ShaderExecutableD3D *vertexExe = nullptr;
-    ANGLE_TRY(programD3D->getVertexExecutableForInputLayout(inputLayout, &vertexExe, nullptr));
+    ANGLE_TRY(programD3D->getVertexExecutableForCachedInputLayout(&vertexExe, nullptr));
 
-    const gl::Framebuffer *drawFramebuffer = glState.getDrawFramebuffer();
-    ShaderExecutableD3D *pixelExe          = nullptr;
-    ANGLE_TRY(programD3D->getPixelExecutableForFramebuffer(context, drawFramebuffer, &pixelExe));
+    ShaderExecutableD3D *pixelExe = nullptr;
+    ANGLE_TRY(programD3D->getPixelExecutableForCachedOutputLayout(&pixelExe, nullptr));
 
     ShaderExecutableD3D *geometryExe = nullptr;
     ANGLE_TRY(programD3D->getGeometryExecutableForPrimitiveType(context->getContextState(),
diff --git a/src/libANGLE/renderer/d3d/d3d9/Renderer9.cpp b/src/libANGLE/renderer/d3d/d3d9/Renderer9.cpp
index 59cc15e..9a1b27a 100644
--- a/src/libANGLE/renderer/d3d/d3d9/Renderer9.cpp
+++ b/src/libANGLE/renderer/d3d/d3d9/Renderer9.cpp
@@ -1796,14 +1796,14 @@
     VertexArray9 *vao      = GetImplAs<VertexArray9>(state.getVertexArray());
     programD3D->updateCachedInputLayout(vao->getCurrentStateSerial(), state);
 
-    const auto &inputLayout = programD3D->getCachedInputLayout();
-
     ShaderExecutableD3D *vertexExe = nullptr;
-    ANGLE_TRY(programD3D->getVertexExecutableForInputLayout(inputLayout, &vertexExe, nullptr));
+    ANGLE_TRY(programD3D->getVertexExecutableForCachedInputLayout(&vertexExe, nullptr));
 
     const gl::Framebuffer *drawFramebuffer = state.getDrawFramebuffer();
-    ShaderExecutableD3D *pixelExe          = nullptr;
-    ANGLE_TRY(programD3D->getPixelExecutableForFramebuffer(context, drawFramebuffer, &pixelExe));
+    programD3D->updateCachedOutputLayout(context, drawFramebuffer);
+
+    ShaderExecutableD3D *pixelExe = nullptr;
+    ANGLE_TRY(programD3D->getPixelExecutableForCachedOutputLayout(&pixelExe, nullptr));
 
     IDirect3DVertexShader9 *vertexShader =
         (vertexExe ? GetAs<ShaderExecutable9>(vertexExe)->getVertexShader() : nullptr);
diff --git a/src/libANGLE/validationES_unittest.cpp b/src/libANGLE/validationES_unittest.cpp
index 49f6e30..d0e601c 100644
--- a/src/libANGLE/validationES_unittest.cpp
+++ b/src/libANGLE/validationES_unittest.cpp
@@ -52,7 +52,7 @@
     {
     }
 
-    MOCK_METHOD1(handleError, void(const Error &));
+    MOCK_METHOD1(handleError, Error(const Error &));
 };
 
 // Test that ANGLE generates an INVALID_OPERATION when validating index data that uses a value