diff --git a/app/src/main/cpp/waylandcomp/CMakeLists.txt b/app/src/main/cpp/waylandcomp/CMakeLists.txt index c2f9cb654..5f62ee851 100644 --- a/app/src/main/cpp/waylandcomp/CMakeLists.txt +++ b/app/src/main/cpp/waylandcomp/CMakeLists.txt @@ -14,6 +14,7 @@ add_library(droiddeckwayland SHARED src/waylandcomp_jni.c src/droiddeck_ext.c src/wl_dmabuf.c + src/wl_android_buffer.c src/wl_clipboard.c src/wl_text_input.c src/wl_toplevel_icon.c @@ -24,6 +25,7 @@ add_library(droiddeckwayland SHARED generated/color-management-v1-protocol.c generated/xdg-shell-protocol.c generated/linux-dmabuf-v1-protocol.c + generated/wayland-android-protocol.c generated/viewporter-protocol.c generated/banner-desktop-v1-protocol.c generated/presentation-time-protocol.c diff --git a/app/src/main/cpp/waylandcomp/generated/wayland-android-protocol.c b/app/src/main/cpp/waylandcomp/generated/wayland-android-protocol.c new file mode 100644 index 000000000..79e171faa --- /dev/null +++ b/app/src/main/cpp/waylandcomp/generated/wayland-android-protocol.c @@ -0,0 +1,104 @@ +/* Generated by wayland-scanner 1.26.0 */ + +/* + * Copyright © 2012 Collabora, Ltd. + * + * Permission to use, copy, modify, distribute, and sell this + * software and its documentation for any purpose is hereby granted + * without fee, provided that the above copyright notice appear in + * all copies and that both that copyright notice and this permission + * notice appear in supporting documentation, and that the name of + * the copyright holders not be used in advertising or publicity + * pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no + * representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied + * warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + * THIS SOFTWARE. + */ + +#include +#include +#include +#include "wayland-util.h" + +#ifndef __has_attribute +# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ +#endif + +#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) +#define WL_PRIVATE __attribute__ ((visibility("hidden"))) +#else +#define WL_PRIVATE +#endif + +extern const struct wl_interface android_wlegl_handle_interface; +extern const struct wl_interface android_wlegl_server_buffer_handle_interface; +extern const struct wl_interface wl_buffer_interface; + +static const struct wl_interface *android_types[] = { + NULL, + NULL, + &android_wlegl_handle_interface, + NULL, + NULL, + &wl_buffer_interface, + NULL, + NULL, + NULL, + NULL, + NULL, + &android_wlegl_handle_interface, + &android_wlegl_server_buffer_handle_interface, + NULL, + NULL, + NULL, + NULL, + &wl_buffer_interface, + NULL, + NULL, +}; + +static const struct wl_message android_wlegl_requests[] = { + { "create_handle", "nia", android_types + 2 }, + { "create_buffer", "niiiiio", android_types + 5 }, + { "get_server_buffer_handle", "2niiii", android_types + 12 }, +}; + +WL_PRIVATE const struct wl_interface android_wlegl_interface = { + "android_wlegl", 3, + 3, android_wlegl_requests, + 0, NULL, +}; + +static const struct wl_message android_wlegl_handle_requests[] = { + { "add_fd", "h", android_types + 0 }, + { "destroy", "", android_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface android_wlegl_handle_interface = { + "android_wlegl_handle", 1, + 2, android_wlegl_handle_requests, + 0, NULL, +}; + +static const struct wl_message android_wlegl_server_buffer_handle_events[] = { + { "buffer_fd", "h", android_types + 0 }, + { "buffer_ints", "a", android_types + 0 }, + { "buffer", "nii", android_types + 17 }, + { "linear_layout", "3uu", android_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface android_wlegl_server_buffer_handle_interface = { + "android_wlegl_server_buffer_handle", 3, + 0, NULL, + 4, android_wlegl_server_buffer_handle_events, +}; diff --git a/app/src/main/cpp/waylandcomp/generated/wayland-android-server-protocol.h b/app/src/main/cpp/waylandcomp/generated/wayland-android-server-protocol.h new file mode 100644 index 000000000..0fd3b6d68 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/generated/wayland-android-server-protocol.h @@ -0,0 +1,341 @@ +/* Generated by wayland-scanner 1.26.0 */ + +#ifndef ANDROID_SERVER_PROTOCOL_H +#define ANDROID_SERVER_PROTOCOL_H + +#include +#include +#include "wayland-server.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct wl_client; +struct wl_resource; + +/** + * @page page_android The android protocol + * @section page_ifaces_android Interfaces + * - @subpage page_iface_android_wlegl - Android EGL graphics buffer support + * - @subpage page_iface_android_wlegl_handle - An Android native_handle_t object + * - @subpage page_iface_android_wlegl_server_buffer_handle - a server allocated buffer + * @section page_copyright_android Copyright + *
+ *
+ * Copyright © 2012 Collabora, Ltd.
+ *
+ * Permission to use, copy, modify, distribute, and sell this
+ * software and its documentation for any purpose is hereby granted
+ * without fee, provided that the above copyright notice appear in
+ * all copies and that both that copyright notice and this permission
+ * notice appear in supporting documentation, and that the name of
+ * the copyright holders not be used in advertising or publicity
+ * pertaining to distribution of the software without specific,
+ * written prior permission.  The copyright holders make no
+ * representations about the suitability of this software for any
+ * purpose.  It is provided "as is" without express or implied
+ * warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+ * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+ * THIS SOFTWARE.
+ * 
+ */ +struct android_wlegl; +struct android_wlegl_handle; +struct android_wlegl_server_buffer_handle; +struct wl_buffer; + +#ifndef ANDROID_WLEGL_INTERFACE +#define ANDROID_WLEGL_INTERFACE +/** + * @page page_iface_android_wlegl android_wlegl + * @section page_iface_android_wlegl_desc Description + * + * Interface used in the Android wrapper libEGL to share + * graphics buffers between the server and the client. + * @section page_iface_android_wlegl_api API + * See @ref iface_android_wlegl. + */ +/** + * @defgroup iface_android_wlegl The android_wlegl interface + * + * Interface used in the Android wrapper libEGL to share + * graphics buffers between the server and the client. + */ +extern const struct wl_interface android_wlegl_interface; +#endif +#ifndef ANDROID_WLEGL_HANDLE_INTERFACE +#define ANDROID_WLEGL_HANDLE_INTERFACE +/** + * @page page_iface_android_wlegl_handle android_wlegl_handle + * @section page_iface_android_wlegl_handle_desc Description + * + * The Android native_handle_t is a semi-opaque object, that + * contains an EGL implementation specific number of int32 + * values and file descriptors. + * + * We cannot send a variable size array of file descriptors + * over the Wayland protocol, so we send them one by one. + * @section page_iface_android_wlegl_handle_api API + * See @ref iface_android_wlegl_handle. + */ +/** + * @defgroup iface_android_wlegl_handle The android_wlegl_handle interface + * + * The Android native_handle_t is a semi-opaque object, that + * contains an EGL implementation specific number of int32 + * values and file descriptors. + * + * We cannot send a variable size array of file descriptors + * over the Wayland protocol, so we send them one by one. + */ +extern const struct wl_interface android_wlegl_handle_interface; +#endif +#ifndef ANDROID_WLEGL_SERVER_BUFFER_HANDLE_INTERFACE +#define ANDROID_WLEGL_SERVER_BUFFER_HANDLE_INTERFACE +/** + * @page page_iface_android_wlegl_server_buffer_handle android_wlegl_server_buffer_handle + * @section page_iface_android_wlegl_server_buffer_handle_desc Description + * + * On creation a server_side_buffer object will immediately send + * the "buffer_fd" and "buffer_ints" events needed by the client to + * reference the gralloc buffer, followed by the "buffer" event carrying + * the wl_buffer object. + * @section page_iface_android_wlegl_server_buffer_handle_api API + * See @ref iface_android_wlegl_server_buffer_handle. + */ +/** + * @defgroup iface_android_wlegl_server_buffer_handle The android_wlegl_server_buffer_handle interface + * + * On creation a server_side_buffer object will immediately send + * the "buffer_fd" and "buffer_ints" events needed by the client to + * reference the gralloc buffer, followed by the "buffer" event carrying + * the wl_buffer object. + */ +extern const struct wl_interface android_wlegl_server_buffer_handle_interface; +#endif + +#ifndef ANDROID_WLEGL_ERROR_ENUM +#define ANDROID_WLEGL_ERROR_ENUM +enum android_wlegl_error { + ANDROID_WLEGL_ERROR_BAD_HANDLE = 0, + ANDROID_WLEGL_ERROR_BAD_VALUE = 1, +}; +#endif /* ANDROID_WLEGL_ERROR_ENUM */ + +#ifndef ANDROID_WLEGL_ERROR_ENUM_IS_VALID +#define ANDROID_WLEGL_ERROR_ENUM_IS_VALID +/** + * @ingroup iface_android_wlegl + * Validate a android_wlegl error value. + * + * @return true on success, false on error. + * @ref android_wlegl_error + */ +static inline bool +android_wlegl_error_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case ANDROID_WLEGL_ERROR_BAD_HANDLE: + return version >= 1; + case ANDROID_WLEGL_ERROR_BAD_VALUE: + return version >= 1; + default: + return false; + } +} +#endif /* ANDROID_WLEGL_ERROR_ENUM_IS_VALID */ + +/** + * @ingroup iface_android_wlegl + * @struct android_wlegl_interface + */ +struct android_wlegl_interface { + /** + * Create an Android native_handle_t object + * + * This creator method initialises the native_handle_t object + * with everything except the file descriptors, which have to be + * submitted separately. + * @param ints an array of int32_t + */ + void (*create_handle)(struct wl_client *client, + struct wl_resource *resource, + uint32_t id, + int32_t num_fds, + struct wl_array *ints); + /** + * Create a wl_buffer from the native handle + * + * Pass the Android native_handle_t to the server and attach it + * to the new wl_buffer object. + * + * The android_wlegl_handle object must be destroyed immediately + * after this request. + */ + void (*create_buffer)(struct wl_client *client, + struct wl_resource *resource, + uint32_t id, + int32_t width, + int32_t height, + int32_t stride, + int32_t format, + int32_t usage, + struct wl_resource *native_handle); + /** + * @since 2 + */ + void (*get_server_buffer_handle)(struct wl_client *client, + struct wl_resource *resource, + uint32_t id, + int32_t width, + int32_t height, + int32_t format, + int32_t usage); +}; + + +/** + * @ingroup iface_android_wlegl + */ +#define ANDROID_WLEGL_CREATE_HANDLE_SINCE_VERSION 1 +/** + * @ingroup iface_android_wlegl + */ +#define ANDROID_WLEGL_CREATE_BUFFER_SINCE_VERSION 1 +/** + * @ingroup iface_android_wlegl + */ +#define ANDROID_WLEGL_GET_SERVER_BUFFER_HANDLE_SINCE_VERSION 2 + +#ifndef ANDROID_WLEGL_HANDLE_ERROR_ENUM +#define ANDROID_WLEGL_HANDLE_ERROR_ENUM +enum android_wlegl_handle_error { + ANDROID_WLEGL_HANDLE_ERROR_TOO_MANY_FDS = 0, +}; +#endif /* ANDROID_WLEGL_HANDLE_ERROR_ENUM */ + +#ifndef ANDROID_WLEGL_HANDLE_ERROR_ENUM_IS_VALID +#define ANDROID_WLEGL_HANDLE_ERROR_ENUM_IS_VALID +/** + * @ingroup iface_android_wlegl_handle + * Validate a android_wlegl_handle error value. + * + * @return true on success, false on error. + * @ref android_wlegl_handle_error + */ +static inline bool +android_wlegl_handle_error_is_valid(uint32_t value, uint32_t version) { + switch (value) { + case ANDROID_WLEGL_HANDLE_ERROR_TOO_MANY_FDS: + return version >= 1; + default: + return false; + } +} +#endif /* ANDROID_WLEGL_HANDLE_ERROR_ENUM_IS_VALID */ + +/** + * @ingroup iface_android_wlegl_handle + * @struct android_wlegl_handle_interface + */ +struct android_wlegl_handle_interface { + /** + */ + void (*add_fd)(struct wl_client *client, + struct wl_resource *resource, + int32_t fd); + /** + */ + void (*destroy)(struct wl_client *client, + struct wl_resource *resource); +}; + + +/** + * @ingroup iface_android_wlegl_handle + */ +#define ANDROID_WLEGL_HANDLE_ADD_FD_SINCE_VERSION 1 +/** + * @ingroup iface_android_wlegl_handle + */ +#define ANDROID_WLEGL_HANDLE_DESTROY_SINCE_VERSION 1 + +#define ANDROID_WLEGL_SERVER_BUFFER_HANDLE_BUFFER_FD 0 +#define ANDROID_WLEGL_SERVER_BUFFER_HANDLE_BUFFER_INTS 1 +#define ANDROID_WLEGL_SERVER_BUFFER_HANDLE_BUFFER 2 +#define ANDROID_WLEGL_SERVER_BUFFER_HANDLE_LINEAR_LAYOUT 3 + +/** + * @ingroup iface_android_wlegl_server_buffer_handle + */ +#define ANDROID_WLEGL_SERVER_BUFFER_HANDLE_BUFFER_FD_SINCE_VERSION 1 +/** + * @ingroup iface_android_wlegl_server_buffer_handle + */ +#define ANDROID_WLEGL_SERVER_BUFFER_HANDLE_BUFFER_INTS_SINCE_VERSION 1 +/** + * @ingroup iface_android_wlegl_server_buffer_handle + */ +#define ANDROID_WLEGL_SERVER_BUFFER_HANDLE_BUFFER_SINCE_VERSION 1 +/** + * @ingroup iface_android_wlegl_server_buffer_handle + */ +#define ANDROID_WLEGL_SERVER_BUFFER_HANDLE_LINEAR_LAYOUT_SINCE_VERSION 3 + + +/** + * @ingroup iface_android_wlegl_server_buffer_handle + * Sends an buffer_fd event to the client owning the resource. + * @param resource_ The client's resource + */ +static inline void +android_wlegl_server_buffer_handle_send_buffer_fd(struct wl_resource *resource_, int32_t fd) +{ + wl_resource_post_event(resource_, ANDROID_WLEGL_SERVER_BUFFER_HANDLE_BUFFER_FD, fd); +} + +/** + * @ingroup iface_android_wlegl_server_buffer_handle + * Sends an buffer_ints event to the client owning the resource. + * @param resource_ The client's resource + */ +static inline void +android_wlegl_server_buffer_handle_send_buffer_ints(struct wl_resource *resource_, struct wl_array *ints) +{ + wl_resource_post_event(resource_, ANDROID_WLEGL_SERVER_BUFFER_HANDLE_BUFFER_INTS, ints); +} + +/** + * @ingroup iface_android_wlegl_server_buffer_handle + * Sends an buffer event to the client owning the resource. + * @param resource_ The client's resource + */ +static inline void +android_wlegl_server_buffer_handle_send_buffer(struct wl_resource *resource_, struct wl_resource *buffer, int32_t format, int32_t stride) +{ + wl_resource_post_event(resource_, ANDROID_WLEGL_SERVER_BUFFER_HANDLE_BUFFER, buffer, format, stride); +} + +/** + * @ingroup iface_android_wlegl_server_buffer_handle + * Sends an linear_layout event to the client owning the resource. + * @param resource_ The client's resource + */ +static inline void +android_wlegl_server_buffer_handle_send_linear_layout(struct wl_resource *resource_, uint32_t drm_format, uint32_t stride) +{ + wl_resource_post_event(resource_, ANDROID_WLEGL_SERVER_BUFFER_HANDLE_LINEAR_LAYOUT, drm_format, stride); +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/app/src/main/cpp/waylandcomp/protocols/wayland-android.xml b/app/src/main/cpp/waylandcomp/protocols/wayland-android.xml new file mode 100644 index 000000000..de8a78b66 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/protocols/wayland-android.xml @@ -0,0 +1,139 @@ + + + + + Copyright © 2012 Collabora, Ltd. + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + + Interface used in the Android wrapper libEGL to share + graphics buffers between the server and the client. + + + + + + + + + + This creator method initialises the native_handle_t object + with everything except the file descriptors, which have to be + submitted separately. + + + + + + + + + + Pass the Android native_handle_t to the server and attach it + to the new wl_buffer object. + + The android_wlegl_handle object must be destroyed immediately + after this request. + + + + + + + + + + + + + + + + + + + + + + + + + The Android native_handle_t is a semi-opaque object, that + contains an EGL implementation specific number of int32 + values and file descriptors. + + We cannot send a variable size array of file descriptors + over the Wayland protocol, so we send them one by one. + + + + + + + + + + + + + + + + + + On creation a server_side_buffer object will immediately send + the "buffer_fd" and "buffer_ints" events needed by the client to + reference the gralloc buffer, followed by the "buffer" event carrying + the wl_buffer object. + + + + + + + + + + This event will be sent after the ints and all the fds have been sent + + + + + + + + Optional metadata sent before the buffer event. The first buffer_fd + contains one uncompressed 32-bit RGB plane at byte offset zero. + drm_format is its DRM FourCC and stride is its row pitch in bytes. + The server has verified the layout and allocation bounds using + Android mapper metadata, not inferred them from opaque handle ints. + Absence of this event means the layout is unspecified. + + + + + + + diff --git a/app/src/main/cpp/waylandcomp/src/compositor.c b/app/src/main/cpp/waylandcomp/src/compositor.c index 805eff9fa..905c11689 100644 --- a/app/src/main/cpp/waylandcomp/src/compositor.c +++ b/app/src/main/cpp/waylandcomp/src/compositor.c @@ -3534,6 +3534,7 @@ int droiddeck_wayland_run(void) { wl_global_create(display, &wl_output_interface, 4, NULL, bind_output); wl_global_create(display, &xdg_wm_base_interface, 1, NULL, bind_xdg_wm_base); wl_global_create(display, &zwp_linux_dmabuf_v1_interface, 4, NULL, bind_dmabuf); + droiddeck_android_wlegl_init(display); /* gamescope's Wayland backend refuses a seat older than 8. */ wl_global_create(display, &wl_seat_interface, 9, NULL, bind_seat); wl_global_create(display, &banner_desktop_v1_interface, 1, NULL, bind_desktop); diff --git a/app/src/main/cpp/waylandcomp/src/compositor_internal.h b/app/src/main/cpp/waylandcomp/src/compositor_internal.h index 0468f007f..08b726092 100644 --- a/app/src/main/cpp/waylandcomp/src/compositor_internal.h +++ b/app/src/main/cpp/waylandcomp/src/compositor_internal.h @@ -77,3 +77,6 @@ extern volatile int g_no_render_node; /* DROIDDECK_WAYLAND_NO_RENDER_NODE */ /* ---- wl_dmabuf.c */ COMPOSITOR_INTERNAL extern dev_t g_main_device; /* the render node clients are told to allocate on */ COMPOSITOR_INTERNAL void bind_dmabuf(struct wl_client *c, void *data, uint32_t ver, uint32_t id); + +/* ---- wl_android_buffer.c */ +COMPOSITOR_INTERNAL void droiddeck_android_wlegl_init(struct wl_display *display); diff --git a/app/src/main/cpp/waylandcomp/src/vk_external_image_sync.h b/app/src/main/cpp/waylandcomp/src/vk_external_image_sync.h new file mode 100644 index 000000000..676c36a14 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/src/vk_external_image_sync.h @@ -0,0 +1,47 @@ +#ifndef VK_EXTERNAL_IMAGE_SYNC_H +#define VK_EXTERNAL_IMAGE_SYNC_H + +#include + +struct vkp_external_client_barrier_pair { + VkImageMemoryBarrier acquire; + VkImageMemoryBarrier release; +}; + +static inline int vkp_is_external_client_image(int dmabuf, int blit_dst) { + return dmabuf && !blit_dst; +} + +/* Client-owned dma-bufs, including AHardwareBuffers, live in GENERAL while + * FOREIGN owns them. Layer pool dma-bufs are destinations with a separate + * acquire/release lifecycle and must never take this source-image path. */ +static inline int vkp_external_client_barrier_pair( + VkImage image, uint32_t queue_family, int dmabuf, int blit_dst, + struct vkp_external_client_barrier_pair *pair) { + if (!vkp_is_external_client_image(dmabuf, blit_dst) || !pair) return 0; + + const VkImageSubresourceRange range = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; + pair->acquire = (VkImageMemoryBarrier){ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .oldLayout = VK_IMAGE_LAYOUT_GENERAL, + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_FOREIGN_EXT, + .dstQueueFamilyIndex = queue_family, + .image = image, + .subresourceRange = range, + .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT, + }; + pair->release = (VkImageMemoryBarrier){ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = queue_family, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_FOREIGN_EXT, + .image = image, + .subresourceRange = range, + .srcAccessMask = VK_ACCESS_MEMORY_READ_BIT, + }; + return 1; +} + +#endif diff --git a/app/src/main/cpp/waylandcomp/src/vk_present.c b/app/src/main/cpp/waylandcomp/src/vk_present.c index 4551c4806..8d18748da 100644 --- a/app/src/main/cpp/waylandcomp/src/vk_present.c +++ b/app/src/main/cpp/waylandcomp/src/vk_present.c @@ -3,6 +3,7 @@ #define _POSIX_C_SOURCE 200809L #include "vk_present.h" #include "vk_loader.h" +#include "vk_external_image_sync.h" #include "sc_layer.h" #include "effects_chain.h" #include "framegen_bridge.h" @@ -35,6 +36,7 @@ struct vkp_image { int in_general; /* shm images: moved from PREINITIALIZED to GENERAL */ VkFormat fmt; int sampled; /* created with SAMPLED usage: the alpha pass can read it */ + AHardwareBuffer *android_buffer; }; static ANativeWindow *g_window; /* the window frames go to; compositor thread only */ @@ -44,6 +46,7 @@ static VkInstance g_inst; static VkPhysicalDevice g_pd; static VkDevice g_dev; static VkQueue g_queue; +static PFN_vkGetAndroidHardwareBufferPropertiesANDROID g_ahb_properties; static uint32_t g_qfam; static VkCommandPool g_pool; /* One command buffer + acquire/render-done semaphore pair per PRESENT. A scene frame is one @@ -464,7 +467,7 @@ static int dev_init(void) { g_vk.GetPhysicalDeviceMemoryProperties(g_pd, &g_memprops); /* Verify the dmabuf-import extensions are present, and log any that are missing. */ - const char *dev_exts[7] = {VK_KHR_SWAPCHAIN_EXTENSION_NAME, "VK_KHR_external_memory_fd", + const char *dev_exts[9] = {VK_KHR_SWAPCHAIN_EXTENSION_NAME, "VK_KHR_external_memory_fd", "VK_EXT_external_memory_dma_buf", "VK_EXT_image_drm_format_modifier", "VK_KHR_image_format_list", NULL, NULL}; uint32_t n_dev_exts = 5; @@ -485,6 +488,13 @@ static int dev_init(void) { * back to waiting for them on the CPU without it. */ const int want_sem_fd = has_ext(exts, ne, VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME); if (want_sem_fd) dev_exts[n_dev_exts++] = VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME; + const int want_ahb = !g_library_name && + has_ext(exts, ne, VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME) && + has_ext(exts, ne, VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME); + if (want_ahb) { + dev_exts[n_dev_exts++] = VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME; + dev_exts[n_dev_exts++] = VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME; + } free(exts); float prio = 1.0f; @@ -511,6 +521,9 @@ static int dev_init(void) { } vk_loader_load_device(g_dev); g_vk.GetDeviceQueue(g_dev, g_qfam, 0, &g_queue); + if (want_ahb) + g_ahb_properties = (PFN_vkGetAndroidHardwareBufferPropertiesANDROID) + g_vk.GetDeviceProcAddr(g_dev, "vkGetAndroidHardwareBufferPropertiesANDROID"); if (want_sem_fd) { g_import_sem_fd = (PFN_vkImportSemaphoreFdKHR)g_vk.GetDeviceProcAddr(g_dev, "vkImportSemaphoreFdKHR"); VkSemaphoreCreateInfo wsci = {.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO}; @@ -949,7 +962,98 @@ struct vkp_image *vkp_image_from_dmabuf(int fd, uint32_t drm_format, uint64_t mo return vkp_image_import_dmabuf(fd, drm_format, modifier, w, h, stride, offset, 0); } -int vkp_image_is_dmabuf(const struct vkp_image *img) { return img && img->dmabuf && !img->blit_dst; } +/* Return client sources to FOREIGN after their final read. The consuming paths + * wait for our fence before Wayland can release an ordinary dma-buf or an + * android_wlegl AHardwareBuffer. Producer-fence synchronization is separate from + * these image-layout and ownership transfers. */ +static void release_external_client_image(VkCommandBuffer cmd, struct vkp_image *img) { + struct vkp_external_client_barrier_pair pair; + if (!img || !vkp_external_client_barrier_pair( + img->image, g_qfam, img->dmabuf, img->blit_dst, &pair)) + return; + g_vk.CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, NULL, 0, NULL, 1, &pair.release); +} + +static void release_external_client_draws(VkCommandBuffer cmd, const struct vkp_draw *draws, int count) { + for (int i = 0; i < count; i++) { + int seen = 0; + for (int j = 0; j < i; j++) if (draws[j].img == draws[i].img) { seen = 1; break; } + if (!seen) release_external_client_image(cmd, draws[i].img); + } +} + +int vkp_image_is_dmabuf(const struct vkp_image *img) { + return img && vkp_is_external_client_image(img->dmabuf, img->blit_dst); +} + +int vkp_android_buffer_supported(void) { + return dev_init() == 0 && g_ahb_properties != NULL; +} + +struct vkp_image *vkp_image_from_android_buffer(AHardwareBuffer *buffer) { + if (!buffer || !vkp_android_buffer_supported()) return NULL; + AHardwareBuffer_Desc desc; + AHardwareBuffer_describe(buffer, &desc); + if (!desc.width || !desc.height || desc.layers != 1 || + (desc.usage & (AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT | AHARDWAREBUFFER_USAGE_GPU_MIPMAP_COMPLETE)) || + !(desc.usage & AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE)) return NULL; + VkAndroidHardwareBufferFormatPropertiesANDROID format = { + .sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_FORMAT_PROPERTIES_ANDROID}; + VkAndroidHardwareBufferPropertiesANDROID props = { + .sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_PROPERTIES_ANDROID, .pNext = &format}; + VkResult result = g_ahb_properties(g_dev, buffer, &props); + if (result != VK_SUCCESS || + (format.format != VK_FORMAT_R8G8B8A8_UNORM && format.format != VK_FORMAT_R8G8B8A8_SRGB && + format.format != VK_FORMAT_B8G8R8A8_UNORM && format.format != VK_FORMAT_B8G8R8A8_SRGB) || + !props.memoryTypeBits || + !(format.formatFeatures & VK_FORMAT_FEATURE_BLIT_SRC_BIT)) { + LOGE("android_wlegl: unsupported Android buffer import (%s, format %d)", + vk_result_name(result), format.format); + return NULL; + } + struct vkp_image *img = calloc(1, sizeof(*img)); + if (!img) return NULL; + img->w = (int)desc.width; img->h = (int)desc.height; + img->fmt = format.format; img->dmabuf = 1; + const VkFormatFeatureFlags sampled = VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | + VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT; + img->sampled = (format.formatFeatures & sampled) == sampled; + VkExternalMemoryImageCreateInfo external = { + .sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO, + .handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID}; + VkImageCreateInfo create = { + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, .pNext = &external, + .imageType = VK_IMAGE_TYPE_2D, .format = img->fmt, + .extent = {desc.width, desc.height, 1}, .mipLevels = 1, .arrayLayers = 1, + .samples = VK_SAMPLE_COUNT_1_BIT, .tiling = VK_IMAGE_TILING_OPTIMAL, + .usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | (img->sampled ? VK_IMAGE_USAGE_SAMPLED_BIT : 0), + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED}; + result = g_vk.CreateImage(g_dev, &create, NULL, &img->image); + if (result != VK_SUCCESS) goto fail; + /* AHB image requirements are not queryable before binding. The AHB query supplies them. */ + uint32_t memory_type = 0; + while (!(props.memoryTypeBits & (1u << memory_type))) memory_type++; + VkImportAndroidHardwareBufferInfoANDROID import = { + .sType = VK_STRUCTURE_TYPE_IMPORT_ANDROID_HARDWARE_BUFFER_INFO_ANDROID, .buffer = buffer}; + VkMemoryDedicatedAllocateInfo dedicated = { + .sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO, + .pNext = &import, .image = img->image}; + VkMemoryAllocateInfo allocate = { + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, .pNext = &dedicated, + .allocationSize = props.allocationSize, .memoryTypeIndex = memory_type}; + result = g_vk.AllocateMemory(g_dev, &allocate, NULL, &img->mem); + if (result != VK_SUCCESS) goto fail; + result = g_vk.BindImageMemory(g_dev, img->image, img->mem, 0); + if (result != VK_SUCCESS) goto fail; + AHardwareBuffer_acquire(buffer); + img->android_buffer = buffer; + return img; +fail: + LOGE("android_wlegl: Vulkan buffer import failed: %s", vk_result_name(result)); + vkp_image_destroy(img); + return NULL; +} struct vkp_image *vkp_image_import_dmabuf(int fd, uint32_t drm_format, uint64_t modifier, int w, int h, uint32_t stride, uint32_t offset, int as_blit_dst) { @@ -1106,6 +1210,7 @@ void vkp_image_destroy(struct vkp_image *img) { if (img->image) g_vk.DestroyImage(g_dev, img->image, NULL); if (img->mem) g_vk.FreeMemory(g_dev, img->mem, NULL); } + if (img->android_buffer) AHardwareBuffer_release(img->android_buffer); free(img); } @@ -1155,11 +1260,14 @@ static int draw_to_scene_blit(const struct vkp_draw *d, int scene_w, int scene_h static void record_draw(VkCommandBuffer cmd, const struct vkp_draw *d, const VkImageBlit *blit, VkImage target, VkFormat target_fmt, int target_w, int target_h, VkFilter filter) { const struct vkp_image *im = d->img; + const int external_client = vkp_image_is_dmabuf(im); if (d->blend && im->sampled && - blendp_draw(cmd, im->image, im->fmt, im->w, im->h, !im->dmabuf, target, target_fmt, target_w, target_h, + blendp_draw(cmd, im->image, im->fmt, im->w, im->h, !external_client, + target, target_fmt, target_w, target_h, blit) == 0) return; - g_vk.CmdBlitImage(cmd, im->image, im->dmabuf ? VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL : VK_IMAGE_LAYOUT_GENERAL, + g_vk.CmdBlitImage(cmd, im->image, + external_client ? VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL : VK_IMAGE_LAYOUT_GENERAL, target, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, blit, filter); } @@ -1623,13 +1731,11 @@ static int render_impl(int scene_w, int scene_h, const struct vkp_draw *draws, i int seen = 0; for (int j = 0; j < i; j++) if (draws[j].img == im) { seen = 1; break; } if (seen || !im) continue; - if (im->dmabuf) { - bars[nb++] = (VkImageMemoryBarrier){ - .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, - .newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_FOREIGN_EXT, .dstQueueFamilyIndex = g_qfam, - .image = im->image, .subresourceRange = range, .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT}; - } else if (!im->in_general) { + struct vkp_external_client_barrier_pair pair; + if (vkp_external_client_barrier_pair( + im->image, g_qfam, im->dmabuf, im->blit_dst, &pair)) { + bars[nb++] = pair.acquire; + } else if (!im->blit_dst && !im->in_general) { bars[nb++] = (VkImageMemoryBarrier){ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .oldLayout = VK_IMAGE_LAYOUT_PREINITIALIZED, .newLayout = VK_IMAGE_LAYOUT_GENERAL, @@ -1696,6 +1802,7 @@ static int render_impl(int scene_w, int scene_h, const struct vkp_draw *draws, i drawn++; } + release_external_client_draws(cmd, draws, n); int ngen = 0; VkImage gens[VKP_FG_MAX_GENERATIONS] = {VK_NULL_HANDLE}; VkImage result = scene_img; @@ -1901,7 +2008,7 @@ int vkp_map_draw(const struct vkp_draw *d, int out[8]) { * owned by the "foreign" queue family (the game's driver / the display) between our uses, so each * use acquires them and the destination is released back for the display to read. */ int vkp_blit_image(struct vkp_image *src, struct vkp_image *dst, int wait_fd) { - if (!src || !dst || !dst->blit_dst || g_dev_state == -2 || dev_init() != 0) { + if (!src || src->blit_dst || !dst || !dst->blit_dst || g_dev_state == -2 || dev_init() != 0) { if (wait_fd >= 0) close(wait_fd); return -1; } @@ -1914,17 +2021,21 @@ int vkp_blit_image(struct vkp_image *src, struct vkp_image *dst, int wait_fd) { VkCommandBufferBeginInfo bi = {.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT}; g_vk.BeginCommandBuffer(g_cmd, &bi); + struct vkp_external_client_barrier_pair src_pair; + const int src_external = vkp_external_client_barrier_pair( + src->image, g_qfam, src->dmabuf, src->blit_dst, &src_pair); VkImageMemoryBarrier acq[2] = { - {.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, + src_external ? src_pair.acquire : (VkImageMemoryBarrier){ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, .newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - .srcQueueFamilyIndex = src->dmabuf ? VK_QUEUE_FAMILY_FOREIGN_EXT : VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = src->dmabuf ? g_qfam : VK_QUEUE_FAMILY_IGNORED, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .image = src->image, .subresourceRange = range, .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT}, {.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_FOREIGN_EXT, .dstQueueFamilyIndex = g_qfam, .image = dst->image, .subresourceRange = range, .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT}}; - if (!src->dmabuf) { /* shm image: host-written, GENERAL */ + if (!src_external) { /* shm image: host-written, GENERAL */ acq[0].oldLayout = src->in_general ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_PREINITIALIZED; acq[0].newLayout = VK_IMAGE_LAYOUT_GENERAL; acq[0].srcAccessMask = VK_ACCESS_HOST_WRITE_BIT; @@ -1938,8 +2049,9 @@ int vkp_blit_image(struct vkp_image *src, struct vkp_image *dst, int wait_fd) { .dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}, .dstOffsets = {{0, 0, 0}, {bw, bh, 1}}}; g_vk.CmdBlitImage(g_cmd, src->image, - src->dmabuf ? VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL : VK_IMAGE_LAYOUT_GENERAL, + src_external ? VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL : VK_IMAGE_LAYOUT_GENERAL, dst->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &blit, VK_FILTER_NEAREST); + release_external_client_image(g_cmd, src); VkImageMemoryBarrier rel = { .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, .newLayout = VK_IMAGE_LAYOUT_GENERAL, .srcQueueFamilyIndex = g_qfam, @@ -1974,7 +2086,7 @@ int vkp_blit_image(struct vkp_image *src, struct vkp_image *dst, int wait_fd) { static struct vkp_image *g_rb; /* the readback image, kept for the next shape */ int vkp_image_readback(struct vkp_image *src, uint32_t *out, int max_px) { - if (!src || !out || src->w <= 0 || src->h <= 0 || src->w * src->h > max_px || + if (!src || src->blit_dst || !out || src->w <= 0 || src->h <= 0 || src->w * src->h > max_px || g_dev_state == -2 || dev_init() != 0) return -1; if (g_rb && (g_rb->w != src->w || g_rb->h != src->h)) { vkp_image_destroy(g_rb); g_rb = NULL; } @@ -2015,11 +2127,15 @@ int vkp_image_readback(struct vkp_image *src, uint32_t *out, int max_px) { VkCommandBufferBeginInfo bi = {.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT}; g_vk.BeginCommandBuffer(cmd, &bi); + struct vkp_external_client_barrier_pair src_pair; + const int src_external = vkp_external_client_barrier_pair( + src->image, g_qfam, src->dmabuf, src->blit_dst, &src_pair); VkImageMemoryBarrier acq[2] = { - {.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, + src_external ? src_pair.acquire : (VkImageMemoryBarrier){ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, .newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - .srcQueueFamilyIndex = src->dmabuf ? VK_QUEUE_FAMILY_FOREIGN_EXT : VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = src->dmabuf ? g_qfam : VK_QUEUE_FAMILY_IGNORED, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .image = src->image, .subresourceRange = range, .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT}, {.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, .newLayout = VK_IMAGE_LAYOUT_GENERAL, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, @@ -2037,16 +2153,20 @@ int vkp_image_readback(struct vkp_image *src, uint32_t *out, int max_px) { {.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .oldLayout = VK_IMAGE_LAYOUT_GENERAL, .newLayout = VK_IMAGE_LAYOUT_GENERAL, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .image = g_rb->image, .subresourceRange = range, - .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, .dstAccessMask = VK_ACCESS_HOST_READ_BIT}, - /* the client's buffer goes back to it */ - {.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, .dstAccessMask = VK_ACCESS_HOST_READ_BIT}}; + uint32_t done_count = 1; + if (!src_external) { + done[done_count++] = (VkImageMemoryBarrier){ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, .newLayout = VK_IMAGE_LAYOUT_GENERAL, - .srcQueueFamilyIndex = src->dmabuf ? g_qfam : VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = src->dmabuf ? VK_QUEUE_FAMILY_FOREIGN_EXT : VK_QUEUE_FAMILY_IGNORED, - .image = src->image, .subresourceRange = range, .srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT}}; + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = src->image, .subresourceRange = range, .srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT}; + } g_vk.CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_HOST_BIT | VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, - 0, 0, NULL, 0, NULL, 2, done); + 0, 0, NULL, 0, NULL, done_count, done); + release_external_client_image(cmd, src); g_vk.EndCommandBuffer(cmd); VkSubmitInfo si = {.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, .commandBufferCount = 1, .pCommandBuffers = &cmd}; g_vk.ResetFences(g_dev, 1, &g_fence); @@ -2149,13 +2269,11 @@ static int pass_begin_impl(int scene_w, int scene_h, const struct vkp_draw *draw int seen = 0; for (int j = 0; j < i; j++) if (draws[j].img == im) { seen = 1; break; } if (seen || !im) continue; - if (im->dmabuf) { - bars[nb++] = (VkImageMemoryBarrier){ - .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, - .newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_FOREIGN_EXT, .dstQueueFamilyIndex = g_qfam, - .image = im->image, .subresourceRange = range, .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT}; - } else if (!im->in_general) { + struct vkp_external_client_barrier_pair pair; + if (vkp_external_client_barrier_pair( + im->image, g_qfam, im->dmabuf, im->blit_dst, &pair)) { + bars[nb++] = pair.acquire; + } else if (!im->blit_dst && !im->in_general) { bars[nb++] = (VkImageMemoryBarrier){ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .oldLayout = VK_IMAGE_LAYOUT_PREINITIALIZED, .newLayout = VK_IMAGE_LAYOUT_GENERAL, @@ -2196,6 +2314,7 @@ static int pass_begin_impl(int scene_w, int scene_h, const struct vkp_draw *draw if (vkp_effects_active()) vkp_effects_set_formats(VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_R8G8B8A8_UNORM); } + release_external_client_draws(cmd, draws, n); int mapped_w = (int)(scene_w * g_map.kx + 0.5f), mapped_h = (int)(scene_h * g_map.ky + 0.5f); g_pass.rw = scene_w; g_pass.rh = scene_h; g_pass.result = vkp_effects_run(cmd, scene_img, scene_w, scene_h, mapped_w, mapped_h, diff --git a/app/src/main/cpp/waylandcomp/src/vk_present.h b/app/src/main/cpp/waylandcomp/src/vk_present.h index 2a752c343..6a1ea816c 100644 --- a/app/src/main/cpp/waylandcomp/src/vk_present.h +++ b/app/src/main/cpp/waylandcomp/src/vk_present.h @@ -2,6 +2,7 @@ #define VK_PRESENT_H #include #include +#include /* * Android-surface render backend for the embedded Wayland compositor. * Owns a Turnip VkDevice + a swapchain on the SurfaceView's ANativeWindow. Each @@ -39,6 +40,11 @@ void vkp_output_size(int *w, int *h); struct vkp_image; +/* android_wlegl clients share complete Android buffer handles, not guessed DMA-BUF layouts. + * The image retains its own AHB reference. Call only from the compositor thread. */ +int vkp_android_buffer_supported(void); +struct vkp_image *vkp_image_from_android_buffer(AHardwareBuffer *buffer); + /* DRM format modifiers this backend knows the memory layout of (both single-plane on Adreno): * LINEAR, and QCOM_COMPRESSED = UBWC (drm_fourcc.h: fourcc_mod_code(QCOM = 0x05, 1)). */ #define VKP_MOD_LINEAR 0x0000000000000000ULL diff --git a/app/src/main/cpp/waylandcomp/src/wl_android_buffer.c b/app/src/main/cpp/waylandcomp/src/wl_android_buffer.c new file mode 100644 index 000000000..a52c3eac7 --- /dev/null +++ b/app/src/main/cpp/waylandcomp/src/wl_android_buffer.c @@ -0,0 +1,212 @@ +/* android_wlegl buffer import for the existing libhybris Wayland WSI. + * Protocol: Collabora/libhybris, vendored with its license in protocols/wayland-android.xml. + * Native handles are cloned through Android; opaque vendor integers are never interpreted. */ +#include "compositor_internal.h" +#include "droiddeck_ext.h" +#include "wayland-android-server-protocol.h" +#include +#include +#include +#include +#include + +/* Stable native_handle_t ABI, unavailable in the public NDK. */ +struct android_native_handle { + int version, num_fds, num_ints; + int data[]; +}; +struct wire_handle { + struct android_native_handle *handle; + int received; +}; +enum { MAX_HANDLE_FDS = 64, MAX_HANDLE_INTS = 256, MAX_BUFFER_SIDE = 8192 }; +/* VNDK ABI (not exposed by the NDK): frameworks/native/libs/nativewindow/include/ + * vndk/hardware_buffer.h, AHARDWAREBUFFER_CREATE_FROM_HANDLE_METHOD_CLONE = 3. + * Method 1 is not accepted by AHardwareBuffer_createFromHandle. */ +enum { CREATE_FROM_HANDLE_CLONE = 3 }; +static int (*import_handle)(const AHardwareBuffer_Desc *, const struct android_native_handle *, + int32_t, AHardwareBuffer **); +static const struct android_native_handle *(*native_handle)(const AHardwareBuffer *); + +static int valid_description(int32_t width, int32_t height, int32_t stride, int32_t format) { + return width > 0 && height > 0 && width <= MAX_BUFFER_SIDE && height <= MAX_BUFFER_SIDE && + stride >= width && stride <= MAX_BUFFER_SIDE * 4 && + (format == 1 || format == 2 || format == 5); +} + +static void handle_resource_destroy(struct wl_resource *resource) { + struct wire_handle *wire = wl_resource_get_user_data(resource); + if (!wire) return; + for (int i = 0; i < wire->received; i++) close(wire->handle->data[i]); + free(wire->handle); + free(wire); +} + +static void handle_add_fd(struct wl_client *client, struct wl_resource *resource, int32_t fd) { + struct wire_handle *wire = wl_resource_get_user_data(resource); + if (wire->received >= wire->handle->num_fds) { + close(fd); + wl_resource_post_error(resource, ANDROID_WLEGL_HANDLE_ERROR_TOO_MANY_FDS, + "more native handle descriptors than declared"); + return; + } + wire->handle->data[wire->received++] = fd; +} + +static void handle_destroy(struct wl_client *client, struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct android_wlegl_handle_interface handle_impl = { + .add_fd = handle_add_fd, .destroy = handle_destroy, +}; + +static void create_handle(struct wl_client *client, struct wl_resource *resource, + uint32_t id, int32_t fds, struct wl_array *ints) { + if (fds < 0 || fds > MAX_HANDLE_FDS || ints->size % sizeof(int32_t) || + ints->size > MAX_HANDLE_INTS * sizeof(int32_t)) { + wl_resource_post_error(resource, ANDROID_WLEGL_ERROR_BAD_VALUE, "invalid native handle size"); + return; + } + struct wire_handle *wire = calloc(1, sizeof(*wire)); + if (!wire) { wl_client_post_no_memory(client); return; } + wire->handle = calloc(1, sizeof(*wire->handle) + (size_t)fds * sizeof(int32_t) + ints->size); + if (!wire->handle) { free(wire); wl_client_post_no_memory(client); return; } + wire->handle->version = sizeof(*wire->handle); + wire->handle->num_fds = fds; + wire->handle->num_ints = (int)(ints->size / sizeof(int32_t)); + if (ints->size) memcpy(wire->handle->data + fds, ints->data, ints->size); + struct wl_resource *handle = wl_resource_create(client, &android_wlegl_handle_interface, 1, id); + if (!handle) { + free(wire->handle); free(wire); wl_client_post_no_memory(client); return; + } + wl_resource_set_implementation(handle, &handle_impl, wire, handle_resource_destroy); +} + +static void buffer_resource_destroy(struct wl_resource *resource) { + dmabuf_buffer_unref(wl_resource_get_user_data(resource)); +} + +static struct wl_resource *create_android_buffer(struct wl_client *client, uint32_t id, + AHardwareBuffer *hardware) { + AHardwareBuffer_Desc desc; + AHardwareBuffer_describe(hardware, &desc); + struct vkp_image *image = vkp_image_from_android_buffer(hardware); + if (!image) return NULL; + struct dmabuf_buffer *buffer = calloc(1, sizeof(*buffer)); + if (!buffer) { vkp_image_destroy(image); wl_client_post_no_memory(client); return NULL; } + buffer->refs = 1; + buffer->width = (int32_t)desc.width; buffer->height = (int32_t)desc.height; + buffer->format = desc.format == 5 ? DRM_ARGB8888 : desc.format == 2 ? DRM_XBGR8888 : DRM_ABGR8888; + buffer->modifier = MOD_INVALID; /* No DMA-BUF layout was inferred from the native handle. */ + buffer->img = image; + for (int i = 0; i < MAX_PLANES; i++) buffer->fd[i] = -1; + struct wl_resource *result = wl_resource_create(client, &wl_buffer_interface, 1, id); + if (!result) { dmabuf_buffer_unref(buffer); wl_client_post_no_memory(client); return NULL; } + wl_resource_set_implementation(result, &dbuf_buffer_impl, buffer, buffer_resource_destroy); + struct client_info *info = client_info_of(client); + if (info) info->dmabuf_buffers++; + return result; +} + +static void create_buffer(struct wl_client *client, struct wl_resource *resource, uint32_t id, + int32_t width, int32_t height, int32_t stride, int32_t format, + int32_t usage, struct wl_resource *handle_resource) { + if (!valid_description(width, height, stride, format) || + !wl_resource_instance_of(handle_resource, &android_wlegl_handle_interface, &handle_impl)) { + wl_resource_post_error(resource, ANDROID_WLEGL_ERROR_BAD_VALUE, "invalid Android buffer description"); + return; + } + struct wire_handle *wire = wl_resource_get_user_data(handle_resource); + if (wire->received != wire->handle->num_fds || !wire->received) { + wl_resource_post_error(resource, ANDROID_WLEGL_ERROR_BAD_HANDLE, "incomplete native handle"); + return; + } + AHardwareBuffer_Desc desc = { + .width = (uint32_t)width, .height = (uint32_t)height, .layers = 1, + .format = (uint32_t)format, .usage = (uint32_t)usage, .stride = (uint32_t)stride}; + AHardwareBuffer *hardware = NULL; + int status = import_handle(&desc, wire->handle, CREATE_FROM_HANDLE_CLONE, &hardware); + if (status != 0 || !hardware) { + droiddeck_log("display", "android_wlegl import failed: status=%d %dx%d stride=%d format=%d usage=0x%x fds=%d ints=%d", + status, width, height, stride, format, (uint32_t)usage, + wire->handle->num_fds, wire->handle->num_ints); + wl_resource_post_error(resource, ANDROID_WLEGL_ERROR_BAD_HANDLE, + "Android rejected the native handle (status %d)", status); + return; + } + struct wl_resource *buffer = create_android_buffer(client, id, hardware); + AHardwareBuffer_release(hardware); + if (!buffer) wl_resource_post_error(resource, ANDROID_WLEGL_ERROR_BAD_HANDLE, "Vulkan cannot import this buffer"); +} + +static void get_server_buffer(struct wl_client *client, struct wl_resource *resource, uint32_t id, + int32_t width, int32_t height, int32_t format, int32_t usage) { + if (!format) format = 1; + if (!valid_description(width, height, width, format)) { + wl_resource_post_error(resource, ANDROID_WLEGL_ERROR_BAD_VALUE, "invalid buffer allocation request"); + return; + } + AHardwareBuffer_Desc desc = { + .width = (uint32_t)width, .height = (uint32_t)height, .layers = 1, + .format = (uint32_t)format, + .usage = (uint32_t)usage | AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE | AHARDWAREBUFFER_USAGE_GPU_COLOR_OUTPUT}; + AHardwareBuffer *hardware = NULL; + if (AHardwareBuffer_allocate(&desc, &hardware) != 0 || !hardware) { + wl_resource_post_error(resource, ANDROID_WLEGL_ERROR_BAD_VALUE, "Android buffer allocation failed"); + return; + } + AHardwareBuffer_describe(hardware, &desc); + const struct android_native_handle *handle = native_handle(hardware); + if (!handle || handle->num_fds < 1 || handle->num_fds > MAX_HANDLE_FDS || + handle->num_ints < 0 || handle->num_ints > MAX_HANDLE_INTS) { + AHardwareBuffer_release(hardware); + wl_resource_post_error(resource, ANDROID_WLEGL_ERROR_BAD_HANDLE, "invalid allocated native handle"); + return; + } + struct wl_resource *buffer = create_android_buffer(client, 0, hardware); + if (!buffer) { + AHardwareBuffer_release(hardware); + wl_resource_post_error(resource, ANDROID_WLEGL_ERROR_BAD_HANDLE, "Vulkan cannot import allocated buffer"); + return; + } + struct wl_resource *reply = wl_resource_create(client, &android_wlegl_server_buffer_handle_interface, + wl_resource_get_version(resource), id); + if (!reply) { + wl_resource_destroy(buffer); AHardwareBuffer_release(hardware); + wl_client_post_no_memory(client); return; + } + struct wl_array ints = {.size = (size_t)handle->num_ints * sizeof(int32_t), + .alloc = 0, .data = (void *)(handle->data + handle->num_fds)}; + android_wlegl_server_buffer_handle_send_buffer_ints(reply, &ints); + for (int i = 0; i < handle->num_fds; i++) + android_wlegl_server_buffer_handle_send_buffer_fd(reply, handle->data[i]); + /* No linear_layout event: Android's opaque allocation need not be linear. */ + android_wlegl_server_buffer_handle_send_buffer(reply, buffer, (int32_t)desc.format, (int32_t)desc.stride); + wl_resource_destroy(reply); + AHardwareBuffer_release(hardware); +} + +static const struct android_wlegl_interface wlegl_impl = { + .create_handle = create_handle, .create_buffer = create_buffer, + .get_server_buffer_handle = get_server_buffer, +}; + +static void bind_android_wlegl(struct wl_client *client, void *data, uint32_t version, uint32_t id) { + struct wl_resource *resource = wl_resource_create(client, &android_wlegl_interface, + version < 3 ? version : 3, id); + if (!resource) { wl_client_post_no_memory(client); return; } + wl_resource_set_implementation(resource, &wlegl_impl, NULL, NULL); +} + +void droiddeck_android_wlegl_init(struct wl_display *display) { + if (!vkp_android_buffer_supported()) return; + void *library = dlopen("libnativewindow.so", RTLD_NOW | RTLD_LOCAL); + if (!library) return; + import_handle = dlsym(library, "AHardwareBuffer_createFromHandle"); + native_handle = dlsym(library, "AHardwareBuffer_getNativeHandle"); + if (!import_handle || !native_handle) { dlclose(library); return; } + /* Keep the platform entry points valid for the compositor's lifetime. */ + if (wl_global_create(display, &android_wlegl_interface, 3, NULL, bind_android_wlegl)) + droiddeck_log("display", "android_wlegl v3: Android buffer import available"); +} diff --git a/tools/tests/test_vk_external_image_sync.py b/tools/tests/test_vk_external_image_sync.py new file mode 100644 index 000000000..a82dac25d --- /dev/null +++ b/tools/tests/test_vk_external_image_sync.py @@ -0,0 +1,63 @@ +import os +from pathlib import Path +import shutil +import subprocess +import tempfile +import unittest + + +def vulkan_include_args(): + candidates = [] + for name in ("ANDROID_NDK_HOME", "ANDROID_NDK_ROOT"): + if os.environ.get(name): + candidates.append(Path(os.environ[name])) + + android_home = os.environ.get("ANDROID_HOME") + if android_home: + ndk_root = Path(android_home) / "ndk" + version = os.environ.get("NDK_VERSION") + if version: + candidates.append(ndk_root / version) + if ndk_root.is_dir(): + candidates.extend(sorted(ndk_root.iterdir(), reverse=True)) + + for ndk in candidates: + include = ndk / "toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include" + if (include / "vulkan/vulkan.h").is_file(): + return ["-idirafter", str(include)] + return [] + + +class VkExternalImageSyncTest(unittest.TestCase): + def test_client_barrier_pair(self): + compiler = shutil.which(os.environ.get("CC", "cc")) + if not compiler: + self.fail("A C compiler is required for the Vulkan image-sync regression test") + + tests = Path(__file__).resolve().parent + source = tests / "vk_external_image_sync_test.c" + includes = tests.parents[1] / "app/src/main/cpp/waylandcomp/src" + with tempfile.TemporaryDirectory(prefix="droiddeck-vk-sync-") as tmp: + program = Path(tmp) / "vk-external-image-sync" + command = [ + compiler, + "-std=c11", + "-Wall", + "-Wextra", + "-Werror", + "-UNDEBUG", + "-I", + str(includes), + *vulkan_include_args(), + str(source), + "-o", + str(program), + ] + built = subprocess.run(command, capture_output=True, text=True) + self.assertEqual(built.returncode, 0, built.stdout + built.stderr) + ran = subprocess.run([str(program)], capture_output=True, text=True, timeout=30) + self.assertEqual(ran.returncode, 0, ran.stdout + ran.stderr) + + +if __name__ == "__main__": + unittest.main() diff --git a/tools/tests/vk_external_image_sync_test.c b/tools/tests/vk_external_image_sync_test.c new file mode 100644 index 000000000..c6114d257 --- /dev/null +++ b/tools/tests/vk_external_image_sync_test.c @@ -0,0 +1,63 @@ +#include +#include +#include + +#include "vk_external_image_sync.h" + +static void check_external_pair(const struct vkp_external_client_barrier_pair *pair, + VkImage image, uint32_t queue_family) { + assert(pair->acquire.sType == VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER); + assert(pair->acquire.oldLayout == VK_IMAGE_LAYOUT_GENERAL); + assert(pair->acquire.newLayout == VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); + assert(pair->acquire.srcQueueFamilyIndex == VK_QUEUE_FAMILY_FOREIGN_EXT); + assert(pair->acquire.dstQueueFamilyIndex == queue_family); + assert(pair->acquire.image == image); + assert(pair->acquire.srcAccessMask == 0); + assert(pair->acquire.dstAccessMask == VK_ACCESS_TRANSFER_READ_BIT); + assert(pair->acquire.subresourceRange.aspectMask == VK_IMAGE_ASPECT_COLOR_BIT); + assert(pair->acquire.subresourceRange.baseMipLevel == 0); + assert(pair->acquire.subresourceRange.levelCount == 1); + assert(pair->acquire.subresourceRange.baseArrayLayer == 0); + assert(pair->acquire.subresourceRange.layerCount == 1); + + assert(pair->release.sType == VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER); + assert(pair->release.oldLayout == VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); + assert(pair->release.newLayout == VK_IMAGE_LAYOUT_GENERAL); + assert(pair->release.srcQueueFamilyIndex == queue_family); + assert(pair->release.dstQueueFamilyIndex == VK_QUEUE_FAMILY_FOREIGN_EXT); + assert(pair->release.image == image); + assert(pair->release.srcAccessMask == VK_ACCESS_MEMORY_READ_BIT); + assert(pair->release.dstAccessMask == 0); + assert(pair->release.subresourceRange.aspectMask == VK_IMAGE_ASPECT_COLOR_BIT); + assert(pair->release.subresourceRange.levelCount == 1); + assert(pair->release.subresourceRange.layerCount == 1); +} + +int main(void) { + const VkImage image = (VkImage)(uintptr_t)0x1234; + const VkImage ahb_image = (VkImage)(uintptr_t)0x5678; + const uint32_t queue_family = 7; + struct vkp_external_client_barrier_pair generic, ahb; + + assert(vkp_external_client_barrier_pair(image, queue_family, 1, 0, &generic)); + check_external_pair(&generic, image, queue_family); + + /* AHardwareBuffer imports deliberately use the same client flags and lifecycle. */ + assert(vkp_external_client_barrier_pair(ahb_image, queue_family, 1, 0, &ahb)); + check_external_pair(&ahb, ahb_image, queue_family); + + unsigned char untouched[sizeof(generic)]; + memset(&generic, 0xa5, sizeof(generic)); + memcpy(untouched, &generic, sizeof(generic)); + assert(!vkp_external_client_barrier_pair(image, queue_family, 0, 0, &generic)); + assert(memcmp(&generic, untouched, sizeof(generic)) == 0); + assert(!vkp_is_external_client_image(0, 0)); + + memset(&generic, 0x5a, sizeof(generic)); + memcpy(untouched, &generic, sizeof(generic)); + assert(!vkp_external_client_barrier_pair(image, queue_family, 1, 1, &generic)); + assert(memcmp(&generic, untouched, sizeof(generic)) == 0); + assert(!vkp_is_external_client_image(1, 1)); + assert(vkp_is_external_client_image(1, 0)); + return 0; +}