Vulkan: Implement simple case ANGLE_get_image.

A couple cases are left unimplemented:

Incomplete/unused Textures. This leads to a slightly more tricky
implementation. Since we need to read back from the staging buffer
we will need to flush the Image contents to a temporary buffer.

Depth/stencil readback. Requires a more complex pixel packing
step.

3D/Cube/2D Array readback. Also requires a more complex packing
step.

Bug: angleproject:3944
Change-Id: Ic5d9a606177ba7e3e5ab945feb5f555afa11741f
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/1879964
Commit-Queue: Jamie Madill <jmadill@chromium.org>
Reviewed-by: Cody Northrop <cnorthrop@google.com>
diff --git a/src/libANGLE/renderer/vulkan/FramebufferVk.cpp b/src/libANGLE/renderer/vulkan/FramebufferVk.cpp
index 2e2d3a7..55834a6 100644
--- a/src/libANGLE/renderer/vulkan/FramebufferVk.cpp
+++ b/src/libANGLE/renderer/vulkan/FramebufferVk.cpp
@@ -458,10 +458,14 @@
         return angle::Result::Continue;
     }
 
+    const gl::State &glState = contextVk->getState();
+    gl::Buffer *packBuffer   = glState.getTargetBuffer(gl::BufferBinding::PixelPack);
+
     GLuint outputSkipBytes = 0;
     PackPixelsParams params;
-    ANGLE_TRY(vk::ImageHelper::GetReadPixelsParams(contextVk, format, type, area, clippedArea,
-                                                   &params, &outputSkipBytes));
+    ANGLE_TRY(vk::ImageHelper::GetReadPixelsParams(contextVk, glState.getPackState(), packBuffer,
+                                                   format, type, area, clippedArea, &params,
+                                                   &outputSkipBytes));
 
     if (contextVk->isViewportFlipEnabledForReadFBO())
     {
diff --git a/src/libANGLE/renderer/vulkan/RenderbufferVk.cpp b/src/libANGLE/renderer/vulkan/RenderbufferVk.cpp
index 5622f3a..c2c1b12 100644
--- a/src/libANGLE/renderer/vulkan/RenderbufferVk.cpp
+++ b/src/libANGLE/renderer/vulkan/RenderbufferVk.cpp
@@ -225,16 +225,22 @@
     mImageViews.release(renderer);
 }
 
+const gl::InternalFormat &RenderbufferVk::getImplementationSizedFormat() const
+{
+    GLenum internalFormat = mImage->getFormat().actualImageFormat().glInternalFormat;
+    return gl::GetSizedInternalFormatInfo(internalFormat);
+}
+
 GLenum RenderbufferVk::getColorReadFormat(const gl::Context *context)
 {
-    UNIMPLEMENTED();
-    return GL_NONE;
+    const gl::InternalFormat &sizedFormat = getImplementationSizedFormat();
+    return sizedFormat.format;
 }
 
 GLenum RenderbufferVk::getColorReadType(const gl::Context *context)
 {
-    UNIMPLEMENTED();
-    return GL_NONE;
+    const gl::InternalFormat &sizedFormat = getImplementationSizedFormat();
+    return sizedFormat.type;
 }
 
 angle::Result RenderbufferVk::getRenderbufferImage(const gl::Context *context,
@@ -244,7 +250,13 @@
                                                    GLenum type,
                                                    void *pixels)
 {
-    UNIMPLEMENTED();
-    return angle::Result::Continue;
+    // Storage not defined.
+    if (!mImage || !mImage->valid())
+        return angle::Result::Continue;
+
+    ContextVk *contextVk = vk::GetImpl(context);
+    ANGLE_TRY(mImage->flushAllStagedUpdates(contextVk));
+    return mImage->readPixelsForGetImage(contextVk, packState, packBuffer, 0, 0, format, type,
+                                         pixels);
 }
 }  // namespace rx
diff --git a/src/libANGLE/renderer/vulkan/RenderbufferVk.h b/src/libANGLE/renderer/vulkan/RenderbufferVk.h
index cee5b7c..a38627d2 100644
--- a/src/libANGLE/renderer/vulkan/RenderbufferVk.h
+++ b/src/libANGLE/renderer/vulkan/RenderbufferVk.h
@@ -68,6 +68,8 @@
                                  size_t width,
                                  size_t height);
 
+    const gl::InternalFormat &getImplementationSizedFormat() const;
+
     bool mOwnsImage;
     vk::ImageHelper *mImage;
     vk::ImageViewHelper mImageViews;
diff --git a/src/libANGLE/renderer/vulkan/TextureVk.cpp b/src/libANGLE/renderer/vulkan/TextureVk.cpp
index 5e50cab..ee50dc9 100644
--- a/src/libANGLE/renderer/vulkan/TextureVk.cpp
+++ b/src/libANGLE/renderer/vulkan/TextureVk.cpp
@@ -1269,9 +1269,7 @@
     const gl::ImageDesc &baseLevelDesc  = mState.getBaseLevelDesc();
     const gl::Extents &baseLevelExtents = baseLevelDesc.size;
     const uint32_t levelCount           = getMipLevelCount(mipLevels);
-    const vk::Format &format =
-        contextVk->getRenderer()->getFormat(baseLevelDesc.format.info->sizedInternalFormat);
-
+    const vk::Format &format            = getBaseLevelFormat(contextVk->getRenderer());
     return ensureImageInitializedImpl(contextVk, baseLevelExtents, levelCount, format);
 }
 
@@ -1704,16 +1702,34 @@
     return angle::Result::Continue;
 }
 
+const gl::InternalFormat &TextureVk::getImplementationSizedFormat(const gl::Context *context) const
+{
+    GLenum sizedFormat = GL_NONE;
+
+    if (mImage && mImage->valid())
+    {
+        sizedFormat = mImage->getFormat().actualImageFormat().glInternalFormat;
+    }
+    else
+    {
+        ContextVk *contextVk     = vk::GetImpl(context);
+        const vk::Format &format = getBaseLevelFormat(contextVk->getRenderer());
+        sizedFormat              = format.actualImageFormat().glInternalFormat;
+    }
+
+    return gl::GetSizedInternalFormatInfo(sizedFormat);
+}
+
 GLenum TextureVk::getColorReadFormat(const gl::Context *context)
 {
-    UNIMPLEMENTED();
-    return GL_NONE;
+    const gl::InternalFormat &sizedFormat = getImplementationSizedFormat(context);
+    return sizedFormat.format;
 }
 
 GLenum TextureVk::getColorReadType(const gl::Context *context)
 {
-    UNIMPLEMENTED();
-    return GL_NONE;
+    const gl::InternalFormat &sizedFormat = getImplementationSizedFormat(context);
+    return sizedFormat.type;
 }
 
 angle::Result TextureVk::getTexImage(const gl::Context *context,
@@ -1725,7 +1741,26 @@
                                      GLenum type,
                                      void *pixels)
 {
+    ContextVk *contextVk = vk::GetImpl(context);
+    if (mImage && mImage->valid())
+    {
+        ANGLE_TRY(mImage->flushAllStagedUpdates(contextVk));
+
+        size_t layer =
+            gl::IsCubeMapFaceTarget(target) ? gl::CubeMapTextureTargetToFaceIndex(target) : 0;
+        return mImage->readPixelsForGetImage(contextVk, packState, packBuffer, level,
+                                             static_cast<uint32_t>(layer), format, type, pixels);
+    }
+
+    // Incomplete or unused texture. Will require a staging texture.
+    // TODO(http://anglebug.com/4058): Incomplete texture readback.
     UNIMPLEMENTED();
     return angle::Result::Continue;
 }
+
+const vk::Format &TextureVk::getBaseLevelFormat(RendererVk *renderer) const
+{
+    const gl::ImageDesc &baseLevelDesc = mState.getBaseLevelDesc();
+    return renderer->getFormat(baseLevelDesc.format.info->sizedInternalFormat);
+}
 }  // namespace rx
diff --git a/src/libANGLE/renderer/vulkan/TextureVk.h b/src/libANGLE/renderer/vulkan/TextureVk.h
index 8013f7c..c9cc1f3 100644
--- a/src/libANGLE/renderer/vulkan/TextureVk.h
+++ b/src/libANGLE/renderer/vulkan/TextureVk.h
@@ -342,6 +342,8 @@
     void onStagingBufferChange() { onStateChange(angle::SubjectMessage::SubjectChanged); }
 
     angle::Result changeLevels(ContextVk *contextVk, GLuint baseLevel, GLuint maxLevel);
+    const gl::InternalFormat &getImplementationSizedFormat(const gl::Context *context) const;
+    const vk::Format &getBaseLevelFormat(RendererVk *renderer) const;
 
     bool mOwnsImage;
 
diff --git a/src/libANGLE/renderer/vulkan/vk_helpers.cpp b/src/libANGLE/renderer/vulkan/vk_helpers.cpp
index b07a4a3..470e1e3 100644
--- a/src/libANGLE/renderer/vulkan/vk_helpers.cpp
+++ b/src/libANGLE/renderer/vulkan/vk_helpers.cpp
@@ -2958,6 +2958,8 @@
 
 // static
 angle::Result ImageHelper::GetReadPixelsParams(ContextVk *contextVk,
+                                               const gl::PixelPackState &packState,
+                                               gl::Buffer *packBuffer,
                                                GLenum format,
                                                GLenum type,
                                                const gl::Rectangle &area,
@@ -2965,9 +2967,6 @@
                                                PackPixelsParams *paramsOut,
                                                GLuint *skipBytesOut)
 {
-    const gl::State &glState            = contextVk->getState();
-    const gl::PixelPackState &packState = glState.getPackState();
-
     const gl::InternalFormat &sizedFormatInfo = gl::GetInternalFormatInfo(format, type);
 
     GLuint outputPitch = 0;
@@ -2983,10 +2982,48 @@
     const angle::Format &angleFormat = GetFormatFromFormatType(format, type);
 
     *paramsOut = PackPixelsParams(clippedArea, angleFormat, outputPitch, packState.reverseRowOrder,
-                                  glState.getTargetBuffer(gl::BufferBinding::PixelPack), 0);
+                                  packBuffer, 0);
     return angle::Result::Continue;
 }
 
+angle::Result ImageHelper::readPixelsForGetImage(ContextVk *contextVk,
+                                                 const gl::PixelPackState &packState,
+                                                 gl::Buffer *packBuffer,
+                                                 uint32_t level,
+                                                 uint32_t layer,
+                                                 GLenum format,
+                                                 GLenum type,
+                                                 void *pixels)
+{
+    const angle::Format &angleFormat = GetFormatFromFormatType(format, type);
+
+    // Depth/stencil readback is not yet implemented.
+    // TODO(http://anglebug.com/4058): Depth/stencil readback.
+    if (angleFormat.depthBits > 0 || angleFormat.stencilBits > 0)
+    {
+        UNIMPLEMENTED();
+        return angle::Result::Continue;
+    }
+
+    PackPixelsParams params;
+    GLuint outputSkipBytes = 0;
+
+    uint32_t width  = std::max(1u, mExtents.width >> level);
+    uint32_t height = std::max(1u, mExtents.height >> level);
+    gl::Rectangle area(0, 0, width, height);
+
+    ANGLE_TRY(GetReadPixelsParams(contextVk, packState, packBuffer, format, type, area, area,
+                                  &params, &outputSkipBytes));
+
+    // Use a temporary staging buffer. Could be optimized.
+    vk::RendererScoped<vk::DynamicBuffer> stagingBuffer(contextVk->getRenderer());
+    stagingBuffer.get().init(contextVk->getRenderer(), VK_BUFFER_USAGE_TRANSFER_DST_BIT, 1,
+                             kStagingBufferSize, true);
+
+    return readPixels(contextVk, area, params, VK_IMAGE_ASPECT_COLOR_BIT, level, 0,
+                      static_cast<uint8_t *>(pixels) + outputSkipBytes, &stagingBuffer.get());
+}
+
 angle::Result ImageHelper::readPixels(ContextVk *contextVk,
                                       const gl::Rectangle &area,
                                       const PackPixelsParams &packPixelsParams,
@@ -3101,12 +3138,10 @@
     // created with the host coherent bit.
     ANGLE_TRY(stagingBuffer->invalidate(contextVk));
 
-    const gl::State &glState = contextVk->getState();
-    gl::Buffer *packBuffer   = glState.getTargetBuffer(gl::BufferBinding::PixelPack);
-    if (packBuffer != nullptr)
+    if (packPixelsParams.packBuffer)
     {
         // Must map the PBO in order to read its contents (and then unmap it later)
-        BufferVk *packBufferVk = GetImpl(packBuffer);
+        BufferVk *packBufferVk = GetImpl(packPixelsParams.packBuffer);
         void *mapPtr           = nullptr;
         ANGLE_TRY(packBufferVk->mapImpl(contextVk, &mapPtr));
         uint8_t *dest = static_cast<uint8_t *>(mapPtr) + reinterpret_cast<ptrdiff_t>(pixels);
diff --git a/src/libANGLE/renderer/vulkan/vk_helpers.h b/src/libANGLE/renderer/vulkan/vk_helpers.h
index 90b9ab5..e0e35cc 100644
--- a/src/libANGLE/renderer/vulkan/vk_helpers.h
+++ b/src/libANGLE/renderer/vulkan/vk_helpers.h
@@ -892,6 +892,8 @@
                                         uint8_t **outDataPtr);
 
     static angle::Result GetReadPixelsParams(ContextVk *contextVk,
+                                             const gl::PixelPackState &packState,
+                                             gl::Buffer *packBuffer,
                                              GLenum format,
                                              GLenum type,
                                              const gl::Rectangle &area,
@@ -899,6 +901,15 @@
                                              PackPixelsParams *paramsOut,
                                              GLuint *skipBytesOut);
 
+    angle::Result readPixelsForGetImage(ContextVk *contextVk,
+                                        const gl::PixelPackState &packState,
+                                        gl::Buffer *packBuffer,
+                                        uint32_t level,
+                                        uint32_t layer,
+                                        GLenum format,
+                                        GLenum type,
+                                        void *pixels);
+
     angle::Result readPixels(ContextVk *contextVk,
                              const gl::Rectangle &area,
                              const PackPixelsParams &packPixelsParams,
diff --git a/src/tests/gl_tests/GetImageTest.cpp b/src/tests/gl_tests/GetImageTest.cpp
index 3dd71e9..ed838ef 100644
--- a/src/tests/gl_tests/GetImageTest.cpp
+++ b/src/tests/gl_tests/GetImageTest.cpp
@@ -37,31 +37,37 @@
     GetImageTestNoExtensions() { setExtensionsEnabled(false); }
 };
 
-GLTexture InitSimpleTexture()
+GLTexture InitTextureWithSize(uint32_t size, void *pixelData)
 {
-    std::vector<GLColor> pixelData(kSize * kSize, GLColor::red);
-
     // Create a simple texture.
     GLTexture tex;
     glBindTexture(GL_TEXTURE_2D, tex);
-    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
-                 pixelData.data());
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixelData);
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-
     return tex;
 }
 
-GLRenderbuffer InitSimpleRenderbuffer()
+GLTexture InitSimpleTexture()
+{
+    std::vector<GLColor> pixelData(kSize * kSize, GLColor::red);
+    return InitTextureWithSize(kSize, pixelData.data());
+}
+
+GLRenderbuffer InitRenderbufferWithSize(uint32_t size)
 {
     // Create a simple renderbuffer.
     GLRenderbuffer renderbuf;
     glBindRenderbuffer(GL_RENDERBUFFER, renderbuf);
-    glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA4, kSize, kSize);
-
+    glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA4, size, size);
     return renderbuf;
 }
 
+GLRenderbuffer InitSimpleRenderbuffer()
+{
+    return InitRenderbufferWithSize(kSize);
+}
+
 // Test validation for the extension functions.
 TEST_P(GetImageTest, NegativeAPI)
 {
@@ -139,6 +145,71 @@
     }
 }
 
+// Simple test for GetTexImage
+TEST_P(GetImageTest, GetTexImage)
+{
+    // Verify the extension is enabled.
+    ASSERT_TRUE(IsGLExtensionEnabled(kExtensionName));
+
+    constexpr uint32_t kSmallSize     = 2;
+    std::vector<GLColor> expectedData = {GLColor::red, GLColor::blue, GLColor::green,
+                                         GLColor::yellow};
+
+    glViewport(0, 0, kSmallSize, kSmallSize);
+
+    // Draw once with simple texture.
+    GLTexture tex = InitTextureWithSize(kSmallSize, expectedData.data());
+    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D());
+    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5, 1.0f, true);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
+    ASSERT_GL_NO_ERROR();
+
+    // Pack pixels tightly.
+    glPixelStorei(GL_PACK_ALIGNMENT, 1);
+
+    // Verify GetImage.
+    std::vector<GLColor> actualData(kSmallSize * kSmallSize);
+    glGetTexImageANGLE(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, actualData.data());
+    EXPECT_GL_NO_ERROR();
+    EXPECT_EQ(expectedData, actualData);
+}
+
+// Simple test for GetRenderbufferImage
+TEST_P(GetImageTest, GetRenderbufferImage)
+{
+    // Verify the extension is enabled.
+    ASSERT_TRUE(IsGLExtensionEnabled(kExtensionName));
+
+    constexpr uint32_t kSmallSize     = 2;
+    std::vector<GLColor> expectedData = {GLColor::red, GLColor::blue, GLColor::green,
+                                         GLColor::yellow};
+
+    glViewport(0, 0, kSmallSize, kSmallSize);
+
+    // Set up a simple Framebuffer with a Renderbuffer.
+    GLRenderbuffer renderbuffer = InitRenderbufferWithSize(kSmallSize);
+    GLFramebuffer framebuffer;
+    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
+    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbuffer);
+    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
+
+    // Draw once with simple texture.
+    GLTexture tex = InitTextureWithSize(kSmallSize, expectedData.data());
+    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D());
+    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5, 1.0f, true);
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
+    ASSERT_GL_NO_ERROR();
+
+    // Pack pixels tightly.
+    glPixelStorei(GL_PACK_ALIGNMENT, 1);
+
+    // Verify GetImage.
+    std::vector<GLColor> actualData(kSmallSize * kSmallSize);
+    glGetRenderbufferImageANGLE(GL_RENDERBUFFER, GL_RGBA, GL_UNSIGNED_BYTE, actualData.data());
+    EXPECT_GL_NO_ERROR();
+    EXPECT_EQ(expectedData, actualData);
+}
+
 // Verifies that the extension enums and entry points are invalid when the extension is disabled.
 TEST_P(GetImageTestNoExtensions, EntryPointsInactive)
 {