Vulkan: Force flush staged updates for external textures
For textures that are backed by external memory,
immediately flush sub image updates instead of
staging them.
Bug: angleproject:4828
Tests: angle_end2end_tests --gtest_filter=ImageTest.UpdatedExternalTexture*
Change-Id: I51e5bd0cb5df7df3af21f0cdb3007eebc1be29cd
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2290490
Reviewed-by: Jamie Madill <jmadill@chromium.org>
Reviewed-by: Shahbaz Youssefi <syoussefi@chromium.org>
Commit-Queue: Mohan Maiya <m.maiya@samsung.com>
diff --git a/src/libANGLE/renderer/vulkan/TextureVk.cpp b/src/libANGLE/renderer/vulkan/TextureVk.cpp
index 82b1790..7d100e4 100644
--- a/src/libANGLE/renderer/vulkan/TextureVk.cpp
+++ b/src/libANGLE/renderer/vulkan/TextureVk.cpp
@@ -394,6 +394,11 @@
gl::Offset(area.x, area.y, area.z), formatInfo, unpack, type, pixels, vkFormat));
}
+ if (!mOwnsImage)
+ {
+ ANGLE_TRY(mImage->flushAllStagedUpdates(contextVk));
+ }
+
return angle::Result::Continue;
}
diff --git a/src/tests/gl_tests/ImageTest.cpp b/src/tests/gl_tests/ImageTest.cpp
index b846d84..a8bfa68 100644
--- a/src/tests/gl_tests/ImageTest.cpp
+++ b/src/tests/gl_tests/ImageTest.cpp
@@ -567,6 +567,58 @@
glDeleteFramebuffers(1, &framebuffer);
}
+ void verifyResultAHB(AHardwareBuffer *source,
+ GLubyte *referenceData,
+ size_t dataSize,
+ size_t bytesPerPixel)
+ {
+#if defined(ANGLE_AHARDWARE_BUFFER_SUPPORT)
+ void *mappedMemory = nullptr;
+ GLubyte externalMemoryData[dataSize];
+ AHardwareBuffer_Desc aHardwareBufferDescription = {};
+
+ ASSERT_EQ(0, AHardwareBuffer_lock(source, AHARDWAREBUFFER_USAGE_CPU_WRITE_RARELY, -1,
+ nullptr, &mappedMemory));
+
+ AHardwareBuffer_describe(source, &aHardwareBufferDescription);
+ const uint32_t width = aHardwareBufferDescription.width;
+ const uint32_t height = aHardwareBufferDescription.height;
+ const uint32_t stride = aHardwareBufferDescription.stride;
+ const uint32_t rowSize = bytesPerPixel * width;
+ const size_t bufferSize = rowSize * height;
+
+ EXPECT_EQ(dataSize, bufferSize);
+
+ for (uint32_t i = 0; i < height; i++)
+ {
+ uint32_t srcPtrOffset = stride * i * bytesPerPixel;
+ uint32_t dstPtrOffset = width * i * bytesPerPixel;
+ size_t copySize = rowSize;
+
+ if (dstPtrOffset > dataSize)
+ {
+ // Current destination ptr offset is out of range
+ break;
+ }
+ else if (dstPtrOffset + copySize > dataSize)
+ {
+ // Copy data only until the end of the buffer
+ copySize = dataSize - dstPtrOffset;
+ }
+
+ void *src = reinterpret_cast<uint8_t *>(mappedMemory) + srcPtrOffset;
+ memcpy(externalMemoryData + dstPtrOffset, src, copySize);
+ }
+
+ ASSERT_EQ(0, AHardwareBuffer_unlock(source, nullptr));
+
+ for (uint32_t i = 0; i < dataSize; i++)
+ {
+ EXPECT_EQ(externalMemoryData[i], referenceData[i]);
+ }
+#endif
+ }
+
template <typename destType, typename sourcetype>
destType reinterpretHelper(sourcetype source)
{
@@ -2694,6 +2746,73 @@
glDeleteRenderbuffers(1, &targetRenderbuffer);
}
+// Check that the external texture is successfully updated when only glTexSubImage2D is called.
+TEST_P(ImageTest, UpdatedExternalTexture)
+{
+ EGLWindow *window = getEGLWindow();
+
+ ANGLE_SKIP_TEST_IF(!IsAndroid());
+ ANGLE_SKIP_TEST_IF(!hasOESExt() || !hasBaseExt() || !has2DTextureExt());
+ ANGLE_SKIP_TEST_IF(!hasAndroidImageNativeBufferExt() || !hasAndroidHardwareBufferSupport());
+
+ GLubyte originalData[4] = {255, 0, 255, 255};
+ GLubyte updateData[4] = {0, 255, 0, 255};
+ const uint32_t bytesPerPixel = 4;
+
+ // Create the Image
+ AHardwareBuffer *source;
+ EGLImageKHR image;
+ createEGLImageAndroidHardwareBufferSource(1, 1, 1, GL_RGBA8, kDefaultAttribs, originalData,
+ bytesPerPixel, &source, &image);
+
+ // Create target
+ GLuint targetTexture;
+ createEGLImageTargetTexture2D(image, &targetTexture);
+
+ // Expect that both the target have the original data
+ verifyResults2D(targetTexture, originalData);
+
+ // Update the data of the source
+ glBindTexture(GL_TEXTURE_2D, targetTexture);
+ glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, updateData);
+
+ // Set sync object and flush the GL commands
+ EGLSyncKHR fence = eglCreateSyncKHR(window->getDisplay(), EGL_SYNC_FENCE_KHR, NULL);
+ ASSERT_NE(fence, EGL_NO_SYNC_KHR);
+ glFlush();
+
+ // Delete the target texture
+ glDeleteTextures(1, &targetTexture);
+
+ // Wait that the flush command is finished
+ EGLint result = eglClientWaitSyncKHR(window->getDisplay(), fence, 0, 1000000000);
+ ASSERT_EQ(result, EGL_CONDITION_SATISFIED_KHR);
+ ASSERT_EGL_TRUE(eglDestroySyncKHR(window->getDisplay(), fence));
+
+ // Delete the EGL image
+ eglDestroyImageKHR(window->getDisplay(), image);
+
+ // Access the android hardware buffer directly to check the data is updated
+ verifyResultAHB(source, updateData, 4, bytesPerPixel);
+
+ // Create the EGL image again
+ image =
+ eglCreateImageKHR(window->getDisplay(), EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID,
+ angle::android::AHardwareBufferToClientBuffer(source), kDefaultAttribs);
+ ASSERT_EGL_SUCCESS();
+
+ // Create the target texture again
+ createEGLImageTargetTexture2D(image, &targetTexture);
+
+ // Expect that the target have the update data
+ verifyResults2D(targetTexture, updateData);
+
+ // Clean up
+ eglDestroyImageKHR(window->getDisplay(), image);
+ destroyAndroidHardwareBuffer(source);
+ glDeleteTextures(1, &targetTexture);
+}
+
// Use this to select which configurations (e.g. which renderer, which GLES major version) these
// tests should be run against.
ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(ImageTest);