From f41c623d2b41b1a711da85f0d05d1542fa1293d4 Mon Sep 17 00:00:00 2001 From: Alexey Lysenko Date: Tue, 11 Aug 2026 10:45:38 +0300 Subject: [PATCH 1/3] wsi/android: release direct AHardwareBuffer images The direct AHardwareBuffer path exports exclusive swapchain images without releasing queue-family ownership to VK_QUEUE_FAMILY_FOREIGN_EXT. On drivers with private render-target layouts, an external importer can therefore observe unresolved or compressed contents. Pre-record one release barrier per usable queue family and submit it with present. Use GENERAL as the shared layout and a generic memory-write source dependency because WSI cannot know how the application produced the image. Restrict the change to exclusive, non-blit Android images: legacy VkImageMemoryBarrier does not permit FOREIGN_EXT transfers for concurrent images, and the image-blit path has separate presentation commands. --- src/vulkan/wsi/wsi_common.c | 26 +++++++ src/vulkan/wsi/wsi_common_android.c | 106 ++++++++++++++++++++++++++++ src/vulkan/wsi/wsi_common_private.h | 4 ++ 3 files changed, 136 insertions(+) diff --git a/src/vulkan/wsi/wsi_common.c b/src/vulkan/wsi/wsi_common.c index cbcec139..cb054d27 100644 --- a/src/vulkan/wsi/wsi_common.c +++ b/src/vulkan/wsi/wsi_common.c @@ -888,6 +888,20 @@ wsi_destroy_image(const struct wsi_swapchain *chain, vk_free(&chain->alloc, image->blit.cmd_buffers); } +#ifdef __TERMUX__ + if (image->ahb_release_cmd_buffers) { + for (uint32_t i = 0; i < wsi->queue_family_count; i++) { + if (!chain->cmd_pools[i] || + image->ahb_release_cmd_buffers[i] == VK_NULL_HANDLE) + continue; + wsi->FreeCommandBuffers(chain->device, chain->cmd_pools[i], + 1, &image->ahb_release_cmd_buffers[i]); + } + vk_free(&chain->alloc, image->ahb_release_cmd_buffers); + image->ahb_release_cmd_buffers = NULL; + } +#endif + wsi->FreeMemory(chain->device, image->memory, &chain->alloc); wsi->DestroyImage(chain->device, image->image, &chain->alloc); wsi->DestroyImage(chain->device, image->blit.image, &chain->alloc); @@ -1598,6 +1612,18 @@ wsi_common_queue_present(const struct wsi_device *wsi, submit_info.pWaitDstStageMask = stage_flags; } } +#ifdef __TERMUX__ + else if (image->ahb_release_cmd_buffers && + image->ahb_release_cmd_buffers[queue_family_index] != VK_NULL_HANDLE) { + /* No blit: an external consumer reads this AHardwareBuffer directly, + * so hand the image over explicitly. The release transfer makes the + * contents available outside this device; see + * wsi_create_ahb_release_cmd_buffers. */ + submit_info.commandBufferCount = 1; + submit_info.pCommandBuffers = + &image->ahb_release_cmd_buffers[queue_family_index]; + } +#endif VkFence fence = swapchain->fences[image_index]; diff --git a/src/vulkan/wsi/wsi_common_android.c b/src/vulkan/wsi/wsi_common_android.c index 9f03ea4a..36b21579 100644 --- a/src/vulkan/wsi/wsi_common_android.c +++ b/src/vulkan/wsi/wsi_common_android.c @@ -189,6 +189,107 @@ wsi_create_ahardware_buffer_image_mem(const struct wsi_swapchain *chain, return VK_SUCCESS; } +/* Release the image to an external consumer. + * + * The direct AHardwareBuffer path exports an image that another Vulkan device + * may read. Vulkan models that handoff as a queue-family ownership transfer. + * The producing device must perform the release so driver-private image state + * is made available to the importer. + */ +static VkResult +wsi_create_ahb_release_cmd_buffers(const struct wsi_swapchain *chain, + struct wsi_image *image) +{ + const struct wsi_device *wsi = chain->wsi; + VkResult result; + + const uint32_t count = wsi->queue_family_count; + image->ahb_release_cmd_buffers = + vk_zalloc(&chain->alloc, sizeof(VkCommandBuffer) * count, 8, + VK_SYSTEM_ALLOCATION_SCOPE_OBJECT); + if (!image->ahb_release_cmd_buffers) + return VK_ERROR_OUT_OF_HOST_MEMORY; + + for (uint32_t i = 0; i < count; i++) { + if (!chain->cmd_pools[i]) + continue; + + const VkCommandBufferAllocateInfo cmd_buffer_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + .commandPool = chain->cmd_pools[i], + .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, + .commandBufferCount = 1, + }; + result = wsi->AllocateCommandBuffers(chain->device, &cmd_buffer_info, + &image->ahb_release_cmd_buffers[i]); + if (result != VK_SUCCESS) { + WRAPPER_LOG(error, "Failed to allocate ahb release cmd buffer, res %d", + result); + return result; + } + + const VkCommandBufferBeginInfo begin_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + }; + result = wsi->BeginCommandBuffer(image->ahb_release_cmd_buffers[i], + &begin_info); + if (result != VK_SUCCESS) { + WRAPPER_LOG(error, "Failed to begin ahb release cmd buffer, res %d", + result); + return result; + } + + const VkImageMemoryBarrier release = { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + /* The WSI does not know how the application produced the image. + * Cover every write rather than assuming color-attachment or blit + * presentation. */ + .srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT, + /* Ignored for a release operation. */ + .dstAccessMask = 0, + .oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + /* The importer has to begin its acquire from the layout this release + * ends in, and it cannot name PRESENT_SRC_KHR for an image that does + * not belong to its own swapchain. GENERAL is valid on both sides and, + * unlike UNDEFINED, keeps the contents -- which is the whole point of + * handing the image over. */ + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = i, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_FOREIGN_EXT, + .image = image->image, + .subresourceRange = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }, + }; + wsi->CmdPipelineBarrier(image->ahb_release_cmd_buffers[i], + VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, + 0, 0, NULL, 0, NULL, 1, &release); + + result = wsi->EndCommandBuffer(image->ahb_release_cmd_buffers[i]); + if (result != VK_SUCCESS) { + WRAPPER_LOG(error, "Failed to record ahb release barrier, res %d", + result); + return result; + } + } + + WRAPPER_LOG(info, "Recorded AHB ownership release barriers"); + return VK_SUCCESS; +} + +static VkResult +wsi_finish_create_ahardware_buffer_image(const struct wsi_swapchain *chain, + const struct wsi_image_info *info, + struct wsi_image *image) +{ + return wsi_create_ahb_release_cmd_buffers(chain, image); +} + static VkResult wsi_create_ahardware_buffer_blit_context(const struct wsi_swapchain *chain, const struct wsi_image_info *info, @@ -408,6 +509,11 @@ wsi_configure_android_image( info->create_mem = wsi_create_ahardware_buffer_blit_context; } else { info->create_mem = wsi_create_ahardware_buffer_image_mem; + /* Legacy VkImageMemoryBarrier only permits FOREIGN_EXT ownership + * transfers for exclusive images. Preserve the existing concurrent + * path instead of recording an invalid barrier for it. */ + if (info->create.sharingMode == VK_SHARING_MODE_EXCLUSIVE) + info->finish_create = wsi_finish_create_ahardware_buffer_image; } return VK_SUCCESS; diff --git a/src/vulkan/wsi/wsi_common_private.h b/src/vulkan/wsi/wsi_common_private.h index 54ade681..4905fdbe 100644 --- a/src/vulkan/wsi/wsi_common_private.h +++ b/src/vulkan/wsi/wsi_common_private.h @@ -174,6 +174,10 @@ struct wsi_image { void *cpu_map; #ifdef __TERMUX__ struct AHardwareBuffer *ahardware_buffer; + /* Command buffers, one per queue family, holding the queue-family ownership + * release into VK_QUEUE_FAMILY_FOREIGN_EXT. Submitted before the image is + * handed to an external consumer. See wsi_create_ahb_release_cmd_buffers. */ + VkCommandBuffer *ahb_release_cmd_buffers; #endif }; From 97f86f348a17dc975023464705edb8e5f7a75335 Mon Sep 17 00:00:00 2001 From: Alexey Lysenko Date: Sat, 22 Aug 2026 18:47:37 +0300 Subject: [PATCH 2/3] wrapper: retain allocation size for placed memory maps wrapper_device_memory::alloc_size is used to select the backing AHardwareBuffer fd and to resolve VK_WHOLE_SIZE, but it is left zero-initialized. With a multi-fd AHardwareBuffer this accepts the first seekable metadata fd even when it is smaller than the Vulkan allocation, producing a successful but truncated mapping. Retain the authoritative VkMemoryAllocateInfo::allocationSize when the wrapper creates its placed-memory object. The change remains inside the existing host-visible VK_EXT_map_memory_placed allocation path; imported, exported, non-host-visible, and ordinary driver mappings are unchanged. --- src/vulkan/wrapper/wrapper_device_memory.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vulkan/wrapper/wrapper_device_memory.c b/src/vulkan/wrapper/wrapper_device_memory.c index f4ef45d9..649d2060 100644 --- a/src/vulkan/wrapper/wrapper_device_memory.c +++ b/src/vulkan/wrapper/wrapper_device_memory.c @@ -561,6 +561,7 @@ wrapper_AllocateMemory(VkDevice _device, vk_error(device, result); goto out; } + mem->alloc_size = pAllocateInfo->allocationSize; VkExternalMemoryHandleTypeFlags valid_handle_types = 0; if (dedicated_allocate_info) { From 56b11b1728751613fe86c823949f764562d79a0f Mon Sep 17 00:00:00 2001 From: Alexey Lysenko Date: Sun, 30 Aug 2026 17:49:45 +0300 Subject: [PATCH 3/3] wsi/android: gate AHB ownership release Restrict the default AHB ownership release path to NVIDIA's proprietary driver to avoid changing direct no-blit presentation on unvalidated devices.\n\nAdd WRAPPER_AHB_OWNERSHIP_RELEASE as a tri-state override: unset follows the driver default, true opts other drivers in, and false provides a kill switch. Move the per-image success message to Mesa's debug logger. --- src/vulkan/wsi/wsi_common.c | 5 +++++ src/vulkan/wsi/wsi_common.h | 1 + src/vulkan/wsi/wsi_common_android.c | 8 ++++++-- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/vulkan/wsi/wsi_common.c b/src/vulkan/wsi/wsi_common.c index cb054d27..869bc320 100644 --- a/src/vulkan/wsi/wsi_common.c +++ b/src/vulkan/wsi/wsi_common.c @@ -120,6 +120,11 @@ wsi_device_init(struct wsi_device *wsi, }; GetPhysicalDeviceProperties2(pdevice, &wsi->properties2); + wsi->enable_ahb_ownership_release = + debug_get_bool_option("WRAPPER_AHB_OWNERSHIP_RELEASE", + pddp.driverID == + VK_DRIVER_ID_NVIDIA_PROPRIETARY); + const char *wine_preload_reserve = getenv("WINEPRELOADRESERVE"); // e.g. 000400000-0008b4000 or 140000000-1400a8000 bool is_win32 = false; diff --git a/src/vulkan/wsi/wsi_common.h b/src/vulkan/wsi/wsi_common.h index eb9d5546..19ed2278 100644 --- a/src/vulkan/wsi/wsi_common.h +++ b/src/vulkan/wsi/wsi_common.h @@ -184,6 +184,7 @@ struct wsi_device { bool needs_blit; bool emulate_bgra8; + bool enable_ahb_ownership_release; /* Set to true if the implementation is ok with linear WSI images. */ bool wants_linear; diff --git a/src/vulkan/wsi/wsi_common_android.c b/src/vulkan/wsi/wsi_common_android.c index 36b21579..51b0f138 100644 --- a/src/vulkan/wsi/wsi_common_android.c +++ b/src/vulkan/wsi/wsi_common_android.c @@ -278,7 +278,7 @@ wsi_create_ahb_release_cmd_buffers(const struct wsi_swapchain *chain, } } - WRAPPER_LOG(info, "Recorded AHB ownership release barriers"); + mesa_logd("Recorded AHB ownership release barriers"); return VK_SUCCESS; } @@ -512,8 +512,12 @@ wsi_configure_android_image( /* Legacy VkImageMemoryBarrier only permits FOREIGN_EXT ownership * transfers for exclusive images. Preserve the existing concurrent * path instead of recording an invalid barrier for it. */ - if (info->create.sharingMode == VK_SHARING_MODE_EXCLUSIVE) + if (info->create.sharingMode == VK_SHARING_MODE_EXCLUSIVE && + chain->wsi->enable_ahb_ownership_release) { + WRAPPER_LOG(info, + "Enabling AHB ownership release for direct exclusive swapchain"); info->finish_create = wsi_finish_create_ahardware_buffer_image; + } } return VK_SUCCESS;