ci/deqp: Add Android WSI support

This is implement with a headless WSI fallback using AImageReader, which
allows running EGL and Vulkan WSI tests from the command-line executable
(DEQP_ANDROID_EXE) on Android.

Signed-off-by: Valentine Burley <valentine.burley@collabora.com>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/41314>
This commit is contained in:
Valentine Burley 2026-04-23 13:25:08 +02:00
parent 1eb9eed8ea
commit 06816dbdbc
5 changed files with 351 additions and 24 deletions

View file

@ -1,8 +1,4 @@
# Skip these tests when running fractional dEQP batches, as the AHB tests are expected
# to be handled separately in a non-fractional run within the deqp-runner suite.
# Skip these tests when running fractional dEQP batches, as the AHB and WSI tests are
# expected to be handled separately in non-fractional runs within the deqp-runner suite.
dEQP-VK.api.external.memory.android_hardware_buffer.*
# Skip all WSI tests: the DEQP_ANDROID_EXE build used can't create native windows, as
# only APKs support window creation on Android.
dEQP-VK.image.swapchain_mutable.*
dEQP-VK.wsi.*

View file

@ -52,6 +52,7 @@ vk_cts_commits_to_backport=(
# shellcheck disable=SC2034
vk_cts_patch_files=(
build-deqp-android-Implement-headless-WSI-fallback-using-AImageR.patch
)
# shellcheck disable=SC2034
@ -60,6 +61,7 @@ gl_cts_commits_to_backport=(
# shellcheck disable=SC2034
gl_cts_patch_files=(
build-deqp-android-Implement-headless-WSI-fallback-using-AImageR.patch # We're only applying this avoid conflicts in the second patch
build-deqp-gl_Build-Don-t-build-Vulkan-utilities-for-GL-builds.patch
)
@ -74,6 +76,7 @@ gles_cts_commits_to_backport=(
# shellcheck disable=SC2034
gles_cts_patch_files=(
build-deqp-android-Implement-headless-WSI-fallback-using-AImageR.patch
build-deqp-gl_Build-Don-t-build-Vulkan-utilities-for-GL-builds.patch
)

View file

@ -0,0 +1,277 @@
From 0ce7ca82289ec3351ccec4b37540d8fdbc8dd095 Mon Sep 17 00:00:00 2001
From: Valentine Burley <valentine.burley@collabora.com>
Date: Thu, 23 Apr 2026 11:03:14 +0200
Subject: [PATCH] android: Implement headless WSI fallback using AImageReader
When running dEQP as a native executable (DEQP_ANDROID_EXE) on Android,
a system-provided NativeActivity window is typically unavailable. This
prevents Vulkan and EGL WSI tests from running as they require a valid
ANativeWindow.
This patch:
- Implements a headless fallback using the AImageReader NDK API to
create an off-screen ANativeWindow in tcuAndroidPlatform.cpp.
- Adds ImageReaderNativeWindow (EGL) and ImageReaderVulkanWindow (Vulkan)
to manage the AImageReader lifecycle within the framework.
- Extracts the AImageReader creation logic into a shared helper
acquireImageReaderWindow().
- Links against mediandk in android.cmake to provide AImageReader
support for Android API levels >= 24.
- On Android API levels < 24 (where AImageReader is unavailable), window
acquisition failure now consistently throws ResourceError instead of
NotSupportedError. This aligns the EGL path with the existing Vulkan
behavior and reverts EGL to the previous CTS behavior.
This allows surface and swapchain tests to function correctly in headless
environments for both EGL and Vulkan.
Components: Android, EGL, Framework, Vulkan
VK-GL-CTS issue: 6468
Affects:
dEQP-EGL.*
dEQP-VK.wsi.android.*
Change-Id: I462e617ae60e4dc3d9f0aeec11fd1628d0c6ff12
Signed-off-by: Valentine Burley <valentine.burley@collabora.com>
---
external/openglcts/README.md | 7 +-
.../platform/android/tcuAndroidPlatform.cpp | 159 +++++++++++++++++-
targets/android/android.cmake | 5 +
3 files changed, 164 insertions(+), 7 deletions(-)
diff --git a/external/openglcts/README.md b/external/openglcts/README.md
index 69eb8a5c1e..f464291858 100644
--- a/external/openglcts/README.md
+++ b/external/openglcts/README.md
@@ -323,9 +323,10 @@ This is identical to the builds on other platforms and is better for iterative
runs of headless tests as CTS can be invoked and the output can be checked from
a single interactive terminal.
-This build doesn't support WSI tests and shouldn't be used for conformance
-submissions, it also isn't recommended for longer running tests since Android
-will terminate this process as soon as the `adb shell` session ends which may
+This build supports WSI tests via a headless AImageReader fallback for both EGL
+and Vulkan (Android API 24+). However, it shouldn't be used for conformance
+submissions. It also isn't recommended for longer running tests since Android
+will terminate this process as soon as the `adb shell` session ends, which may
happen due to an unintentional device disconnection.
cmake <path to openglcts> -GNinja -DCMAKE_BUILD_TYPE=Debug \
diff --git a/framework/platform/android/tcuAndroidPlatform.cpp b/framework/platform/android/tcuAndroidPlatform.cpp
index af56dabb83..86984202fb 100644
--- a/framework/platform/android/tcuAndroidPlatform.cpp
+++ b/framework/platform/android/tcuAndroidPlatform.cpp
@@ -35,6 +35,12 @@
// Assume no call translation is needed
#include <android/native_window.h>
+#if DE_ANDROID_API >= 24
+#include <media/NdkImageReader.h>
+#endif
+#if DE_ANDROID_API >= 26
+#include <android/hardware_buffer.h>
+#endif
struct egl_native_pixmap_t;
DE_STATIC_ASSERT(sizeof(eglw::EGLNativeDisplayType) == sizeof(void *));
DE_STATIC_ASSERT(sizeof(eglw::EGLNativePixmapType) == sizeof(struct egl_native_pixmap_t *));
@@ -136,6 +142,81 @@ private:
WindowRegistry &m_windowRegistry;
};
+#if DE_ANDROID_API >= 24
+static ANativeWindow *acquireImageReaderWindow(int width, int height, int32_t format, AImageReader **outReader)
+{
+ AImageReader *reader = nullptr;
+#if DE_ANDROID_API >= 26
+ uint64_t usage = AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE | AHARDWAREBUFFER_USAGE_COMPOSER_OVERLAY;
+ media_status_t status = AImageReader_newWithUsage(width, height, format, usage, 2, &reader);
+#else
+ media_status_t status = AImageReader_new(width, height, format, 2, &reader);
+#endif
+ if (status != AMEDIA_OK || !reader)
+ throw ResourceError("Failed to create AImageReader", nullptr, __FILE__, __LINE__);
+
+ ANativeWindow *nativeWindow = nullptr;
+ status = AImageReader_getWindow(reader, &nativeWindow);
+ if (status != AMEDIA_OK || !nativeWindow)
+ {
+ AImageReader_delete(reader);
+ throw ResourceError("Failed to get window from AImageReader", nullptr, __FILE__, __LINE__);
+ }
+
+ *outReader = reader;
+ return nativeWindow;
+}
+
+class ImageReaderNativeWindow : public eglu::NativeWindow
+{
+public:
+ ImageReaderNativeWindow(AImageReader *reader, ANativeWindow *window, int width, int height)
+ : eglu::NativeWindow(WINDOW_CAPABILITIES)
+ , m_reader(reader)
+ , m_window(window)
+ , m_size(width, height)
+ {
+ }
+
+ virtual ~ImageReaderNativeWindow(void)
+ {
+ if (m_reader)
+ AImageReader_delete(m_reader);
+ }
+
+ virtual eglw::EGLNativeWindowType getLegacyNative(void)
+ {
+ return m_window;
+ }
+ virtual void *getPlatformExtension(void)
+ {
+ return m_window;
+ }
+ virtual void *getPlatformNative(void)
+ {
+ return m_window;
+ }
+ tcu::IVec2 getScreenSize(void) const
+ {
+ return m_size;
+ }
+ void setSurfaceSize(tcu::IVec2 size)
+ {
+ int32_t format = 0; // 0 means keep the existing format
+ ANativeWindow_setBuffersGeometry(m_window, size.x(), size.y(), format);
+ m_size = size;
+ }
+ virtual void processEvents(void)
+ {
+ }
+
+private:
+ AImageReader *m_reader;
+ ANativeWindow *m_window;
+ tcu::IVec2 m_size;
+};
+#endif
+
// NativeWindow
NativeWindow::NativeWindow(Window *window, int width, int height, int32_t format)
@@ -197,10 +278,29 @@ eglu::NativeWindow *NativeWindowFactory::createWindow(const eglu::WindowParams &
{
Window *window = m_windowRegistry.tryAcquireWindow();
- if (!window)
- throw NotSupportedError("Native window is not available", nullptr, __FILE__, __LINE__);
-
- return new NativeWindow(window, params.width, params.height, format);
+ if (window)
+ {
+ return new NativeWindow(window, params.width, params.height, format);
+ }
+ else
+ {
+#if DE_ANDROID_API >= 24
+ int width = params.width != eglu::WindowParams::SIZE_DONT_CARE ? params.width : 256;
+ int height = params.height != eglu::WindowParams::SIZE_DONT_CARE ? params.height : 256;
+ width = width > 0 ? width : 256;
+ height = height > 0 ? height : 256;
+
+ AImageReader *reader = nullptr;
+ // Always use AIMAGE_FORMAT_RGBA_8888: the AImageReader is only used as a
+ // surface handle provider, and AIMAGE_FORMAT_* constants are not
+ // interchangeable with ANativeWindow_LegacyFormat values.
+ ANativeWindow *nativeWindow = acquireImageReaderWindow(width, height, AIMAGE_FORMAT_RGBA_8888, &reader);
+
+ return new ImageReaderNativeWindow(reader, nativeWindow, width, height);
+#else
+ throw ResourceError("Native window is not available", nullptr, __FILE__, __LINE__);
+#endif
+ }
}
// NativeDisplayFactory
@@ -279,6 +379,43 @@ private:
tcu::Android::Window &m_window;
};
+#if DE_ANDROID_API >= 24
+class ImageReaderVulkanWindow : public vk::wsi::AndroidWindowInterface
+{
+public:
+ ImageReaderVulkanWindow(AImageReader *reader, ANativeWindow *window)
+ : vk::wsi::AndroidWindowInterface(vk::pt::AndroidNativeWindowPtr(window))
+ , m_reader(reader)
+ {
+ }
+
+ void setVisible(bool visible)
+ {
+ DE_UNREF(visible);
+ }
+
+ void resize(const UVec2 &newSize)
+ {
+ DE_UNREF(newSize);
+ }
+
+ void setMinimized(bool minimized)
+ {
+ DE_UNREF(minimized);
+ TCU_THROW(NotSupportedError, "Minimized on Android is not implemented");
+ }
+
+ ~ImageReaderVulkanWindow(void)
+ {
+ if (m_reader)
+ AImageReader_delete(m_reader);
+ }
+
+private:
+ AImageReader *m_reader;
+};
+#endif
+
class VulkanDisplay : public vk::wsi::Display
{
public:
@@ -306,7 +443,21 @@ public:
}
}
else
+ {
+#if DE_ANDROID_API >= 24
+ uint32_t width = initialSize ? initialSize->x() : 256;
+ uint32_t height = initialSize ? initialSize->y() : 256;
+ width = width > 0 ? width : 256;
+ height = height > 0 ? height : 256;
+
+ AImageReader *reader = nullptr;
+ ANativeWindow *nativeWindow = acquireImageReaderWindow(width, height, AIMAGE_FORMAT_RGBA_8888, &reader);
+
+ return new ImageReaderVulkanWindow(reader, nativeWindow);
+#else
TCU_THROW(ResourceError, "Native window is not available");
+#endif
+ }
}
private:
diff --git a/targets/android/android.cmake b/targets/android/android.cmake
index 33843fcc09..6da33d3c17 100644
--- a/targets/android/android.cmake
+++ b/targets/android/android.cmake
@@ -64,6 +64,11 @@ if (DE_ANDROID_API GREATER 8)
set(DEQP_PLATFORM_LIBRARIES ${DEQP_PLATFORM_LIBRARIES} ${ANDROID_LIBRARY})
endif ()
+if (DE_ANDROID_API GREATER 23)
+ find_library(MEDIANDK_LIBRARY NAMES mediandk PATHS /usr/lib)
+ set(DEQP_PLATFORM_LIBRARIES ${DEQP_PLATFORM_LIBRARIES} ${MEDIANDK_LIBRARY})
+endif ()
+
# Android uses customized execserver
include_directories(execserver)
set(DEQP_PLATFORM_LIBRARIES xscore ${DEQP_PLATFORM_LIBRARIES})
--
2.51.0

View file

@ -1,4 +1,4 @@
From ccdc2b9341c703507cba6017d2a494595335ffdc Mon Sep 17 00:00:00 2001
From 8a9b46a0fd740679ff576ae70e5ac4bae12ac8b0 Mon Sep 17 00:00:00 2001
From: Daniel Stone <daniels@collabora.com>
Date: Wed, 29 Jan 2025 12:50:33 +0000
Subject: [PATCH] Build: Don't build Vulkan utilities for GL builds
@ -6,15 +6,15 @@ Subject: [PATCH] Build: Don't build Vulkan utilities for GL builds
Change-Id: Ie412f914bb6264ffbd502deea57d80cc11a9948e
Signed-off-by: Daniel Stone <daniels@collabora.com>
---
framework/platform/CMakeLists.txt | 9 --
.../platform/android/tcuAndroidPlatform.cpp | 126 +-----------------
framework/platform/CMakeLists.txt | 9 -
.../platform/android/tcuAndroidPlatform.cpp | 177 +-----------------
.../platform/android/tcuAndroidPlatform.hpp | 14 +-
framework/platform/lnx/tcuLnxPlatform.cpp | 9 +-
.../surfaceless/tcuSurfacelessPlatform.cpp | 54 --------
5 files changed, 3 insertions(+), 209 deletions(-)
.../surfaceless/tcuSurfacelessPlatform.cpp | 54 ------
5 files changed, 3 insertions(+), 260 deletions(-)
diff --git a/framework/platform/CMakeLists.txt b/framework/platform/CMakeLists.txt
index ec1deb5c2..49fe412a7 100644
index 64d248b30f..833df5a08a 100644
--- a/framework/platform/CMakeLists.txt
+++ b/framework/platform/CMakeLists.txt
@@ -15,8 +15,6 @@ if (NOT DEFINED TCUTIL_PLATFORM_SRCS)
@ -35,7 +35,7 @@ index ec1deb5c2..49fe412a7 100644
)
include_directories(lnx)
@@ -164,8 +160,6 @@ if (NOT DEFINED TCUTIL_PLATFORM_SRCS)
@@ -168,8 +164,6 @@ if (NOT DEFINED TCUTIL_PLATFORM_SRCS)
set(TCUTIL_PLATFORM_SRCS
osx/tcuOSXPlatform.cpp
osx/tcuOSXPlatform.hpp
@ -44,7 +44,7 @@ index ec1deb5c2..49fe412a7 100644
osx/tcuOSXMetalView.mm
osx/tcuOSXMetalView.hpp
)
@@ -191,9 +185,6 @@ if (DEQP_USE_WAYLAND)
@@ -195,9 +189,6 @@ if (DEQP_USE_WAYLAND)
add_dependencies(tcutil-platform deqp-xdg-shell)
endif()
@ -55,7 +55,7 @@ index ec1deb5c2..49fe412a7 100644
# Always link to glutil as some platforms such as Win32 always support GL
diff --git a/framework/platform/android/tcuAndroidPlatform.cpp b/framework/platform/android/tcuAndroidPlatform.cpp
index af56dabb8..6b0de6dba 100644
index 9b117f50c6..b007c783cf 100644
--- a/framework/platform/android/tcuAndroidPlatform.cpp
+++ b/framework/platform/android/tcuAndroidPlatform.cpp
@@ -31,7 +31,6 @@
@ -66,7 +66,7 @@ index af56dabb8..6b0de6dba 100644
// Assume no call translation is needed
#include <android/native_window.h>
@@ -217,102 +216,6 @@ eglu::NativeDisplay *NativeDisplayFactory::createDisplay(const EGLAttrib *attrib
@@ -309,153 +308,6 @@ eglu::NativeDisplay *NativeDisplayFactory::createDisplay(const EGLAttrib *attrib
return new NativeDisplay();
}
@ -132,6 +132,43 @@ index af56dabb8..6b0de6dba 100644
- tcu::Android::Window &m_window;
-};
-
-#if DE_ANDROID_API >= 24
-class ImageReaderVulkanWindow : public vk::wsi::AndroidWindowInterface
-{
-public:
- ImageReaderVulkanWindow(AImageReader *reader, ANativeWindow *window)
- : vk::wsi::AndroidWindowInterface(vk::pt::AndroidNativeWindowPtr(window))
- , m_reader(reader)
- {
- }
-
- void setVisible(bool visible)
- {
- DE_UNREF(visible);
- }
-
- void resize(const UVec2 &newSize)
- {
- DE_UNREF(newSize);
- }
-
- void setMinimized(bool minimized)
- {
- DE_UNREF(minimized);
- TCU_THROW(NotSupportedError, "Minimized on Android is not implemented");
- }
-
- ~ImageReaderVulkanWindow(void)
- {
- if (m_reader)
- AImageReader_delete(m_reader);
- }
-
-private:
- AImageReader *m_reader;
-};
-#endif
-
-class VulkanDisplay : public vk::wsi::Display
-{
-public:
@ -159,7 +196,21 @@ index af56dabb8..6b0de6dba 100644
- }
- }
- else
- {
-#if DE_ANDROID_API >= 24
- uint32_t width = initialSize ? initialSize->x() : 256;
- uint32_t height = initialSize ? initialSize->y() : 256;
- width = width > 0 ? width : 256;
- height = height > 0 ? height : 256;
-
- AImageReader *reader = nullptr;
- ANativeWindow *nativeWindow = acquireImageReaderWindow(width, height, AIMAGE_FORMAT_RGBA_8888, &reader);
-
- return new ImageReaderVulkanWindow(reader, nativeWindow);
-#else
- TCU_THROW(ResourceError, "Native window is not available");
-#endif
- }
- }
-
-private:
@ -169,7 +220,7 @@ index af56dabb8..6b0de6dba 100644
static size_t getTotalSystemMemory(ANativeActivity *activity)
{
const size_t MiB = (size_t)(1 << 20);
@@ -341,8 +244,7 @@ static size_t getTotalSystemMemory(ANativeActivity *activity)
@@ -484,8 +336,7 @@ static size_t getTotalSystemMemory(ANativeActivity *activity)
// Platform
Platform::Platform(NativeActivity &activity)
@ -179,7 +230,7 @@ index af56dabb8..6b0de6dba 100644
{
m_nativeDisplayFactoryRegistry.registerFactory(new NativeDisplayFactory(m_windowRegistry));
m_contextFactoryRegistry.registerFactory(new eglu::GLContextFactory(m_nativeDisplayFactoryRegistry));
@@ -358,16 +260,6 @@ bool Platform::processEvents(void)
@@ -501,16 +352,6 @@ bool Platform::processEvents(void)
return true;
}
@ -196,7 +247,7 @@ index af56dabb8..6b0de6dba 100644
void Platform::getMemoryLimits(tcu::PlatformMemoryLimits &limits) const
{
// Worst-case estimates
@@ -401,22 +293,6 @@ void Platform::getMemoryLimits(tcu::PlatformMemoryLimits &limits) const
@@ -544,22 +385,6 @@ void Platform::getMemoryLimits(tcu::PlatformMemoryLimits &limits) const
limits.devicePageTableHierarchyLevels = 3;
}
@ -220,7 +271,7 @@ index af56dabb8..6b0de6dba 100644
} // namespace tcu
diff --git a/framework/platform/android/tcuAndroidPlatform.hpp b/framework/platform/android/tcuAndroidPlatform.hpp
index 32cc9068c..3c3f02d98 100644
index 32cc9068c4..3c3f02d98d 100644
--- a/framework/platform/android/tcuAndroidPlatform.hpp
+++ b/framework/platform/android/tcuAndroidPlatform.hpp
@@ -27,7 +27,6 @@
@ -267,7 +318,7 @@ index 32cc9068c..3c3f02d98 100644
const size_t m_totalSystemMemory;
};
diff --git a/framework/platform/lnx/tcuLnxPlatform.cpp b/framework/platform/lnx/tcuLnxPlatform.cpp
index 8c0a3ef06..6b1a4985a 100644
index 8c0a3ef06e..6b1a4985a1 100644
--- a/framework/platform/lnx/tcuLnxPlatform.cpp
+++ b/framework/platform/lnx/tcuLnxPlatform.cpp
@@ -23,12 +23,10 @@
@ -309,7 +360,7 @@ index 8c0a3ef06..6b1a4985a 100644
#if defined(DEQP_SUPPORT_GLX)
m_glPlatform.registerFactory(x11::glx::createContextFactory(m_eventState));
diff --git a/framework/platform/surfaceless/tcuSurfacelessPlatform.cpp b/framework/platform/surfaceless/tcuSurfacelessPlatform.cpp
index 585bfbbae..9f386b3ca 100644
index 2224f4164e..c9f044b130 100644
--- a/framework/platform/surfaceless/tcuSurfacelessPlatform.cpp
+++ b/framework/platform/surfaceless/tcuSurfacelessPlatform.cpp
@@ -43,7 +43,6 @@
@ -388,5 +439,5 @@ index 585bfbbae..9f386b3ca 100644
class ContextFactory : public glu::ContextFactory
--
2.45.2
2.51.0

View file

@ -23,7 +23,7 @@ variables:
DEBIAN_BUILD_TAG: "20260430-imgui.2"
DEBIAN_TEST_BASE_TAG: "20260502-virgl"
DEBIAN_TEST_ANDROID_TAG: "20260430-imgui.2"
DEBIAN_TEST_ANDROID_TAG: "20260505-wsi"
DEBIAN_TEST_GL_TAG: "20260506-vvl-84"
DEBIAN_TEST_VIDEO_TAG: "20260430-imgui.2"
DEBIAN_TEST_VK_TAG: "20260430-vkd3d-2"