D3D11: implement SSBOs in pixel and vertex shaders.

Since the 'u' register space for UAVs in pixel shaders is shared
with render targets, and the number of render targets may vary
depending on GL state, this required deferring register allocation
until draw-time output in DynamicHLSL.

Since non-compute shaders aren't able to immediately output the
SSBO declaration, initial register allocation was broken out from
ResourcesHLSL::shaderStorageBlocksHeader()
into ResourcesHLSL::allocateShaderStorageBlockRegisters() with
the former only called for compute shaders. These initial allocations
are offset by the number of RTs at draw time.

Since Raw UAVs may now be created at draw time for non-compute
shaders, call markRawBufferUsage() from the Renderer11::draw*()
entry points as it is from dispatchCompute*().

Bug: angleproject:7156
Change-Id: I6ab65af1ff36df0313e3c1f8f79661b1547ab9a2
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/3565562
Reviewed-by: Jamie Madill <jmadill@chromium.org>
Commit-Queue: Stephen White <senorblanco@chromium.org>
diff --git a/src/compiler/translator/OutputHLSL.cpp b/src/compiler/translator/OutputHLSL.cpp
index 04ba480..44ff552 100644
--- a/src/compiler/translator/OutputHLSL.cpp
+++ b/src/compiler/translator/OutputHLSL.cpp
@@ -660,7 +660,7 @@
 
     mResourcesHLSL->uniformsHeader(out, mOutputType, mReferencedUniforms, mSymbolTable);
     out << mResourcesHLSL->uniformBlocksHeader(mReferencedUniformBlocks, mUniformBlockOptimizedMap);
-    mSSBOOutputHLSL->writeShaderStorageBlocksHeader(out);
+    mSSBOOutputHLSL->writeShaderStorageBlocksHeader(mShaderType, out);
 
     if (!mEqualityFunctions.empty())
     {
diff --git a/src/compiler/translator/ResourcesHLSL.cpp b/src/compiler/translator/ResourcesHLSL.cpp
index a6bebfe..3478f0a 100644
--- a/src/compiler/translator/ResourcesHLSL.cpp
+++ b/src/compiler/translator/ResourcesHLSL.cpp
@@ -762,6 +762,27 @@
     return (interfaceBlocks.empty() ? "" : ("// Uniform Blocks\n\n" + interfaceBlocks));
 }
 
+void ResourcesHLSL::allocateShaderStorageBlockRegisters(
+    const ReferencedInterfaceBlocks &referencedInterfaceBlocks)
+{
+    for (const auto &interfaceBlockReference : referencedInterfaceBlocks)
+    {
+        const TInterfaceBlock &interfaceBlock = *interfaceBlockReference.second->block;
+        const TVariable *instanceVariable     = interfaceBlockReference.second->instanceVariable;
+
+        mShaderStorageBlockRegisterMap[interfaceBlock.name().data()] = mUAVRegister;
+
+        if (instanceVariable != nullptr && instanceVariable->getType().isArray())
+        {
+            mUAVRegister += instanceVariable->getType().getOutermostArraySize();
+        }
+        else
+        {
+            mUAVRegister += 1u;
+        }
+    }
+}
+
 TString ResourcesHLSL::shaderStorageBlocksHeader(
     const ReferencedInterfaceBlocks &referencedInterfaceBlocks)
 {
@@ -772,8 +793,7 @@
         const TInterfaceBlock &interfaceBlock = *interfaceBlockReference.second->block;
         const TVariable *instanceVariable     = interfaceBlockReference.second->instanceVariable;
 
-        unsigned int activeRegister                                  = mUAVRegister;
-        mShaderStorageBlockRegisterMap[interfaceBlock.name().data()] = activeRegister;
+        unsigned int activeRegister = mShaderStorageBlockRegisterMap[interfaceBlock.name().data()];
 
         if (instanceVariable != nullptr && instanceVariable->getType().isArray())
         {
@@ -783,17 +803,15 @@
                 interfaceBlocks += shaderStorageBlockString(
                     interfaceBlock, instanceVariable, activeRegister + arrayIndex, arrayIndex);
             }
-            mUAVRegister += instanceArraySize;
         }
         else
         {
             interfaceBlocks += shaderStorageBlockString(interfaceBlock, instanceVariable,
                                                         activeRegister, GL_INVALID_INDEX);
-            mUAVRegister += 1u;
         }
     }
 
-    return (interfaceBlocks.empty() ? "" : ("// Shader Storage Blocks\n\n" + interfaceBlocks));
+    return interfaceBlocks;
 }
 
 TString ResourcesHLSL::uniformBlockString(const TInterfaceBlock &interfaceBlock,
diff --git a/src/compiler/translator/ResourcesHLSL.h b/src/compiler/translator/ResourcesHLSL.h
index ef2d938..5af8dd4 100644
--- a/src/compiler/translator/ResourcesHLSL.h
+++ b/src/compiler/translator/ResourcesHLSL.h
@@ -41,6 +41,8 @@
     TString uniformBlocksHeader(
         const ReferencedInterfaceBlocks &referencedInterfaceBlocks,
         const std::map<int, const TInterfaceBlock *> &uniformBlockOptimizedMap);
+    void allocateShaderStorageBlockRegisters(
+        const ReferencedInterfaceBlocks &referencedInterfaceBlocks);
     TString shaderStorageBlocksHeader(const ReferencedInterfaceBlocks &referencedInterfaceBlocks);
 
     // Used for direct index references
diff --git a/src/compiler/translator/ShaderStorageBlockOutputHLSL.cpp b/src/compiler/translator/ShaderStorageBlockOutputHLSL.cpp
index 016e546..2ff01e4 100644
--- a/src/compiler/translator/ShaderStorageBlockOutputHLSL.cpp
+++ b/src/compiler/translator/ShaderStorageBlockOutputHLSL.cpp
@@ -36,6 +36,9 @@
 namespace
 {
 
+constexpr const char kShaderStorageDeclarationString[] =
+    "// @@ SHADER STORAGE DECLARATION STRING @@";
+
 void GetBlockLayoutInfo(TIntermTyped *node,
                         bool rowMajorAlreadyAssigned,
                         TLayoutBlockStorage *storage,
@@ -503,9 +506,24 @@
     loc->traverse(mOutputHLSL);
 }
 
-void ShaderStorageBlockOutputHLSL::writeShaderStorageBlocksHeader(TInfoSinkBase &out) const
+void ShaderStorageBlockOutputHLSL::writeShaderStorageBlocksHeader(GLenum shaderType,
+                                                                  TInfoSinkBase &out) const
 {
-    out << mResourcesHLSL->shaderStorageBlocksHeader(mReferencedShaderStorageBlocks);
+    if (mReferencedShaderStorageBlocks.empty())
+    {
+        return;
+    }
+
+    mResourcesHLSL->allocateShaderStorageBlockRegisters(mReferencedShaderStorageBlocks);
+    out << "// Shader Storage Blocks\n\n";
+    if (shaderType == GL_COMPUTE_SHADER)
+    {
+        out << mResourcesHLSL->shaderStorageBlocksHeader(mReferencedShaderStorageBlocks);
+    }
+    else
+    {
+        out << kShaderStorageDeclarationString << "\n";
+    }
     mSSBOFunctionHLSL->shaderStorageBlockFunctionHeader(out);
 }
 
diff --git a/src/compiler/translator/ShaderStorageBlockOutputHLSL.h b/src/compiler/translator/ShaderStorageBlockOutputHLSL.h
index 4aeed3f..2acf06a 100644
--- a/src/compiler/translator/ShaderStorageBlockOutputHLSL.h
+++ b/src/compiler/translator/ShaderStorageBlockOutputHLSL.h
@@ -56,7 +56,7 @@
     // Writes the atomic memory function calls for SSBO.
     void outputAtomicMemoryFunctionCallPrefix(TIntermTyped *node, TOperator op);
 
-    void writeShaderStorageBlocksHeader(TInfoSinkBase &out) const;
+    void writeShaderStorageBlocksHeader(GLenum shaderType, TInfoSinkBase &out) const;
 
   private:
     void traverseSSBOAccess(TIntermTyped *node, SSBOMethod method);
diff --git a/src/libANGLE/renderer/d3d/DynamicHLSL.cpp b/src/libANGLE/renderer/d3d/DynamicHLSL.cpp
index 96aeb76..930e371 100644
--- a/src/libANGLE/renderer/d3d/DynamicHLSL.cpp
+++ b/src/libANGLE/renderer/d3d/DynamicHLSL.cpp
@@ -28,6 +28,10 @@
 namespace
 {
 
+// kShaderStorageDeclarationString must be the same as outputHLSL.
+constexpr const char kShaderStorageDeclarationString[] =
+    "// @@ SHADER STORAGE DECLARATION STRING @@";
+
 const char *HLSLComponentTypeString(GLenum componentType)
 {
     switch (componentType)
@@ -125,6 +129,39 @@
     strstr << "]";
 }
 
+bool ReplaceShaderStorageDeclaration(const std::vector<ShaderStorageBlock> &shaderStorageBlocks,
+                                     std::string *hlsl,
+                                     size_t baseUAVRegister,
+                                     gl::ShaderType shaderType)
+{
+    std::string ssboHeader;
+    std::ostringstream out(ssboHeader);
+    for (const ShaderStorageBlock &ssbo : shaderStorageBlocks)
+    {
+        size_t uavRegister = baseUAVRegister + ssbo.registerIndex;
+        std::string name   = ssbo.name;
+        if (ssbo.arraySize > 0)
+        {
+            for (unsigned int arrayIndex = 0; arrayIndex < ssbo.arraySize; arrayIndex++)
+            {
+                out << "RWByteAddressBuffer "
+                    << "dx_" << name << "_" << arrayIndex << ": register(u"
+                    << uavRegister + arrayIndex << ");\n";
+            }
+        }
+        else
+        {
+            out << "RWByteAddressBuffer "
+                << "_" << name << ": register(u" << uavRegister << ");\n";
+        }
+    }
+    if (out.str().empty())
+    {
+        return true;
+    }
+    return angle::ReplaceSubstring(hlsl, kShaderStorageDeclarationString, out.str());
+}
+
 constexpr const char *VERTEX_ATTRIBUTE_STUB_STRING      = "@@ VERTEX ATTRIBUTES @@";
 constexpr const char *VERTEX_OUTPUT_STUB_STRING         = "@@ VERTEX OUTPUT @@";
 constexpr const char *PIXEL_OUTPUT_STUB_STRING          = "@@ PIXEL OUTPUT @@";
@@ -144,7 +181,9 @@
 std::string DynamicHLSL::generateVertexShaderForInputLayout(
     const std::string &sourceShader,
     const InputLayout &inputLayout,
-    const std::vector<sh::ShaderVariable> &shaderAttributes) const
+    const std::vector<sh::ShaderVariable> &shaderAttributes,
+    const std::vector<rx::ShaderStorageBlock> &shaderStorageBlocks,
+    size_t baseUAVRegister) const
 {
     std::ostringstream structStream;
     std::ostringstream initStream;
@@ -269,6 +308,10 @@
         angle::ReplaceSubstring(&vertexHLSL, VERTEX_ATTRIBUTE_STUB_STRING, structStream.str());
     ASSERT(success);
 
+    success = ReplaceShaderStorageDeclaration(shaderStorageBlocks, &vertexHLSL, inputLayout.size(),
+                                              gl::ShaderType::Vertex);
+    ASSERT(success);
+
     return vertexHLSL;
 }
 
@@ -276,7 +319,8 @@
     const std::string &sourceShader,
     const std::vector<PixelShaderOutputVariable> &outputVariables,
     bool usesFragDepth,
-    const std::vector<GLenum> &outputLayout) const
+    const std::vector<GLenum> &outputLayout,
+    const std::vector<ShaderStorageBlock> &shaderStorageBlocks) const
 {
     const int shaderModel      = mRenderer->getMajorShaderModel();
     std::string targetSemantic = (shaderModel >= 4) ? "SV_TARGET" : "COLOR";
@@ -350,6 +394,10 @@
         angle::ReplaceSubstring(&pixelHLSL, PIXEL_OUTPUT_STUB_STRING, declarationStream.str());
     ASSERT(success);
 
+    success = ReplaceShaderStorageDeclaration(shaderStorageBlocks, &pixelHLSL, numOutputs,
+                                              gl::ShaderType::Fragment);
+    ASSERT(success);
+
     return pixelHLSL;
 }
 
diff --git a/src/libANGLE/renderer/d3d/DynamicHLSL.h b/src/libANGLE/renderer/d3d/DynamicHLSL.h
index 37ece6f..4785b7b 100644
--- a/src/libANGLE/renderer/d3d/DynamicHLSL.h
+++ b/src/libANGLE/renderer/d3d/DynamicHLSL.h
@@ -38,6 +38,7 @@
 {
 class ProgramD3DMetadata;
 class ShaderD3D;
+struct ShaderStorageBlock;
 
 // This class needs to match OutputHLSL::decorate
 class DecorateVariable final : angle::NonCopyable
@@ -148,12 +149,15 @@
     std::string generateVertexShaderForInputLayout(
         const std::string &sourceShader,
         const gl::InputLayout &inputLayout,
-        const std::vector<sh::ShaderVariable> &shaderAttributes) const;
+        const std::vector<sh::ShaderVariable> &shaderAttributes,
+        const std::vector<rx::ShaderStorageBlock> &shaderStorageBlocks,
+        size_t baseUAVRegister) const;
     std::string generatePixelShaderForOutputSignature(
         const std::string &sourceShader,
         const std::vector<PixelShaderOutputVariable> &outputVariables,
         bool usesFragDepth,
-        const std::vector<GLenum> &outputLayout) const;
+        const std::vector<GLenum> &outputLayout,
+        const std::vector<rx::ShaderStorageBlock> &shaderStorageBlocks) const;
     std::string generateShaderForImage2DBindSignature(
         const d3d::Context *context,
         ProgramD3D &programD3D,
diff --git a/src/libANGLE/renderer/d3d/ProgramD3D.cpp b/src/libANGLE/renderer/d3d/ProgramD3D.cpp
index 4b35a03..6970928 100644
--- a/src/libANGLE/renderer/d3d/ProgramD3D.cpp
+++ b/src/libANGLE/renderer/d3d/ProgramD3D.cpp
@@ -1544,7 +1544,7 @@
 
     std::string finalPixelHLSL = mDynamicHLSL->generatePixelShaderForOutputSignature(
         mShaderHLSL[gl::ShaderType::Fragment], mPixelShaderKey, mUsesFragDepth,
-        mPixelShaderOutputLayoutCache);
+        mPixelShaderOutputLayoutCache, mShaderStorageBlocks[gl::ShaderType::Fragment]);
 
     // Generate new pixel executable
     ShaderExecutableD3D *pixelExecutable = nullptr;
@@ -1587,7 +1587,8 @@
 
     // Generate new dynamic layout with attribute conversions
     std::string finalVertexHLSL = mDynamicHLSL->generateVertexShaderForInputLayout(
-        mShaderHLSL[gl::ShaderType::Vertex], mCachedInputLayout, mState.getProgramInputs());
+        mShaderHLSL[gl::ShaderType::Vertex], mCachedInputLayout, mState.getProgramInputs(),
+        mShaderStorageBlocks[gl::ShaderType::Vertex], getNumPixelShaderOutputs());
 
     // Generate new vertex executable
     ShaderExecutableD3D *vertexExecutable = nullptr;
@@ -2190,12 +2191,10 @@
     {
         shadersD3D[shaderType] = SafeGetImplAs<ShaderD3D>(mState.getAttachedShader(shaderType));
     }
-
     for (const gl::InterfaceBlock &shaderStorageBlock : mState.getShaderStorageBlocks())
     {
         unsigned int shaderStorageBlockElement =
             shaderStorageBlock.isArray ? shaderStorageBlock.arrayElement : 0;
-
         D3DInterfaceBlock d3dShaderStorageBlock;
 
         for (gl::ShaderType shaderType : gl::AllShaderTypes())
@@ -2205,13 +2204,35 @@
                 ASSERT(shadersD3D[shaderType]);
                 unsigned int baseRegister =
                     shadersD3D[shaderType]->getShaderStorageBlockRegister(shaderStorageBlock.name);
+
                 d3dShaderStorageBlock.mShaderRegisterIndexes[shaderType] =
                     baseRegister + shaderStorageBlockElement;
             }
         }
-
         mD3DShaderStorageBlocks.push_back(d3dShaderStorageBlock);
     }
+
+    for (gl::ShaderType shaderType : gl::AllShaderTypes())
+    {
+        gl::Shader *shader = mState.getAttachedShader(shaderType);
+        if (!shader)
+        {
+            continue;
+        }
+        ShaderD3D *shaderD3D = SafeGetImplAs<ShaderD3D>(shader);
+        for (const sh::InterfaceBlock &ssbo : shader->getShaderStorageBlocks())
+        {
+            if (!ssbo.active)
+            {
+                continue;
+            }
+            ShaderStorageBlock block;
+            block.name          = !ssbo.instanceName.empty() ? ssbo.instanceName : ssbo.name;
+            block.arraySize     = ssbo.isArray() ? ssbo.arraySize : 0;
+            block.registerIndex = shaderD3D->getShaderStorageBlockRegister(ssbo.name);
+            mShaderStorageBlocks[shaderType].push_back(block);
+        }
+    }
 }
 
 void ProgramD3D::initializeUniformBlocks()
diff --git a/src/libANGLE/renderer/d3d/ProgramD3D.h b/src/libANGLE/renderer/d3d/ProgramD3D.h
index f2e5739..cfe183c 100644
--- a/src/libANGLE/renderer/d3d/ProgramD3D.h
+++ b/src/libANGLE/renderer/d3d/ProgramD3D.h
@@ -106,6 +106,13 @@
     gl::ShaderMap<unsigned int> mStructureByteStrides;
 };
 
+struct ShaderStorageBlock
+{
+    std::string name;
+    unsigned int arraySize     = 0;
+    unsigned int registerIndex = 0;
+};
+
 struct D3DUBOCache
 {
     unsigned int registerIndex;
@@ -350,6 +357,7 @@
     bool hasNamedUniform(const std::string &name);
 
     bool usesVertexID() const { return mUsesVertexID; }
+    size_t getNumPixelShaderOutputs() const { return mPixelShaderOutputLayoutCache.size(); }
 
   private:
     // These forward-declared tasks are used for multi-thread shader compiles.
@@ -585,6 +593,7 @@
     std::map<std::string, int> mAtomicBindingMap;
     std::vector<D3DUniformBlock> mD3DUniformBlocks;
     std::vector<D3DInterfaceBlock> mD3DShaderStorageBlocks;
+    gl::ShaderMap<std::vector<ShaderStorageBlock>> mShaderStorageBlocks;
     std::array<unsigned int, gl::IMPLEMENTATION_MAX_ATOMIC_COUNTER_BUFFER_BINDINGS>
         mComputeAtomicCounterBufferRegisterIndices;
 
diff --git a/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp b/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp
index 6782322..24f3501 100644
--- a/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp
+++ b/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp
@@ -1768,6 +1768,8 @@
         return angle::Result::Continue;
     }
 
+    ANGLE_TRY(markRawBufferUsage(context));
+
     ProgramD3D *programD3D        = mStateManager.getProgramD3D();
     GLsizei adjustedInstanceCount = GetAdjustedInstanceCount(programD3D, instanceCount);
 
@@ -1860,6 +1862,8 @@
         return angle::Result::Continue;
     }
 
+    ANGLE_TRY(markRawBufferUsage(context));
+
     // Transform feedback is not allowed for DrawElements, this error should have been caught at the
     // API validation layer.
     const gl::State &glState = context->getState();
@@ -1945,6 +1949,8 @@
         return angle::Result::Continue;
     }
 
+    ANGLE_TRY(markRawBufferUsage(context));
+
     const gl::State &glState = context->getState();
     ASSERT(!glState.isTransformFeedbackActiveUnpaused());
 
@@ -1967,6 +1973,8 @@
         return angle::Result::Continue;
     }
 
+    ANGLE_TRY(markRawBufferUsage(context));
+
     const gl::State &glState = context->getState();
     ASSERT(!glState.isTransformFeedbackActiveUnpaused());
 
diff --git a/src/libANGLE/renderer/d3d/d3d11/StateManager11.cpp b/src/libANGLE/renderer/d3d/d3d11/StateManager11.cpp
index 843b54c..916de4c 100644
--- a/src/libANGLE/renderer/d3d/d3d11/StateManager11.cpp
+++ b/src/libANGLE/renderer/d3d/d3d11/StateManager11.cpp
@@ -817,7 +817,6 @@
                                                     UINT resourceSlot,
                                                     const UAVType *uav)
 {
-    ASSERT(shaderType == gl::ShaderType::Compute);
     ASSERT(static_cast<size_t>(resourceSlot) < mCurComputeUAVs.size());
     const ViewRecord<D3D11_UNORDERED_ACCESS_VIEW_DESC> &record = mCurComputeUAVs[resourceSlot];
 
@@ -837,7 +836,24 @@
             unsetConflictingSRVs(gl::PipelineType::ComputePipeline, gl::ShaderType::Compute,
                                  resource, nullptr, false);
         }
-        deviceContext->CSSetUnorderedAccessViews(resourceSlot, 1, &uavPtr, nullptr);
+        switch (shaderType)
+        {
+            case gl::ShaderType::Vertex:
+            case gl::ShaderType::Fragment:
+            {
+                UINT baseUAVRegister = static_cast<UINT>(mProgramD3D->getNumPixelShaderOutputs());
+                deviceContext->OMSetRenderTargetsAndUnorderedAccessViews(
+                    D3D11_KEEP_RENDER_TARGETS_AND_DEPTH_STENCIL, nullptr, nullptr,
+                    baseUAVRegister + resourceSlot, 1, &uavPtr, nullptr);
+                break;
+            }
+            case gl::ShaderType::Compute:
+                deviceContext->CSSetUnorderedAccessViews(resourceSlot, 1, &uavPtr, nullptr);
+                break;
+            default:
+                UNIMPLEMENTED();
+                break;
+        }
 
         mCurComputeUAVs.update(resourceSlot, uavPtr);
     }
@@ -2342,7 +2358,7 @@
                 // TODO(jie.a.chen@intel.com): http://anglebug.com/1729
                 break;
             case DIRTY_BIT_PROGRAM_SHADER_STORAGE_BUFFERS:
-                // TODO(jie.a.chen@intel.com): http://anglebug.com/1951
+                ANGLE_TRY(syncShaderStorageBuffers(context));
                 break;
             case DIRTY_BIT_SHADERS:
                 ANGLE_TRY(syncProgram(context, mode));
@@ -3761,14 +3777,14 @@
 
         switch (shaderType)
         {
+            case gl::ShaderType::Vertex:
+            case gl::ShaderType::Fragment:
             case gl::ShaderType::Compute:
             {
                 setUnorderedAccessViewInternal(shaderType, registerIndex, uavPtr);
                 break;
             }
 
-            case gl::ShaderType::Vertex:
-            case gl::ShaderType::Fragment:
             case gl::ShaderType::Geometry:
                 UNIMPLEMENTED();
                 break;
@@ -3863,9 +3879,12 @@
 
 angle::Result StateManager11::syncShaderStorageBuffers(const gl::Context *context)
 {
-    if (mProgramD3D->hasShaderStage(gl::ShaderType::Compute))
+    for (gl::ShaderType shaderType : gl::AllShaderTypes())
     {
-        ANGLE_TRY(syncShaderStorageBuffersForShader(context, gl::ShaderType::Compute));
+        if (mProgramD3D->hasShaderStage(shaderType))
+        {
+            ANGLE_TRY(syncShaderStorageBuffersForShader(context, shaderType));
+        }
     }
 
     return angle::Result::Continue;
diff --git a/src/tests/gl_tests/StateChangeTest.cpp b/src/tests/gl_tests/StateChangeTest.cpp
index 3253605..119986f 100644
--- a/src/tests/gl_tests/StateChangeTest.cpp
+++ b/src/tests/gl_tests/StateChangeTest.cpp
@@ -4560,9 +4560,6 @@
 // call works correctly.  This requires a barrier in between the draw calls.
 TEST_P(SimpleStateChangeTestES31, DrawWithUBOThenDrawWithSSBO)
 {
-    // http://anglebug.com/5593
-    ANGLE_SKIP_TEST_IF(IsD3D11());
-
     GLint maxFragmentShaderStorageBlocks;
     glGetIntegerv(GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS, &maxFragmentShaderStorageBlocks);
 
@@ -4664,6 +4661,115 @@
     glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
 }
 
+// Tests that writing to an SSBO in the fragment shader before and after a change to the drawbuffers
+// still works
+TEST_P(SimpleStateChangeTestES31, FragWriteSSBOThenChangeDrawbuffersThenWriteSSBO)
+{
+    GLint maxFragmentShaderStorageBlocks;
+    glGetIntegerv(GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS, &maxFragmentShaderStorageBlocks);
+
+    ANGLE_SKIP_TEST_IF(maxFragmentShaderStorageBlocks < 1);
+
+    constexpr GLsizei kSize = 1;
+
+    GLTexture color;
+    glBindTexture(GL_TEXTURE_2D, color);
+    glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kSize, kSize);
+    EXPECT_GL_NO_ERROR();
+
+    GLFramebuffer fbo;
+    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color, 0);
+    EXPECT_GL_NO_ERROR();
+
+    constexpr std::array<float, 4> kBufferInitValue = {0.125f, 0.25f, 0.5f, 1.0f};
+    GLBuffer buffer;
+    glBindBuffer(GL_SHADER_STORAGE_BUFFER, buffer);
+    glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(kBufferInitValue), kBufferInitValue.data(),
+                 GL_STATIC_DRAW);
+    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, buffer);
+
+    // Create a program that writes to the SSBO in the fragment shader.
+    constexpr char kFS[] = R"(#version 310 es
+precision mediump float;
+layout(binding = 0, std430) buffer Output {
+    vec4 value;
+} b;
+out vec4 colorOut;
+uniform vec4 value;
+void main()
+{
+    b.value = value;
+    colorOut = vec4(1, 1, 1, 0);
+})";
+
+    GLuint vs = CompileShader(GL_VERTEX_SHADER, essl31_shaders::vs::Simple());
+    GLuint fs = CompileShader(GL_FRAGMENT_SHADER, kFS);
+
+    const GLuint program = glCreateProgram();
+    glAttachShader(program, vs);
+    glAttachShader(program, fs);
+    glLinkProgram(program);
+    CheckLinkStatusAndReturnProgram(program, true);
+
+    // Detach the shaders, so any draw-time shader rewriting won't be able to use them.
+    glDetachShader(program, vs);
+    glDetachShader(program, fs);
+
+    glUseProgram(program);
+    GLint positionLoc = glGetAttribLocation(program, essl31_shaders::PositionAttrib());
+    ASSERT_NE(-1, positionLoc);
+    GLint valueLoc = glGetUniformLocation(program, "value");
+    ASSERT_NE(-1, valueLoc);
+
+    const std::array<Vector3, 6> &quadVertices = GetQuadVertices();
+    const size_t posBufferSize                 = quadVertices.size() * sizeof(Vector3);
+
+    GLBuffer posBuffer;
+    glBindBuffer(GL_ARRAY_BUFFER, posBuffer);
+    glBufferData(GL_ARRAY_BUFFER, posBufferSize, quadVertices.data(), GL_STATIC_DRAW);
+    glVertexAttribPointer(positionLoc, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
+    glEnableVertexAttribArray(positionLoc);
+
+    glUseProgram(program);
+    constexpr float kValue1[4] = {0.1f, 0.2f, 0.3f, 0.4f};
+
+    glUniform4fv(valueLoc, 1, kValue1);
+    glDrawArrays(GL_TRIANGLES, 0, 6);
+    glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
+    EXPECT_GL_NO_ERROR();
+
+    // Verify that the program wrote the SSBO correctly.
+    const float *ptr = reinterpret_cast<const float *>(
+        glMapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, sizeof(kBufferInitValue), GL_MAP_READ_BIT));
+
+    for (int i = 0; i < 4; ++i)
+    {
+        EXPECT_NEAR(ptr[i], kValue1[i], 0.001);
+    }
+
+    glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
+
+    GLenum drawBuffers[] = {GL_NONE};
+    glDrawBuffers(1, drawBuffers);
+
+    constexpr float kValue2[4] = {0.5f, 0.6f, 0.7f, 0.9f};
+    glUniform4fv(valueLoc, 1, kValue2);
+    glDrawArrays(GL_TRIANGLES, 0, 6);
+    glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
+    EXPECT_GL_NO_ERROR();
+    // Verify that the program wrote the SSBO correctly.
+    ptr = reinterpret_cast<const float *>(
+        glMapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, sizeof(kBufferInitValue), GL_MAP_READ_BIT));
+
+    for (int i = 0; i < 4; ++i)
+    {
+        EXPECT_NEAR(ptr[i], kValue2[i], 0.001);
+    }
+
+    glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
+}
+
 // Tests that rendering to a texture in one draw call followed by sampling from it in a dispatch
 // call works correctly.  This requires an implicit barrier in between the calls.
 TEST_P(SimpleStateChangeTestES31, DrawThenSampleWithCompute)