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.
This commit is contained in:
Aliaksandr Kalenik 2026-04-07 20:38:22 +02:00 committed by Alexander Kalenik
parent 0fdafaaf48
commit 9a4e731746
5 changed files with 272 additions and 50 deletions

View file

@ -8,6 +8,9 @@
#include <LibIPC/Attachment.h>
#include <LibIPC/Decoder.h>
#include <LibIPC/Encoder.h>
#ifdef USE_VULKAN_DMABUF_IMAGES
# include <LibGfx/VulkanImage.h>
#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<int>(vulkan_image.info.extent.width), static_cast<int>(vulkan_image.info.extent.height)),
.drm_format = vk_format_to_drm_format(vulkan_image.info.format),
.pitch = static_cast<size_t>(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<void> 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<Gfx::LinuxDmaBufHandle> decode(Decoder& decoder)
{
return Gfx::LinuxDmaBufHandle {
.bitmap_format = TRY(decoder.decode<Gfx::BitmapFormat>()),
.alpha_type = TRY(decoder.decode<Gfx::AlphaType>()),
.size = TRY(decoder.decode<Gfx::IntSize>()),
.drm_format = TRY(decoder.decode<u32>()),
.pitch = TRY(decoder.decode<size_t>()),
.modifier = TRY(decoder.decode<u64>()),
.file = TRY(decoder.decode<IPC::File>()),
};
}
#endif
template<>
ErrorOr<void> 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<void> {
TRY(encoder.encode(SharedImageBackingType::ShareableBitmap));
TRY(encoder.encode(shareable_bitmap));
return {};
},
[&](Gfx::LinuxDmaBufHandle const& dmabuf) -> ErrorOr<void> {
TRY(encoder.encode(SharedImageBackingType::LinuxDmaBuf));
TRY(encoder.encode(dmabuf));
return {};
});
#endif
return {};
}
@ -55,9 +133,14 @@ ErrorOr<Gfx::SharedImage> 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<Gfx::ShareableBitmap>());
VERIFY(shareable_bitmap.is_valid());
return Gfx::SharedImage { move(shareable_bitmap) };
switch (TRY(decoder.decode<SharedImageBackingType>())) {
case SharedImageBackingType::ShareableBitmap:
return Gfx::SharedImage { TRY(decoder.decode<Gfx::ShareableBitmap>()) };
case SharedImageBackingType::LinuxDmaBuf:
return Gfx::SharedImage { TRY(decoder.decode<Gfx::LinuxDmaBufHandle>()) };
default:
VERIFY_NOT_REACHED();
}
#endif
}

View file

@ -8,18 +8,39 @@
#include <AK/Error.h>
#include <AK/Noncopyable.h>
#include <AK/Variant.h>
#include <LibGfx/Bitmap.h>
#include <LibGfx/Forward.h>
#include <LibGfx/ShareableBitmap.h>
#include <LibIPC/File.h>
#include <LibIPC/Forward.h>
#ifdef AK_OS_MACOS
# include <LibCore/MachPort.h>
#else
# include <LibGfx/ShareableBitmap.h>
#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<ShareableBitmap, LinuxDmaBufHandle> m_data;
#endif
friend class SharedImageBuffer;
@ -50,6 +76,14 @@ private:
namespace IPC {
#ifndef AK_OS_MACOS
template<>
ErrorOr<void> encode(Encoder&, Gfx::LinuxDmaBufHandle const&);
template<>
ErrorOr<Gfx::LinuxDmaBufHandle> decode(Decoder&);
#endif
template<>
ErrorOr<void> encode(Encoder&, Gfx::SharedImage const&);

View file

@ -7,6 +7,11 @@
#include <LibGfx/Bitmap.h>
#include <LibGfx/SharedImageBuffer.h>
#ifdef USE_VULKAN_DMABUF_IMAGES
# include <libdrm/drm_fourcc.h>
# include <sys/mman.h>
#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<Bitmap> 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> 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
}

View file

@ -6,12 +6,24 @@
#ifdef USE_VULKAN_DMABUF_IMAGES
# include <AK/Array.h>
# include <AK/Format.h>
# include <AK/Vector.h>
# include <LibGfx/VulkanImage.h>
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<NonnullRefPtr<VulkanImage>> 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");
}

View file

@ -4,6 +4,7 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Array.h>
#include <LibCore/Timer.h>
#include <LibGfx/PaintingSurface.h>
#include <LibGfx/SharedImageBuffer.h>
@ -12,6 +13,10 @@
#include <LibWeb/Painting/BackingStoreManager.h>
#include <WebContent/PageClient.h>
#ifdef USE_VULKAN_DMABUF_IMAGES
# include <LibGfx/VulkanImage.h>
#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<Gfx::PaintingSurface> front_store;
RefPtr<Gfx::PaintingSurface> 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<Gfx::PaintingSurface> front;
RefPtr<Gfx::PaintingSurface> back;
};
#ifdef USE_VULKAN
static NonnullRefPtr<Gfx::PaintingSurface> 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<Gfx::SkiaBackendContext> 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<BackingStorePair> 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<uint64_t, 1> 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<Gfx::PaintingSurface> front_store, RefPtr<Gfx::PaintingSurface> 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)