Vulkan: Implement multisampled framebuffers

Simultaneously implements ANGLE_framebuffer_multisample and ES3
multisampled framebuffers.

Additionally, implements ES3 framebuffer blitting where multisampled
framebuffers are involved.

Bug: angleproject:3203
Bug: angleproject:3204
Bug: angleproject:3200
Change-Id: I5694a30f71168e807688a9568e3742b81d907918
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/1622667
Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org>
Reviewed-by: Yuly Novikov <ynovikov@chromium.org>
diff --git a/src/libANGLE/renderer/vulkan/FramebufferVk.cpp b/src/libANGLE/renderer/vulkan/FramebufferVk.cpp
index e723eae..43cefff 100644
--- a/src/libANGLE/renderer/vulkan/FramebufferVk.cpp
+++ b/src/libANGLE/renderer/vulkan/FramebufferVk.cpp
@@ -601,6 +601,77 @@
     return angle::Result::Continue;
 }
 
+angle::Result FramebufferVk::blitWithCommand(ContextVk *contextVk,
+                                             const gl::Rectangle &readRectIn,
+                                             const gl::Rectangle &drawRectIn,
+                                             RenderTargetVk *readRenderTarget,
+                                             RenderTargetVk *drawRenderTarget,
+                                             GLenum filter,
+                                             bool colorBlit,
+                                             bool depthBlit,
+                                             bool stencilBlit,
+                                             bool flipSource,
+                                             bool flipDest)
+{
+    // Since blitRenderbufferRect is called for each render buffer that needs to be blitted,
+    // it should never be the case that both color and depth/stencil need to be blitted at
+    // at the same time.
+    ASSERT(colorBlit != (depthBlit || stencilBlit));
+
+    vk::ImageHelper *dstImage = drawRenderTarget->getImageForWrite(&mFramebuffer);
+
+    vk::CommandBuffer *commandBuffer = nullptr;
+    ANGLE_TRY(mFramebuffer.recordCommands(contextVk, &commandBuffer));
+
+    const vk::Format &readImageFormat = readRenderTarget->getImageFormat();
+    VkImageAspectFlags aspectMask =
+        colorBlit ? VK_IMAGE_ASPECT_COLOR_BIT
+                  : vk::GetDepthStencilAspectFlags(readImageFormat.imageFormat());
+    vk::ImageHelper *srcImage = readRenderTarget->getImageForRead(
+        &mFramebuffer, vk::ImageLayout::TransferSrc, commandBuffer);
+
+    const gl::Extents sourceFrameBufferExtents = readRenderTarget->getExtents();
+    gl::Rectangle readRect                     = readRectIn;
+
+    if (flipSource)
+    {
+        readRect.y = sourceFrameBufferExtents.height - readRect.y - readRect.height;
+    }
+
+    VkImageBlit blit               = {};
+    blit.srcOffsets[0]             = {readRect.x0(), flipSource ? readRect.y1() : readRect.y0(), 0};
+    blit.srcOffsets[1]             = {readRect.x1(), flipSource ? readRect.y0() : readRect.y1(), 1};
+    blit.srcSubresource.aspectMask = aspectMask;
+    blit.srcSubresource.mipLevel   = readRenderTarget->getLevelIndex();
+    blit.srcSubresource.baseArrayLayer = readRenderTarget->getLayerIndex();
+    blit.srcSubresource.layerCount     = 1;
+    blit.dstSubresource.aspectMask     = aspectMask;
+    blit.dstSubresource.mipLevel       = drawRenderTarget->getLevelIndex();
+    blit.dstSubresource.baseArrayLayer = drawRenderTarget->getLayerIndex();
+    blit.dstSubresource.layerCount     = 1;
+
+    const gl::Extents drawFrameBufferExtents = drawRenderTarget->getExtents();
+    gl::Rectangle drawRect                   = drawRectIn;
+
+    if (flipDest)
+    {
+        drawRect.y = drawFrameBufferExtents.height - drawRect.y - drawRect.height;
+    }
+
+    blit.dstOffsets[0] = {drawRect.x0(), flipDest ? drawRect.y1() : drawRect.y0(), 0};
+    blit.dstOffsets[1] = {drawRect.x1(), flipDest ? drawRect.y0() : drawRect.y1(), 1};
+
+    // Requirement of the copyImageToBuffer, the dst image must be in
+    // VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL layout.
+    dstImage->changeLayout(aspectMask, vk::ImageLayout::TransferDst, commandBuffer);
+
+    commandBuffer->blitImage(srcImage->getImage(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+                             dstImage->getImage(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &blit,
+                             gl_vk::GetFilter(filter));
+
+    return angle::Result::Continue;
+}
+
 angle::Result FramebufferVk::blit(const gl::Context *context,
                                   const gl::Rectangle &sourceArea,
                                   const gl::Rectangle &destArea,
@@ -612,9 +683,21 @@
 
     const gl::State &glState                 = context->getState();
     const gl::Framebuffer *sourceFramebuffer = glState.getReadFramebuffer();
-    bool blitColorBuffer                     = (mask & GL_COLOR_BUFFER_BIT) != 0;
-    bool blitDepthBuffer                     = (mask & GL_DEPTH_BUFFER_BIT) != 0;
-    bool blitStencilBuffer                   = (mask & GL_STENCIL_BUFFER_BIT) != 0;
+
+    bool blitColorBuffer   = (mask & GL_COLOR_BUFFER_BIT) != 0;
+    bool blitDepthBuffer   = (mask & GL_DEPTH_BUFFER_BIT) != 0;
+    bool blitStencilBuffer = (mask & GL_STENCIL_BUFFER_BIT) != 0;
+
+    // If multisampled, blit only does a resolve.
+    if (sourceFramebuffer->getCachedSamples(context) > 1)
+    {
+        // Note: GLES (all 3.x versions) require source and dest area to be identical when
+        // resolving.
+        ASSERT(sourceArea.x == destArea.x && sourceArea.y == destArea.y &&
+               sourceArea.width == destArea.width && sourceArea.height == destArea.height);
+
+        return resolve(contextVk, destArea, blitColorBuffer, blitDepthBuffer, blitStencilBuffer);
+    }
 
     FramebufferVk *sourceFramebufferVk = vk::GetImpl(sourceFramebuffer);
     bool flipSource                    = contextVk->isViewportFlipEnabledForReadFBO();
@@ -638,6 +721,18 @@
     }
 
     // After cropping for the scissor, we also want to crop for the size of the buffers.
+    //
+    // TODO(syoussefi): GL requires that scaling factor is preserved even if further clipping is
+    // done:
+    //
+    // > Whether or not the source or destination regions are altered due to these limits, the
+    // > scaling and offset applied to pixels being transferred is performed as though no such
+    // > limits were present.
+    //
+    // Using vkCmdBlitFramebuffer after these clippings are done breaks this behavior.  This
+    // function can be turned into a single draw call that does everything, similar to resolve().
+    //
+    // http://anglebug.com/3200
 
     if (blitColorBuffer)
     {
@@ -724,74 +819,201 @@
     return angle::Result::Continue;
 }
 
-angle::Result FramebufferVk::blitWithCommand(ContextVk *contextVk,
-                                             const gl::Rectangle &readRectIn,
-                                             const gl::Rectangle &drawRectIn,
-                                             RenderTargetVk *readRenderTarget,
-                                             RenderTargetVk *drawRenderTarget,
-                                             GLenum filter,
-                                             bool colorBlit,
-                                             bool depthBlit,
-                                             bool stencilBlit,
-                                             bool flipSource,
-                                             bool flipDest)
+angle::Result FramebufferVk::resolve(ContextVk *contextVk,
+                                     const gl::Rectangle &area,
+                                     bool resolveColorBuffer,
+                                     bool resolveDepthBuffer,
+                                     bool resolveStencilBuffer)
 {
-    // Since blitRenderbufferRect is called for each render buffer that needs to be blitted,
-    // it should never be the case that both color and depth/stencil need to be blitted at
-    // at the same time.
-    ASSERT(colorBlit != (depthBlit || stencilBlit));
+    UtilsVk &utilsVk = contextVk->getUtils();
 
-    vk::ImageHelper *dstImage = drawRenderTarget->getImageForWrite(&mFramebuffer);
+    const gl::State &glState              = contextVk->getState();
+    const gl::Framebuffer *srcFramebuffer = glState.getReadFramebuffer();
+
+    FramebufferVk *srcFramebufferVk = vk::GetImpl(srcFramebuffer);
+    bool srcFramebufferFlippedY     = contextVk->isViewportFlipEnabledForReadFBO();
+    bool destFramebufferFlippedY    = contextVk->isViewportFlipEnabledForDrawFBO();
+
+    gl::Rectangle srcFramebufferDimensions = srcFramebufferVk->mState.getDimensions().toRect();
+
+    gl::Rectangle sourceArea = area;
+    gl::Rectangle destArea   = area;
+
+    // If framebuffers are flipped in Y, flip the source and dest area first for simplicity.
+    if (srcFramebufferFlippedY)
+    {
+        sourceArea.y      = srcFramebufferDimensions.height - sourceArea.y;
+        sourceArea.height = -sourceArea.height;
+    }
+    if (destFramebufferFlippedY)
+    {
+        destArea.y      = mState.getDimensions().height - destArea.y;
+        destArea.height = -destArea.height;
+    }
+
+    // If X (or Y) is flipped in neither source nor dest, or is flipped in both, we want the offset
+    // to be at the left (or top) of the copy area.  If it's flipped in source xor dest, we want
+    // the offset to be at the right (or bottom) of the copy area (which is what the shader
+    // expects).  x0() already chooses the left or right side of the source area based on whether
+    // it's flipped, so we just need to test dest for flip and choose x0() or x1() accordingly.
+    int srcOffset[2] = {destArea.isReversedX() ? sourceArea.x1() : sourceArea.x0(),
+                        destArea.isReversedY() ? sourceArea.y1() : sourceArea.y0()};
+    bool flipX       = sourceArea.isReversedX() != destArea.isReversedX();
+    bool flipY       = sourceArea.isReversedY() != destArea.isReversedY();
+
+    // GLES doesn't allow flipping the parameters of glBlitFramebuffer if performing a resolve.
+    ASSERT(flipX == false && flipY == (srcFramebufferFlippedY != destFramebufferFlippedY));
+
+    // Destination offset is always set to the unflipped rectangle, as flipping as handled entirely
+    // on source side.
+    gl::Rectangle absDestArea = destArea.removeReversal();
+    int destOffset[2]         = {absDestArea.x, absDestArea.y};
+
+    gl::Rectangle scissoredDestArea = absDestArea;
+    if (contextVk->getState().isScissorTestEnabled())
+    {
+        // Now that the src and dest offsets are calculated (defining the resolve transformation),
+        // scissor the destination area.
+        gl::Rectangle scissor = contextVk->getState().getScissor();
+        if (destFramebufferFlippedY)
+        {
+            scissor.y = mState.getDimensions().height - scissor.y - scissor.height;
+        }
+
+        if (!gl::ClipRectangle(scissor, absDestArea, &scissoredDestArea))
+        {
+            return angle::Result::Continue;
+        }
+    }
+
+    // Clip the resolve area to the destination framebuffer.
+    gl::Rectangle resolveArea;
+    if (!gl::ClipRectangle(srcFramebufferDimensions, scissoredDestArea, &resolveArea))
+    {
+        return angle::Result::Continue;
+    }
+
+    UtilsVk::ResolveParameters params;
+    params.srcOffset[0]  = srcOffset[0];
+    params.srcOffset[1]  = srcOffset[1];
+    params.srcExtents[0] = srcFramebufferDimensions.width;
+    params.srcExtents[1] = srcFramebufferDimensions.height;
+    params.destOffset[0] = destOffset[0];
+    params.destOffset[1] = destOffset[1];
+    params.resolveArea   = resolveArea;
+    params.flipX         = flipX;
+    params.flipY         = flipY;
+
+    if (resolveColorBuffer)
+    {
+        RenderTargetVk *readRenderTarget = srcFramebufferVk->getColorReadRenderTarget();
+        params.srcLayer                  = readRenderTarget->getLayerIndex();
+
+        // Multisampled images are not allowed to have mips.
+        ASSERT(readRenderTarget->getLevelIndex() == 0);
+
+        // If we're not flipping, use Vulkan's builtin resolve.
+        if (!flipX && !flipY)
+        {
+            ANGLE_TRY(resolveColorWithCommand(contextVk, params, &readRenderTarget->getImage()));
+        }
+        else
+        {
+            ANGLE_TRY(utilsVk.colorResolve(contextVk, this, &readRenderTarget->getImage(),
+                                           readRenderTarget->getFetchImageView(), params));
+        }
+    }
+
+    if (resolveDepthBuffer || resolveStencilBuffer)
+    {
+        RenderTargetVk *readRenderTarget = srcFramebufferVk->getDepthStencilRenderTarget();
+        params.srcLayer                  = readRenderTarget->getLayerIndex();
+
+        // Multisampled images are not allowed to have mips.
+        ASSERT(readRenderTarget->getLevelIndex() == 0);
+
+        // Create depth- and stencil-only views for reading.
+        vk::Scoped<vk::ImageView> depthView(contextVk->getDevice());
+        vk::Scoped<vk::ImageView> stencilView(contextVk->getDevice());
+
+        vk::ImageHelper *depthStencilImage = &readRenderTarget->getImage();
+        uint32_t levelIndex                = readRenderTarget->getLevelIndex();
+        uint32_t layerIndex                = readRenderTarget->getLayerIndex();
+        gl::TextureType textureType = vk::Get2DTextureType(depthStencilImage->getLayerCount(),
+                                                           depthStencilImage->getSamples());
+
+        if (resolveDepthBuffer)
+        {
+            ANGLE_TRY(depthStencilImage->initLayerImageView(
+                contextVk, textureType, VK_IMAGE_ASPECT_DEPTH_BIT, gl::SwizzleState(),
+                &depthView.get(), levelIndex, 1, layerIndex, 1));
+        }
+
+        if (resolveStencilBuffer)
+        {
+            ANGLE_TRY(depthStencilImage->initLayerImageView(
+                contextVk, textureType, VK_IMAGE_ASPECT_STENCIL_BIT, gl::SwizzleState(),
+                &stencilView.get(), levelIndex, 1, layerIndex, 1));
+        }
+
+        ANGLE_TRY(utilsVk.depthStencilResolve(contextVk, this, depthStencilImage, &depthView.get(),
+                                              &stencilView.get(), params));
+
+        vk::ImageView depthViewObject   = depthView.release();
+        vk::ImageView stencilViewObject = stencilView.release();
+
+        contextVk->releaseObject(contextVk->getCurrentQueueSerial(), &depthViewObject);
+        contextVk->releaseObject(contextVk->getCurrentQueueSerial(), &stencilViewObject);
+    }
+
+    return angle::Result::Continue;
+}
+
+angle::Result FramebufferVk::resolveColorWithCommand(ContextVk *contextVk,
+                                                     const UtilsVk::ResolveParameters &params,
+                                                     vk::ImageHelper *srcImage)
+{
+    if (srcImage->isLayoutChangeNecessary(vk::ImageLayout::TransferSrc))
+    {
+        vk::CommandBuffer *srcLayoutChange;
+        ANGLE_TRY(srcImage->recordCommands(contextVk, &srcLayoutChange));
+        srcImage->changeLayout(VK_IMAGE_ASPECT_COLOR_BIT, vk::ImageLayout::TransferSrc,
+                               srcLayoutChange);
+    }
 
     vk::CommandBuffer *commandBuffer = nullptr;
     ANGLE_TRY(mFramebuffer.recordCommands(contextVk, &commandBuffer));
 
-    const vk::Format &readImageFormat = readRenderTarget->getImageFormat();
-    VkImageAspectFlags aspectMask =
-        colorBlit ? VK_IMAGE_ASPECT_COLOR_BIT
-                  : vk::GetDepthStencilAspectFlags(readImageFormat.imageFormat());
-    vk::ImageHelper *srcImage = readRenderTarget->getImageForRead(
-        &mFramebuffer, vk::ImageLayout::TransferSrc, commandBuffer);
+    // Source's layout change should happen before rendering
+    srcImage->addReadDependency(&mFramebuffer);
 
-    const gl::Extents sourceFrameBufferExtents = readRenderTarget->getExtents();
-    gl::Rectangle readRect                     = readRectIn;
+    VkImageResolve resolveRegion                = {};
+    resolveRegion.srcSubresource.aspectMask     = VK_IMAGE_ASPECT_COLOR_BIT;
+    resolveRegion.srcSubresource.mipLevel       = 0;
+    resolveRegion.srcSubresource.baseArrayLayer = params.srcLayer;
+    resolveRegion.srcSubresource.layerCount     = 1;
+    resolveRegion.srcOffset.x                   = params.srcOffset[0];
+    resolveRegion.srcOffset.y                   = params.srcOffset[1];
+    resolveRegion.srcOffset.z                   = 0;
+    resolveRegion.dstSubresource.aspectMask     = VK_IMAGE_ASPECT_COLOR_BIT;
+    resolveRegion.dstSubresource.layerCount     = 1;
+    resolveRegion.dstOffset.x                   = params.destOffset[0];
+    resolveRegion.dstOffset.y                   = params.destOffset[1];
+    resolveRegion.dstOffset.z                   = 0;
+    resolveRegion.extent.width                  = params.srcExtents[0];
+    resolveRegion.extent.height                 = params.srcExtents[1];
+    resolveRegion.extent.depth                  = 1;
 
-    if (flipSource)
+    for (size_t colorIndexGL : mState.getEnabledDrawBuffers())
     {
-        readRect.y = sourceFrameBufferExtents.height - readRect.y - readRect.height;
+        RenderTargetVk *drawRenderTarget = mRenderTargetCache.getColors()[colorIndexGL];
+
+        resolveRegion.dstSubresource.mipLevel       = drawRenderTarget->getLevelIndex();
+        resolveRegion.dstSubresource.baseArrayLayer = drawRenderTarget->getLayerIndex();
+
+        srcImage->resolve(&drawRenderTarget->getImage(), resolveRegion, commandBuffer);
     }
 
-    VkImageBlit blit               = {};
-    blit.srcOffsets[0]             = {readRect.x0(), flipSource ? readRect.y1() : readRect.y0(), 0};
-    blit.srcOffsets[1]             = {readRect.x1(), flipSource ? readRect.y0() : readRect.y1(), 1};
-    blit.srcSubresource.aspectMask = aspectMask;
-    blit.srcSubresource.mipLevel   = 0;
-    blit.srcSubresource.baseArrayLayer = 0;
-    blit.srcSubresource.layerCount     = 1;
-    blit.dstSubresource.aspectMask     = aspectMask;
-    blit.dstSubresource.mipLevel       = 0;
-    blit.dstSubresource.baseArrayLayer = 0;
-    blit.dstSubresource.layerCount     = 1;
-
-    const gl::Extents drawFrameBufferExtents = drawRenderTarget->getExtents();
-    gl::Rectangle drawRect                   = drawRectIn;
-
-    if (flipDest)
-    {
-        drawRect.y = drawFrameBufferExtents.height - drawRect.y - drawRect.height;
-    }
-
-    blit.dstOffsets[0] = {drawRect.x0(), flipDest ? drawRect.y1() : drawRect.y0(), 0};
-    blit.dstOffsets[1] = {drawRect.x1(), flipDest ? drawRect.y0() : drawRect.y1(), 1};
-
-    // Requirement of the copyImageToBuffer, the dst image must be in
-    // VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL layout.
-    dstImage->changeLayout(aspectMask, vk::ImageLayout::TransferDst, commandBuffer);
-
-    commandBuffer->blitImage(srcImage->getImage(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
-                             dstImage->getImage(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &blit,
-                             gl_vk::GetFilter(filter));
-
     return angle::Result::Continue;
 }
 
@@ -1068,8 +1290,6 @@
                                            uint8_t clearStencilValue)
 {
     UtilsVk::ClearFramebufferParameters params = {};
-    params.renderPassDesc                      = &getRenderPassDesc();
-    params.renderAreaHeight                    = mState.getDimensions().height;
     params.clearArea                           = clearArea;
     params.colorClearValue                     = clearColorValue;
     params.stencilClearValue                   = clearStencilValue;
@@ -1217,11 +1437,10 @@
             VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT, 1));
         resolvedImage.get().updateQueueSerial(contextVk->getCurrentQueueSerial());
 
-        // TODO(syoussefi): resolve only works on color images (not depth/stencil).  If readback
-        // on multisampled depth/stencil image is done, we would need a different path.  One
-        // possible solution would be a compute shader that directly reads from the multisampled
-        // image, performs the resolve and outputs to the buffer in one go.
-        // http://anglebug.com/3200
+        // Note: resolve only works on color images (not depth/stencil).
+        //
+        // TODO: Currently, depth/stencil blit can perform a depth/stencil readback, but that code
+        // path will be optimized away.  http://anglebug.com/3200
         ASSERT(copyAspectFlags == VK_IMAGE_ASPECT_COLOR_BIT);
 
         VkImageResolve resolveRegion                = {};