Vulkan: Use render pass loadOp for scissored clears

At this point, every clear is done through render pass loadOp, except
masked color or stencil clears.  The only fallback is clearWithDraw,
that can clear both color and stencil at the same time.

Bug: angleproject:2361
Change-Id: I805fc12475e832ad2f573f665cdfeb766e61a6d0
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/1553740
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/angletypes.cpp b/src/libANGLE/angletypes.cpp
index 838b976..0fa6c3a 100644
--- a/src/libANGLE/angletypes.cpp
+++ b/src/libANGLE/angletypes.cpp
@@ -257,6 +257,11 @@
     return unreversed;
 }
 
+bool Rectangle::encloses(const gl::Rectangle &inside) const
+{
+    return x0() <= inside.x0() && y0() <= inside.y0() && x1() >= inside.x1() && y1() >= inside.y1();
+}
+
 bool ClipRectangle(const Rectangle &source, const Rectangle &clip, Rectangle *intersection)
 {
     int minSourceX, maxSourceX, minSourceY, maxSourceY;
diff --git a/src/libANGLE/angletypes.h b/src/libANGLE/angletypes.h
index dcb95f4..e7b5bf0 100644
--- a/src/libANGLE/angletypes.h
+++ b/src/libANGLE/angletypes.h
@@ -48,6 +48,8 @@
     // Returns a rectangle with the same area but with height and width guaranteed to be positive.
     Rectangle removeReversal() const;
 
+    bool encloses(const gl::Rectangle &inside) const;
+
     int x;
     int y;
     int width;
diff --git a/src/libANGLE/renderer/renderer_utils.cpp b/src/libANGLE/renderer/renderer_utils.cpp
index 4cbc899..7ca9753 100644
--- a/src/libANGLE/renderer/renderer_utils.cpp
+++ b/src/libANGLE/renderer/renderer_utils.cpp
@@ -608,4 +608,32 @@
     }
     return angle::Result::Continue;
 }
+
+gl::Rectangle ClipRectToScissor(const gl::State &glState, const gl::Rectangle &rect, bool invertY)
+{
+    if (glState.isScissorTestEnabled())
+    {
+        gl::Rectangle clippedRect;
+        if (!gl::ClipRectangle(glState.getScissor(), rect, &clippedRect))
+        {
+            return gl::Rectangle();
+        }
+
+        if (invertY)
+        {
+            clippedRect.y = rect.height - clippedRect.y - clippedRect.height;
+        }
+
+        return clippedRect;
+    }
+
+    // If the scissor test isn't enabled, assume it has infinite size.  Its intersection with the
+    // rect would be the rect itself.
+    //
+    // Note that on Vulkan, returning this (as opposed to a fixed max-int-sized rect) could lead to
+    // unnecessary pipeline creations if two otherwise identical pipelines are used on framebuffers
+    // with different sizes.  If such usage is observed in an application, we should investigate
+    // possible optimizations.
+    return rect;
+}
 }  // namespace rx
diff --git a/src/libANGLE/renderer/renderer_utils.h b/src/libANGLE/renderer/renderer_utils.h
index e7a4b86..1025005 100644
--- a/src/libANGLE/renderer/renderer_utils.h
+++ b/src/libANGLE/renderer/renderer_utils.h
@@ -28,6 +28,7 @@
 {
 struct FormatType;
 struct InternalFormat;
+class State;
 }  // namespace gl
 
 namespace egl
@@ -286,6 +287,8 @@
                                  GLint baseVertex,
                                  GLint *startVertexOut,
                                  size_t *vertexCountOut);
+
+gl::Rectangle ClipRectToScissor(const gl::State &glState, const gl::Rectangle &rect, bool invertY);
 }  // namespace rx
 
 #endif  // LIBANGLE_RENDERER_RENDERER_UTILS_H_
diff --git a/src/libANGLE/renderer/vulkan/CommandGraph.cpp b/src/libANGLE/renderer/vulkan/CommandGraph.cpp
index 500704d..b9685b8 100644
--- a/src/libANGLE/renderer/vulkan/CommandGraph.cpp
+++ b/src/libANGLE/renderer/vulkan/CommandGraph.cpp
@@ -208,12 +208,6 @@
     return angle::Result::Continue;
 }
 
-const gl::Rectangle &CommandGraphResource::getRenderPassRenderArea() const
-{
-    ASSERT(hasStartedRenderPass());
-    return mCurrentWritingNode->getRenderPassRenderArea();
-}
-
 angle::Result CommandGraphResource::beginRenderPass(
     ContextVk *contextVk,
     const Framebuffer &framebuffer,
@@ -654,11 +648,6 @@
     return result;
 }
 
-const gl::Rectangle &CommandGraphNode::getRenderPassRenderArea() const
-{
-    return mRenderPassRenderArea;
-}
-
 // CommandGraph implementation.
 CommandGraph::CommandGraph(bool enableGraphDiagnostics, angle::PoolAllocator *poolAllocator)
     : mEnableGraphDiagnostics(enableGraphDiagnostics),
diff --git a/src/libANGLE/renderer/vulkan/CommandGraph.h b/src/libANGLE/renderer/vulkan/CommandGraph.h
index bc28c91..811c9c3 100644
--- a/src/libANGLE/renderer/vulkan/CommandGraph.h
+++ b/src/libANGLE/renderer/vulkan/CommandGraph.h
@@ -157,7 +157,7 @@
     uintptr_t getResourceIDForDiagnostics() const { return mResourceID; }
     std::string dumpCommandsForDiagnostics(const char *separator) const;
 
-    const gl::Rectangle &getRenderPassRenderArea() const;
+    const gl::Rectangle &getRenderPassRenderArea() const { return mRenderPassRenderArea; }
 
     CommandGraphNodeFunction getFunction() const { return mFunction; }
 
@@ -292,21 +292,30 @@
                                   const std::vector<VkClearValue> &clearValues,
                                   CommandBuffer **commandBufferOut);
 
-    // Checks if we're in a RenderPass, returning true if so. Updates serial internally.
-    // Returns the started command buffer in commandBufferOut.
+    // Checks if we're in a RenderPass without children.
+    bool hasStartedRenderPass() const
+    {
+        return hasChildlessWritingNode() &&
+               mCurrentWritingNode->getInsideRenderPassCommands()->valid();
+    }
+
+    // Checks if we're in a RenderPass that encompasses renderArea, returning true if so. Updates
+    // serial internally. Returns the started command buffer in commandBufferOut.
     ANGLE_INLINE bool appendToStartedRenderPass(Serial currentQueueSerial,
+                                                const gl::Rectangle &renderArea,
                                                 CommandBuffer **commandBufferOut)
     {
         updateQueueSerial(currentQueueSerial);
         if (hasStartedRenderPass())
         {
-            *commandBufferOut = mCurrentWritingNode->getInsideRenderPassCommands();
-            return true;
+            if (mCurrentWritingNode->getRenderPassRenderArea().encloses(renderArea))
+            {
+                *commandBufferOut = mCurrentWritingNode->getInsideRenderPassCommands();
+                return true;
+            }
         }
-        else
-        {
-            return false;
-        }
+
+        return false;
     }
 
     // Returns true if the render pass is started, but there are no commands yet recorded in it.
@@ -336,7 +345,11 @@
     }
 
     // Accessor for RenderPass RenderArea.
-    const gl::Rectangle &getRenderPassRenderArea() const;
+    const gl::Rectangle &getRenderPassRenderArea() const
+    {
+        ASSERT(hasStartedRenderPass());
+        return mCurrentWritingNode->getRenderPassRenderArea();
+    }
 
     // Called when 'this' object changes, but we'd like to start a new command buffer later.
     void finishCurrentCommands(RendererVk *renderer);
@@ -365,13 +378,6 @@
         return (mCurrentWritingNode != nullptr && !mCurrentWritingNode->hasChildren());
     }
 
-    // Checks if we're in a RenderPass without children.
-    bool hasStartedRenderPass() const
-    {
-        return hasChildlessWritingNode() &&
-               mCurrentWritingNode->getInsideRenderPassCommands()->valid();
-    }
-
     void startNewCommands(RendererVk *renderer);
 
     void onWriteImpl(CommandGraphNode *writingNode, Serial currentSerial);
diff --git a/src/libANGLE/renderer/vulkan/ContextVk.cpp b/src/libANGLE/renderer/vulkan/ContextVk.cpp
index 1d7595a..02a48bd 100644
--- a/src/libANGLE/renderer/vulkan/ContextVk.cpp
+++ b/src/libANGLE/renderer/vulkan/ContextVk.cpp
@@ -260,10 +260,13 @@
     if (!mCommandBuffer)
     {
         mDirtyBits |= mNewCommandBufferDirtyBits;
+
+        gl::Rectangle scissoredRenderArea = mDrawFramebuffer->getScissoredRenderArea(this);
         if (!mDrawFramebuffer->appendToStartedRenderPass(mRenderer->getCurrentQueueSerial(),
-                                                         &mCommandBuffer))
+                                                         scissoredRenderArea, &mCommandBuffer))
         {
-            ANGLE_TRY(mDrawFramebuffer->startNewRenderPass(this, &mCommandBuffer));
+            ANGLE_TRY(
+                mDrawFramebuffer->startNewRenderPass(this, scissoredRenderArea, &mCommandBuffer));
         }
     }
 
@@ -686,12 +689,11 @@
 void ContextVk::updateScissor(const gl::State &glState)
 {
     FramebufferVk *framebufferVk = vk::GetImpl(glState.getDrawFramebuffer());
-    gl::Box dimensions           = framebufferVk->getState().getDimensions();
-    gl::Rectangle renderArea(0, 0, dimensions.width, dimensions.height);
-
-    VkRect2D scissor;
-    gl_vk::GetScissor(glState, isViewportFlipEnabledForDrawFBO(), renderArea, &scissor);
+    gl::Rectangle scissoredRenderArea = framebufferVk->getScissoredRenderArea(this);
+    VkRect2D scissor                  = gl_vk::GetRect(scissoredRenderArea);
     mGraphicsPipelineDesc->updateScissor(&mGraphicsPipelineTransition, scissor);
+
+    framebufferVk->onScissorChange(this);
 }
 
 angle::Result ContextVk::syncState(const gl::Context *context,
diff --git a/src/libANGLE/renderer/vulkan/FramebufferVk.cpp b/src/libANGLE/renderer/vulkan/FramebufferVk.cpp
index 00a1a76..8288b2e 100644
--- a/src/libANGLE/renderer/vulkan/FramebufferVk.cpp
+++ b/src/libANGLE/renderer/vulkan/FramebufferVk.cpp
@@ -218,14 +218,10 @@
 {
     ContextVk *contextVk = vk::GetImpl(context);
 
-    const gl::State &glState     = context->getState();
-    const gl::Rectangle &scissor = glState.getScissor();
-    const gl::Rectangle renderArea(0, 0, mState.getDimensions().width,
-                                   mState.getDimensions().height);
-    gl::Rectangle scissorRenderAreaIntersection;
+    const gl::Rectangle scissoredRenderArea = getScissoredRenderArea(contextVk);
+
     // Discard clear altogether if scissor has 0 width or height.
-    if (glState.isScissorTestEnabled() &&
-        !gl::ClipRectangle(scissor, renderArea, &scissorRenderAreaIntersection))
+    if (scissoredRenderArea.width == 0 || scissoredRenderArea.height == 0)
     {
         return angle::Result::Continue;
     }
@@ -268,11 +264,6 @@
 
     VkClearDepthStencilValue modifiedDepthStencilValue = clearDepthStencilValue;
 
-    // If scissor is enabled, but covers the whole of framebuffer, it can be considered disabled for
-    // the sake of clear.
-    bool isScissorTestEffectivelyEnabled =
-        glState.isScissorTestEnabled() && scissorRenderAreaIntersection != renderArea;
-
     // 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 =
@@ -287,20 +278,20 @@
     bool clearAnyWithRenderPassLoadOp =
         clearColorWithRenderPassLoadOp || clearDepth || clearStencilWithRenderPassLoadOp;
 
-    if (clearAnyWithRenderPassLoadOp && !isScissorTestEffectivelyEnabled)
+    if (clearAnyWithRenderPassLoadOp)
     {
         // Clearing color is indicated by the set bits in this mask.  If not clearing colors with
         // render pass loadOp, the default value of all-zeros means the clear is not done in
-        // clearWithRenderPassOp below.
+        // clearWithRenderPassOp below.  In that case, only clear depth/stencil with render pass
+        // loadOp.
         gl::DrawBufferMask clearBuffersWithRenderPassLoadOp;
         if (clearColorWithRenderPassLoadOp)
         {
             clearBuffersWithRenderPassLoadOp = clearColorBuffers;
         }
-        // If there's a color mask, only clear depth/stencil with render pass loadOp.
-        ANGLE_TRY(clearWithRenderPassOp(contextVk, clearBuffersWithRenderPassLoadOp, clearDepth,
-                                        clearStencilWithRenderPassLoadOp, clearColorValue,
-                                        modifiedDepthStencilValue));
+        ANGLE_TRY(clearWithRenderPassOp(
+            contextVk, scissoredRenderArea, clearBuffersWithRenderPassLoadOp, clearDepth,
+            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
@@ -328,25 +319,19 @@
         }
     }
 
+    // Note: if no driver bug workaround is necessary, the clearDepth feature of
+    // clearWithDraw can be removed.
+    ASSERT(clearDepth == false);
+
     // 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)
-    {
-        return clearWithDraw(contextVk, clearColorBuffers, clearDepth, clearStencil, colorMaskFlags,
-                             stencilMask, clearColorValue, modifiedDepthStencilValue);
-    }
-
-    ASSERT(isScissorTestEffectivelyEnabled);
-
-    // With scissor test enabled, we clear very differently and we don't need to access
-    // the image inside each attachment we can just use clearCmdAttachments with our
-    // scissor region instead.
-    return clearWithClearAttachments(contextVk, clearColorBuffers, clearDepth, clearStencil,
-                                     clearColorValue, modifiedDepthStencilValue);
+    return clearWithDraw(contextVk, scissoredRenderArea, clearColorBuffers, clearDepth,
+                         clearStencil, colorMaskFlags, stencilMask, clearColorValue,
+                         modifiedDepthStencilValue);
 }
 
 angle::Result FramebufferVk::clearBufferfv(const gl::Context *context,
@@ -990,18 +975,27 @@
 
 angle::Result FramebufferVk::clearWithRenderPassOp(
     ContextVk *contextVk,
+    const gl::Rectangle &clearArea,
     gl::DrawBufferMask clearColorBuffers,
     bool clearDepth,
     bool clearStencil,
     const VkClearColorValue &clearColorValue,
     const VkClearDepthStencilValue &clearDepthStencilValue)
 {
-    // If render pass hasn't started, start it.  If it's started and contains commands, we cannot
-    // modify its ops, so start a new render pass.
-    if (!mFramebuffer.valid() || !mFramebuffer.renderPassStartedButEmpty())
+    // Start a new render pass if:
+    //
+    // - no render pass has started,
+    // - there is a render pass started but it contains commands; we cannot modify its ops, so new
+    // render pass is needed,
+    // - the current render area doesn't match the clear area.  We need the render area to be
+    // exactly as specified by the scissor for the loadOp to clear only that area.  See
+    // onScissorChange for more information.
+
+    if (!mFramebuffer.valid() || !mFramebuffer.renderPassStartedButEmpty() ||
+        mFramebuffer.getRenderPassRenderArea() != clearArea)
     {
         vk::CommandBuffer *commandBuffer;
-        ANGLE_TRY(startNewRenderPass(contextVk, &commandBuffer));
+        ANGLE_TRY(startNewRenderPass(contextVk, clearArea, &commandBuffer));
     }
 
     size_t attachmentIndex = 0;
@@ -1047,113 +1041,8 @@
     return angle::Result::Continue;
 }
 
-angle::Result FramebufferVk::clearWithClearAttachments(
-    ContextVk *contextVk,
-    gl::DrawBufferMask clearColorBuffers,
-    bool clearDepth,
-    bool clearStencil,
-    const VkClearColorValue &clearColorValue,
-    const VkClearDepthStencilValue &clearDepthStencilValue)
-{
-    // Trigger a new command node to ensure overlapping writes happen sequentially.
-    mFramebuffer.finishCurrentCommands(contextVk->getRenderer());
-
-    // This command can only happen inside a render pass, so obtain one if its already happening
-    // or create a new one if not.
-    vk::CommandBuffer *commandBuffer = nullptr;
-    vk::RecordingMode mode           = vk::RecordingMode::Start;
-    ANGLE_TRY(getCommandBufferForDraw(contextVk, &commandBuffer, &mode));
-
-    // The array layer is offset by the ImageView. So we shouldn't need to set a base array layer.
-    VkClearRect clearRect    = {};
-    clearRect.baseArrayLayer = 0;
-    clearRect.layerCount     = 1;
-
-    // When clearing, the scissor region must be clipped to the renderArea per the validation rules
-    // in Vulkan.
-    gl::Rectangle intersection;
-    if (!gl::ClipRectangle(contextVk->getState().getScissor(),
-                           mFramebuffer.getRenderPassRenderArea(), &intersection))
-    {
-        // There is nothing to clear since the scissor is outside of the render area.
-        return angle::Result::Continue;
-    }
-
-    clearRect.rect = gl_vk::GetRect(intersection);
-
-    if (contextVk->isViewportFlipEnabledForDrawFBO())
-    {
-        clearRect.rect.offset.y = mFramebuffer.getRenderPassRenderArea().height -
-                                  clearRect.rect.offset.y - clearRect.rect.extent.height;
-    }
-
-    gl::AttachmentArray<VkClearAttachment> clearAttachments;
-    int clearAttachmentIndex = 0;
-
-    if (clearColorBuffers.any())
-    {
-        RenderTargetVk *renderTarget = getColorReadRenderTarget();
-        const vk::Format &format     = renderTarget->getImageFormat();
-        VkClearValue modifiedClear   = {clearColorValue};
-
-        // We need to make sure we are not clearing the alpha channel if we are using a buffer
-        // format that doesn't have an alpha channel.
-        if (format.angleFormat().alphaBits == 0)
-        {
-            SetEmulatedAlphaValue(format, &modifiedClear.color);
-        }
-
-        // TODO(jmadill): Support gaps in RenderTargets. http://anglebug.com/2394
-        for (size_t colorIndex : clearColorBuffers)
-        {
-            VkClearAttachment &clearAttachment = clearAttachments[clearAttachmentIndex];
-            clearAttachment.aspectMask         = VK_IMAGE_ASPECT_COLOR_BIT;
-            clearAttachment.colorAttachment    = static_cast<uint32_t>(colorIndex);
-            clearAttachment.clearValue         = modifiedClear;
-            ++clearAttachmentIndex;
-        }
-    }
-
-    VkClearValue depthStencilClearValue = {};
-    depthStencilClearValue.depthStencil = clearDepthStencilValue;
-
-    if (clearDepth && clearStencil && mState.getDepthStencilAttachment() != nullptr)
-    {
-        // When we have a packed depth/stencil attachment we can do 1 clear for both when it
-        // applies.
-        VkClearAttachment &clearAttachment = clearAttachments[clearAttachmentIndex];
-        clearAttachment.aspectMask      = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
-        clearAttachment.colorAttachment = VK_ATTACHMENT_UNUSED;
-        clearAttachment.clearValue      = depthStencilClearValue;
-        ++clearAttachmentIndex;
-    }
-    else
-    {
-        if (clearDepth)
-        {
-            VkClearAttachment &clearAttachment = clearAttachments[clearAttachmentIndex];
-            clearAttachment.aspectMask         = VK_IMAGE_ASPECT_DEPTH_BIT;
-            clearAttachment.colorAttachment    = VK_ATTACHMENT_UNUSED;
-            clearAttachment.clearValue         = depthStencilClearValue;
-            ++clearAttachmentIndex;
-        }
-
-        if (clearStencil)
-        {
-            VkClearAttachment &clearAttachment = clearAttachments[clearAttachmentIndex];
-            clearAttachment.aspectMask         = VK_IMAGE_ASPECT_STENCIL_BIT;
-            clearAttachment.colorAttachment    = VK_ATTACHMENT_UNUSED;
-            clearAttachment.clearValue         = depthStencilClearValue;
-            ++clearAttachmentIndex;
-        }
-    }
-
-    commandBuffer->clearAttachments(static_cast<uint32_t>(clearAttachmentIndex),
-                                    clearAttachments.data(), 1, &clearRect);
-    return angle::Result::Continue;
-}
-
 angle::Result FramebufferVk::clearWithDraw(ContextVk *contextVk,
+                                           const gl::Rectangle &clearArea,
                                            gl::DrawBufferMask clearColorBuffers,
                                            bool clearDepth,
                                            bool clearStencil,
@@ -1167,6 +1056,7 @@
     UtilsVk::ClearFramebufferParameters params = {};
     params.renderPassDesc                      = &getRenderPassDesc();
     params.renderAreaHeight                    = mState.getDimensions().height;
+    params.clearArea                           = clearArea;
     params.colorClearValue                     = clearColorValue;
     params.depthStencilClearValue              = clearDepthStencilValue;
     params.stencilMask                         = stencilMask;
@@ -1214,23 +1104,8 @@
     return angle::Result::Stop;
 }
 
-angle::Result FramebufferVk::getCommandBufferForDraw(ContextVk *contextVk,
-                                                     vk::CommandBuffer **commandBufferOut,
-                                                     vk::RecordingMode *modeOut)
-{
-    RendererVk *renderer = contextVk->getRenderer();
-
-    // This will clear the current write operation if it is complete.
-    if (appendToStartedRenderPass(renderer->getCurrentQueueSerial(), commandBufferOut))
-    {
-        *modeOut = vk::RecordingMode::Append;
-        return angle::Result::Continue;
-    }
-
-    return startNewRenderPass(contextVk, commandBufferOut);
-}
-
 angle::Result FramebufferVk::startNewRenderPass(ContextVk *contextVk,
+                                                const gl::Rectangle &renderArea,
                                                 vk::CommandBuffer **commandBufferOut)
 {
     vk::Framebuffer *framebuffer = nullptr;
@@ -1273,9 +1148,6 @@
         attachmentClearValues.emplace_back(kUninitializedClearValue);
     }
 
-    gl::Rectangle renderArea =
-        gl::Rectangle(0, 0, mState.getDimensions().width, mState.getDimensions().height);
-
     return mFramebuffer.beginRenderPass(contextVk, *framebuffer, renderArea, mRenderPassDesc,
                                         renderPassAttachmentOps, attachmentClearValues,
                                         commandBufferOut);
@@ -1362,9 +1234,48 @@
 
 gl::Extents FramebufferVk::getReadImageExtents() const
 {
+    ASSERT(getColorReadRenderTarget()->getExtents().width == mState.getDimensions().width);
+    ASSERT(getColorReadRenderTarget()->getExtents().height == mState.getDimensions().height);
+
     return getColorReadRenderTarget()->getExtents();
 }
 
+gl::Rectangle FramebufferVk::getCompleteRenderArea() const
+{
+    return gl::Rectangle(0, 0, mState.getDimensions().width, mState.getDimensions().height);
+}
+
+gl::Rectangle FramebufferVk::getScissoredRenderArea(ContextVk *contextVk) const
+{
+    const gl::Rectangle renderArea(0, 0, mState.getDimensions().width,
+                                   mState.getDimensions().height);
+    bool invertViewport = contextVk->isViewportFlipEnabledForDrawFBO();
+
+    return ClipRectToScissor(contextVk->getState(), renderArea, invertViewport);
+}
+
+void FramebufferVk::onScissorChange(ContextVk *contextVk)
+{
+    gl::Rectangle scissoredRenderArea = getScissoredRenderArea(contextVk);
+
+    // If the scissor has grown beyond the previous scissoredRenderArea, make sure the render pass
+    // is restarted.  Otherwise, we can continue using the same renderpass area.
+    //
+    // Without a scissor, the render pass area covers the whole of the framebuffer.  With a
+    // scissored clear, the render pass area could be smaller than the framebuffer size.  When the
+    // scissor changes, if the scissor area is completely encompassed by the render pass area, it's
+    // possible to continue using the same render pass.  However, if the current render pass area
+    // is too small, we need to start a new one.  The latter can happen if a scissored clear starts
+    // a render pass, the scissor is disabled and a draw call is issued to affect the whole
+    // framebuffer.
+    mFramebuffer.updateQueueSerial(contextVk->getRenderer()->getCurrentQueueSerial());
+    if (mFramebuffer.hasStartedRenderPass() &&
+        !mFramebuffer.getRenderPassRenderArea().encloses(scissoredRenderArea))
+    {
+        mFramebuffer.finishCurrentCommands(contextVk->getRenderer());
+    }
+}
+
 RenderTargetVk *FramebufferVk::getFirstRenderTarget() const
 {
     for (auto *renderTarget : mRenderTargetCache.getColors())
diff --git a/src/libANGLE/renderer/vulkan/FramebufferVk.h b/src/libANGLE/renderer/vulkan/FramebufferVk.h
index 121cd1b..8aeb7ff 100644
--- a/src/libANGLE/renderer/vulkan/FramebufferVk.h
+++ b/src/libANGLE/renderer/vulkan/FramebufferVk.h
@@ -101,19 +101,28 @@
                                  void *pixels);
 
     gl::Extents getReadImageExtents() const;
+    gl::Rectangle getCompleteRenderArea() const;
+    gl::Rectangle getScissoredRenderArea(ContextVk *contextVk) const;
+
+    void onScissorChange(ContextVk *contextVk);
 
     const gl::DrawBufferMask &getEmulatedAlphaAttachmentMask() const;
     RenderTargetVk *getColorReadRenderTarget() const;
 
     // This will clear the current write operation if it is complete.
-    bool appendToStartedRenderPass(Serial currentQueueSerial, vk::CommandBuffer **commandBufferOut)
+    bool appendToStartedRenderPass(Serial currentQueueSerial,
+                                   const gl::Rectangle &renderArea,
+                                   vk::CommandBuffer **commandBufferOut)
     {
-        return mFramebuffer.appendToStartedRenderPass(currentQueueSerial, commandBufferOut);
+        return mFramebuffer.appendToStartedRenderPass(currentQueueSerial, renderArea,
+                                                      commandBufferOut);
     }
 
     vk::FramebufferHelper *getFramebuffer() { return &mFramebuffer; }
 
-    angle::Result startNewRenderPass(ContextVk *context, vk::CommandBuffer **commandBufferOut);
+    angle::Result startNewRenderPass(ContextVk *context,
+                                     const gl::Rectangle &renderArea,
+                                     vk::CommandBuffer **commandBufferOut);
 
     RenderTargetVk *getFirstRenderTarget() const;
     GLint getSamples() const;
@@ -125,11 +134,6 @@
                   const gl::FramebufferState &state,
                   WindowSurfaceVk *backbuffer);
 
-    // Helper for appendToStarted/else startNewRenderPass.
-    angle::Result getCommandBufferForDraw(ContextVk *contextVk,
-                                          vk::CommandBuffer **commandBufferOut,
-                                          vk::RecordingMode *modeOut);
-
     // The 'in' rectangles must be clipped to the scissor and FBO. The clipping is done in 'blit'.
     angle::Result blitWithCommand(ContextVk *contextVk,
                                   const gl::Rectangle &readRectIn,
@@ -166,18 +170,14 @@
                             const VkClearColorValue &clearColorValue,
                             const VkClearDepthStencilValue &clearDepthStencilValue);
     angle::Result clearWithRenderPassOp(ContextVk *contextVk,
+                                        const gl::Rectangle &clearArea,
                                         gl::DrawBufferMask clearColorBuffers,
                                         bool clearDepth,
                                         bool clearStencil,
                                         const VkClearColorValue &clearColorValue,
                                         const VkClearDepthStencilValue &clearDepthStencilValue);
-    angle::Result clearWithClearAttachments(ContextVk *contextVk,
-                                            gl::DrawBufferMask clearColorBuffers,
-                                            bool clearDepth,
-                                            bool clearStencil,
-                                            const VkClearColorValue &clearColorValue,
-                                            const VkClearDepthStencilValue &clearDepthStencilValue);
     angle::Result clearWithDraw(ContextVk *contextVk,
+                                const gl::Rectangle &clearArea,
                                 gl::DrawBufferMask clearColorBuffers,
                                 bool clearDepth,
                                 bool clearStencil,
diff --git a/src/libANGLE/renderer/vulkan/UtilsVk.cpp b/src/libANGLE/renderer/vulkan/UtilsVk.cpp
index 9cdb02d..6afd569 100644
--- a/src/libANGLE/renderer/vulkan/UtilsVk.cpp
+++ b/src/libANGLE/renderer/vulkan/UtilsVk.cpp
@@ -668,10 +668,13 @@
 
     ANGLE_TRY(ensureImageClearResourcesInitialized(contextVk));
 
+    const gl::Rectangle &scissoredRenderArea = params.clearArea;
+
     vk::CommandBuffer *commandBuffer;
-    if (!framebuffer->appendToStartedRenderPass(renderer->getCurrentQueueSerial(), &commandBuffer))
+    if (!framebuffer->appendToStartedRenderPass(renderer->getCurrentQueueSerial(),
+                                                scissoredRenderArea, &commandBuffer))
     {
-        ANGLE_TRY(framebuffer->startNewRenderPass(contextVk, &commandBuffer));
+        ANGLE_TRY(framebuffer->startNewRenderPass(contextVk, scissoredRenderArea, &commandBuffer));
     }
 
     FullScreenQuadParams vsParams;
@@ -716,17 +719,14 @@
         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);
+    gl::Rectangle completeRenderArea = framebuffer->getCompleteRenderArea();
+    bool invertViewport              = contextVk->isViewportFlipEnabledForDrawFBO();
+    gl_vk::GetViewport(completeRenderArea, 0.0f, 1.0f, invertViewport, params.renderAreaHeight,
+                       &viewport);
     pipelineDesc.setViewport(viewport);
 
-    VkRect2D scissor;
-    const gl::State &glState = contextVk->getState();
-    gl_vk::GetScissor(glState, invertViewport, renderArea, &scissor);
-    pipelineDesc.setScissor(scissor);
+    pipelineDesc.setScissor(gl_vk::GetRect(params.clearArea));
 
     vk::ShaderLibrary &shaderLibrary                    = renderer->getShaderLibrary();
     vk::RefCounted<vk::ShaderAndSerial> *vertexShader   = nullptr;
@@ -875,4 +875,18 @@
     return angle::Result::Continue;
 }
 
+UtilsVk::ClearFramebufferParameters::ClearFramebufferParameters()
+    : renderPassDesc(nullptr),
+      renderAreaHeight(0),
+      clearColor(false),
+      clearDepth(false),
+      clearStencil(false),
+      stencilMask(0),
+      colorMaskFlags(0),
+      colorAttachmentIndex(0),
+      colorFormat(nullptr),
+      colorClearValue{},
+      depthStencilClearValue{}
+{}
+
 }  // namespace rx
diff --git a/src/libANGLE/renderer/vulkan/UtilsVk.h b/src/libANGLE/renderer/vulkan/UtilsVk.h
index b5fd0b3..d2d021c 100644
--- a/src/libANGLE/renderer/vulkan/UtilsVk.h
+++ b/src/libANGLE/renderer/vulkan/UtilsVk.h
@@ -61,8 +61,13 @@
 
     struct ClearFramebufferParameters
     {
+        // Satisfy chromium-style with a constructor that does what = {} was already doing in a
+        // safer way.
+        ClearFramebufferParameters();
+
         const vk::RenderPassDesc *renderPassDesc;
         GLint renderAreaHeight;
+        gl::Rectangle clearArea;
 
         bool clearColor;
         bool clearDepth;
diff --git a/src/libANGLE/renderer/vulkan/vk_utils.cpp b/src/libANGLE/renderer/vulkan/vk_utils.cpp
index 9c1c429..d49cca0 100644
--- a/src/libANGLE/renderer/vulkan/vk_utils.cpp
+++ b/src/libANGLE/renderer/vulkan/vk_utils.cpp
@@ -33,11 +33,6 @@
             return 0;
     }
 }
-
-constexpr gl::Rectangle kMaxSizedScissor(0,
-                                         0,
-                                         std::numeric_limits<int>::max(),
-                                         std::numeric_limits<int>::max());
 }  // anonymous namespace
 
 namespace angle
@@ -826,36 +821,5 @@
         viewportOut->height = -viewportOut->height;
     }
 }
-
-void GetScissor(const gl::State &glState,
-                bool invertViewport,
-                const gl::Rectangle &renderArea,
-                VkRect2D *scissorOut)
-{
-    if (glState.isScissorTestEnabled())
-    {
-        gl::Rectangle clippedRect;
-        if (!gl::ClipRectangle(glState.getScissor(), renderArea, &clippedRect))
-        {
-            memset(scissorOut, 0, sizeof(VkRect2D));
-            return;
-        }
-
-        *scissorOut = gl_vk::GetRect(clippedRect);
-
-        if (invertViewport)
-        {
-            scissorOut->offset.y =
-                renderArea.height - scissorOut->offset.y - scissorOut->extent.height;
-        }
-    }
-    else
-    {
-        // If the scissor test isn't enabled, we can simply use a really big scissor that's
-        // certainly larger than the current surface using the maximum size of a 2D texture
-        // for the width and height.
-        *scissorOut = gl_vk::GetRect(kMaxSizedScissor);
-    }
-}
 }  // namespace gl_vk
 }  // namespace rx
diff --git a/src/libANGLE/renderer/vulkan/vk_utils.h b/src/libANGLE/renderer/vulkan/vk_utils.h
index 47362fc..6487520 100644
--- a/src/libANGLE/renderer/vulkan/vk_utils.h
+++ b/src/libANGLE/renderer/vulkan/vk_utils.h
@@ -529,10 +529,6 @@
                  bool invertViewport,
                  GLint renderAreaHeight,
                  VkViewport *viewportOut);
-void GetScissor(const gl::State &glState,
-                bool invertViewport,
-                const gl::Rectangle &renderArea,
-                VkRect2D *scissorOut);
 }  // namespace gl_vk
 
 }  // namespace rx
diff --git a/src/tests/gl_tests/ClearTest.cpp b/src/tests/gl_tests/ClearTest.cpp
index 1bc5438..0c128f4 100644
--- a/src/tests/gl_tests/ClearTest.cpp
+++ b/src/tests/gl_tests/ClearTest.cpp
@@ -825,7 +825,7 @@
 
     glClearColor(clearColor[0], clearColor[1], clearColor[2], clearColor[3]);
     glEnable(GL_SCISSOR_TEST);
-    glScissor(kSize / 4, kSize / 4, kSize / 2, kSize / 2);
+    glScissor(kSize / 6, kSize / 6, kSize / 3, kSize / 3);
     glClear(GL_COLOR_BUFFER_BIT);
     ASSERT_GL_NO_ERROR();
 
@@ -840,8 +840,11 @@
         EXPECT_PIXEL_COLOR_EQ(0, kSize - 1, clearColorMasked);
         EXPECT_PIXEL_COLOR_EQ(kSize - 1, 0, clearColorMasked);
         EXPECT_PIXEL_COLOR_EQ(kSize - 1, kSize - 1, clearColorMasked);
+        EXPECT_PIXEL_COLOR_EQ(kSize / 3, 2 * kSize / 3, clearColorMasked);
+        EXPECT_PIXEL_COLOR_EQ(2 * kSize / 3, kSize / 3, clearColorMasked);
+        EXPECT_PIXEL_COLOR_EQ(2 * kSize / 3, 2 * kSize / 3, clearColorMasked);
 
-        EXPECT_PIXEL_COLOR_EQ(kSize / 2, kSize / 2, clearColorMaskedScissored);
+        EXPECT_PIXEL_COLOR_EQ(kSize / 3, kSize / 3, clearColorMaskedScissored);
     }
 
     // Scissored clear
@@ -863,8 +866,11 @@
         EXPECT_PIXEL_COLOR_EQ(0, kSize - 1, clearColorMasked);
         EXPECT_PIXEL_COLOR_EQ(kSize - 1, 0, clearColorMasked);
         EXPECT_PIXEL_COLOR_EQ(kSize - 1, kSize - 1, clearColorMasked);
+        EXPECT_PIXEL_COLOR_EQ(kSize / 3, 2 * kSize / 3, clearColorMasked);
+        EXPECT_PIXEL_COLOR_EQ(2 * kSize / 3, kSize / 3, clearColorMasked);
+        EXPECT_PIXEL_COLOR_EQ(2 * kSize / 3, 2 * kSize / 3, clearColorMasked);
 
-        EXPECT_PIXEL_COLOR_EQ(kSize / 2, kSize / 2, clearColorScissored);
+        EXPECT_PIXEL_COLOR_EQ(kSize / 3, kSize / 3, clearColorScissored);
     }
 }
 
@@ -1239,8 +1245,8 @@
 
     const int w     = getWindowWidth();
     const int h     = getWindowHeight();
-    const int whalf = w >> 1;
-    const int hhalf = h >> 1;
+    const int wthird = w / 3;
+    const int hthird = h / 3;
 
     constexpr float kPreClearDepth     = 0.9f;
     constexpr float kClearDepth        = 0.5f;
@@ -1283,7 +1289,7 @@
     if (scissor)
     {
         glEnable(GL_SCISSOR_TEST);
-        glScissor(whalf / 2, hhalf / 2, whalf, hhalf);
+        glScissor(wthird / 2, hthird / 2, wthird, hthird);
     }
 
     // Use color and stencil masks to clear to a second color, 0.5 depth and 0x59 stencil.
@@ -1325,12 +1331,15 @@
     GLColor expectedCornerColorRGB = scissor ? color1RGB : expectedCenterColorRGB;
 
     // Verify second clear color mask worked as expected.
-    EXPECT_PIXEL_COLOR_NEAR(whalf, hhalf, expectedCenterColorRGB, 1);
+    EXPECT_PIXEL_COLOR_NEAR(wthird, hthird, expectedCenterColorRGB, 1);
 
     EXPECT_PIXEL_COLOR_NEAR(0, 0, expectedCornerColorRGB, 1);
     EXPECT_PIXEL_COLOR_NEAR(w - 1, 0, expectedCornerColorRGB, 1);
     EXPECT_PIXEL_COLOR_NEAR(0, h - 1, expectedCornerColorRGB, 1);
     EXPECT_PIXEL_COLOR_NEAR(w - 1, h - 1, expectedCornerColorRGB, 1);
+    EXPECT_PIXEL_COLOR_NEAR(wthird, 2 * hthird, expectedCornerColorRGB, 1);
+    EXPECT_PIXEL_COLOR_NEAR(2 * wthird, hthird, expectedCornerColorRGB, 1);
+    EXPECT_PIXEL_COLOR_NEAR(2 * wthird, 2 * hthird, expectedCornerColorRGB, 1);
 
     // If there is depth, but depth is not asked to be cleared, the depth buffer contains garbage,
     // so no particular behavior can be expected.
@@ -1356,12 +1365,15 @@
         expectedCornerColorRGB =
             mHasDepth && scissor && !maskDepth ? expectedCornerColorRGB : GLColor::blue;
 
-        EXPECT_PIXEL_COLOR_NEAR(whalf, hhalf, expectedCenterColorRGB, 1);
+        EXPECT_PIXEL_COLOR_NEAR(wthird, hthird, expectedCenterColorRGB, 1);
 
         EXPECT_PIXEL_COLOR_NEAR(0, 0, expectedCornerColorRGB, 1);
         EXPECT_PIXEL_COLOR_NEAR(w - 1, 0, expectedCornerColorRGB, 1);
         EXPECT_PIXEL_COLOR_NEAR(0, h - 1, expectedCornerColorRGB, 1);
         EXPECT_PIXEL_COLOR_NEAR(w - 1, h - 1, expectedCornerColorRGB, 1);
+        EXPECT_PIXEL_COLOR_NEAR(wthird, 2 * hthird, expectedCornerColorRGB, 1);
+        EXPECT_PIXEL_COLOR_NEAR(2 * wthird, hthird, expectedCornerColorRGB, 1);
+        EXPECT_PIXEL_COLOR_NEAR(2 * wthird, 2 * hthird, expectedCornerColorRGB, 1);
     }
 
     // If there is stencil, but it's not asked to be cleared, there is similarly no expectation.
@@ -1387,12 +1399,15 @@
         // If there is no stencil, stencil test always passes so the whole image must be green.
         expectedCornerColorRGB = mHasStencil && scissor ? expectedCornerColorRGB : GLColor::green;
 
-        EXPECT_PIXEL_COLOR_NEAR(whalf, hhalf, expectedCenterColorRGB, 1);
+        EXPECT_PIXEL_COLOR_NEAR(wthird, hthird, expectedCenterColorRGB, 1);
 
         EXPECT_PIXEL_COLOR_NEAR(0, 0, expectedCornerColorRGB, 1);
         EXPECT_PIXEL_COLOR_NEAR(w - 1, 0, expectedCornerColorRGB, 1);
         EXPECT_PIXEL_COLOR_NEAR(0, h - 1, expectedCornerColorRGB, 1);
         EXPECT_PIXEL_COLOR_NEAR(w - 1, h - 1, expectedCornerColorRGB, 1);
+        EXPECT_PIXEL_COLOR_NEAR(wthird, 2 * hthird, expectedCornerColorRGB, 1);
+        EXPECT_PIXEL_COLOR_NEAR(2 * wthird, hthird, expectedCornerColorRGB, 1);
+        EXPECT_PIXEL_COLOR_NEAR(2 * wthird, 2 * hthird, expectedCornerColorRGB, 1);
     }
 }