Everywhere: Use IOSurface as backing store on macOS

Using mmap-allocated memory for backing stores does not allow us to
benefit from using GPU-accelerated painting, because all the performance
increase we get is mostly negated by reading the GPU-allocated texture
back into RAM, so it can be shared with the browser process.

With IOSurface, we get a framebuffer that is both shareable between
processes and can be used as underlying memory for an OpenGL/Metal
texture.

This change does not yet benefit from using IOSurface and merely wraps
them into Gfx::Bitmap to be used by the CPU painter.
This commit is contained in:
Aliaksandr Kalenik 2024-06-20 21:34:51 +03:00 committed by Andreas Kling
parent e37071ae05
commit c92f8ab1ea
15 changed files with 266 additions and 56 deletions

View File

@ -18,6 +18,8 @@
#include <LibWebView/Database.h>
#include <LibWebView/ProcessManager.h>
#include <LibWebView/URL.h>
#include <LibWebView/ViewImplementation.h>
#include <LibWebView/WebContentClient.h>
#import <Application/Application.h>
#import <Application/ApplicationDelegate.h>
@ -123,6 +125,10 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
mach_port_server->on_receive_child_mach_port = [](auto pid, auto port) {
WebView::ProcessManager::the().add_process(pid, move(port));
};
mach_port_server->on_receive_backing_stores = [](Ladybird::MachPortServer::BackingStoresMessage message) {
auto view = WebView::WebContentClient::view_for_pid_and_page_id(message.pid, message.page_id);
view->did_allocate_iosurface_backing_stores(message.front_backing_store_id, move(message.front_backing_store_port), message.back_backing_store_id, move(message.back_backing_store_port));
};
auto database = TRY(WebView::Database::create());
auto cookie_jar = TRY(WebView::CookieJar::create(*database));

View File

@ -6,6 +6,7 @@
#include "MachPortServer.h"
#include <AK/Debug.h>
#include <LibCore/Platform/MachMessageTypes.h>
#include <LibCore/Platform/ProcessStatisticsMach.h>
namespace Ladybird {
@ -55,8 +56,7 @@ ErrorOr<void> MachPortServer::allocate_server_port()
void MachPortServer::thread_loop()
{
while (!m_should_stop.load(MemoryOrder::memory_order_acquire)) {
Core::Platform::ParentPortMessage message {};
Core::Platform::ReceivedMachMessage message {};
// Get the pid of the child from the audit trailer so we can associate the port w/it
mach_msg_options_t const options = MACH_RCV_MSG | MACH_RCV_TRAILER_TYPE(MACH_RCV_TRAILER_AUDIT) | MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT);
@ -68,23 +68,38 @@ void MachPortServer::thread_loop()
break;
}
if (message.header.msgh_id != Core::Platform::SELF_TASK_PORT_MESSAGE_ID) {
dbgln("Received message with id {}, ignoring", message.header.msgh_id);
if (message.header.msgh_id == Core::Platform::BACKING_STORE_IOSURFACES_MESSAGE_ID) {
auto pid = static_cast<pid_t>(message.body.parent_iosurface.trailer.msgh_audit.val[5]);
auto const& backing_stores_message = message.body.parent_iosurface;
auto front_child_port = Core::MachPort::adopt_right(backing_stores_message.front_descriptor.name, Core::MachPort::PortRight::Send);
auto back_child_port = Core::MachPort::adopt_right(backing_stores_message.back_descriptor.name, Core::MachPort::PortRight::Send);
auto const& metadata = backing_stores_message.metadata;
if (on_receive_backing_stores)
on_receive_backing_stores({ .pid = pid,
.page_id = metadata.page_id,
.front_backing_store_id = metadata.front_backing_store_id,
.back_backing_store_id = metadata.back_backing_store_id,
.front_backing_store_port = move(front_child_port),
.back_backing_store_port = move(back_child_port) });
continue;
}
if (MACH_MSGH_BITS_LOCAL(message.header.msgh_bits) != MACH_MSG_TYPE_MOVE_SEND) {
dbgln("Received message with invalid local port rights {}, ignoring", MACH_MSGH_BITS_LOCAL(message.header.msgh_bits));
if (message.header.msgh_id == Core::Platform::SELF_TASK_PORT_MESSAGE_ID) {
if (MACH_MSGH_BITS_LOCAL(message.header.msgh_bits) != MACH_MSG_TYPE_MOVE_SEND) {
dbgln("Received message with invalid local port rights {}, ignoring", MACH_MSGH_BITS_LOCAL(message.header.msgh_bits));
continue;
}
auto const& task_port_message = message.body.parent;
auto pid = static_cast<pid_t>(task_port_message.trailer.msgh_audit.val[5]);
auto child_port = Core::MachPort::adopt_right(task_port_message.port_descriptor.name, Core::MachPort::PortRight::Send);
dbgln_if(MACH_PORT_DEBUG, "Received child port {:x} from pid {}", child_port.port(), pid);
if (on_receive_child_mach_port)
on_receive_child_mach_port(pid, move(child_port));
continue;
}
auto pid = static_cast<pid_t>(message.trailer.msgh_audit.val[5]);
auto child_port = Core::MachPort::adopt_right(message.port_descriptor.name, Core::MachPort::PortRight::Send);
dbgln_if(MACH_PORT_DEBUG, "Received child port {:x} from pid {}", child_port.port(), pid);
if (on_receive_child_mach_port)
on_receive_child_mach_port(pid, move(child_port));
dbgln("Received message with id {}, ignoring", message.header.msgh_id);
}
}
}

View File

@ -31,6 +31,15 @@ public:
bool is_initialized();
Function<void(pid_t, Core::MachPort)> on_receive_child_mach_port;
struct BackingStoresMessage {
pid_t pid { -1 };
u64 page_id { 0 };
i32 front_backing_store_id { 0 };
i32 back_backing_store_id { 0 };
Core::MachPort front_backing_store_port;
Core::MachPort back_backing_store_port;
};
Function<void(BackingStoresMessage)> on_receive_backing_stores;
ByteString const& server_port_name() const { return m_server_port_name; }

View File

@ -150,6 +150,10 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
mach_port_server->on_receive_child_mach_port = [](auto pid, auto port) {
WebView::ProcessManager::the().add_process(pid, move(port));
};
mach_port_server->on_receive_backing_stores = [](Ladybird::MachPortServer::BackingStoresMessage message) {
auto view = WebView::WebContentClient::view_for_pid_and_page_id(message.pid, message.page_id);
view->did_allocate_iosurface_backing_stores(message.front_backing_store_id, move(message.front_backing_store_port), message.back_backing_store_id, move(message.back_backing_store_port));
};
#endif
RefPtr<WebView::Database> database;

View File

@ -148,7 +148,8 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
#if defined(AK_OS_MACOS)
if (!mach_server_name.is_empty()) {
Core::Platform::register_with_mach_server(mach_server_name);
auto server_port = Core::Platform::register_with_mach_server(mach_server_name);
WebContent::BackingStoreManager::set_browser_mach_port(move(server_port));
}
#endif

View File

@ -49,4 +49,8 @@ class UDPSocket;
enum class TimerShouldFireWhenNotVisible;
#ifdef AK_OS_MACH
class MachPort;
#endif
}

View File

@ -0,0 +1,65 @@
/*
* Copyright (c) 2024, Andrew Kaster <akaster@serenityos.org>
* Copyright (c) 2024, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Platform.h>
#if !defined(AK_OS_MACH)
# error "This file is only available on Mach platforms"
#endif
#include <mach/mach.h>
namespace Core::Platform {
struct MessageBodyWithSelfTaskPort {
mach_msg_body_t body;
mach_msg_port_descriptor_t port_descriptor;
mach_msg_audit_trailer_t trailer;
};
struct MessageWithSelfTaskPort {
mach_msg_header_t header;
mach_msg_body_t body;
mach_msg_port_descriptor_t port_descriptor;
};
struct BackingStoreMetadata {
u64 page_id { 0 };
i32 back_backing_store_id { 0 };
i32 front_backing_store_id { 0 };
};
struct MessageBodyWithBackingStores {
mach_msg_body_t body;
mach_msg_port_descriptor_t front_descriptor;
mach_msg_port_descriptor_t back_descriptor;
BackingStoreMetadata metadata;
mach_msg_audit_trailer_t trailer;
};
struct MessageWithBackingStores {
mach_msg_header_t header;
mach_msg_body_t body;
mach_msg_port_descriptor_t front_descriptor;
mach_msg_port_descriptor_t back_descriptor;
BackingStoreMetadata metadata;
};
struct ReceivedMachMessage {
mach_msg_header_t header;
union {
MessageBodyWithSelfTaskPort parent;
MessageBodyWithBackingStores parent_iosurface;
} body;
};
static constexpr mach_msg_id_t SELF_TASK_PORT_MESSAGE_ID = 0x1234CAFE;
static constexpr mach_msg_id_t BACKING_STORE_IOSURFACES_MESSAGE_ID = 0x1234CAFF;
}

View File

@ -13,6 +13,7 @@
#include <AK/ByteString.h>
#include <AK/Time.h>
#include <LibCore/MachPort.h>
#include <LibCore/Platform/MachMessageTypes.h>
#include <LibCore/Platform/ProcessStatisticsMach.h>
namespace Core::Platform {
@ -75,17 +76,17 @@ ErrorOr<void> update_process_statistics(ProcessStatistics& statistics)
return {};
}
void register_with_mach_server(ByteString const& server_name)
MachPort register_with_mach_server(ByteString const& server_name)
{
auto server_port_or_error = Core::MachPort::look_up_from_bootstrap_server(server_name);
if (server_port_or_error.is_error()) {
dbgln("Failed to lookup server port: {}", server_port_or_error.error());
return;
VERIFY_NOT_REACHED();
}
auto server_port = server_port_or_error.release_value();
// Send our own task port to the server so they can query statistics about us
ChildPortMessage message {};
MessageWithSelfTaskPort message {};
message.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSGH_BITS_ZERO) | MACH_MSGH_BITS_COMPLEX;
message.header.msgh_size = sizeof(message);
message.header.msgh_remote_port = server_port.port();
@ -101,8 +102,10 @@ void register_with_mach_server(ByteString const& server_name)
auto const send_result = mach_msg(&message.header, MACH_SEND_MSG | MACH_SEND_TIMEOUT, message.header.msgh_size, 0, MACH_PORT_NULL, timeout, MACH_PORT_NULL);
if (send_result != KERN_SUCCESS) {
dbgln("Failed to send message to server: {}", mach_error_string(send_result));
return;
VERIFY_NOT_REACHED();
}
return server_port;
}
}

View File

@ -17,21 +17,6 @@
namespace Core::Platform {
struct ChildPortMessage {
mach_msg_header_t header;
mach_msg_body_t body;
mach_msg_port_descriptor_t port_descriptor;
};
struct ParentPortMessage {
mach_msg_header_t header;
mach_msg_body_t body;
mach_msg_port_descriptor_t port_descriptor;
mach_msg_audit_trailer_t trailer; // for the child's pid
};
static constexpr mach_msg_id_t SELF_TASK_PORT_MESSAGE_ID = 0x1234CAFE;
void register_with_mach_server(ByteString const& server_name);
MachPort register_with_mach_server(ByteString const& server_name);
}

View File

@ -12,6 +12,11 @@
#include <LibWeb/Infra/Strings.h>
#include <LibWebView/ViewImplementation.h>
#ifdef AK_OS_MACOS
# include <LibCore/IOSurface.h>
# include <LibCore/MachPort.h>
#endif
namespace WebView {
ViewImplementation::ViewImplementation()
@ -397,6 +402,32 @@ void ViewImplementation::did_allocate_backing_stores(Badge<WebContentClient>, i3
m_client_state.back_bitmap.id = back_bitmap_id;
}
#ifdef AK_OS_MACOS
void ViewImplementation::did_allocate_iosurface_backing_stores(i32 front_id, Core::MachPort&& front_port, i32 back_id, Core::MachPort&& back_port)
{
if (m_client_state.has_usable_bitmap) {
// NOTE: We keep the outgoing front bitmap as a backup so we have something to paint until we get a new one.
m_backup_bitmap = m_client_state.front_bitmap.bitmap;
m_backup_bitmap_size = m_client_state.front_bitmap.last_painted_size;
}
m_client_state.has_usable_bitmap = false;
auto front_iosurface = Core::IOSurfaceHandle::from_mach_port(move(front_port));
auto back_iosurface = Core::IOSurfaceHandle::from_mach_port(move(back_port));
auto front_size = Gfx::IntSize { front_iosurface.width(), front_iosurface.height() };
auto back_size = Gfx::IntSize { back_iosurface.width(), back_iosurface.height() };
auto front_bitmap = Gfx::Bitmap::create_wrapper(Gfx::BitmapFormat::BGRA8888, front_size, front_size.width() * front_iosurface.bytes_per_element(), front_iosurface.data(), [handle = move(front_iosurface)] {});
auto back_bitmap = Gfx::Bitmap::create_wrapper(Gfx::BitmapFormat::BGRA8888, back_size, back_size.width() * back_iosurface.bytes_per_element(), back_iosurface.data(), [handle = move(back_iosurface)] {});
m_client_state.front_bitmap.bitmap = front_bitmap.release_value_but_fixme_should_propagate_errors();
m_client_state.front_bitmap.id = front_id;
m_client_state.back_bitmap.bitmap = back_bitmap.release_value_but_fixme_should_propagate_errors();
m_client_state.back_bitmap.id = back_id;
}
#endif
void ViewImplementation::handle_resize()
{
client().async_set_viewport_size(page_id(), this->viewport_size());

View File

@ -12,6 +12,7 @@
#include <AK/LexicalPath.h>
#include <AK/Queue.h>
#include <AK/String.h>
#include <LibCore/Forward.h>
#include <LibCore/Promise.h>
#include <LibGfx/Forward.h>
#include <LibGfx/StandardCursor.h>
@ -118,6 +119,9 @@ public:
void did_update_navigation_buttons_state(Badge<WebContentClient>, bool back_enabled, bool forward_enabled) const;
void did_allocate_backing_stores(Badge<WebContentClient>, i32 front_bitmap_id, Gfx::ShareableBitmap const&, i32 back_bitmap_id, Gfx::ShareableBitmap const&);
#ifdef AK_OS_MACOS
void did_allocate_iosurface_backing_stores(i32 front_bitmap_id, Core::MachPort&&, i32 back_bitmap_id, Core::MachPort&&);
#endif
enum class ScreenshotType {
Visible,

View File

@ -11,12 +11,29 @@
namespace WebView {
static HashTable<WebContentClient*> s_clients;
Optional<ViewImplementation&> WebContentClient::view_for_pid_and_page_id(pid_t pid, u64 page_id)
{
for (auto* client : s_clients) {
if (client->m_process_handle.pid == pid)
return client->view_for_page_id(page_id);
}
return {};
}
WebContentClient::WebContentClient(NonnullOwnPtr<Core::LocalSocket> socket, ViewImplementation& view)
: IPC::ConnectionToServer<WebContentClientEndpoint, WebContentServerEndpoint>(*this, move(socket))
{
s_clients.set(this);
m_views.set(0, &view);
}
WebContentClient::~WebContentClient()
{
s_clients.remove(this);
}
void WebContentClient::die()
{
VERIFY(on_web_content_process_crash);

View File

@ -26,7 +26,10 @@ class WebContentClient final
IPC_CLIENT_CONNECTION(WebContentClient, "/tmp/session/%sid/portal/webcontent"sv);
public:
static Optional<ViewImplementation&> view_for_pid_and_page_id(pid_t pid, u64 page_id);
WebContentClient(NonnullOwnPtr<Core::LocalSocket>, ViewImplementation&);
~WebContentClient();
void register_view(u64 page_id, ViewImplementation&);
void unregister_view(u64 page_id);

View File

@ -9,8 +9,22 @@
#include <WebContent/BackingStoreManager.h>
#include <WebContent/PageClient.h>
#ifdef AK_OS_MACOS
# include <LibCore/IOSurface.h>
# include <LibCore/MachPort.h>
# include <LibCore/Platform/MachMessageTypes.h>
#endif
namespace WebContent {
#ifdef AK_OS_MACOS
static Optional<Core::MachPort> s_browser_mach_port;
void BackingStoreManager::set_browser_mach_port(Core::MachPort&& port)
{
s_browser_mach_port = move(port);
}
#endif
BackingStoreManager::BackingStoreManager(PageClient& page_client)
: m_page_client(page_client)
{
@ -24,10 +38,71 @@ void BackingStoreManager::restart_resize_timer()
m_backing_store_shrink_timer->restart();
}
void BackingStoreManager::reallocate_backing_stores(Gfx::IntSize size)
{
#ifdef AK_OS_MACOS
if (s_browser_mach_port.has_value()) {
auto back_iosurface = Core::IOSurfaceHandle::create(size.width(), size.height());
auto back_iosurface_port = back_iosurface.create_mach_port();
auto front_iosurface = Core::IOSurfaceHandle::create(size.width(), size.height());
auto front_iosurface_port = front_iosurface.create_mach_port();
m_front_bitmap_id = m_next_bitmap_id++;
m_back_bitmap_id = m_next_bitmap_id++;
m_front_bitmap = Gfx::Bitmap::create_wrapper(Gfx::BitmapFormat::BGRA8888, size, size.width() * front_iosurface.bytes_per_element(), front_iosurface.data(), [handle = move(front_iosurface)] {}).release_value();
m_back_bitmap = Gfx::Bitmap::create_wrapper(Gfx::BitmapFormat::BGRA8888, size, size.width() * back_iosurface.bytes_per_element(), back_iosurface.data(), [handle = move(back_iosurface)] {}).release_value();
Core::Platform::BackingStoreMetadata metadata;
metadata.page_id = m_page_client.m_id;
metadata.front_backing_store_id = m_front_bitmap_id;
metadata.back_backing_store_id = m_back_bitmap_id;
Core::Platform::MessageWithBackingStores message;
message.header.msgh_remote_port = s_browser_mach_port->port();
message.header.msgh_local_port = MACH_PORT_NULL;
message.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0) | MACH_MSGH_BITS_COMPLEX;
message.header.msgh_size = sizeof(message);
message.header.msgh_id = Core::Platform::BACKING_STORE_IOSURFACES_MESSAGE_ID;
message.body.msgh_descriptor_count = 2;
message.front_descriptor.name = front_iosurface_port.release();
message.front_descriptor.disposition = MACH_MSG_TYPE_MOVE_SEND;
message.front_descriptor.type = MACH_MSG_PORT_DESCRIPTOR;
message.back_descriptor.name = back_iosurface_port.release();
message.back_descriptor.disposition = MACH_MSG_TYPE_MOVE_SEND;
message.back_descriptor.type = MACH_MSG_PORT_DESCRIPTOR;
message.metadata = metadata;
mach_msg_timeout_t const timeout = 100; // milliseconds
auto const send_result = mach_msg(&message.header, MACH_SEND_MSG | MACH_SEND_TIMEOUT, message.header.msgh_size, 0, MACH_PORT_NULL, timeout, MACH_PORT_NULL);
if (send_result != KERN_SUCCESS) {
dbgln("Failed to send message to server: {}", mach_error_string(send_result));
VERIFY_NOT_REACHED();
}
return;
}
#endif
m_front_bitmap_id = m_next_bitmap_id++;
m_back_bitmap_id = m_next_bitmap_id++;
m_front_bitmap = Gfx::Bitmap::create_shareable(Gfx::BitmapFormat::BGRA8888, size).release_value();
m_back_bitmap = Gfx::Bitmap::create_shareable(Gfx::BitmapFormat::BGRA8888, size).release_value();
m_page_client.page_did_allocate_backing_stores(m_front_bitmap_id, m_front_bitmap->to_shareable_bitmap(), m_back_bitmap_id, m_back_bitmap->to_shareable_bitmap());
}
void BackingStoreManager::resize_backing_stores_if_needed(WindowResizingInProgress window_resize_in_progress)
{
auto css_pixels_viewport_rect = m_page_client.page().top_level_traversable()->viewport_rect();
auto viewport_size = m_page_client.page().css_to_device_rect(css_pixels_viewport_rect).size();
auto css_pixels_viewpor_rect = m_page_client.page().top_level_traversable()->viewport_rect();
auto viewport_size = m_page_client.page().css_to_device_rect(css_pixels_viewpor_rect).size();
if (viewport_size.is_empty())
return;
@ -43,26 +118,8 @@ void BackingStoreManager::resize_backing_stores_if_needed(WindowResizingInProgre
m_back_bitmap.clear();
}
auto old_front_bitmap_id = m_front_bitmap_id;
auto old_back_bitmap_id = m_back_bitmap_id;
auto reallocate_backing_store_if_needed = [&](RefPtr<Gfx::Bitmap>& bitmap, int& id) {
if (!bitmap || !bitmap->size().contains(minimum_needed_size.to_type<int>())) {
if (auto new_bitmap_or_error = Gfx::Bitmap::create_shareable(Gfx::BitmapFormat::BGRA8888, minimum_needed_size.to_type<int>()); !new_bitmap_or_error.is_error()) {
bitmap = new_bitmap_or_error.release_value();
id = m_next_bitmap_id++;
}
}
};
reallocate_backing_store_if_needed(m_front_bitmap, m_front_bitmap_id);
reallocate_backing_store_if_needed(m_back_bitmap, m_back_bitmap_id);
auto& front_bitmap = m_front_bitmap;
auto& back_bitmap = m_back_bitmap;
if (m_front_bitmap_id != old_front_bitmap_id || m_back_bitmap_id != old_back_bitmap_id) {
m_page_client.page_did_allocate_backing_stores(m_front_bitmap_id, front_bitmap->to_shareable_bitmap(), m_back_bitmap_id, back_bitmap->to_shareable_bitmap());
if (!m_front_bitmap || !m_back_bitmap || !m_front_bitmap->size().contains(minimum_needed_size.to_type<int>())) {
reallocate_backing_stores(minimum_needed_size.to_type<int>());
}
}

View File

@ -6,6 +6,7 @@
#pragma once
#include <LibCore/Forward.h>
#include <LibWeb/Page/Page.h>
#include <WebContent/Forward.h>
@ -13,11 +14,16 @@ namespace WebContent {
class BackingStoreManager {
public:
#ifdef AK_OS_MACOS
static void set_browser_mach_port(Core::MachPort&&);
#endif
enum class WindowResizingInProgress {
No,
Yes
};
void resize_backing_stores_if_needed(WindowResizingInProgress window_resize_in_progress);
void reallocate_backing_stores(Gfx::IntSize);
void restart_resize_timer();
RefPtr<Gfx::Bitmap> back_bitmap() { return m_back_bitmap; }