From 9a4e731746db7af02748d17194642089fe60b6ed Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Tue, 7 Apr 2026 20:38:22 +0200 Subject: [PATCH] LibGfx+LibWeb: Add Linux dmabuf backing stores to SharedImage On Linux builds with Vulkan, WebContent already paints into GPU-backed Skia surfaces, but the backing store shared with the UI process was still a CPU ShareableBitmap. That forced every flush to read the GPU image back into a bitmap so the UI could sample it, defeating most of the benefit of GPU painting. Teach SharedImage to carry a LinuxDmaBufHandle alongside ShareableBitmap as a Variant, with a tagged IPC encoding and an fd clone on encode so both processes own an independent handle. When USE_VULKAN_DMABUF_IMAGES is enabled, BackingStoreManager now allocates the front/back buffers as linear-modifier Vulkan images and publishes their dmabuf fds to the UI; the Skia painting surfaces wrap those Vulkan images directly, so no readback is needed. The old shareable-bitmap path is preserved as a fallback for the non-Vulkan case and when image creation fails. On the receive side, SharedImageBuffer::import_from_shared_image mmaps a linear dmabuf to reconstruct a CPU Bitmap, keeping existing consumers that expect CPU access working unchanged. VulkanImage memory type selection is factored into a small helper, and linear images now request host-visible (cached if available) memory rather than device-local, since a linear dmabuf has to be CPU-mappable on the importer side. --- Libraries/LibGfx/SharedImage.cpp | 93 ++++++++++++- Libraries/LibGfx/SharedImage.h | 44 ++++++- Libraries/LibGfx/SharedImageBuffer.cpp | 37 +++++- Libraries/LibGfx/VulkanImage.cpp | 26 +++- .../LibWeb/Painting/BackingStoreManager.cpp | 122 +++++++++++++----- 5 files changed, 272 insertions(+), 50 deletions(-) diff --git a/Libraries/LibGfx/SharedImage.cpp b/Libraries/LibGfx/SharedImage.cpp index e3b4a4238a..181adb368d 100644 --- a/Libraries/LibGfx/SharedImage.cpp +++ b/Libraries/LibGfx/SharedImage.cpp @@ -8,6 +8,9 @@ #include #include #include +#ifdef USE_VULKAN_DMABUF_IMAGES +# include +#endif #ifdef AK_OS_MACOS static Core::MachPort copy_send_right(Core::MachPort const& port) @@ -27,22 +30,97 @@ SharedImage::SharedImage(Core::MachPort&& port) } #else SharedImage::SharedImage(ShareableBitmap shareable_bitmap) - : m_shareable_bitmap(move(shareable_bitmap)) + : m_data(move(shareable_bitmap)) { } + +SharedImage::SharedImage(LinuxDmaBufHandle&& dmabuf) + : m_data(move(dmabuf)) +{ +} + +# ifdef USE_VULKAN_DMABUF_IMAGES +static constexpr auto shared_image_bitmap_format = BitmapFormat::BGRA8888; +static constexpr auto shared_image_alpha_type = AlphaType::Premultiplied; + +SharedImage duplicate_shared_image(VulkanImage const& vulkan_image) +{ + return SharedImage { duplicate_linux_dmabuf_handle(vulkan_image) }; +} + +LinuxDmaBufHandle duplicate_linux_dmabuf_handle(VulkanImage const& vulkan_image) +{ + VERIFY(vulkan_image.info.format == VK_FORMAT_B8G8R8A8_UNORM); + auto fd = vulkan_image.get_dma_buf_fd(); + VERIFY(fd >= 0); + return LinuxDmaBufHandle { + .bitmap_format = shared_image_bitmap_format, + .alpha_type = shared_image_alpha_type, + .size = IntSize(static_cast(vulkan_image.info.extent.width), static_cast(vulkan_image.info.extent.height)), + .drm_format = vk_format_to_drm_format(vulkan_image.info.format), + .pitch = static_cast(vulkan_image.info.row_pitch), + .modifier = vulkan_image.info.modifier, + .file = IPC::File::adopt_fd(fd), + }; +} +# endif #endif } namespace IPC { +#ifndef AK_OS_MACOS +enum class SharedImageBackingType : u8 { + ShareableBitmap, + LinuxDmaBuf, +}; + +template<> +ErrorOr encode(Encoder& encoder, Gfx::LinuxDmaBufHandle const& dmabuf) +{ + TRY(encoder.encode(dmabuf.bitmap_format)); + TRY(encoder.encode(dmabuf.alpha_type)); + TRY(encoder.encode(dmabuf.size)); + TRY(encoder.encode(dmabuf.drm_format)); + TRY(encoder.encode(dmabuf.pitch)); + TRY(encoder.encode(dmabuf.modifier)); + TRY(encoder.encode(TRY(IPC::File::clone_fd(dmabuf.file.fd())))); + return {}; +} + +template<> +ErrorOr decode(Decoder& decoder) +{ + return Gfx::LinuxDmaBufHandle { + .bitmap_format = TRY(decoder.decode()), + .alpha_type = TRY(decoder.decode()), + .size = TRY(decoder.decode()), + .drm_format = TRY(decoder.decode()), + .pitch = TRY(decoder.decode()), + .modifier = TRY(decoder.decode()), + .file = TRY(decoder.decode()), + }; +} +#endif + template<> ErrorOr encode(Encoder& encoder, Gfx::SharedImage const& shared_image) { #ifdef AK_OS_MACOS TRY(encoder.append_attachment(Attachment::from_mach_port(copy_send_right(shared_image.m_port), Core::MachPort::MessageRight::MoveSend))); #else - TRY(encoder.encode(shared_image.m_shareable_bitmap)); + return shared_image.m_data.visit( + [&](Gfx::ShareableBitmap const& shareable_bitmap) -> ErrorOr { + TRY(encoder.encode(SharedImageBackingType::ShareableBitmap)); + TRY(encoder.encode(shareable_bitmap)); + return {}; + }, + [&](Gfx::LinuxDmaBufHandle const& dmabuf) -> ErrorOr { + TRY(encoder.encode(SharedImageBackingType::LinuxDmaBuf)); + TRY(encoder.encode(dmabuf)); + return {}; + }); #endif return {}; } @@ -55,9 +133,14 @@ ErrorOr decode(Decoder& decoder) VERIFY(attachment.message_right() == Core::MachPort::MessageRight::MoveSend); return Gfx::SharedImage { attachment.release_mach_port() }; #else - auto shareable_bitmap = TRY(decoder.decode()); - VERIFY(shareable_bitmap.is_valid()); - return Gfx::SharedImage { move(shareable_bitmap) }; + switch (TRY(decoder.decode())) { + case SharedImageBackingType::ShareableBitmap: + return Gfx::SharedImage { TRY(decoder.decode()) }; + case SharedImageBackingType::LinuxDmaBuf: + return Gfx::SharedImage { TRY(decoder.decode()) }; + default: + VERIFY_NOT_REACHED(); + } #endif } diff --git a/Libraries/LibGfx/SharedImage.h b/Libraries/LibGfx/SharedImage.h index 8cfd5b3877..e75f0cd008 100644 --- a/Libraries/LibGfx/SharedImage.h +++ b/Libraries/LibGfx/SharedImage.h @@ -8,18 +8,39 @@ #include #include +#include +#include +#include +#include +#include #include #ifdef AK_OS_MACOS # include -#else -# include #endif namespace Gfx { class SharedImageBuffer; +#ifndef AK_OS_MACOS +struct LinuxDmaBufHandle { + BitmapFormat bitmap_format; + AlphaType alpha_type; + IntSize size; + u32 drm_format; + size_t pitch; + u64 modifier; + IPC::File file; +}; +#endif + +#ifdef USE_VULKAN_DMABUF_IMAGES +struct VulkanImage; +SharedImage duplicate_shared_image(VulkanImage const&); +LinuxDmaBufHandle duplicate_linux_dmabuf_handle(VulkanImage const&); +#endif + class SharedImage { AK_MAKE_NONCOPYABLE(SharedImage); @@ -28,13 +49,18 @@ public: SharedImage& operator=(SharedImage&&) = default; ~SharedImage() = default; -private: #ifdef AK_OS_MACOS explicit SharedImage(Core::MachPort&&); - Core::MachPort m_port; #else explicit SharedImage(ShareableBitmap); - ShareableBitmap m_shareable_bitmap; + explicit SharedImage(LinuxDmaBufHandle&&); +#endif + +private: +#ifdef AK_OS_MACOS + Core::MachPort m_port; +#else + Variant m_data; #endif friend class SharedImageBuffer; @@ -50,6 +76,14 @@ private: namespace IPC { +#ifndef AK_OS_MACOS +template<> +ErrorOr encode(Encoder&, Gfx::LinuxDmaBufHandle const&); + +template<> +ErrorOr decode(Decoder&); +#endif + template<> ErrorOr encode(Encoder&, Gfx::SharedImage const&); diff --git a/Libraries/LibGfx/SharedImageBuffer.cpp b/Libraries/LibGfx/SharedImageBuffer.cpp index 1bdd793f25..510ec59e90 100644 --- a/Libraries/LibGfx/SharedImageBuffer.cpp +++ b/Libraries/LibGfx/SharedImageBuffer.cpp @@ -7,6 +7,11 @@ #include #include +#ifdef USE_VULKAN_DMABUF_IMAGES +# include +# include +#endif + namespace Gfx { #ifdef AK_OS_MACOS @@ -28,6 +33,23 @@ SharedImageBuffer::SharedImageBuffer(Core::IOSurfaceHandle&& iosurface_handle, N #else static constexpr auto shared_image_buffer_format = BitmapFormat::BGRA8888; static constexpr auto shared_image_buffer_alpha_type = AlphaType::Premultiplied; +# ifdef USE_VULKAN_DMABUF_IMAGES +static constexpr auto shared_image_buffer_drm_format = DRM_FORMAT_ARGB8888; + +static NonnullRefPtr create_bitmap_from_linux_dmabuf(LinuxDmaBufHandle const& dmabuf) +{ + VERIFY(dmabuf.bitmap_format == shared_image_buffer_format); + VERIFY(dmabuf.alpha_type == shared_image_buffer_alpha_type); + VERIFY(dmabuf.drm_format == shared_image_buffer_drm_format); + VERIFY(dmabuf.modifier == DRM_FORMAT_MOD_LINEAR); + auto data_size = Bitmap::size_in_bytes(dmabuf.pitch, dmabuf.size.height()); + auto* data = ::mmap(nullptr, data_size, PROT_READ | PROT_WRITE, MAP_SHARED, dmabuf.file.fd(), 0); + VERIFY(data != MAP_FAILED); + return MUST(Bitmap::create_wrapper(dmabuf.bitmap_format, dmabuf.alpha_type, dmabuf.size, dmabuf.pitch, data, [data, data_size] { + VERIFY(::munmap(data, data_size) == 0); + })); +} +# endif SharedImageBuffer::SharedImageBuffer(NonnullRefPtr bitmap) : m_bitmap(move(bitmap)) @@ -53,9 +75,18 @@ SharedImageBuffer SharedImageBuffer::import_from_shared_image(SharedImage shared auto bitmap = create_bitmap_from_iosurface(iosurface_handle); return SharedImageBuffer(move(iosurface_handle), move(bitmap)); #else - auto* bitmap = shared_image.m_shareable_bitmap.bitmap(); - VERIFY(bitmap); - return SharedImageBuffer(NonnullRefPtr { *bitmap }); + return shared_image.m_data.visit( + [](ShareableBitmap& shareable_bitmap) -> SharedImageBuffer { + return SharedImageBuffer(*shareable_bitmap.bitmap()); + }, + [](LinuxDmaBufHandle& dmabuf) -> SharedImageBuffer { +# ifdef USE_VULKAN_DMABUF_IMAGES + return SharedImageBuffer(create_bitmap_from_linux_dmabuf(dmabuf)); +# else + (void)dmabuf; + VERIFY_NOT_REACHED(); +# endif + }); #endif } diff --git a/Libraries/LibGfx/VulkanImage.cpp b/Libraries/LibGfx/VulkanImage.cpp index adc98841eb..6bbfa22077 100644 --- a/Libraries/LibGfx/VulkanImage.cpp +++ b/Libraries/LibGfx/VulkanImage.cpp @@ -6,12 +6,24 @@ #ifdef USE_VULKAN_DMABUF_IMAGES +# include # include # include # include namespace Gfx { +static uint32_t find_memory_type_index(VkPhysicalDeviceMemoryProperties const& memory_properties, VkMemoryRequirements const& memory_requirements, VkMemoryPropertyFlags required_flags) +{ + for (uint32_t i = 0; i < memory_properties.memoryTypeCount; ++i) { + auto const property_flags = memory_properties.memoryTypes[i].propertyFlags; + if ((memory_requirements.memoryTypeBits & (1u << i)) && (property_flags & required_flags) == required_flags) + return i; + } + + return memory_properties.memoryTypeCount; +} + VulkanImage::~VulkanImage() { if (image != VK_NULL_HANDLE) { @@ -151,12 +163,18 @@ ErrorOr> create_shared_vulkan_image(VulkanContext con vkGetImageMemoryRequirements(context.logical_device, image->image, &mem_reqs); VkPhysicalDeviceMemoryProperties mem_props; vkGetPhysicalDeviceMemoryProperties(context.physical_device, &mem_props); - uint32_t mem_type_idx; - for (mem_type_idx = 0; mem_type_idx < mem_props.memoryTypeCount; ++mem_type_idx) { - if ((mem_reqs.memoryTypeBits & (1 << mem_type_idx)) && (mem_props.memoryTypes[mem_type_idx].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) { - break; + bool const is_linear_image = format_mods.size() == 1 && format_mods[0] == DRM_FORMAT_MOD_LINEAR; + uint32_t mem_type_idx = mem_props.memoryTypeCount; + + if (is_linear_image) { + mem_type_idx = find_memory_type_index(mem_props, mem_reqs, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT); + if (mem_type_idx == mem_props.memoryTypeCount) { + mem_type_idx = find_memory_type_index(mem_props, mem_reqs, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); } + } else { + mem_type_idx = find_memory_type_index(mem_props, mem_reqs, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); } + if (mem_type_idx == mem_props.memoryTypeCount) { return Error::from_string_literal("unable to find suitable image memory type"); } diff --git a/Libraries/LibWeb/Painting/BackingStoreManager.cpp b/Libraries/LibWeb/Painting/BackingStoreManager.cpp index 00f98d4852..50b096d26d 100644 --- a/Libraries/LibWeb/Painting/BackingStoreManager.cpp +++ b/Libraries/LibWeb/Painting/BackingStoreManager.cpp @@ -4,6 +4,7 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include #include #include #include @@ -12,6 +13,10 @@ #include #include +#ifdef USE_VULKAN_DMABUF_IMAGES +# include +#endif + namespace Web::Painting { GC_DEFINE_ALLOCATOR(BackingStoreManager); @@ -35,56 +40,107 @@ void BackingStoreManager::restart_resize_timer() m_backing_store_shrink_timer->restart(); } -void BackingStoreManager::reallocate_backing_stores(Gfx::IntSize size) +static void publish_backing_store_pair_if_needed(HTML::Navigable& navigable, i32 front_bitmap_id, Gfx::SharedImage front_backing_store, i32 back_bitmap_id, Gfx::SharedImage back_backing_store) { - auto skia_backend_context = Gfx::SkiaBackendContext::the(); + if (!navigable.is_top_level_traversable()) + return; - RefPtr front_store; - RefPtr back_store; + auto& page_client = navigable.top_level_traversable()->page().client(); + page_client.page_did_allocate_backing_stores(front_bitmap_id, move(front_backing_store), back_bitmap_id, move(back_backing_store)); +} + +struct BackingStorePair { + RefPtr front; + RefPtr back; +}; + +#ifdef USE_VULKAN +static NonnullRefPtr create_gpu_painting_surface_with_bitmap_flush(Gfx::IntSize size, Gfx::SharedImageBuffer& buffer) +{ + auto surface = Gfx::PaintingSurface::create_with_size(size, Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied); + auto bitmap = buffer.bitmap(); + surface->on_flush = [bitmap = move(bitmap)](auto& surface) { + surface.read_into_bitmap(*bitmap); + }; + return surface; +} +#endif + +static BackingStorePair create_shareable_bitmap_backing_stores(HTML::Navigable& navigable, Gfx::IntSize size, i32 front_bitmap_id, i32 back_bitmap_id, RefPtr const& skia_backend_context) +{ auto front_buffer = Gfx::SharedImageBuffer::create(size); auto back_buffer = Gfx::SharedImageBuffer::create(size); - - m_front_bitmap_id = m_next_bitmap_id++; - m_back_bitmap_id = m_next_bitmap_id++; - - if (m_navigable->is_top_level_traversable()) { - auto& page_client = m_navigable->top_level_traversable()->page().client(); - page_client.page_did_allocate_backing_stores(m_front_bitmap_id, front_buffer.export_shared_image(), m_back_bitmap_id, back_buffer.export_shared_image()); - } + publish_backing_store_pair_if_needed(navigable, front_bitmap_id, front_buffer.export_shared_image(), back_bitmap_id, back_buffer.export_shared_image()); #ifdef AK_OS_MACOS if (skia_backend_context) { - front_store = Gfx::PaintingSurface::create_from_shared_image_buffer(front_buffer, *skia_backend_context); - back_store = Gfx::PaintingSurface::create_from_shared_image_buffer(back_buffer, *skia_backend_context); - } else { - front_store = Gfx::PaintingSurface::wrap_bitmap(*front_buffer.bitmap()); - back_store = Gfx::PaintingSurface::wrap_bitmap(*back_buffer.bitmap()); + return { + .front = Gfx::PaintingSurface::create_from_shared_image_buffer(front_buffer, *skia_backend_context), + .back = Gfx::PaintingSurface::create_from_shared_image_buffer(back_buffer, *skia_backend_context), + }; } #else # ifdef USE_VULKAN if (skia_backend_context) { - front_store = Gfx::PaintingSurface::create_with_size(size, Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied); - auto front_bitmap = front_buffer.bitmap(); - front_store->on_flush = [front_bitmap = move(front_bitmap)](auto& surface) { - surface.read_into_bitmap(*front_bitmap); - }; - back_store = Gfx::PaintingSurface::create_with_size(size, Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied); - auto back_bitmap = back_buffer.bitmap(); - back_store->on_flush = [back_bitmap = move(back_bitmap)](auto& surface) { - surface.read_into_bitmap(*back_bitmap); + return { + .front = create_gpu_painting_surface_with_bitmap_flush(size, front_buffer), + .back = create_gpu_painting_surface_with_bitmap_flush(size, back_buffer), }; } +# else + (void)skia_backend_context; # endif - - if (!front_store) - front_store = Gfx::PaintingSurface::wrap_bitmap(*front_buffer.bitmap()); - if (!back_store) - back_store = Gfx::PaintingSurface::wrap_bitmap(*back_buffer.bitmap()); #endif - m_allocated_size = size; + return { + .front = Gfx::PaintingSurface::wrap_bitmap(*front_buffer.bitmap()), + .back = Gfx::PaintingSurface::wrap_bitmap(*back_buffer.bitmap()), + }; +} - m_navigable->rendering_thread().update_backing_stores(front_store, back_store, m_front_bitmap_id, m_back_bitmap_id); +#ifdef USE_VULKAN_DMABUF_IMAGES +static ErrorOr create_linear_dmabuf_backing_stores(HTML::Navigable& navigable, Gfx::IntSize size, i32 front_bitmap_id, i32 back_bitmap_id, Gfx::SkiaBackendContext& skia_backend_context) +{ + VERIFY(navigable.is_top_level_traversable()); + + auto const& vulkan_context = skia_backend_context.vulkan_context(); + static constexpr Array linear_modifiers = { DRM_FORMAT_MOD_LINEAR }; + auto front_image_value = TRY(Gfx::create_shared_vulkan_image(vulkan_context, size.width(), size.height(), VK_FORMAT_B8G8R8A8_UNORM, linear_modifiers.span())); + auto back_image_value = TRY(Gfx::create_shared_vulkan_image(vulkan_context, size.width(), size.height(), VK_FORMAT_B8G8R8A8_UNORM, linear_modifiers.span())); + publish_backing_store_pair_if_needed(navigable, front_bitmap_id, Gfx::duplicate_shared_image(*front_image_value), back_bitmap_id, Gfx::duplicate_shared_image(*back_image_value)); + + return BackingStorePair { + .front = Gfx::PaintingSurface::create_from_vkimage(skia_backend_context, move(front_image_value), Gfx::PaintingSurface::Origin::TopLeft), + .back = Gfx::PaintingSurface::create_from_vkimage(skia_backend_context, move(back_image_value), Gfx::PaintingSurface::Origin::TopLeft), + }; +} +#endif + +void BackingStoreManager::reallocate_backing_stores(Gfx::IntSize size) +{ + auto skia_backend_context = Gfx::SkiaBackendContext::the(); + + m_front_bitmap_id = m_next_bitmap_id++; + m_back_bitmap_id = m_next_bitmap_id++; + + auto update_backing_stores = [&](RefPtr front_store, RefPtr back_store) { + m_allocated_size = size; + m_navigable->rendering_thread().update_backing_stores(move(front_store), move(back_store), m_front_bitmap_id, m_back_bitmap_id); + }; + +#ifdef USE_VULKAN_DMABUF_IMAGES + if (skia_backend_context && m_navigable->is_top_level_traversable()) { + auto backing_stores = create_linear_dmabuf_backing_stores(*m_navigable, size, m_front_bitmap_id, m_back_bitmap_id, *skia_backend_context); + if (!backing_stores.is_error()) { + auto backing_store_pair = backing_stores.release_value(); + update_backing_stores(move(backing_store_pair.front), move(backing_store_pair.back)); + return; + } + } +#endif + + auto backing_stores = create_shareable_bitmap_backing_stores(*m_navigable, size, m_front_bitmap_id, m_back_bitmap_id, skia_backend_context); + update_backing_stores(move(backing_stores.front), move(backing_stores.back)); } void BackingStoreManager::resize_backing_stores_if_needed(WindowResizingInProgress window_resize_in_progress)