mirror of
https://github.com/LadybirdBrowser/ladybird
synced 2026-04-26 01:35:08 +02:00
LibWeb: Make RenderingThread own display list and backing stores
This change prepares for a future where the rendering thread handles input events directly, allowing it to trigger repainting without waiting for the main thread. To support this, the compositor needs to own the display list, scroll state, and backing stores rather than receiving them per-frame from the main thread.
This commit is contained in:
committed by
Alexander Kalenik
parent
7bc7e80042
commit
516fb5f2fe
Notes:
github-actions[bot]
2026-02-06 11:06:11 +00:00
Author: https://github.com/kalenikaliaksandr Commit: https://github.com/LadybirdBrowser/ladybird/commit/516fb5f2fe3 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/7628
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
|
||||
* Copyright (c) 2025-2026, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
@@ -12,17 +12,167 @@
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
struct BackingStoreState {
|
||||
RefPtr<Gfx::PaintingSurface> front_store;
|
||||
RefPtr<Gfx::PaintingSurface> back_store;
|
||||
i32 front_bitmap_id { -1 };
|
||||
i32 back_bitmap_id { -1 };
|
||||
|
||||
void swap()
|
||||
{
|
||||
AK::swap(front_store, back_store);
|
||||
AK::swap(front_bitmap_id, back_bitmap_id);
|
||||
}
|
||||
|
||||
bool is_valid() const { return front_store && back_store; }
|
||||
};
|
||||
|
||||
struct UpdateDisplayListCommand {
|
||||
NonnullRefPtr<Painting::DisplayList> display_list;
|
||||
Painting::ScrollStateSnapshotByDisplayList scroll_state_snapshot;
|
||||
};
|
||||
|
||||
struct UpdateBackingStoresCommand {
|
||||
RefPtr<Gfx::PaintingSurface> front_store;
|
||||
RefPtr<Gfx::PaintingSurface> back_store;
|
||||
i32 front_bitmap_id;
|
||||
i32 back_bitmap_id;
|
||||
};
|
||||
|
||||
struct PresentFrameCommand {
|
||||
Function<void(i32)> callback;
|
||||
};
|
||||
|
||||
struct ScreenshotCommand {
|
||||
NonnullRefPtr<Gfx::PaintingSurface> target_surface;
|
||||
Function<void()> callback;
|
||||
};
|
||||
|
||||
using CompositorCommand = Variant<UpdateDisplayListCommand, UpdateBackingStoresCommand, PresentFrameCommand, ScreenshotCommand>;
|
||||
|
||||
class RenderingThread::ThreadData final : public AtomicRefCounted<ThreadData> {
|
||||
public:
|
||||
ThreadData(Core::EventLoop& main_thread_event_loop)
|
||||
: m_main_thread_event_loop(main_thread_event_loop)
|
||||
{
|
||||
}
|
||||
|
||||
~ThreadData() = default;
|
||||
|
||||
void set_skia_player(OwnPtr<Painting::DisplayListPlayerSkia>&& player)
|
||||
{
|
||||
m_skia_player = move(player);
|
||||
}
|
||||
|
||||
bool has_skia_player() const { return m_skia_player != nullptr; }
|
||||
|
||||
void exit()
|
||||
{
|
||||
Threading::MutexLocker const locker { m_mutex };
|
||||
m_exit = true;
|
||||
m_command_ready.signal();
|
||||
}
|
||||
|
||||
void enqueue_command(CompositorCommand&& command)
|
||||
{
|
||||
Threading::MutexLocker const locker { m_mutex };
|
||||
m_command_queue.enqueue(move(command));
|
||||
m_command_ready.signal();
|
||||
}
|
||||
|
||||
void compositor_loop()
|
||||
{
|
||||
while (true) {
|
||||
auto command = [this]() -> Optional<CompositorCommand> {
|
||||
Threading::MutexLocker const locker { m_mutex };
|
||||
while (m_command_queue.is_empty() && !m_exit) {
|
||||
m_command_ready.wait();
|
||||
}
|
||||
if (m_exit)
|
||||
return {};
|
||||
return m_command_queue.dequeue();
|
||||
}();
|
||||
|
||||
if (!command.has_value()) {
|
||||
VERIFY(m_exit);
|
||||
break;
|
||||
}
|
||||
|
||||
command->visit(
|
||||
[this](UpdateDisplayListCommand& cmd) {
|
||||
m_cached_display_list = move(cmd.display_list);
|
||||
m_cached_scroll_state_snapshot = move(cmd.scroll_state_snapshot);
|
||||
},
|
||||
[this](UpdateBackingStoresCommand& cmd) {
|
||||
m_backing_stores.front_store = move(cmd.front_store);
|
||||
m_backing_stores.back_store = move(cmd.back_store);
|
||||
m_backing_stores.front_bitmap_id = cmd.front_bitmap_id;
|
||||
m_backing_stores.back_bitmap_id = cmd.back_bitmap_id;
|
||||
},
|
||||
[this](PresentFrameCommand& cmd) {
|
||||
if (!m_cached_display_list || !m_backing_stores.is_valid())
|
||||
return;
|
||||
|
||||
m_skia_player->execute(*m_cached_display_list, Painting::ScrollStateSnapshotByDisplayList(m_cached_scroll_state_snapshot), *m_backing_stores.back_store);
|
||||
i32 rendered_bitmap_id = m_backing_stores.back_bitmap_id;
|
||||
m_backing_stores.swap();
|
||||
|
||||
if (cmd.callback) {
|
||||
invoke_on_main_thread([callback = move(cmd.callback), rendered_bitmap_id]() mutable {
|
||||
callback(rendered_bitmap_id);
|
||||
});
|
||||
}
|
||||
},
|
||||
[this](ScreenshotCommand& cmd) {
|
||||
if (!m_cached_display_list)
|
||||
return;
|
||||
m_skia_player->execute(*m_cached_display_list, Painting::ScrollStateSnapshotByDisplayList(m_cached_scroll_state_snapshot), *cmd.target_surface);
|
||||
if (cmd.callback) {
|
||||
invoke_on_main_thread([callback = move(cmd.callback)]() mutable {
|
||||
callback();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (m_exit)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
template<typename Invokee>
|
||||
void invoke_on_main_thread(Invokee invokee)
|
||||
{
|
||||
if (m_exit)
|
||||
return;
|
||||
m_main_thread_event_loop.deferred_invoke([self = NonnullRefPtr(*this), invokee = move(invokee)]() mutable {
|
||||
invokee();
|
||||
});
|
||||
}
|
||||
|
||||
Core::EventLoop& m_main_thread_event_loop;
|
||||
|
||||
mutable Threading::Mutex m_mutex;
|
||||
mutable Threading::ConditionVariable m_command_ready { m_mutex };
|
||||
Atomic<bool> m_exit { false };
|
||||
|
||||
Queue<CompositorCommand> m_command_queue;
|
||||
|
||||
OwnPtr<Painting::DisplayListPlayerSkia> m_skia_player;
|
||||
RefPtr<Painting::DisplayList> m_cached_display_list;
|
||||
Painting::ScrollStateSnapshotByDisplayList m_cached_scroll_state_snapshot;
|
||||
BackingStoreState m_backing_stores;
|
||||
};
|
||||
|
||||
RenderingThread::RenderingThread()
|
||||
: m_main_thread_event_loop(Core::EventLoop::current())
|
||||
: m_thread_data(adopt_ref(*new ThreadData(Core::EventLoop::current())))
|
||||
, m_main_thread_exit_promise(Core::Promise<NonnullRefPtr<Core::EventReceiver>>::construct())
|
||||
{
|
||||
// FIXME: Come up with a better "event loop exited" notification mechanism.
|
||||
m_main_thread_exit_promise->on_rejection = [this](Error const&) -> void {
|
||||
Threading::MutexLocker const locker { m_rendering_task_mutex };
|
||||
m_exit = true;
|
||||
m_rendering_task_ready_wake_condition.signal();
|
||||
m_main_thread_exit_promise->on_rejection = [thread_data = m_thread_data](Error const&) -> void {
|
||||
thread_data->exit();
|
||||
};
|
||||
m_main_thread_event_loop.add_job(m_main_thread_exit_promise);
|
||||
Core::EventLoop::current().add_job(m_main_thread_exit_promise);
|
||||
}
|
||||
|
||||
RenderingThread::~RenderingThread()
|
||||
@@ -34,12 +184,11 @@ RenderingThread::~RenderingThread()
|
||||
}
|
||||
}
|
||||
|
||||
void RenderingThread::start(DisplayListPlayerType display_list_player_type)
|
||||
void RenderingThread::start(DisplayListPlayerType)
|
||||
{
|
||||
m_display_list_player_type = display_list_player_type;
|
||||
VERIFY(m_skia_player);
|
||||
m_thread = Threading::Thread::construct([this] {
|
||||
rendering_thread_loop();
|
||||
VERIFY(m_thread_data->has_skia_player());
|
||||
m_thread = Threading::Thread::construct([thread_data = m_thread_data] {
|
||||
thread_data->compositor_loop();
|
||||
return static_cast<intptr_t>(0);
|
||||
});
|
||||
m_thread->start();
|
||||
@@ -47,39 +196,27 @@ void RenderingThread::start(DisplayListPlayerType display_list_player_type)
|
||||
|
||||
void RenderingThread::set_skia_player(OwnPtr<Painting::DisplayListPlayerSkia>&& player)
|
||||
{
|
||||
m_skia_player = move(player);
|
||||
m_thread_data->set_skia_player(move(player));
|
||||
}
|
||||
|
||||
void RenderingThread::rendering_thread_loop()
|
||||
void RenderingThread::update_display_list(NonnullRefPtr<Painting::DisplayList> display_list, Painting::ScrollStateSnapshotByDisplayList&& scroll_state_snapshot)
|
||||
{
|
||||
while (true) {
|
||||
auto task = [this]() -> Optional<Task> {
|
||||
Threading::MutexLocker const locker { m_rendering_task_mutex };
|
||||
while (m_rendering_tasks.is_empty() && !m_exit) {
|
||||
m_rendering_task_ready_wake_condition.wait();
|
||||
}
|
||||
if (m_exit)
|
||||
return {};
|
||||
return m_rendering_tasks.dequeue();
|
||||
}();
|
||||
|
||||
if (!task.has_value()) {
|
||||
VERIFY(m_exit);
|
||||
break;
|
||||
}
|
||||
|
||||
m_skia_player->execute(*task->display_list, move(task->scroll_state_snapshot_by_display_list), task->painting_surface);
|
||||
if (m_exit)
|
||||
break;
|
||||
task->callback();
|
||||
}
|
||||
m_thread_data->enqueue_command(UpdateDisplayListCommand { move(display_list), move(scroll_state_snapshot) });
|
||||
}
|
||||
|
||||
void RenderingThread::enqueue_rendering_task(NonnullRefPtr<Painting::DisplayList> display_list, Painting::ScrollStateSnapshotByDisplayList&& scroll_state_snapshot_by_display_list, NonnullRefPtr<Gfx::PaintingSurface> painting_surface, Function<void()>&& callback)
|
||||
void RenderingThread::update_backing_stores(RefPtr<Gfx::PaintingSurface> front, RefPtr<Gfx::PaintingSurface> back, i32 front_id, i32 back_id)
|
||||
{
|
||||
Threading::MutexLocker const locker { m_rendering_task_mutex };
|
||||
m_rendering_tasks.enqueue(Task { move(display_list), move(scroll_state_snapshot_by_display_list), move(painting_surface), move(callback) });
|
||||
m_rendering_task_ready_wake_condition.signal();
|
||||
m_thread_data->enqueue_command(UpdateBackingStoresCommand { move(front), move(back), front_id, back_id });
|
||||
}
|
||||
|
||||
void RenderingThread::present_frame(Function<void(i32)>&& callback)
|
||||
{
|
||||
m_thread_data->enqueue_command(PresentFrameCommand { move(callback) });
|
||||
}
|
||||
|
||||
void RenderingThread::request_screenshot(NonnullRefPtr<Gfx::PaintingSurface> target_surface, Function<void()>&& callback)
|
||||
{
|
||||
m_thread_data->enqueue_command(ScreenshotCommand { move(target_surface), move(callback) });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user