ladybird/Libraries/LibWeb/HTML/RenderingThread.cpp
Aliaksandr Kalenik d7f830cdd1 LibWeb: Execute rasterization callback on the rendering thread
Previously, we enqueued a task on the main thread's event loop to
execute the callback. This meant that even though the rendering thread
had finished producing the next frame, there was still a delay before
the main thread notified the UI process.

This change makes the rendering thread execute the callback directly.
This should be safe, as the only pointer captured by the callback is the
traversable `PageClient`, which is expected to remain alive for as long
as the rendering thread exists. The callback then invokes either
`page_did_paint()` or `page_did_take_screenshot()`, both of which
enqueue an IPC message, which is safe to do since `SendQueue` is
protected by a mutex.
2025-10-10 17:25:55 +02:00

85 lines
2.8 KiB
C++

/*
* Copyright (c) 2025, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibCore/EventLoop.h>
#include <LibThreading/Thread.h>
#include <LibWeb/HTML/RenderingThread.h>
#include <LibWeb/HTML/TraversableNavigable.h>
#include <LibWeb/Painting/DisplayListPlayerSkia.h>
namespace Web::HTML {
RenderingThread::RenderingThread()
: m_main_thread_event_loop(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_event_loop.add_job(m_main_thread_exit_promise);
}
RenderingThread::~RenderingThread()
{
// Note: Promise rejection is expected to signal the thread to exit.
m_main_thread_exit_promise->reject(Error::from_errno(ECANCELED));
if (m_thread) {
(void)m_thread->join();
}
}
void RenderingThread::start(DisplayListPlayerType display_list_player_type)
{
m_display_list_player_type = display_list_player_type;
VERIFY(m_skia_player);
m_thread = Threading::Thread::construct([this] {
rendering_thread_loop();
return static_cast<intptr_t>(0);
});
m_thread->start();
}
void RenderingThread::set_skia_player(OwnPtr<Painting::DisplayListPlayerSkia>&& player)
{
m_skia_player = move(player);
}
void RenderingThread::rendering_thread_loop()
{
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();
}
}
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)
{
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();
}
}