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,
- ¶ms, &outputSkipBytes));
+ ANGLE_TRY(vk::ImageHelper::GetReadPixelsParams(contextVk, glState.getPackState(), packBuffer,
+ format, type, area, clippedArea, ¶ms,
+ &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,
+ ¶ms, &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)
{