mirror of
https://gitlab.freedesktop.org/mesa/mesa.git
synced 2026-05-27 03:38:12 +02:00
Update the headless Android WSI patch to fix intermittent timeout issues. It now uses an ImageReader listener to actively drain and instantly release frames from the buffer queue. This acts as a "null compositor" that prevents buffer starvation while maintaining stable GPU backpressure. This fixes dEQP-VK.wsi.android.maintenance1.* in newer VKCTS versions and resolves the race conditions that caused occasional teardown crashes. Also rebase build-deqp-gl_Build-Don-t-build-Vulkan-utilities-for-GL-builds.patch on top of the updated WSI patch. Signed-off-by: Valentine Burley <valentine.burley@collabora.com> Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/41541>
367 lines
12 KiB
Diff
367 lines
12 KiB
Diff
From a801d75fcb16db89d2af67dd40040bc7c5f59df0 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.
|
|
- Implements an active "null compositor" via an AImageReader listener
|
|
that immediately drains and releases acquired images. This ensures the
|
|
producer never runs out of buffers, fixing intermittent timeouts in
|
|
tests that require active consumption (e.g. maintenance1 tests).
|
|
- Uses AImageReader_acquireNextImageAsync and AImage_deleteAsync (API 26+)
|
|
to handle GPU sync fences.
|
|
- Adds thread-safe teardown logic using an atomic flag and a non-blocking
|
|
mutex lock (std::try_to_lock) to prevent race conditions between the
|
|
main thread and the background listener callback.
|
|
- Adds ImageReaderNativeWindow (EGL) and ImageReaderVulkanWindow (Vulkan)
|
|
to manage the AImageReader and ImageQueue lifecycle.
|
|
- 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.
|
|
- Uses AImageReader_newWithUsage (API 26+) with explicit hardware buffer flags
|
|
(SAMPLED_IMAGE | COMPOSER_OVERLAY) to mimic SurfaceFlinger allocation
|
|
constraints.
|
|
- 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 | 237 +++++++++++++++++-
|
|
targets/android/android.cmake | 5 +
|
|
3 files changed, 242 insertions(+), 7 deletions(-)
|
|
|
|
diff --git a/external/openglcts/README.md b/external/openglcts/README.md
|
|
index 2eae316f58..24b270aee5 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..45861c8aee 100644
|
|
--- a/framework/platform/android/tcuAndroidPlatform.cpp
|
|
+++ b/framework/platform/android/tcuAndroidPlatform.cpp
|
|
@@ -35,6 +35,15 @@
|
|
|
|
// Assume no call translation is needed
|
|
#include <android/native_window.h>
|
|
+#if DE_ANDROID_API >= 24
|
|
+#include <media/NdkImageReader.h>
|
|
+#include <media/NdkImage.h>
|
|
+#include <mutex>
|
|
+#include <atomic>
|
|
+#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 +145,141 @@ private:
|
|
WindowRegistry &m_windowRegistry;
|
|
};
|
|
|
|
+#if DE_ANDROID_API >= 24
|
|
+struct ImageQueue
|
|
+{
|
|
+ std::mutex lock;
|
|
+ std::atomic<bool> closing{false};
|
|
+ AImageReader_ImageListener listener;
|
|
+
|
|
+ ImageQueue()
|
|
+ {
|
|
+ listener.context = this;
|
|
+ listener.onImageAvailable = onImageAvailable;
|
|
+ }
|
|
+
|
|
+ ~ImageQueue()
|
|
+ {
|
|
+ }
|
|
+
|
|
+ static void onImageAvailable(void *context, AImageReader *reader)
|
|
+ {
|
|
+ ImageQueue *queue = reinterpret_cast<ImageQueue *>(context);
|
|
+
|
|
+ std::unique_lock<std::mutex> guard(queue->lock, std::try_to_lock);
|
|
+ if (!guard.owns_lock() || queue->closing.load(std::memory_order_acquire))
|
|
+ return;
|
|
+
|
|
+ AImage *image = nullptr;
|
|
+
|
|
+#if DE_ANDROID_API >= 26
|
|
+ int fenceFd = -1;
|
|
+ while (AImageReader_acquireNextImageAsync(reader, &image, &fenceFd) == AMEDIA_OK && image != nullptr)
|
|
+ {
|
|
+ AImage_deleteAsync(image, fenceFd);
|
|
+ }
|
|
+#else
|
|
+ while (AImageReader_acquireNextImage(reader, &image) == AMEDIA_OK && image != nullptr)
|
|
+ {
|
|
+ AImage_delete(image);
|
|
+ }
|
|
+#endif
|
|
+ }
|
|
+};
|
|
+
|
|
+static ANativeWindow *acquireImageReaderWindow(int width, int height, int32_t format, AImageReader **outReader,
|
|
+ ImageQueue **outQueue)
|
|
+{
|
|
+ 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, 4, &reader);
|
|
+#else
|
|
+ media_status_t status = AImageReader_new(width, height, format, 4, &reader);
|
|
+#endif
|
|
+ if (status != AMEDIA_OK || !reader)
|
|
+ throw ResourceError("Failed to create AImageReader", nullptr, __FILE__, __LINE__);
|
|
+
|
|
+ *outQueue = new ImageQueue();
|
|
+ AImageReader_setImageListener(reader, &(*outQueue)->listener);
|
|
+
|
|
+ ANativeWindow *nativeWindow = nullptr;
|
|
+ status = AImageReader_getWindow(reader, &nativeWindow);
|
|
+ if (status != AMEDIA_OK || !nativeWindow)
|
|
+ {
|
|
+ AImageReader_setImageListener(reader, nullptr);
|
|
+ delete *outQueue;
|
|
+ *outQueue = nullptr;
|
|
+ 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, ImageQueue *queue, ANativeWindow *window, int width, int height)
|
|
+ : eglu::NativeWindow(WINDOW_CAPABILITIES)
|
|
+ , m_reader(reader)
|
|
+ , m_queue(queue)
|
|
+ , m_window(window)
|
|
+ , m_size(width, height)
|
|
+ {
|
|
+ }
|
|
+
|
|
+ virtual ~ImageReaderNativeWindow(void)
|
|
+ {
|
|
+ if (m_reader)
|
|
+ {
|
|
+ if (m_queue)
|
|
+ {
|
|
+ m_queue->closing.store(true, std::memory_order_release);
|
|
+ std::lock_guard<std::mutex> guard(m_queue->lock);
|
|
+ AImageReader_setImageListener(m_reader, nullptr);
|
|
+ }
|
|
+ AImageReader_delete(m_reader);
|
|
+ }
|
|
+ if (m_queue)
|
|
+ delete m_queue;
|
|
+ }
|
|
+
|
|
+ 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;
|
|
+ ImageQueue *m_queue;
|
|
+ ANativeWindow *m_window;
|
|
+ tcu::IVec2 m_size;
|
|
+};
|
|
+#endif
|
|
+
|
|
// NativeWindow
|
|
|
|
NativeWindow::NativeWindow(Window *window, int width, int height, int32_t format)
|
|
@@ -197,10 +341,30 @@ 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;
|
|
+ ImageQueue *queue = 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, &queue);
|
|
+
|
|
+ return new ImageReaderNativeWindow(reader, queue, nativeWindow, width, height);
|
|
+#else
|
|
+ throw ResourceError("Native window is not available", nullptr, __FILE__, __LINE__);
|
|
+#endif
|
|
+ }
|
|
}
|
|
|
|
// NativeDisplayFactory
|
|
@@ -279,6 +443,55 @@ private:
|
|
tcu::Android::Window &m_window;
|
|
};
|
|
|
|
+#if DE_ANDROID_API >= 24
|
|
+class ImageReaderVulkanWindow : public vk::wsi::AndroidWindowInterface
|
|
+{
|
|
+public:
|
|
+ ImageReaderVulkanWindow(AImageReader *reader, ImageQueue *queue, ANativeWindow *window)
|
|
+ : vk::wsi::AndroidWindowInterface(vk::pt::AndroidNativeWindowPtr(window))
|
|
+ , m_reader(reader)
|
|
+ , m_queue(queue)
|
|
+ {
|
|
+ }
|
|
+
|
|
+ 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)
|
|
+ {
|
|
+ if (m_queue)
|
|
+ {
|
|
+ m_queue->closing.store(true, std::memory_order_release);
|
|
+ std::lock_guard<std::mutex> guard(m_queue->lock);
|
|
+ AImageReader_setImageListener(m_reader, nullptr);
|
|
+ }
|
|
+ AImageReader_delete(m_reader);
|
|
+ }
|
|
+ if (m_queue)
|
|
+ delete m_queue;
|
|
+ }
|
|
+
|
|
+private:
|
|
+ AImageReader *m_reader;
|
|
+ ImageQueue *m_queue;
|
|
+};
|
|
+#endif
|
|
+
|
|
class VulkanDisplay : public vk::wsi::Display
|
|
{
|
|
public:
|
|
@@ -306,7 +519,23 @@ 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;
|
|
+ ImageQueue *queue = nullptr;
|
|
+ ANativeWindow *nativeWindow =
|
|
+ acquireImageReaderWindow(width, height, AIMAGE_FORMAT_RGBA_8888, &reader, &queue);
|
|
+
|
|
+ return new ImageReaderVulkanWindow(reader, queue, 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
|
|
|