Vulkan: fix masked stencil clear

Previously, masked stencil clear was done by clearing every stencil bit
to the ClearValue & Mask.  The correct behavior as implemented in this
change is to clear only the bits that are set in Mask.  This can only be
done through a draw call, with ClearValue as the stencil reference, and
Mask as the stencil write mask.

Note: this change relies on the depthClamp Vulkan feature which is not
available on ARM.

Bug: angleproject:3241
Change-Id: I0a181c32f881ee813f144e7bdd6f42c8ea6f1966
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/1548442
Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org>
Reviewed-by: Jamie Madill <jmadill@chromium.org>
Reviewed-by: Tobin Ehlis <tobine@google.com>
diff --git a/src/libANGLE/Framebuffer.cpp b/src/libANGLE/Framebuffer.cpp
index 11e1d90..2520684 100644
--- a/src/libANGLE/Framebuffer.cpp
+++ b/src/libANGLE/Framebuffer.cpp
@@ -1349,7 +1349,31 @@
         return angle::Result::Continue;
     }
 
-    ANGLE_TRY(mImpl->clear(context, mask));
+    // Remove clear bits that are ineffective. An effective clear changes at least one fragment. If
+    // color/depth/stencil masks make the clear ineffective we skip it altogether.
+
+    // If all color channels are masked, don't attempt to clear color.
+    if (context->getState().getBlendState().allChannelsMasked())
+    {
+        mask &= ~GL_COLOR_BUFFER_BIT;
+    }
+
+    // If depth write is disabled, don't attempt to clear depth.
+    if (!context->getState().getDepthStencilState().depthMask)
+    {
+        mask &= ~GL_DEPTH_BUFFER_BIT;
+    }
+
+    // If all stencil bits are masked, don't attempt to clear stencil.
+    if (context->getState().getDepthStencilState().stencilWritemask == 0)
+    {
+        mask &= ~GL_STENCIL_BUFFER_BIT;
+    }
+
+    if (mask != 0)
+    {
+        ANGLE_TRY(mImpl->clear(context, mask));
+    }
 
     return angle::Result::Continue;
 }
@@ -1364,6 +1388,23 @@
         return angle::Result::Continue;
     }
 
+    if (buffer == GL_DEPTH)
+    {
+        // If depth write is disabled, don't attempt to clear depth.
+        if (!context->getState().getDepthStencilState().depthMask)
+        {
+            return angle::Result::Continue;
+        }
+    }
+    else
+    {
+        // If all color channels are masked, don't attempt to clear color.
+        if (context->getState().getBlendState().allChannelsMasked())
+        {
+            return angle::Result::Continue;
+        }
+    }
+
     ANGLE_TRY(mImpl->clearBufferfv(context, buffer, drawbuffer, values));
 
     return angle::Result::Continue;
@@ -1379,6 +1420,12 @@
         return angle::Result::Continue;
     }
 
+    // If all color channels are masked, don't attempt to clear color.
+    if (context->getState().getBlendState().allChannelsMasked())
+    {
+        return angle::Result::Continue;
+    }
+
     ANGLE_TRY(mImpl->clearBufferuiv(context, buffer, drawbuffer, values));
 
     return angle::Result::Continue;
@@ -1394,6 +1441,23 @@
         return angle::Result::Continue;
     }
 
+    if (buffer == GL_STENCIL)
+    {
+        // If all stencil bits are masked, don't attempt to clear stencil.
+        if (context->getState().getDepthStencilState().stencilWritemask == 0)
+        {
+            return angle::Result::Continue;
+        }
+    }
+    else
+    {
+        // If all color channels are masked, don't attempt to clear color.
+        if (context->getState().getBlendState().allChannelsMasked())
+        {
+            return angle::Result::Continue;
+        }
+    }
+
     ANGLE_TRY(mImpl->clearBufferiv(context, buffer, drawbuffer, values));
 
     return angle::Result::Continue;
@@ -1410,7 +1474,22 @@
         return angle::Result::Continue;
     }
 
-    ANGLE_TRY(mImpl->clearBufferfi(context, buffer, drawbuffer, depth, stencil));
+    bool clearDepth   = context->getState().getDepthStencilState().depthMask;
+    bool clearStencil = context->getState().getDepthStencilState().stencilWritemask != 0;
+
+    if (clearDepth && clearStencil)
+    {
+        ASSERT(buffer == GL_DEPTH_STENCIL);
+        ANGLE_TRY(mImpl->clearBufferfi(context, GL_DEPTH_STENCIL, drawbuffer, depth, stencil));
+    }
+    else if (clearDepth && !clearStencil)
+    {
+        ANGLE_TRY(mImpl->clearBufferfv(context, GL_DEPTH, drawbuffer, &depth));
+    }
+    else if (!clearDepth && clearStencil)
+    {
+        ANGLE_TRY(mImpl->clearBufferiv(context, GL_STENCIL, drawbuffer, &stencil));
+    }
 
     return angle::Result::Continue;
 }
diff --git a/src/libANGLE/angletypes.cpp b/src/libANGLE/angletypes.cpp
index 1b51947..838b976 100644
--- a/src/libANGLE/angletypes.cpp
+++ b/src/libANGLE/angletypes.cpp
@@ -59,6 +59,11 @@
     memcpy(this, &other, sizeof(BlendState));
 }
 
+bool BlendState::allChannelsMasked() const
+{
+    return !colorMaskRed && !colorMaskGreen && !colorMaskBlue && !colorMaskAlpha;
+}
+
 bool operator==(const BlendState &a, const BlendState &b)
 {
     return memcmp(&a, &b, sizeof(BlendState)) == 0;
diff --git a/src/libANGLE/angletypes.h b/src/libANGLE/angletypes.h
index 12e4122..dcb95f4 100644
--- a/src/libANGLE/angletypes.h
+++ b/src/libANGLE/angletypes.h
@@ -146,6 +146,8 @@
     BlendState();
     BlendState(const BlendState &other);
 
+    bool allChannelsMasked() const;
+
     bool blend;
     GLenum sourceBlendRGB;
     GLenum destBlendRGB;
diff --git a/src/libANGLE/renderer/vulkan/FramebufferVk.cpp b/src/libANGLE/renderer/vulkan/FramebufferVk.cpp
index a32aec0..23afd1d 100644
--- a/src/libANGLE/renderer/vulkan/FramebufferVk.cpp
+++ b/src/libANGLE/renderer/vulkan/FramebufferVk.cpp
@@ -234,20 +234,37 @@
 
     // This function assumes that only enabled attachments are asked to be cleared.
     ASSERT((clearColorBuffers & mState.getEnabledDrawBuffers()) == clearColorBuffers);
-    bool clearColor = clearColorBuffers.any();
+
+    // Adjust clear behavior based on whether:
+    //
+    // - the respective attachments are present: if asked to clear a non-existent attachment, don't
+    //   attempt to clear it.
+    // - extra clear is necessary: if depth- or stencil-only attachments are emulated with a format
+    //   that has both aspects, clear the emulated aspect.
+
+    VkColorComponentFlags colorMaskFlags = contextVk->getClearColorMask();
+    bool clearColor                      = clearColorBuffers.any();
 
     const gl::FramebufferAttachment *depthAttachment = mState.getDepthAttachment();
     clearDepth                                       = clearDepth && depthAttachment;
     ASSERT(!clearDepth || depthAttachment->isAttached());
 
-    // If depth write is disabled, pretend that depth clear is not requested altogether.
-    clearDepth = clearDepth && contextVk->getState().getDepthStencilState().depthMask;
-
     const gl::FramebufferAttachment *stencilAttachment = mState.getStencilAttachment();
     clearStencil                                       = clearStencil && stencilAttachment;
     ASSERT(!clearStencil || stencilAttachment->isAttached());
 
-    // If the only thing to be cleared was depth and it's masked, there's nothing to do.
+    uint8_t stencilMask =
+        static_cast<uint8_t>(contextVk->getState().getDepthStencilState().stencilWritemask);
+
+    // The front-end should ensure we don't attempt to clear color if all channels are masked.
+    ASSERT(!clearColor || colorMaskFlags != 0);
+    // The front-end should ensure we don't attempt to clear depth if depth write is disabled.
+    ASSERT(!clearDepth || contextVk->getState().getDepthStencilState().depthMask);
+    // The front-end should ensure we don't attempt to clear stencil if all bits are masked.
+    ASSERT(!clearStencil || stencilMask != 0);
+
+    // If there is nothing to clear, return right away (for example, if asked to clear depth, but
+    // there is no depth attachment).
     if (!clearColor && !clearDepth && !clearStencil)
     {
         return angle::Result::Continue;
@@ -255,11 +272,6 @@
 
     VkClearDepthStencilValue modifiedDepthStencilValue = clearDepthStencilValue;
 
-    // Apply the stencil mask to the clear value.
-    // TODO(syoussefi): this logic is flawed.  See http://anglebug.com/3241#c9.
-    modifiedDepthStencilValue.stencil &=
-        contextVk->getState().getDepthStencilState().stencilWritemask;
-
     // If the depth or stencil is being cleared, and the image was originally requested to have a
     // single aspect, but it's emulated with a depth/stencil format, clear both aspects, setting the
     // other aspect to 0.
@@ -287,19 +299,19 @@
     bool isScissorTestEffectivelyEnabled =
         glState.isScissorTestEnabled() && scissorRenderAreaIntersection != renderArea;
 
-    // We can use render pass load ops if clearing depth/stencil or unmasked color.  If there's a
-    // depth mask, depth clearing is disabled.  If there's a stencil mask, the clear value is
-    // already masked.  There is no depth/stencil condition prohibiting the use of render pass
-    // loadOp.
-    VkColorComponentFlags colorMaskFlags = contextVk->getClearColorMask();
+    // We can use render pass load ops if clearing depth, unmasked color or unmasked stencil.  If
+    // there's a depth mask, depth clearing is already disabled.
     bool maskedClearColor =
         clearColor && (mActiveColorComponents & colorMaskFlags) != mActiveColorComponents;
-    bool clearColorWithRenderPassLoadOp = clearColor && !maskedClearColor;
+    bool maskedClearStencil = stencilMask != 0xFF;
+
+    bool clearColorWithRenderPassLoadOp   = clearColor && !maskedClearColor;
+    bool clearStencilWithRenderPassLoadOp = clearStencil && !maskedClearStencil;
 
     // At least one of color, depth or stencil should be clearable with render pass loadOp for us
     // to use this clear path.
     bool clearAnyWithRenderPassLoadOp =
-        clearColorWithRenderPassLoadOp || clearDepth || clearStencil;
+        clearColorWithRenderPassLoadOp || clearDepth || clearStencilWithRenderPassLoadOp;
 
     if (clearAnyWithRenderPassLoadOp && !isScissorTestEffectivelyEnabled)
     {
@@ -313,7 +325,8 @@
         }
         // If there's a color mask, only clear depth/stencil with render pass loadOp.
         ANGLE_TRY(clearWithRenderPassOp(contextVk, clearBuffersWithRenderPassLoadOp, clearDepth,
-                                        clearStencil, clearColorValue, modifiedDepthStencilValue));
+                                        clearStencilWithRenderPassLoadOp, clearColorValue,
+                                        modifiedDepthStencilValue));
 
         // On some hardware, having inline commands at this point results in corrupted output.  In
         // that case, end the render pass immediately.  http://anglebug.com/2361
@@ -323,39 +336,34 @@
         }
 
         // Fallback to other methods for whatever isn't cleared here.
-        clearDepth   = false;
-        clearStencil = false;
+        clearDepth = false;
         if (clearColorWithRenderPassLoadOp)
         {
+            clearColorBuffers.reset();
             clearColor = false;
         }
+        if (clearStencilWithRenderPassLoadOp)
+        {
+            clearStencil = false;
+        }
 
-        if (!clearColor)
+        // If nothing left to clear, early out.
+        if (!clearColor && !clearStencil)
         {
             return angle::Result::Continue;
         }
     }
 
-    // The most costly clear mode is when we need to mask out specific color channels. This can
-    // only be done with a draw call. The scissor region however can easily be integrated with
-    // this method. Similarly for depth/stencil clear.
-    if (maskedClearColor)
+    // The most costly clear mode is when we need to mask out specific color channels or stencil
+    // bits. This can only be done with a draw call. The scissor region however can easily be
+    // integrated with this method.
+    //
+    // Since we have to have a draw call for the sake of masked color or stencil, we can make sure
+    // everything else is cleared with the draw call at the same time as well.
+    if (maskedClearColor || maskedClearStencil)
     {
-        ANGLE_TRY(clearWithDraw(contextVk, clearColorBuffers, clearColorValue, colorMaskFlags));
-
-        // Stencil clears must be handled separately. The only way to write out a stencil value from
-        // a fragment shader in Vulkan is with VK_EXT_shader_stencil_export. Support for this
-        // extension is sparse. Hence, we call into the RenderPass clear path. We similarly clear
-        // depth to keep the code simple, but depth clears could be combined with the masked color
-        // clears as an optimization.
-
-        if (clearDepth || clearStencil)
-        {
-            ANGLE_TRY(clearWithClearAttachments(contextVk, gl::DrawBufferMask(), clearDepth,
-                                                clearStencil, clearColorValue,
-                                                modifiedDepthStencilValue));
-        }
-        return angle::Result::Continue;
+        return clearWithDraw(contextVk, clearColorBuffers, clearDepth, clearStencil, colorMaskFlags,
+                             stencilMask, clearColorValue, modifiedDepthStencilValue);
     }
 
     ASSERT(isScissorTestEffectivelyEnabled);
@@ -1173,15 +1181,25 @@
 
 angle::Result FramebufferVk::clearWithDraw(ContextVk *contextVk,
                                            gl::DrawBufferMask clearColorBuffers,
+                                           bool clearDepth,
+                                           bool clearStencil,
+                                           VkColorComponentFlags colorMaskFlags,
+                                           uint8_t stencilMask,
                                            const VkClearColorValue &clearColorValue,
-                                           VkColorComponentFlags colorMaskFlags)
+                                           const VkClearDepthStencilValue &clearDepthStencilValue)
 {
     RendererVk *renderer = contextVk->getRenderer();
 
-    UtilsVk::ClearImageParameters params = {};
-    params.renderAreaHeight              = mState.getDimensions().height;
-    params.clearValue                    = clearColorValue;
-    params.renderPassDesc                = &getRenderPassDesc();
+    UtilsVk::ClearFramebufferParameters params = {};
+    params.renderPassDesc                      = &getRenderPassDesc();
+    params.renderAreaHeight                    = mState.getDimensions().height;
+    params.colorClearValue                     = clearColorValue;
+    params.depthStencilClearValue              = clearDepthStencilValue;
+    params.stencilMask                         = stencilMask;
+
+    params.clearColor   = true;
+    params.clearDepth   = clearDepth;
+    params.clearStencil = clearStencil;
 
     const auto &colorRenderTargets = mRenderTargetCache.getColors();
     for (size_t colorIndex : clearColorBuffers)
@@ -1189,15 +1207,26 @@
         const RenderTargetVk *colorRenderTarget = colorRenderTargets[colorIndex];
         ASSERT(colorRenderTarget);
 
-        params.format          = &colorRenderTarget->getImage().getFormat().textureFormat();
-        params.attachmentIndex = colorIndex;
-        params.colorMaskFlags  = colorMaskFlags;
+        params.colorFormat          = &colorRenderTarget->getImage().getFormat().textureFormat();
+        params.colorAttachmentIndex = colorIndex;
+        params.colorMaskFlags       = colorMaskFlags;
         if (mEmulatedAlphaAttachmentMask[colorIndex])
         {
             params.colorMaskFlags &= ~VK_COLOR_COMPONENT_A_BIT;
         }
 
-        ANGLE_TRY(renderer->getUtils().clearImage(contextVk, this, params));
+        ANGLE_TRY(renderer->getUtils().clearFramebuffer(contextVk, this, params));
+
+        // Clear depth/stencil only once!
+        params.clearDepth   = false;
+        params.clearStencil = false;
+    }
+
+    // If there was no color clear, clear depth/stencil alone.
+    if (params.clearDepth || params.clearStencil)
+    {
+        params.clearColor = false;
+        ANGLE_TRY(renderer->getUtils().clearFramebuffer(contextVk, this, params));
     }
 
     return angle::Result::Continue;
diff --git a/src/libANGLE/renderer/vulkan/FramebufferVk.h b/src/libANGLE/renderer/vulkan/FramebufferVk.h
index dfbcf4e..121cd1b 100644
--- a/src/libANGLE/renderer/vulkan/FramebufferVk.h
+++ b/src/libANGLE/renderer/vulkan/FramebufferVk.h
@@ -179,8 +179,12 @@
                                             const VkClearDepthStencilValue &clearDepthStencilValue);
     angle::Result clearWithDraw(ContextVk *contextVk,
                                 gl::DrawBufferMask clearColorBuffers,
+                                bool clearDepth,
+                                bool clearStencil,
+                                VkColorComponentFlags colorMaskFlags,
+                                uint8_t stencilMask,
                                 const VkClearColorValue &clearColorValue,
-                                VkColorComponentFlags colorMaskFlags);
+                                const VkClearDepthStencilValue &clearDepthStencilValue);
     void updateActiveColorMasks(size_t colorIndex, bool r, bool g, bool b, bool a);
     void updateRenderPassDesc();
 
diff --git a/src/libANGLE/renderer/vulkan/RendererVk.cpp b/src/libANGLE/renderer/vulkan/RendererVk.cpp
index 56787fa..4b69479 100644
--- a/src/libANGLE/renderer/vulkan/RendererVk.cpp
+++ b/src/libANGLE/renderer/vulkan/RendererVk.cpp
@@ -979,6 +979,7 @@
     enabledFeatures.features.independentBlend    = mPhysicalDeviceFeatures.independentBlend;
     enabledFeatures.features.robustBufferAccess  = mPhysicalDeviceFeatures.robustBufferAccess;
     enabledFeatures.features.samplerAnisotropy   = mPhysicalDeviceFeatures.samplerAnisotropy;
+    enabledFeatures.features.depthClamp          = mPhysicalDeviceFeatures.depthClamp;
     if (!vk::CommandBuffer::ExecutesInline())
     {
         enabledFeatures.features.inheritedQueries = mPhysicalDeviceFeatures.inheritedQueries;
diff --git a/src/libANGLE/renderer/vulkan/UtilsVk.cpp b/src/libANGLE/renderer/vulkan/UtilsVk.cpp
index 0c309d9..8adbb3b 100644
--- a/src/libANGLE/renderer/vulkan/UtilsVk.cpp
+++ b/src/libANGLE/renderer/vulkan/UtilsVk.cpp
@@ -200,6 +200,7 @@
     {
         program.destroy(device);
     }
+    mImageClearProgramVSOnly.destroy(device);
     for (vk::ShaderProgramHelper &program : mImageClearProgram)
     {
         program.destroy(device);
@@ -365,7 +366,10 @@
     else
     {
         program->setShader(gl::ShaderType::Vertex, vsShader);
-        program->setShader(gl::ShaderType::Fragment, fsCsShader);
+        if (fsCsShader)
+        {
+            program->setShader(gl::ShaderType::Fragment, fsCsShader);
+        }
 
         // This value is not used but is passed to getGraphicsPipeline to avoid a nullptr check.
         const vk::GraphicsPipelineDesc *descPtr;
@@ -637,9 +641,9 @@
     return angle::Result::Continue;
 }
 
-angle::Result UtilsVk::clearImage(ContextVk *contextVk,
-                                  FramebufferVk *framebuffer,
-                                  const ClearImageParameters &params)
+angle::Result UtilsVk::clearFramebuffer(ContextVk *contextVk,
+                                        FramebufferVk *framebuffer,
+                                        const ClearFramebufferParameters &params)
 {
     RendererVk *renderer = contextVk->getRenderer();
 
@@ -652,24 +656,54 @@
     }
 
     ImageClearShaderParams shaderParams;
-    shaderParams.clearValue = params.clearValue;
-
-    uint32_t flags = GetImageClearFlags(*params.format, params.attachmentIndex);
+    shaderParams.clearValue = params.colorClearValue;
 
     vk::GraphicsPipelineDesc pipelineDesc;
     pipelineDesc.initDefaults();
     pipelineDesc.setColorWriteMask(0, gl::DrawBufferMask());
-    pipelineDesc.setSingleColorWriteMask(params.attachmentIndex, params.colorMaskFlags);
+    pipelineDesc.setSingleColorWriteMask(params.colorAttachmentIndex, params.colorMaskFlags);
     pipelineDesc.setRenderPassDesc(*params.renderPassDesc);
     // Note: depth test is disabled by default so this should be unnecessary, but works around an
     // Intel bug on windows.  http://anglebug.com/3348
     pipelineDesc.setDepthWriteEnabled(false);
 
+    // Clear depth by enabling depth clamping and setting the viewport depth range to the clear
+    // value.
+    if (params.clearDepth)
+    {
+        pipelineDesc.setDepthTestEnabled(true);
+        pipelineDesc.setDepthWriteEnabled(true);
+        pipelineDesc.setDepthFunc(VK_COMPARE_OP_ALWAYS);
+        pipelineDesc.setDepthClampEnabled(true);
+    }
+
+    // Clear stencil by enabling stencil write with the right mask.
+    if (params.clearStencil)
+    {
+        const uint8_t compareMask = 0xFF;
+        const uint8_t clearStencilValue =
+            static_cast<uint8_t>(params.depthStencilClearValue.stencil);
+
+        pipelineDesc.setStencilTestEnabled(true);
+        pipelineDesc.setStencilFrontFuncs(clearStencilValue, VK_COMPARE_OP_ALWAYS, compareMask);
+        pipelineDesc.setStencilBackFuncs(clearStencilValue, VK_COMPARE_OP_ALWAYS, compareMask);
+        pipelineDesc.setStencilFrontOps(VK_STENCIL_OP_REPLACE, VK_STENCIL_OP_REPLACE,
+                                        VK_STENCIL_OP_REPLACE);
+        pipelineDesc.setStencilBackOps(VK_STENCIL_OP_REPLACE, VK_STENCIL_OP_REPLACE,
+                                       VK_STENCIL_OP_REPLACE);
+        pipelineDesc.setStencilFrontWriteMask(params.stencilMask);
+        pipelineDesc.setStencilBackWriteMask(params.stencilMask);
+    }
+
     const gl::Rectangle &renderArea = framebuffer->getFramebuffer()->getRenderPassRenderArea();
     bool invertViewport             = contextVk->isViewportFlipEnabledForDrawFBO();
 
     VkViewport viewport;
-    gl_vk::GetViewport(renderArea, 0.0f, 1.0f, invertViewport, params.renderAreaHeight, &viewport);
+    // Set depth range to clear value.  If clearing depth, the vertex shader depth output is clamped
+    // to this value, thus clearing the depth buffer to the desired clear value.
+    const float clearDepthValue = params.depthStencilClearValue.depth;
+    gl_vk::GetViewport(renderArea, clearDepthValue, clearDepthValue, invertViewport,
+                       params.renderAreaHeight, &viewport);
     pipelineDesc.setViewport(viewport);
 
     VkRect2D scissor;
@@ -680,11 +714,18 @@
     vk::ShaderLibrary &shaderLibrary                    = renderer->getShaderLibrary();
     vk::RefCounted<vk::ShaderAndSerial> *vertexShader   = nullptr;
     vk::RefCounted<vk::ShaderAndSerial> *fragmentShader = nullptr;
+    vk::ShaderProgramHelper *imageClearProgram          = &mImageClearProgramVSOnly;
+
     ANGLE_TRY(shaderLibrary.getFullScreenQuad_vert(contextVk, 0, &vertexShader));
-    ANGLE_TRY(shaderLibrary.getImageClear_frag(contextVk, flags, &fragmentShader));
+    if (params.clearColor)
+    {
+        uint32_t flags = GetImageClearFlags(*params.colorFormat, params.colorAttachmentIndex);
+        ANGLE_TRY(shaderLibrary.getImageClear_frag(contextVk, flags, &fragmentShader));
+        imageClearProgram = &mImageClearProgram[flags];
+    }
 
     ANGLE_TRY(setupProgram(contextVk, Function::ImageClear, fragmentShader, vertexShader,
-                           &mImageClearProgram[flags], &pipelineDesc, VK_NULL_HANDLE, &shaderParams,
+                           imageClearProgram, &pipelineDesc, VK_NULL_HANDLE, &shaderParams,
                            sizeof(shaderParams), commandBuffer));
     commandBuffer->draw(6, 0);
     return angle::Result::Continue;
diff --git a/src/libANGLE/renderer/vulkan/UtilsVk.h b/src/libANGLE/renderer/vulkan/UtilsVk.h
index 39e2b52..8d207d0 100644
--- a/src/libANGLE/renderer/vulkan/UtilsVk.h
+++ b/src/libANGLE/renderer/vulkan/UtilsVk.h
@@ -59,14 +59,22 @@
         size_t destOffset;
     };
 
-    struct ClearImageParameters
+    struct ClearFramebufferParameters
     {
-        VkClearColorValue clearValue;
-        VkColorComponentFlags colorMaskFlags;
-        GLint renderAreaHeight;
         const vk::RenderPassDesc *renderPassDesc;
-        const angle::Format *format;
-        uint32_t attachmentIndex;
+        GLint renderAreaHeight;
+
+        bool clearColor;
+        bool clearDepth;
+        bool clearStencil;
+
+        uint8_t stencilMask;
+        VkColorComponentFlags colorMaskFlags;
+        uint32_t colorAttachmentIndex;
+        const angle::Format *colorFormat;
+
+        VkClearColorValue colorClearValue;
+        VkClearDepthStencilValue depthStencilClearValue;
     };
 
     struct CopyImageParameters
@@ -95,12 +103,9 @@
                                       vk::BufferHelper *src,
                                       const ConvertVertexParameters &params);
 
-    // Note: this function takes a FramebufferVk instead of ImageHelper, as that's the only user,
-    // which avoids recreating a framebuffer.  An overload taking ImageHelper can be added when
-    // necessary.
-    angle::Result clearImage(ContextVk *contextVk,
-                             FramebufferVk *framebuffer,
-                             const ClearImageParameters &params);
+    angle::Result clearFramebuffer(ContextVk *contextVk,
+                                   FramebufferVk *framebuffer,
+                                   const ClearFramebufferParameters &params);
 
     angle::Result copyImage(ContextVk *contextVk,
                             vk::ImageHelper *dest,
@@ -232,6 +237,7 @@
     vk::ShaderProgramHelper
         mConvertVertexPrograms[vk::InternalShader::ConvertVertex_comp::kFlagsMask |
                                vk::InternalShader::ConvertVertex_comp::kConversionMask];
+    vk::ShaderProgramHelper mImageClearProgramVSOnly;
     vk::ShaderProgramHelper
         mImageClearProgram[vk::InternalShader::ImageClear_frag::kAttachmentIndexMask |
                            vk::InternalShader::ImageClear_frag::kFormatMask];
diff --git a/src/libANGLE/renderer/vulkan/vk_cache_utils.cpp b/src/libANGLE/renderer/vulkan/vk_cache_utils.cpp
index b7195e8..10de54b 100644
--- a/src/libANGLE/renderer/vulkan/vk_cache_utils.cpp
+++ b/src/libANGLE/renderer/vulkan/vk_cache_utils.cpp
@@ -85,7 +85,7 @@
     }
 }
 
-uint8_t PackGLStencilOp(GLenum compareOp)
+VkStencilOp PackGLStencilOp(GLenum compareOp)
 {
     switch (compareOp)
     {
@@ -107,11 +107,11 @@
             return VK_STENCIL_OP_INVERT;
         default:
             UNREACHABLE();
-            return 0;
+            return VK_STENCIL_OP_KEEP;
     }
 }
 
-uint8_t PackGLCompareFunc(GLenum compareFunc)
+VkCompareOp PackGLCompareFunc(GLenum compareFunc)
 {
     switch (compareFunc)
     {
@@ -133,7 +133,7 @@
             return VK_COMPARE_OP_NOT_EQUAL;
         default:
             UNREACHABLE();
-            return 0;
+            return VK_COMPARE_OP_NEVER;
     }
 }
 
@@ -879,26 +879,91 @@
     }
 }
 
+void GraphicsPipelineDesc::setDepthTestEnabled(bool enabled)
+{
+    mDepthStencilStateInfo.enable.depthTest = enabled;
+}
+
 void GraphicsPipelineDesc::setDepthWriteEnabled(bool enabled)
 {
     mDepthStencilStateInfo.enable.depthWrite = enabled;
 }
 
+void GraphicsPipelineDesc::setDepthFunc(VkCompareOp op)
+{
+    SetBitField(mDepthStencilStateInfo.depthCompareOp, op);
+}
+
+void GraphicsPipelineDesc::setDepthClampEnabled(bool enabled)
+{
+    mRasterizationAndMultisampleStateInfo.bits.depthClampEnable = enabled;
+}
+
+void GraphicsPipelineDesc::setStencilTestEnabled(bool enabled)
+{
+    mDepthStencilStateInfo.enable.stencilTest = enabled;
+}
+
+void GraphicsPipelineDesc::setStencilFrontFuncs(uint8_t reference,
+                                                VkCompareOp compareOp,
+                                                uint8_t compareMask)
+{
+    mDepthStencilStateInfo.frontStencilReference = reference;
+    mDepthStencilStateInfo.front.compareMask     = compareMask;
+    SetBitField(mDepthStencilStateInfo.front.ops.compare, compareOp);
+}
+
+void GraphicsPipelineDesc::setStencilBackFuncs(uint8_t reference,
+                                               VkCompareOp compareOp,
+                                               uint8_t compareMask)
+{
+    mDepthStencilStateInfo.backStencilReference = reference;
+    mDepthStencilStateInfo.back.compareMask     = compareMask;
+    SetBitField(mDepthStencilStateInfo.back.ops.compare, compareOp);
+}
+
+void GraphicsPipelineDesc::setStencilFrontOps(VkStencilOp failOp,
+                                              VkStencilOp passOp,
+                                              VkStencilOp depthFailOp)
+{
+    SetBitField(mDepthStencilStateInfo.front.ops.fail, failOp);
+    SetBitField(mDepthStencilStateInfo.front.ops.pass, passOp);
+    SetBitField(mDepthStencilStateInfo.front.ops.depthFail, depthFailOp);
+}
+
+void GraphicsPipelineDesc::setStencilBackOps(VkStencilOp failOp,
+                                             VkStencilOp passOp,
+                                             VkStencilOp depthFailOp)
+{
+    SetBitField(mDepthStencilStateInfo.back.ops.fail, failOp);
+    SetBitField(mDepthStencilStateInfo.back.ops.pass, passOp);
+    SetBitField(mDepthStencilStateInfo.back.ops.depthFail, depthFailOp);
+}
+
+void GraphicsPipelineDesc::setStencilFrontWriteMask(uint8_t mask)
+{
+    mDepthStencilStateInfo.front.writeMask = mask;
+}
+
+void GraphicsPipelineDesc::setStencilBackWriteMask(uint8_t mask)
+{
+    mDepthStencilStateInfo.back.writeMask = mask;
+}
+
 void GraphicsPipelineDesc::updateDepthTestEnabled(GraphicsPipelineTransitionBits *transition,
                                                   const gl::DepthStencilState &depthStencilState,
                                                   const gl::Framebuffer *drawFramebuffer)
 {
     // Only enable the depth test if the draw framebuffer has a depth buffer.  It's possible that
     // we're emulating a stencil-only buffer with a depth-stencil buffer
-    mDepthStencilStateInfo.enable.depthTest =
-        static_cast<uint8_t>(depthStencilState.depthTest && drawFramebuffer->hasDepth());
+    setDepthTestEnabled(depthStencilState.depthTest && drawFramebuffer->hasDepth());
     transition->set(ANGLE_GET_TRANSITION_BIT(mDepthStencilStateInfo, enable));
 }
 
 void GraphicsPipelineDesc::updateDepthFunc(GraphicsPipelineTransitionBits *transition,
                                            const gl::DepthStencilState &depthStencilState)
 {
-    mDepthStencilStateInfo.depthCompareOp = PackGLCompareFunc(depthStencilState.depthFunc);
+    setDepthFunc(PackGLCompareFunc(depthStencilState.depthFunc));
     transition->set(ANGLE_GET_TRANSITION_BIT(mDepthStencilStateInfo, depthCompareOp));
 }
 
@@ -907,8 +972,7 @@
                                                    const gl::Framebuffer *drawFramebuffer)
 {
     // Don't write to depth buffers that should not exist
-    mDepthStencilStateInfo.enable.depthWrite =
-        static_cast<uint8_t>(drawFramebuffer->hasDepth() ? depthStencilState.depthMask : 0);
+    setDepthWriteEnabled(drawFramebuffer->hasDepth() ? depthStencilState.depthMask : false);
     transition->set(ANGLE_GET_TRANSITION_BIT(mDepthStencilStateInfo, enable));
 }
 
@@ -918,8 +982,7 @@
 {
     // Only enable the stencil test if the draw framebuffer has a stencil buffer.  It's possible
     // that we're emulating a depth-only buffer with a depth-stencil buffer
-    mDepthStencilStateInfo.enable.stencilTest =
-        static_cast<uint8_t>(depthStencilState.stencilTest && drawFramebuffer->hasStencil());
+    setStencilTestEnabled(depthStencilState.stencilTest && drawFramebuffer->hasStencil());
     transition->set(ANGLE_GET_TRANSITION_BIT(mDepthStencilStateInfo, enable));
 }
 
@@ -927,9 +990,9 @@
                                                    GLint ref,
                                                    const gl::DepthStencilState &depthStencilState)
 {
-    mDepthStencilStateInfo.frontStencilReference = static_cast<uint8_t>(ref);
-    mDepthStencilStateInfo.front.ops.compare     = PackGLCompareFunc(depthStencilState.stencilFunc);
-    mDepthStencilStateInfo.front.compareMask = static_cast<uint8_t>(depthStencilState.stencilMask);
+    setStencilFrontFuncs(static_cast<uint8_t>(ref),
+                         PackGLCompareFunc(depthStencilState.stencilFunc),
+                         static_cast<uint8_t>(depthStencilState.stencilMask));
     transition->set(ANGLE_GET_TRANSITION_BIT(mDepthStencilStateInfo, front));
     transition->set(ANGLE_GET_TRANSITION_BIT(mDepthStencilStateInfo, frontStencilReference));
 }
@@ -938,10 +1001,9 @@
                                                   GLint ref,
                                                   const gl::DepthStencilState &depthStencilState)
 {
-    mDepthStencilStateInfo.backStencilReference = static_cast<uint8_t>(ref);
-    mDepthStencilStateInfo.back.ops.compare = PackGLCompareFunc(depthStencilState.stencilBackFunc);
-    mDepthStencilStateInfo.back.compareMask =
-        static_cast<uint8_t>(depthStencilState.stencilBackMask);
+    setStencilBackFuncs(static_cast<uint8_t>(ref),
+                        PackGLCompareFunc(depthStencilState.stencilBackFunc),
+                        static_cast<uint8_t>(depthStencilState.stencilBackMask));
     transition->set(ANGLE_GET_TRANSITION_BIT(mDepthStencilStateInfo, back));
     transition->set(ANGLE_GET_TRANSITION_BIT(mDepthStencilStateInfo, backStencilReference));
 }
@@ -949,21 +1011,18 @@
 void GraphicsPipelineDesc::updateStencilFrontOps(GraphicsPipelineTransitionBits *transition,
                                                  const gl::DepthStencilState &depthStencilState)
 {
-    mDepthStencilStateInfo.front.ops.pass = PackGLStencilOp(depthStencilState.stencilPassDepthPass);
-    mDepthStencilStateInfo.front.ops.fail = PackGLStencilOp(depthStencilState.stencilFail);
-    mDepthStencilStateInfo.front.ops.depthFail =
-        PackGLStencilOp(depthStencilState.stencilPassDepthFail);
+    setStencilFrontOps(PackGLStencilOp(depthStencilState.stencilFail),
+                       PackGLStencilOp(depthStencilState.stencilPassDepthPass),
+                       PackGLStencilOp(depthStencilState.stencilPassDepthFail));
     transition->set(ANGLE_GET_TRANSITION_BIT(mDepthStencilStateInfo, front));
 }
 
 void GraphicsPipelineDesc::updateStencilBackOps(GraphicsPipelineTransitionBits *transition,
                                                 const gl::DepthStencilState &depthStencilState)
 {
-    mDepthStencilStateInfo.back.ops.pass =
-        PackGLStencilOp(depthStencilState.stencilBackPassDepthPass);
-    mDepthStencilStateInfo.back.ops.fail = PackGLStencilOp(depthStencilState.stencilBackFail);
-    mDepthStencilStateInfo.back.ops.depthFail =
-        PackGLStencilOp(depthStencilState.stencilBackPassDepthFail);
+    setStencilBackOps(PackGLStencilOp(depthStencilState.stencilBackFail),
+                      PackGLStencilOp(depthStencilState.stencilBackPassDepthPass),
+                      PackGLStencilOp(depthStencilState.stencilBackPassDepthFail));
     transition->set(ANGLE_GET_TRANSITION_BIT(mDepthStencilStateInfo, back));
 }
 
@@ -973,8 +1032,8 @@
     const gl::Framebuffer *drawFramebuffer)
 {
     // Don't write to stencil buffers that should not exist
-    mDepthStencilStateInfo.front.writeMask = static_cast<uint8_t>(
-        drawFramebuffer->hasStencil() ? depthStencilState.stencilWritemask : 0);
+    setStencilFrontWriteMask(static_cast<uint8_t>(
+        drawFramebuffer->hasStencil() ? depthStencilState.stencilWritemask : 0));
     transition->set(ANGLE_GET_TRANSITION_BIT(mDepthStencilStateInfo, front));
 }
 
@@ -984,8 +1043,8 @@
     const gl::Framebuffer *drawFramebuffer)
 {
     // Don't write to stencil buffers that should not exist
-    mDepthStencilStateInfo.back.writeMask = static_cast<uint8_t>(
-        drawFramebuffer->hasStencil() ? depthStencilState.stencilBackWritemask : 0);
+    setStencilBackWriteMask(static_cast<uint8_t>(
+        drawFramebuffer->hasStencil() ? depthStencilState.stencilBackWritemask : 0));
     transition->set(ANGLE_GET_TRANSITION_BIT(mDepthStencilStateInfo, back));
 }
 
diff --git a/src/libANGLE/renderer/vulkan/vk_cache_utils.h b/src/libANGLE/renderer/vulkan/vk_cache_utils.h
index 431f81e..fff720a 100644
--- a/src/libANGLE/renderer/vulkan/vk_cache_utils.h
+++ b/src/libANGLE/renderer/vulkan/vk_cache_utils.h
@@ -373,7 +373,17 @@
                               const gl::DrawBufferMask &alphaMask);
 
     // Depth/stencil states.
+    void setDepthTestEnabled(bool enabled);
     void setDepthWriteEnabled(bool enabled);
+    void setDepthFunc(VkCompareOp op);
+    void setDepthClampEnabled(bool enabled);
+    void setStencilTestEnabled(bool enabled);
+    void setStencilFrontFuncs(uint8_t reference, VkCompareOp compareOp, uint8_t compareMask);
+    void setStencilBackFuncs(uint8_t reference, VkCompareOp compareOp, uint8_t compareMask);
+    void setStencilFrontOps(VkStencilOp failOp, VkStencilOp passOp, VkStencilOp depthFailOp);
+    void setStencilBackOps(VkStencilOp failOp, VkStencilOp passOp, VkStencilOp depthFailOp);
+    void setStencilFrontWriteMask(uint8_t mask);
+    void setStencilBackWriteMask(uint8_t mask);
     void updateDepthTestEnabled(GraphicsPipelineTransitionBits *transition,
                                 const gl::DepthStencilState &depthStencilState,
                                 const gl::Framebuffer *drawFramebuffer);
diff --git a/src/tests/deqp_support/deqp_gles2_test_expectations.txt b/src/tests/deqp_support/deqp_gles2_test_expectations.txt
index 009d64e..f345a36 100644
--- a/src/tests/deqp_support/deqp_gles2_test_expectations.txt
+++ b/src/tests/deqp_support/deqp_gles2_test_expectations.txt
@@ -266,12 +266,6 @@
 // General Vulkan failures
 3300 VULKAN : dEQP-GLES2.functional.shaders.texture_functions.vertex.texturecubelod = FAIL
 
-// Depth/stencil clear
-3241 VULKAN : dEQP-GLES2.functional.depth_stencil_clear.stencil_masked = FAIL
-3241 VULKAN : dEQP-GLES2.functional.depth_stencil_clear.stencil_scissored_masked = FAIL
-3241 VULKAN : dEQP-GLES2.functional.depth_stencil_clear.depth_stencil_masked = FAIL
-3241 VULKAN : dEQP-GLES2.functional.depth_stencil_clear.depth_stencil_scissored_masked = FAIL
-
 // Only seen failing on Android
 3241 VULKAN ANDROID : dEQP-GLES2.functional.depth_stencil_clear.depth_scissored_masked = FAIL
 
@@ -378,4 +372,4 @@
 2976 VULKAN NVIDIA : dEQP-GLES2.functional.shaders.invariance.* = FAIL
 
 // Tests were being hidden by flakiness (anglebug.com/3271)
-3328 VULKAN WIN NVIDIA : dEQP-GLES2.functional.shaders.swizzles.vector_swizzles.mediump_vec4_qqqt_vertex = SKIP
\ No newline at end of file
+3328 VULKAN WIN NVIDIA : dEQP-GLES2.functional.shaders.swizzles.vector_swizzles.mediump_vec4_qqqt_vertex = SKIP
diff --git a/src/tests/gl_tests/ClearTest.cpp b/src/tests/gl_tests/ClearTest.cpp
index f150177..b2ced95 100644
--- a/src/tests/gl_tests/ClearTest.cpp
+++ b/src/tests/gl_tests/ClearTest.cpp
@@ -84,6 +84,7 @@
   protected:
     void MaskedScissoredColorDepthStencilClear(bool mask,
                                                bool scissor,
+                                               bool clearColor,
                                                bool clearDepth,
                                                bool clearStencil);
 
@@ -1131,6 +1132,7 @@
 
 void ClearTest::MaskedScissoredColorDepthStencilClear(bool mask,
                                                       bool scissor,
+                                                      bool clearColor,
                                                       bool clearDepth,
                                                       bool clearStencil)
 {
@@ -1143,14 +1145,31 @@
     const int whalf = w >> 1;
     const int hhalf = h >> 1;
 
+    constexpr float kPreClearDepth     = 0.9f;
+    constexpr float kClearDepth        = 0.5f;
+    constexpr uint8_t kPreClearStencil = 0xFF;
+    constexpr uint8_t kClearStencil    = 0x16;
+    constexpr uint8_t kStencilMask     = 0x59;
+    constexpr uint8_t kMaskedClearStencil =
+        (kPreClearStencil & ~kStencilMask) | (kClearStencil & kStencilMask);
+
     // Clear to a random color, 0.9 depth and 0x00 stencil
     Vector4 color1(0.1f, 0.2f, 0.3f, 0.4f);
     GLColor color1RGB(color1);
 
     glClearColor(color1[0], color1[1], color1[2], color1[3]);
-    glClearDepthf(0.9f);
-    glClearStencil(0x00);
-    glClear(GL_COLOR_BUFFER_BIT | (clearDepth ? GL_DEPTH_BUFFER_BIT : 0) |
+    glClearDepthf(kPreClearDepth);
+    glClearStencil(kPreClearStencil);
+
+    if (!clearColor)
+    {
+        // If not asked to clear color, clear it anyway, but individually.  The clear value is
+        // still used to verify that the depth/stencil clear happened correctly.  This allows
+        // testing for depth/stencil-only clear implementations.
+        glClear(GL_COLOR_BUFFER_BIT);
+    }
+
+    glClear((clearColor ? GL_COLOR_BUFFER_BIT : 0) | (clearDepth ? GL_DEPTH_BUFFER_BIT : 0) |
             (clearStencil ? GL_STENCIL_BUFFER_BIT : 0));
     ASSERT_GL_NO_ERROR();
 
@@ -1167,15 +1186,15 @@
     Vector4 color2(0.2f, 0.4f, 0.6f, 0.8f);
     GLColor color2RGB(color2);
     glClearColor(color2[0], color2[1], color2[2], color2[3]);
-    glClearDepthf(0.5f);
-    glClearStencil(0xFF);
+    glClearDepthf(kClearDepth);
+    glClearStencil(kClearStencil);
     if (mask)
     {
         glColorMask(GL_TRUE, GL_FALSE, GL_TRUE, GL_FALSE);
         glDepthMask(GL_FALSE);
-        glStencilMask(0x59);
+        glStencilMask(kStencilMask);
     }
-    glClear(GL_COLOR_BUFFER_BIT | (clearDepth ? GL_DEPTH_BUFFER_BIT : 0) |
+    glClear((clearColor ? GL_COLOR_BUFFER_BIT : 0) | (clearDepth ? GL_DEPTH_BUFFER_BIT : 0) |
             (clearStencil ? GL_STENCIL_BUFFER_BIT : 0));
     glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
     glDepthMask(GL_TRUE);
@@ -1188,7 +1207,10 @@
     // Verify second clear mask worked as expected.
     GLColor color2MaskedRGB(color2RGB[0], color1RGB[1], color2RGB[2], color1RGB[3]);
 
-    GLColor expectedCenterColorRGB = mask ? color2MaskedRGB : color2RGB;
+    // If not clearing color, the original color should be left both in the center and corners.  If
+    // using a scissor, the corners should be left to the original color, while the center is
+    // possibly changed.  If using a mask, the center (and corers if not scissored), h
+    GLColor expectedCenterColorRGB = !clearColor ? color1RGB : mask ? color2MaskedRGB : color2RGB;
     GLColor expectedCornerColorRGB = scissor ? color1RGB : expectedCenterColorRGB;
 
     EXPECT_PIXEL_COLOR_NEAR(whalf, hhalf, expectedCenterColorRGB, 1);
@@ -1207,8 +1229,8 @@
                          essl1_shaders::fs::Blue());
         glEnable(GL_DEPTH_TEST);
         glDepthFunc(mask ? GL_GREATER : GL_EQUAL);
-        // - If depth is cleared, but it's masked, 0.9 should be in the depth buffer.
-        // - If depth is cleared, but it's not masked, 0.5 should be in the depth buffer.
+        // - If depth is cleared, but it's masked, kPreClearDepth should be in the depth buffer.
+        // - If depth is cleared, but it's not masked, kClearDepth should be in the depth buffer.
         // - If depth is not cleared, the if above ensures there is no depth buffer at all,
         //   which means depth test will always pass.
         drawQuad(depthTestProgram, essl1_shaders::PositionAttrib(), mask ? 1.0f : 0.0f);
@@ -1237,11 +1259,13 @@
         ANGLE_GL_PROGRAM(stencilTestProgram, essl1_shaders::vs::Passthrough(),
                          essl1_shaders::fs::Green());
         glEnable(GL_STENCIL_TEST);
-        // - If stencil is cleared, but it's masked, 0x59 should be in the stencil buffer.
-        // - If stencil is cleared, but it's not masked, 0xFF should be in the stencil buffer.
+        // - If stencil is cleared, but it's masked, kMaskedClearStencil should be in the stencil
+        //   buffer.
+        // - If stencil is cleared, but it's not masked, kClearStencil should be in the stencil
+        // buffer.
         // - If stencil is not cleared, the if above ensures there is no stencil buffer at all,
         //   which means stencil test will always pass.
-        glStencilFunc(GL_EQUAL, mask ? 0x59 : 0xFF, 0xFF);
+        glStencilFunc(GL_EQUAL, mask ? kMaskedClearStencil : kClearStencil, 0xFF);
         drawQuad(stencilTestProgram, essl1_shaders::PositionAttrib(), 0.0f);
         glDisable(GL_STENCIL_TEST);
         ASSERT_GL_NO_ERROR();
@@ -1263,61 +1287,106 @@
 // Tests combined color+depth+stencil clears.
 TEST_P(ClearTest, MaskedColorAndDepthClear)
 {
-    MaskedScissoredColorDepthStencilClear(true, false, true, false);
+    MaskedScissoredColorDepthStencilClear(true, false, true, true, false);
 }
 
 TEST_P(ClearTest, MaskedColorAndStencilClear)
 {
-    MaskedScissoredColorDepthStencilClear(true, false, false, true);
+    MaskedScissoredColorDepthStencilClear(true, false, true, false, true);
 }
 
 TEST_P(ClearTest, MaskedColorAndDepthAndStencilClear)
 {
-    MaskedScissoredColorDepthStencilClear(true, false, true, true);
+    MaskedScissoredColorDepthStencilClear(true, false, true, true, true);
+}
+
+TEST_P(ClearTest, MaskedDepthClear)
+{
+    MaskedScissoredColorDepthStencilClear(true, false, false, true, false);
+}
+
+TEST_P(ClearTest, MaskedStencilClear)
+{
+    MaskedScissoredColorDepthStencilClear(true, false, false, false, true);
+}
+
+TEST_P(ClearTest, MaskedDepthAndStencilClear)
+{
+    MaskedScissoredColorDepthStencilClear(true, false, false, true, true);
 }
 
 // Simple scissored clear.
 TEST_P(ScissoredClearTest, BasicScissoredColorClear)
 {
-    MaskedScissoredColorDepthStencilClear(false, true, false, false);
+    MaskedScissoredColorDepthStencilClear(false, true, true, false, false);
 }
 
 // Simple scissored masked clear.
 TEST_P(ScissoredClearTest, MaskedScissoredColorClear)
 {
-    MaskedScissoredColorDepthStencilClear(true, true, false, false);
+    MaskedScissoredColorDepthStencilClear(true, true, true, false, false);
 }
 
 // Tests combined color+depth+stencil scissored clears.
 TEST_P(ScissoredClearTest, ScissoredColorAndDepthClear)
 {
-    MaskedScissoredColorDepthStencilClear(false, true, true, false);
+    MaskedScissoredColorDepthStencilClear(false, true, true, true, false);
 }
 
 TEST_P(ScissoredClearTest, ScissoredColorAndStencilClear)
 {
-    MaskedScissoredColorDepthStencilClear(false, true, false, true);
+    MaskedScissoredColorDepthStencilClear(false, true, true, false, true);
 }
 
 TEST_P(ScissoredClearTest, ScissoredColorAndDepthAndStencilClear)
 {
-    MaskedScissoredColorDepthStencilClear(false, true, true, true);
+    MaskedScissoredColorDepthStencilClear(false, true, true, true, true);
+}
+
+TEST_P(ScissoredClearTest, ScissoredDepthClear)
+{
+    MaskedScissoredColorDepthStencilClear(false, true, false, true, false);
+}
+
+TEST_P(ScissoredClearTest, ScissoredStencilClear)
+{
+    MaskedScissoredColorDepthStencilClear(false, true, false, false, true);
+}
+
+TEST_P(ScissoredClearTest, ScissoredDepthAndStencilClear)
+{
+    MaskedScissoredColorDepthStencilClear(false, true, false, true, true);
 }
 
 // Tests combined color+depth+stencil scissored masked clears.
 TEST_P(ScissoredClearTest, MaskedScissoredColorAndDepthClear)
 {
-    MaskedScissoredColorDepthStencilClear(true, true, true, false);
+    MaskedScissoredColorDepthStencilClear(true, true, true, true, false);
 }
 
 TEST_P(ScissoredClearTest, MaskedScissoredColorAndStencilClear)
 {
-    MaskedScissoredColorDepthStencilClear(true, true, false, true);
+    MaskedScissoredColorDepthStencilClear(true, true, true, false, true);
 }
 
 TEST_P(ScissoredClearTest, MaskedScissoredColorAndDepthAndStencilClear)
 {
-    MaskedScissoredColorDepthStencilClear(true, true, true, true);
+    MaskedScissoredColorDepthStencilClear(true, true, true, true, true);
+}
+
+TEST_P(ScissoredClearTest, MaskedScissoredgDepthClear)
+{
+    MaskedScissoredColorDepthStencilClear(true, true, false, true, false);
+}
+
+TEST_P(ScissoredClearTest, MaskedScissoredgStencilClear)
+{
+    MaskedScissoredColorDepthStencilClear(true, true, false, false, true);
+}
+
+TEST_P(ScissoredClearTest, MaskedScissoredgDepthAndStencilClear)
+{
+    MaskedScissoredColorDepthStencilClear(true, true, false, true, true);
 }
 
 // Tests combined color+stencil scissored masked clears for a depth-stencil-emulated
@@ -1325,25 +1394,49 @@
 TEST_P(VulkanClearTest, ColorAndStencilClear)
 {
     bindColorStencilFBO();
-    MaskedScissoredColorDepthStencilClear(false, false, false, true);
+    MaskedScissoredColorDepthStencilClear(false, false, true, false, true);
 }
 
 TEST_P(VulkanClearTest, MaskedColorAndStencilClear)
 {
     bindColorStencilFBO();
-    MaskedScissoredColorDepthStencilClear(true, false, false, true);
+    MaskedScissoredColorDepthStencilClear(true, false, true, false, true);
 }
 
 TEST_P(VulkanClearTest, ScissoredColorAndStencilClear)
 {
     bindColorStencilFBO();
-    MaskedScissoredColorDepthStencilClear(false, true, false, true);
+    MaskedScissoredColorDepthStencilClear(false, true, true, false, true);
 }
 
 TEST_P(VulkanClearTest, MaskedScissoredColorAndStencilClear)
 {
     bindColorStencilFBO();
-    MaskedScissoredColorDepthStencilClear(true, true, false, true);
+    MaskedScissoredColorDepthStencilClear(true, true, true, false, true);
+}
+
+TEST_P(VulkanClearTest, StencilClear)
+{
+    bindColorStencilFBO();
+    MaskedScissoredColorDepthStencilClear(false, false, false, false, true);
+}
+
+TEST_P(VulkanClearTest, MaskedStencilClear)
+{
+    bindColorStencilFBO();
+    MaskedScissoredColorDepthStencilClear(true, false, false, false, true);
+}
+
+TEST_P(VulkanClearTest, ScissoredStencilClear)
+{
+    bindColorStencilFBO();
+    MaskedScissoredColorDepthStencilClear(false, true, false, false, true);
+}
+
+TEST_P(VulkanClearTest, MaskedScissoredStencilClear)
+{
+    bindColorStencilFBO();
+    MaskedScissoredColorDepthStencilClear(true, true, false, false, true);
 }
 
 // Tests combined color+depth scissored masked clears for a depth-stencil-emulated
@@ -1352,28 +1445,56 @@
 {
     ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3);
     bindColorDepthFBO();
-    MaskedScissoredColorDepthStencilClear(false, false, true, false);
+    MaskedScissoredColorDepthStencilClear(false, false, true, true, false);
 }
 
 TEST_P(VulkanClearTest, MaskedColorAndDepthClear)
 {
     ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3);
     bindColorDepthFBO();
-    MaskedScissoredColorDepthStencilClear(true, false, true, false);
+    MaskedScissoredColorDepthStencilClear(true, false, true, true, false);
 }
 
 TEST_P(VulkanClearTest, ScissoredColorAndDepthClear)
 {
     ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3);
     bindColorDepthFBO();
-    MaskedScissoredColorDepthStencilClear(false, true, true, false);
+    MaskedScissoredColorDepthStencilClear(false, true, true, true, false);
 }
 
 TEST_P(VulkanClearTest, MaskedScissoredColorAndDepthClear)
 {
     ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3);
     bindColorDepthFBO();
-    MaskedScissoredColorDepthStencilClear(true, true, true, false);
+    MaskedScissoredColorDepthStencilClear(true, true, true, true, false);
+}
+
+TEST_P(VulkanClearTest, DepthClear)
+{
+    ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3);
+    bindColorDepthFBO();
+    MaskedScissoredColorDepthStencilClear(false, false, false, true, false);
+}
+
+TEST_P(VulkanClearTest, MaskedDepthClear)
+{
+    ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3);
+    bindColorDepthFBO();
+    MaskedScissoredColorDepthStencilClear(true, false, false, true, false);
+}
+
+TEST_P(VulkanClearTest, ScissoredDepthClear)
+{
+    ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3);
+    bindColorDepthFBO();
+    MaskedScissoredColorDepthStencilClear(false, true, false, true, false);
+}
+
+TEST_P(VulkanClearTest, MaskedScissoredDepthClear)
+{
+    ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3);
+    bindColorDepthFBO();
+    MaskedScissoredColorDepthStencilClear(true, true, false, true, false);
 }
 
 // Test that just clearing a nonexistent drawbuffer of the default framebuffer doesn't cause an