Vulkan: Have a cubemap as 2D-array view handy

Previously, only texture copies used a shader that performed
texelFetch().  To support cubemaps, a hack was used to temporarily
create a 2D array view.

With upcoming support for multisample resolve, more shaders will be
using texelFetch() all requiring this workaround.  This change instead
makes sure that a separate view is created for cubemaps for the purpose
of being used with these shaders.  As a result, we have three logical
views on textures and render targets:

- Draw: a view that can be used as a color/depth/stencil attachment
- Read: a view that can be used to sample from
- Fetch: a view that can be used to fetch from

The fetch view is generally the same as the read view, except for cube
maps.

Bug: angleproject:3200
Change-Id: I21547f728c16f0aa8f0fcae152c400b5cc1565da
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/1628585
Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org>
Reviewed-by: Geoff Lang <geofflang@chromium.org>
diff --git a/src/libANGLE/renderer/vulkan/RenderTargetVk.cpp b/src/libANGLE/renderer/vulkan/RenderTargetVk.cpp
index 9a00f2a..01b579c 100644
--- a/src/libANGLE/renderer/vulkan/RenderTargetVk.cpp
+++ b/src/libANGLE/renderer/vulkan/RenderTargetVk.cpp
@@ -18,7 +18,11 @@
 namespace rx
 {
 RenderTargetVk::RenderTargetVk()
-    : mImage(nullptr), mImageView(nullptr), mLevelIndex(0), mLayerIndex(0)
+    : mImage(nullptr),
+      mImageView(nullptr),
+      mCubeImageFetchView(nullptr),
+      mLevelIndex(0),
+      mLayerIndex(0)
 {}
 
 RenderTargetVk::~RenderTargetVk() {}
@@ -26,17 +30,20 @@
 RenderTargetVk::RenderTargetVk(RenderTargetVk &&other)
     : mImage(other.mImage),
       mImageView(other.mImageView),
+      mCubeImageFetchView(other.mCubeImageFetchView),
       mLevelIndex(other.mLevelIndex),
       mLayerIndex(other.mLayerIndex)
 {}
 
 void RenderTargetVk::init(vk::ImageHelper *image,
                           vk::ImageView *imageView,
+                          vk::ImageView *cubeImageFetchView,
                           size_t levelIndex,
                           size_t layerIndex)
 {
     mImage      = image;
     mImageView  = imageView;
+    mCubeImageFetchView = cubeImageFetchView;
     mLevelIndex = levelIndex;
     mLayerIndex = layerIndex;
 }
@@ -45,6 +52,7 @@
 {
     mImage      = nullptr;
     mImageView  = nullptr;
+    mCubeImageFetchView = nullptr;
     mLevelIndex = 0;
     mLayerIndex = 0;
 }
@@ -108,6 +116,12 @@
     return getDrawImageView();
 }
 
+vk::ImageView *RenderTargetVk::getFetchImageView() const
+{
+    return mCubeImageFetchView && mCubeImageFetchView->valid() ? mCubeImageFetchView
+                                                               : getReadImageView();
+}
+
 const vk::Format &RenderTargetVk::getImageFormat() const
 {
     ASSERT(mImage && mImage->valid());
@@ -125,6 +139,7 @@
     ASSERT(image && image->valid() && imageView && imageView->valid());
     mImage     = image;
     mImageView = imageView;
+    mCubeImageFetchView = nullptr;
 }
 
 vk::ImageHelper *RenderTargetVk::getImageForRead(vk::CommandGraphResource *readingResource,
diff --git a/src/libANGLE/renderer/vulkan/RenderTargetVk.h b/src/libANGLE/renderer/vulkan/RenderTargetVk.h
index 4bfda37..9c9cd36 100644
--- a/src/libANGLE/renderer/vulkan/RenderTargetVk.h
+++ b/src/libANGLE/renderer/vulkan/RenderTargetVk.h
@@ -45,6 +45,7 @@
 
     void init(vk::ImageHelper *image,
               vk::ImageView *imageView,
+              vk::ImageView *cubeImageFetchView,
               size_t levelIndex,
               size_t layerIndex);
     void reset();
@@ -68,6 +69,10 @@
 
     vk::ImageView *getDrawImageView() const;
     vk::ImageView *getReadImageView() const;
+    // GLSL's texelFetch() needs a 2D array view to read from cube maps.  This function returns the
+    // same view as `getReadImageView()`, except for cubemaps, in which case it returns a 2D array
+    // view of it.
+    vk::ImageView *getFetchImageView() const;
 
     const vk::Format &getImageFormat() const;
     gl::Extents getExtents() const;
@@ -85,6 +90,8 @@
     // Note that the draw and read image views are the same, given the requirements of a render
     // target.
     vk::ImageView *mImageView;
+    // For cubemaps, a 2D-array view is also created to be used with shaders that use texelFetch().
+    vk::ImageView *mCubeImageFetchView;
     size_t mLevelIndex;
     size_t mLayerIndex;
 };
diff --git a/src/libANGLE/renderer/vulkan/RenderbufferVk.cpp b/src/libANGLE/renderer/vulkan/RenderbufferVk.cpp
index 53e1a5f..0850a08 100644
--- a/src/libANGLE/renderer/vulkan/RenderbufferVk.cpp
+++ b/src/libANGLE/renderer/vulkan/RenderbufferVk.cpp
@@ -87,7 +87,7 @@
         // Clear the renderbuffer if it has emulated channels.
         mImage->clearIfEmulatedFormat(vk::GetImpl(context), gl::ImageIndex::Make2D(0), vkFormat);
 
-        mRenderTarget.init(mImage, &mImageView, 0, 0);
+        mRenderTarget.init(mImage, &mImageView, nullptr, 0, 0);
     }
 
     return angle::Result::Continue;
@@ -134,7 +134,19 @@
                                          gl::SwizzleState(), &mImageView, imageVk->getImageLevel(),
                                          1, imageVk->getImageLayer(), 1));
 
-    mRenderTarget.init(mImage, &mImageView, imageVk->getImageLevel(), imageVk->getImageLayer());
+    if (imageVk->getImageTextureType() == gl::TextureType::CubeMap)
+    {
+        gl::TextureType arrayType = imageVk->getImage()->getSamples() > 1
+                                        ? gl::TextureType::_2DMultisampleArray
+                                        : gl::TextureType::_2DArray;
+        ANGLE_TRY(mImage->initLayerImageView(contextVk, arrayType, aspect, gl::SwizzleState(),
+                                             &mCubeImageFetchView, imageVk->getImageLevel(), 1,
+                                             imageVk->getImageLayer(), 1));
+    }
+
+    mRenderTarget.init(mImage, &mImageView,
+                       mCubeImageFetchView.valid() ? &mCubeImageFetchView : nullptr,
+                       imageVk->getImageLevel(), imageVk->getImageLayer());
 
     return angle::Result::Continue;
 }
@@ -185,6 +197,7 @@
     }
 
     renderer->releaseObject(renderer->getCurrentQueueSerial(), &mImageView);
+    renderer->releaseObject(renderer->getCurrentQueueSerial(), &mCubeImageFetchView);
 }
 
 }  // namespace rx
diff --git a/src/libANGLE/renderer/vulkan/RenderbufferVk.h b/src/libANGLE/renderer/vulkan/RenderbufferVk.h
index 57c6ccc..306eebf 100644
--- a/src/libANGLE/renderer/vulkan/RenderbufferVk.h
+++ b/src/libANGLE/renderer/vulkan/RenderbufferVk.h
@@ -54,6 +54,7 @@
     bool mOwnsImage;
     vk::ImageHelper *mImage;
     vk::ImageView mImageView;
+    vk::ImageView mCubeImageFetchView;
     RenderTargetVk mRenderTarget;
 };
 
diff --git a/src/libANGLE/renderer/vulkan/SurfaceVk.cpp b/src/libANGLE/renderer/vulkan/SurfaceVk.cpp
index 01d90a0..59a7420 100644
--- a/src/libANGLE/renderer/vulkan/SurfaceVk.cpp
+++ b/src/libANGLE/renderer/vulkan/SurfaceVk.cpp
@@ -163,9 +163,9 @@
                                        EGLint height)
     : SurfaceVk(surfaceState), mWidth(width), mHeight(height)
 {
-    mColorRenderTarget.init(&mColorAttachment.image, &mColorAttachment.imageView, 0, 0);
+    mColorRenderTarget.init(&mColorAttachment.image, &mColorAttachment.imageView, nullptr, 0, 0);
     mDepthStencilRenderTarget.init(&mDepthStencilAttachment.image,
-                                   &mDepthStencilAttachment.imageView, 0, 0);
+                                   &mDepthStencilAttachment.imageView, nullptr, 0, 0);
 }
 
 OffscreenSurfaceVk::~OffscreenSurfaceVk() {}
@@ -374,8 +374,8 @@
 {
     // Initialize the color render target with the multisampled targets.  If not multisampled, the
     // render target will be updated to refer to a swapchain image on every acquire.
-    mColorRenderTarget.init(&mColorImageMS, &mColorImageViewMS, 0, 0);
-    mDepthStencilRenderTarget.init(&mDepthStencilImage, &mDepthStencilImageView, 0, 0);
+    mColorRenderTarget.init(&mColorImageMS, &mColorImageViewMS, nullptr, 0, 0);
+    mDepthStencilRenderTarget.init(&mDepthStencilImage, &mDepthStencilImageView, nullptr, 0, 0);
 }
 
 WindowSurfaceVk::~WindowSurfaceVk()
diff --git a/src/libANGLE/renderer/vulkan/TextureVk.cpp b/src/libANGLE/renderer/vulkan/TextureVk.cpp
index 2069c1d..4227b85 100644
--- a/src/libANGLE/renderer/vulkan/TextureVk.cpp
+++ b/src/libANGLE/renderer/vulkan/TextureVk.cpp
@@ -426,9 +426,8 @@
         ASSERT(offsetImageIndex.getLayerCount() == 1);
 
         return copySubImageImplWithDraw(contextVk, offsetImageIndex, modifiedDestOffset, destFormat,
-                                        0, colorReadRT->getLayerIndex(), clippedSourceArea,
-                                        isViewportFlipY, false, false, false,
-                                        &colorReadRT->getImage(), colorReadRT->getReadImageView());
+                                        0, clippedSourceArea, isViewportFlipY, false, false, false,
+                                        &colorReadRT->getImage(), colorReadRT->getFetchImageView());
     }
 
     // Do a CPU readback that does the conversion, and then stage the change to the pixel buffer.
@@ -476,9 +475,9 @@
     if (CanCopyWithDraw(renderer, sourceVkFormat, destVkFormat) && !forceCpuPath)
     {
         return copySubImageImplWithDraw(contextVk, offsetImageIndex, destOffset, destVkFormat,
-                                        sourceLevel, 0, sourceArea, false, unpackFlipY,
+                                        sourceLevel, sourceArea, false, unpackFlipY,
                                         unpackPremultiplyAlpha, unpackUnmultiplyAlpha,
-                                        &source->getImage(), &source->getReadImageView());
+                                        &source->getImage(), &source->getFetchImageView());
     }
 
     if (sourceLevel != 0)
@@ -626,7 +625,6 @@
                                                   const gl::Offset &destOffset,
                                                   const vk::Format &destFormat,
                                                   size_t sourceLevel,
-                                                  size_t sourceLayer,
                                                   const gl::Rectangle &sourceArea,
                                                   bool isSrcFlipY,
                                                   bool unpackFlipY,
@@ -657,33 +655,6 @@
     uint32_t baseLayer  = index.hasLayer() ? index.getLayerIndex() : 0;
     uint32_t layerCount = index.getLayerCount();
 
-    // If the source image is a cube map, the view is of VK_IMAGE_VIEW_TYPE_CUBE type.  However,
-    // GLSL's texelFetch cannot take a textureCube.  We need to create a 2D_ARRAY type view to be
-    // able to perform the copy.
-    //
-    // Note(syoussefi): Array textures are not yet supported in Vulkan.  Once they are, the if below
-    // should be changed to detect cube maps only.
-    vk::ImageView cubeAs2DArrayView;
-    if (srcImage->getLayerCount() % 6 == 0)
-    {
-        // TODO(syoussefi): If the cube map is LUMA, we need swizzle.  http://anglebug.com/2911
-        // This can't happen when copying from framebuffers, so only source of concern would be
-        // copy[Sub]Texture copying from a LUMA cube map.
-        ASSERT(!srcImage->getFormat().imageFormat().isLUMA());
-
-        gl::TextureType arrayTextureType =
-            Get2DTextureType(srcImage->getLayerCount(), srcImage->getSamples());
-        ANGLE_TRY(srcImage->initImageView(contextVk, arrayTextureType, VK_IMAGE_ASPECT_COLOR_BIT,
-                                          gl::SwizzleState(), &cubeAs2DArrayView, 0,
-                                          srcImage->getLevelCount()));
-        srcView = &cubeAs2DArrayView;
-    }
-    else
-    {
-        // Source layer is otherwise baked into the view
-        sourceLayer = 0;
-    }
-
     // If destination is valid, copy the source directly into it.
     if (mImage->valid())
     {
@@ -692,7 +663,7 @@
 
         for (uint32_t layerIndex = 0; layerIndex < layerCount; ++layerIndex)
         {
-            params.srcLayer = sourceLayer + layerIndex;
+            params.srcLayer = layerIndex;
 
             vk::ImageView *destView;
             ANGLE_TRY(
@@ -720,7 +691,7 @@
 
         for (uint32_t layerIndex = 0; layerIndex < layerCount; ++layerIndex)
         {
-            params.srcLayer = sourceLayer + layerIndex;
+            params.srcLayer = layerIndex;
 
             // Create a temporary view for this layer.
             vk::ImageView stagingView;
@@ -742,11 +713,6 @@
             gl::Extents(sourceArea.width, sourceArea.height, 1));
     }
 
-    if (cubeAs2DArrayView.valid())
-    {
-        renderer->releaseObject(currentQueueSerial, &cubeAs2DArrayView);
-    }
-
     return angle::Result::Continue;
 }
 
@@ -928,8 +894,8 @@
     mImage            = imageHelper;
     mImage->initStagingBuffer(renderer, format);
 
-    mRenderTarget.init(mImage, &mDrawBaseLevelImageView, getNativeImageLevel(0),
-                       getNativeImageLayer(0));
+    mRenderTarget.init(mImage, &mDrawBaseLevelImageView, &mFetchBaseLevelImageView,
+                       getNativeImageLevel(0), getNativeImageLayer(0));
 
     // Force re-creation of cube map render targets next time they are needed
     mCubeMapRenderTargets.clear();
@@ -1208,13 +1174,26 @@
     if (!mCubeMapRenderTargets.empty())
         return angle::Result::Continue;
 
+    mLayerFetchImageView.resize(gl::kCubeFaceCount);
     mCubeMapRenderTargets.resize(gl::kCubeFaceCount);
     for (size_t cubeMapFaceIndex = 0; cubeMapFaceIndex < gl::kCubeFaceCount; ++cubeMapFaceIndex)
     {
-        vk::ImageView *imageView;
-        ANGLE_TRY(getLayerLevelDrawImageView(contextVk, cubeMapFaceIndex, 0, &imageView));
-        mCubeMapRenderTargets[cubeMapFaceIndex].init(mImage, imageView, getNativeImageLevel(0),
-                                                     getNativeImageLayer(cubeMapFaceIndex));
+        vk::ImageView *drawView;
+        ANGLE_TRY(getLayerLevelDrawImageView(contextVk, cubeMapFaceIndex, 0, &drawView));
+
+        // Users of the render target expect the views to directly view the desired layer, so we
+        // need create a fetch view for each layer as well.
+        gl::SwizzleState mappedSwizzle;
+        MapSwizzleState(mImage->getFormat(), mState.getSwizzleState(), &mappedSwizzle);
+        gl::TextureType arrayType = Get2DTextureType(gl::kCubeFaceCount, mImage->getSamples());
+        ANGLE_TRY(mImage->initLayerImageView(contextVk, arrayType, mImage->getAspectFlags(),
+                                             mappedSwizzle, &mLayerFetchImageView[cubeMapFaceIndex],
+                                             getNativeImageLevel(0), 1,
+                                             getNativeImageLayer(cubeMapFaceIndex), 1));
+
+        mCubeMapRenderTargets[cubeMapFaceIndex].init(
+            mImage, drawView, &mLayerFetchImageView[cubeMapFaceIndex], getNativeImageLevel(0),
+            getNativeImageLayer(cubeMapFaceIndex));
     }
     return angle::Result::Continue;
 }
@@ -1302,8 +1281,7 @@
 {
     ASSERT(mImage->valid());
 
-    const GLenum minFilter = mState.getSamplerState().getMinFilter();
-    if (minFilter == GL_LINEAR || minFilter == GL_NEAREST)
+    if (!gl::IsMipmapFiltered(mState.getSamplerState()))
     {
         return mReadBaseLevelImageView;
     }
@@ -1311,6 +1289,23 @@
     return mReadMipmapImageView;
 }
 
+const vk::ImageView &TextureVk::getFetchImageView() const
+{
+    if (!mFetchBaseLevelImageView.valid())
+    {
+        return getReadImageView();
+    }
+
+    ASSERT(mImage->valid());
+
+    if (!gl::IsMipmapFiltered(mState.getSamplerState()))
+    {
+        return mFetchBaseLevelImageView;
+    }
+
+    return mFetchMipmapImageView;
+}
+
 angle::Result TextureVk::getLayerLevelDrawImageView(vk::Context *context,
                                                     size_t layer,
                                                     size_t level,
@@ -1428,6 +1423,17 @@
     ANGLE_TRY(mImage->initLayerImageView(contextVk, mState.getType(), aspectFlags, mappedSwizzle,
                                          &mReadBaseLevelImageView, baseLevel, 1, baseLayer,
                                          layerCount));
+    if (mState.getType() == gl::TextureType::CubeMap)
+    {
+        gl::TextureType arrayType = Get2DTextureType(layerCount, mImage->getSamples());
+
+        ANGLE_TRY(mImage->initLayerImageView(contextVk, arrayType, aspectFlags, mappedSwizzle,
+                                             &mFetchMipmapImageView, baseLevel, levelCount,
+                                             baseLayer, layerCount));
+        ANGLE_TRY(mImage->initLayerImageView(contextVk, arrayType, aspectFlags, mappedSwizzle,
+                                             &mFetchBaseLevelImageView, baseLevel, 1, baseLayer,
+                                             layerCount));
+    }
     if (!format.imageFormat().isBlock)
     {
         ANGLE_TRY(mImage->initLayerImageView(contextVk, mState.getType(), aspectFlags,
@@ -1457,18 +1463,22 @@
     renderer->releaseObject(currentSerial, &mDrawBaseLevelImageView);
     renderer->releaseObject(currentSerial, &mReadBaseLevelImageView);
     renderer->releaseObject(currentSerial, &mReadMipmapImageView);
+    renderer->releaseObject(currentSerial, &mFetchBaseLevelImageView);
+    renderer->releaseObject(currentSerial, &mFetchMipmapImageView);
 
     for (auto &layerViews : mLayerLevelDrawImageViews)
     {
         for (vk::ImageView &imageView : layerViews)
         {
-            if (imageView.valid())
-            {
-                renderer->releaseObject(currentSerial, &imageView);
-            }
+            renderer->releaseObject(currentSerial, &imageView);
         }
     }
     mLayerLevelDrawImageViews.clear();
+    for (vk::ImageView &imageView : mLayerFetchImageView)
+    {
+        renderer->releaseObject(currentSerial, &imageView);
+    }
+    mLayerFetchImageView.clear();
     mCubeMapRenderTargets.clear();
 }
 
diff --git a/src/libANGLE/renderer/vulkan/TextureVk.h b/src/libANGLE/renderer/vulkan/TextureVk.h
index 21b6192..178ce23 100644
--- a/src/libANGLE/renderer/vulkan/TextureVk.h
+++ b/src/libANGLE/renderer/vulkan/TextureVk.h
@@ -153,6 +153,8 @@
     void releaseOwnershipOfImage(const gl::Context *context);
 
     const vk::ImageView &getReadImageView() const;
+    // A special view for cube maps as a 2D array, used with shaders that do texelFetch().
+    const vk::ImageView &getFetchImageView() const;
     angle::Result getLayerLevelDrawImageView(vk::Context *context,
                                              size_t layer,
                                              size_t level,
@@ -249,7 +251,6 @@
                                            const gl::Offset &destOffset,
                                            const vk::Format &destFormat,
                                            size_t sourceLevel,
-                                           size_t sourceLayer,
                                            const gl::Rectangle &sourceArea,
                                            bool isSrcFlipY,
                                            bool unpackFlipY,
@@ -293,10 +294,13 @@
     vk::ImageView mDrawBaseLevelImageView;
     vk::ImageView mReadBaseLevelImageView;
     vk::ImageView mReadMipmapImageView;
+    vk::ImageView mFetchBaseLevelImageView;
+    vk::ImageView mFetchMipmapImageView;
     std::vector<std::vector<vk::ImageView>> mLayerLevelDrawImageViews;
     vk::Sampler mSampler;
 
     RenderTargetVk mRenderTarget;
+    std::vector<vk::ImageView> mLayerFetchImageView;
     std::vector<RenderTargetVk> mCubeMapRenderTargets;
 };