Vulkan: generateMipmaps with vkCmdBlitImage when possible

Bug: angleproject:2502
Change-Id: Ib32a128a453749c59d751e996017a8a6e2a9972e
Reviewed-on: https://chromium-review.googlesource.com/1072550
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Luc Ferron <lucferron@chromium.org>
diff --git a/src/libANGLE/renderer/vulkan/TextureVk.cpp b/src/libANGLE/renderer/vulkan/TextureVk.cpp
index e83f702..44003fc 100644
--- a/src/libANGLE/renderer/vulkan/TextureVk.cpp
+++ b/src/libANGLE/renderer/vulkan/TextureVk.cpp
@@ -60,6 +60,9 @@
 constexpr VkBufferUsageFlags kStagingBufferFlags =
     (VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT);
 constexpr size_t kStagingBufferSize = 1024 * 16;
+
+constexpr VkFormatFeatureFlags kBlitFeatureFlags =
+    VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT;
 }  // anonymous namespace
 
 // StagingStorage implementation.
@@ -314,15 +317,15 @@
     return gl::NoError();
 }
 
-gl::Error TextureVk::generateMipmapLevels(ContextVk *contextVk,
-                                          const angle::Format &sourceFormat,
-                                          GLuint layer,
-                                          GLuint firstMipLevel,
-                                          GLuint maxMipLevel,
-                                          const size_t sourceWidth,
-                                          const size_t sourceHeight,
-                                          const size_t sourceRowPitch,
-                                          uint8_t *sourceData)
+gl::Error TextureVk::generateMipmapLevelsWithCPU(ContextVk *contextVk,
+                                                 const angle::Format &sourceFormat,
+                                                 GLuint layer,
+                                                 GLuint firstMipLevel,
+                                                 GLuint maxMipLevel,
+                                                 const size_t sourceWidth,
+                                                 const size_t sourceHeight,
+                                                 const size_t sourceRowPitch,
+                                                 uint8_t *sourceData)
 {
     RendererVk *renderer = contextVk->getRenderer();
 
@@ -589,34 +592,92 @@
     return gl::InternalError();
 }
 
-gl::Error TextureVk::generateMipmap(const gl::Context *context)
+void TextureVk::generateMipmapWithBlit(RendererVk *renderer)
+{
+    uint32_t imageLayerCount           = GetImageLayerCount(mState.getType());
+    const gl::Extents baseLevelExtents = mImage.getExtents();
+    vk::CommandBuffer *commandBuffer   = nullptr;
+    getCommandBufferForWrite(renderer, &commandBuffer);
+
+    // We are able to use blitImage since the image format we are using supports it. This
+    // is a faster way we can generate the mips.
+    int32_t mipWidth  = baseLevelExtents.width;
+    int32_t mipHeight = baseLevelExtents.height;
+
+    // Manually manage the image memory barrier because it uses a lot more parameters than our
+    // usual one.
+    VkImageMemoryBarrier barrier;
+    barrier.sType                           = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+    barrier.image                           = mImage.getImage().getHandle();
+    barrier.srcQueueFamilyIndex             = VK_QUEUE_FAMILY_IGNORED;
+    barrier.dstQueueFamilyIndex             = VK_QUEUE_FAMILY_IGNORED;
+    barrier.pNext                           = nullptr;
+    barrier.subresourceRange.aspectMask     = VK_IMAGE_ASPECT_COLOR_BIT;
+    barrier.subresourceRange.baseArrayLayer = 0;
+    barrier.subresourceRange.layerCount     = imageLayerCount;
+    barrier.subresourceRange.levelCount     = 1;
+
+    for (uint32_t mipLevel = 1; mipLevel <= mState.getMipmapMaxLevel(); mipLevel++)
+    {
+        int32_t nextMipWidth  = std::max<int32_t>(1, mipWidth >> 1);
+        int32_t nextMipHeight = std::max<int32_t>(1, mipHeight >> 1);
+
+        barrier.subresourceRange.baseMipLevel = mipLevel - 1;
+        barrier.oldLayout                     = mImage.getCurrentLayout();
+        barrier.newLayout                     = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
+        barrier.srcAccessMask                 = VK_ACCESS_TRANSFER_WRITE_BIT;
+        barrier.dstAccessMask                 = VK_ACCESS_TRANSFER_READ_BIT;
+
+        // We can do it for all layers at once.
+        commandBuffer->singleImageBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT,
+                                          VK_PIPELINE_STAGE_TRANSFER_BIT, 0, barrier);
+
+        VkImageBlit blit                   = {};
+        blit.srcOffsets[0]                 = {0, 0, 0};
+        blit.srcOffsets[1]                 = {mipWidth, mipHeight, 1};
+        blit.srcSubresource.aspectMask     = VK_IMAGE_ASPECT_COLOR_BIT;
+        blit.srcSubresource.mipLevel       = mipLevel - 1;
+        blit.srcSubresource.baseArrayLayer = 0;
+        blit.srcSubresource.layerCount     = imageLayerCount;
+        blit.dstOffsets[0]                 = {0, 0, 0};
+        blit.dstOffsets[1]                 = {nextMipWidth, nextMipHeight, 1};
+        blit.dstSubresource.aspectMask     = VK_IMAGE_ASPECT_COLOR_BIT;
+        blit.dstSubresource.mipLevel       = mipLevel;
+        blit.dstSubresource.baseArrayLayer = 0;
+        blit.dstSubresource.layerCount     = imageLayerCount;
+
+        mipWidth  = nextMipWidth;
+        mipHeight = nextMipHeight;
+
+        commandBuffer->blitImage(mImage.getImage(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+                                 mImage.getImage(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &blit,
+                                 VK_FILTER_LINEAR);
+    }
+
+    // Transition the last mip level to the same layout as all the other ones, so we can declare
+    // our whole image layout to be SRC_OPTIMAL.
+    barrier.subresourceRange.baseMipLevel = mState.getMipmapMaxLevel();
+    barrier.oldLayout                     = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
+    barrier.newLayout                     = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
+
+    // We can do it for all layers at once.
+    commandBuffer->singleImageBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT,
+                                      VK_PIPELINE_STAGE_TRANSFER_BIT, 0, barrier);
+
+    // This is just changing the internal state of the image helper so that the next call
+    // to changeLayoutWithStages will use this layout as the "oldLayout" argument.
+    mImage.updateLayout(VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
+}
+
+gl::Error TextureVk::generateMipmapWithCPU(const gl::Context *context)
 {
     ContextVk *contextVk = vk::GetImpl(context);
     RendererVk *renderer = contextVk->getRenderer();
 
-    // Some data is pending, or the image has not been defined at all yet
-    if (!mImage.valid())
-    {
-        // lets initialize the image so we can generate the next levels.
-        if (!mPixelBuffer.empty())
-        {
-            ANGLE_TRY(ensureImageInitialized(renderer));
-            ASSERT(mImage.valid());
-        }
-        else
-        {
-            // There is nothing to generate if there is nothing uploaded so far.
-            return gl::NoError();
-        }
-    }
-
-    // Before we loop to generate all the next levels, we can get the source level and copy it to a
-    // buffer.
-    const angle::Format &angleFormat = mImage.getFormat().textureFormat();
-    uint32_t imageLayerCount         = GetImageLayerCount(mState.getType());
-
     bool newBufferAllocated            = false;
     const gl::Extents baseLevelExtents = mImage.getExtents();
+    uint32_t imageLayerCount           = GetImageLayerCount(mState.getType());
+    const angle::Format &angleFormat   = mImage.getFormat().textureFormat();
     GLuint sourceRowPitch              = baseLevelExtents.width * angleFormat.pixelBytes;
     size_t baseLevelAllocationSize     = sourceRowPitch * baseLevelExtents.height;
 
@@ -659,9 +720,6 @@
 
     ANGLE_TRY(renderer->finish(context));
 
-    // We're changing this textureVk content, make sure we let the graph know.
-    onResourceChanged(renderer);
-
     // We now have the base level available to be manipulated in the baseLevelBuffer pointer.
     // Generate all the missing mipmaps with the slow path. We can optimize with vkCmdBlitImage
     // later.
@@ -669,12 +727,56 @@
     for (GLuint layer = 0; layer < imageLayerCount; layer++)
     {
         size_t bufferOffset = layer * baseLevelAllocationSize;
-        ANGLE_TRY(generateMipmapLevels(
+
+        ANGLE_TRY(generateMipmapLevelsWithCPU(
             contextVk, angleFormat, layer, mState.getEffectiveBaseLevel() + 1,
             mState.getMipmapMaxLevel(), baseLevelExtents.width, baseLevelExtents.height,
             sourceRowPitch, baseLevelBuffers + bufferOffset));
     }
 
+    mPixelBuffer.flushUpdatesToImage(renderer, &mImage, commandBuffer);
+    return gl::NoError();
+}
+
+gl::Error TextureVk::generateMipmap(const gl::Context *context)
+{
+    ContextVk *contextVk = vk::GetImpl(context);
+    RendererVk *renderer = contextVk->getRenderer();
+
+    // Some data is pending, or the image has not been defined at all yet
+    if (!mImage.valid())
+    {
+        // lets initialize the image so we can generate the next levels.
+        if (!mPixelBuffer.empty())
+        {
+            ANGLE_TRY(ensureImageInitialized(renderer));
+            ASSERT(mImage.valid());
+        }
+        else
+        {
+            // There is nothing to generate if there is nothing uploaded so far.
+            return gl::NoError();
+        }
+    }
+
+    VkFormatProperties imageProperties;
+    vk::GetFormatProperties(renderer->getPhysicalDevice(), mImage.getFormat().vkTextureFormat,
+                            &imageProperties);
+
+    // Check if the image supports blit. If it does, we can do the mipmap generation on the gpu
+    // only.
+    if (IsMaskFlagSet(kBlitFeatureFlags, imageProperties.linearTilingFeatures))
+    {
+        generateMipmapWithBlit(renderer);
+    }
+    else
+    {
+        ANGLE_TRY(generateMipmapWithCPU(context));
+    }
+
+    // We're changing this textureVk content, make sure we let the graph know.
+    onResourceChanged(renderer);
+
     return gl::NoError();
 }
 
diff --git a/src/libANGLE/renderer/vulkan/TextureVk.h b/src/libANGLE/renderer/vulkan/TextureVk.h
index 1fccf9d..895f2ef 100644
--- a/src/libANGLE/renderer/vulkan/TextureVk.h
+++ b/src/libANGLE/renderer/vulkan/TextureVk.h
@@ -175,15 +175,19 @@
     vk::Error ensureImageInitialized(RendererVk *renderer);
 
   private:
-    gl::Error generateMipmapLevels(ContextVk *contextVk,
-                                   const angle::Format &sourceFormat,
-                                   GLuint layer,
-                                   GLuint firstMipLevel,
-                                   GLuint maxMipLevel,
-                                   size_t sourceWidth,
-                                   size_t sourceHeight,
-                                   size_t sourceRowPitch,
-                                   uint8_t *sourceData);
+    void generateMipmapWithBlit(RendererVk *renderer);
+
+    gl::Error generateMipmapWithCPU(const gl::Context *context);
+
+    gl::Error generateMipmapLevelsWithCPU(ContextVk *contextVk,
+                                          const angle::Format &sourceFormat,
+                                          GLuint layer,
+                                          GLuint firstMipLevel,
+                                          GLuint maxMipLevel,
+                                          size_t sourceWidth,
+                                          size_t sourceHeight,
+                                          size_t sourceRowPitch,
+                                          uint8_t *sourceData);
 
     gl::Error copySubImageImpl(const gl::Context *context,
                                const gl::ImageIndex &index,
diff --git a/src/libANGLE/renderer/vulkan/vk_format_utils.cpp b/src/libANGLE/renderer/vulkan/vk_format_utils.cpp
index 98c9b66..1f1fa0a 100644
--- a/src/libANGLE/renderer/vulkan/vk_format_utils.cpp
+++ b/src/libANGLE/renderer/vulkan/vk_format_utils.cpp
@@ -41,6 +41,11 @@
         HasFormatFeatureBits(VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT, formatProperties);
 }
 
+}  // anonymous namespace
+
+namespace vk
+{
+
 void GetFormatProperties(VkPhysicalDevice physicalDevice,
                          VkFormat vkFormat,
                          VkFormatProperties *propertiesOut)
@@ -63,10 +68,7 @@
         *propertiesOut = formatProperties;
     }
 }
-}  // anonymous namespace
 
-namespace vk
-{
 bool HasFullFormatSupport(VkPhysicalDevice physicalDevice, VkFormat vkFormat)
 {
     VkFormatProperties formatProperties;
diff --git a/src/libANGLE/renderer/vulkan/vk_format_utils.h b/src/libANGLE/renderer/vulkan/vk_format_utils.h
index fec2a92..c9fdba0 100644
--- a/src/libANGLE/renderer/vulkan/vk_format_utils.h
+++ b/src/libANGLE/renderer/vulkan/vk_format_utils.h
@@ -28,6 +28,10 @@
 namespace vk
 {
 
+void GetFormatProperties(VkPhysicalDevice physicalDevice,
+                         VkFormat vkFormat,
+                         VkFormatProperties *propertiesOut);
+
 struct Format final : private angle::NonCopyable
 {
     Format();
diff --git a/src/libANGLE/renderer/vulkan/vk_utils.cpp b/src/libANGLE/renderer/vulkan/vk_utils.cpp
index 795e984..d7af475 100644
--- a/src/libANGLE/renderer/vulkan/vk_utils.cpp
+++ b/src/libANGLE/renderer/vulkan/vk_utils.cpp
@@ -384,6 +384,19 @@
     return NoError();
 }
 
+void CommandBuffer::blitImage(const Image &srcImage,
+                              VkImageLayout srcImageLayout,
+                              const Image &dstImage,
+                              VkImageLayout dstImageLayout,
+                              uint32_t regionCount,
+                              VkImageBlit *pRegions,
+                              VkFilter filter)
+{
+    ASSERT(valid());
+    vkCmdBlitImage(mHandle, srcImage.getHandle(), srcImageLayout, dstImage.getHandle(),
+                   dstImageLayout, regionCount, pRegions, filter);
+}
+
 Error CommandBuffer::begin(const VkCommandBufferBeginInfo &info)
 {
     ASSERT(valid());
diff --git a/src/libANGLE/renderer/vulkan/vk_utils.h b/src/libANGLE/renderer/vulkan/vk_utils.h
index 8653438..07461a5 100644
--- a/src/libANGLE/renderer/vulkan/vk_utils.h
+++ b/src/libANGLE/renderer/vulkan/vk_utils.h
@@ -313,6 +313,13 @@
     VkCommandBuffer releaseHandle();
     void destroy(VkDevice device, const CommandPool &commandPool);
     Error init(VkDevice device, const VkCommandBufferAllocateInfo &createInfo);
+    void blitImage(const Image &srcImage,
+                   VkImageLayout srcImageLayout,
+                   const Image &dstImage,
+                   VkImageLayout dstImageLayout,
+                   uint32_t regionCount,
+                   VkImageBlit *pRegions,
+                   VkFilter filter);
     using WrappedObject::operator=;
 
     Error begin(const VkCommandBufferBeginInfo &info);
diff --git a/src/tests/deqp_support/deqp_gles2_test_expectations.txt b/src/tests/deqp_support/deqp_gles2_test_expectations.txt
index eeb406e..8a43232 100644
--- a/src/tests/deqp_support/deqp_gles2_test_expectations.txt
+++ b/src/tests/deqp_support/deqp_gles2_test_expectations.txt
@@ -200,6 +200,7 @@
 2605 VULKAN ANDROID : dEQP-GLES2.functional.state_query.rbo.renderbuffer_component_size_depth = SKIP
 2606 VULKAN ANDROID : dEQP-GLES2.functional.debug_marker.random = SKIP
 2606 VULKAN ANDROID : dEQP-GLES2.functional.debug_marker.supported = SKIP
+2609 VULKAN ANDROID : dEQP-GLES2.functional.texture.mipmap.cube.generate.* = SKIP
 
 // Windows Linux and Mac failures
 1028 WIN LINUX MAC : dEQP-GLES2.functional.fbo.completeness.renderable.texture.color0.srgb8 = FAIL
diff --git a/src/tests/gl_tests/MipmapTest.cpp b/src/tests/gl_tests/MipmapTest.cpp
index 052d2e1..8e69a39 100644
--- a/src/tests/gl_tests/MipmapTest.cpp
+++ b/src/tests/gl_tests/MipmapTest.cpp
@@ -520,10 +520,6 @@
 // In particular, on D3D11 Feature Level 9_3 it ensures that both the zero LOD workaround texture AND the 'normal' texture are copied during conversion.
 TEST_P(MipmapTest, GenerateMipmapFromInitDataThenRender)
 {
-    // TODO(lucferron): Figure out why this test is failing only on Intel Linux.
-    // http://anglebug.com/2502
-    ANGLE_SKIP_TEST_IF(IsVulkan() && IsIntel());
-
     // Pass in initial data so the texture is blue.
     glBindTexture(GL_TEXTURE_2D, mTexture2D);
     glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, getWindowWidth(), getWindowHeight(), 0, GL_RGB, GL_UNSIGNED_BYTE, mLevelZeroBlueInitData);
@@ -578,10 +574,6 @@
 // The test ensures that the zero-level texture is correctly copied into the mipped texture before the mipmaps are generated.
 TEST_P(MipmapTest, GenerateMipmapFromRenderedImage)
 {
-    // TODO(lucferron): Figure out why this test is failing only on Intel Linux.
-    // http://anglebug.com/2502
-    ANGLE_SKIP_TEST_IF(IsVulkan() && IsIntel());
-
     glBindTexture(GL_TEXTURE_2D, mTexture2D);
     // Clear the texture to blue.
     clearTextureLevel0(GL_TEXTURE_2D, mTexture2D, 0.0f, 0.0f, 1.0f, 1.0f);
@@ -610,10 +602,6 @@
 // TODO: This test hits a texture rebind bug in the D3D11 renderer. Fix this.
 TEST_P(MipmapTest, RenderOntoLevelZeroAfterGenerateMipmap)
 {
-    // TODO(lucferron): Figure out why this test is failing only on Intel Linux.
-    // http://anglebug.com/2502
-    ANGLE_SKIP_TEST_IF(IsVulkan() && IsIntel());
-
     // TODO(geofflang): Figure out why this is broken on AMD OpenGL
     ANGLE_SKIP_TEST_IF(IsAMD() && IsOpenGL());