2025-02-25 04:07:53 +01:00
|
|
|
/*
|
2026-01-26 12:59:43 +01:00
|
|
|
* Copyright (c) 2025-2026, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
|
2025-02-25 04:07:53 +01:00
|
|
|
*
|
|
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include <LibCore/EventLoop.h>
|
2025-09-17 15:45:03 -05:00
|
|
|
#include <LibThreading/Thread.h>
|
2025-02-25 04:07:53 +01:00
|
|
|
#include <LibWeb/HTML/RenderingThread.h>
|
2025-04-03 18:03:17 +02:00
|
|
|
#include <LibWeb/HTML/TraversableNavigable.h>
|
2025-07-10 19:24:30 +02:00
|
|
|
#include <LibWeb/Painting/DisplayListPlayerSkia.h>
|
2025-02-25 04:07:53 +01:00
|
|
|
|
|
|
|
|
namespace Web::HTML {
|
|
|
|
|
|
2026-01-26 12:59:43 +01:00
|
|
|
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 ScreenshotCommand {
|
|
|
|
|
NonnullRefPtr<Gfx::PaintingSurface> target_surface;
|
|
|
|
|
Function<void()> callback;
|
|
|
|
|
};
|
|
|
|
|
|
2026-01-26 17:47:49 +01:00
|
|
|
using CompositorCommand = Variant<UpdateDisplayListCommand, UpdateBackingStoresCommand, ScreenshotCommand>;
|
2026-01-26 12:59:43 +01:00
|
|
|
|
|
|
|
|
class RenderingThread::ThreadData final : public AtomicRefCounted<ThreadData> {
|
|
|
|
|
public:
|
2026-01-26 15:53:29 +01:00
|
|
|
ThreadData(Core::EventLoop& main_thread_event_loop, RenderingThread::PresentationCallback presentation_callback)
|
2026-01-26 12:59:43 +01:00
|
|
|
: m_main_thread_event_loop(main_thread_event_loop)
|
2026-01-26 15:53:29 +01:00
|
|
|
, m_presentation_callback(move(presentation_callback))
|
2026-01-26 12:59:43 +01:00
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
~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();
|
2026-01-26 17:47:49 +01:00
|
|
|
m_ready_to_paint.signal();
|
2026-01-26 12:59:43 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void enqueue_command(CompositorCommand&& command)
|
|
|
|
|
{
|
|
|
|
|
Threading::MutexLocker const locker { m_mutex };
|
|
|
|
|
m_command_queue.enqueue(move(command));
|
|
|
|
|
m_command_ready.signal();
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-26 17:47:49 +01:00
|
|
|
void set_needs_present(Gfx::IntRect viewport_rect)
|
|
|
|
|
{
|
|
|
|
|
Threading::MutexLocker const locker { m_mutex };
|
|
|
|
|
m_needs_present = true;
|
|
|
|
|
m_pending_viewport_rect = viewport_rect;
|
|
|
|
|
m_command_ready.signal();
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-26 12:59:43 +01:00
|
|
|
void compositor_loop()
|
|
|
|
|
{
|
|
|
|
|
while (true) {
|
2026-01-26 17:47:49 +01:00
|
|
|
{
|
2026-01-26 12:59:43 +01:00
|
|
|
Threading::MutexLocker const locker { m_mutex };
|
2026-01-26 17:47:49 +01:00
|
|
|
while (m_command_queue.is_empty() && !m_needs_present && !m_exit) {
|
2026-01-26 12:59:43 +01:00
|
|
|
m_command_ready.wait();
|
|
|
|
|
}
|
|
|
|
|
if (m_exit)
|
2026-01-26 17:47:49 +01:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
|
auto command = [this]() -> Optional<CompositorCommand> {
|
|
|
|
|
Threading::MutexLocker const locker { m_mutex };
|
|
|
|
|
if (m_command_queue.is_empty())
|
|
|
|
|
return {};
|
|
|
|
|
return m_command_queue.dequeue();
|
|
|
|
|
}();
|
|
|
|
|
|
|
|
|
|
if (!command.has_value())
|
|
|
|
|
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](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;
|
|
|
|
|
}
|
2026-01-26 12:59:43 +01:00
|
|
|
|
2026-01-26 17:47:49 +01:00
|
|
|
if (m_exit)
|
2026-01-26 12:59:43 +01:00
|
|
|
break;
|
2026-01-26 17:47:49 +01:00
|
|
|
|
|
|
|
|
bool should_present = false;
|
|
|
|
|
Gfx::IntRect viewport_rect;
|
|
|
|
|
{
|
|
|
|
|
Threading::MutexLocker const locker { m_mutex };
|
|
|
|
|
if (m_needs_present) {
|
|
|
|
|
should_present = true;
|
|
|
|
|
viewport_rect = m_pending_viewport_rect;
|
|
|
|
|
m_needs_present = false;
|
|
|
|
|
}
|
2026-01-26 12:59:43 +01:00
|
|
|
}
|
|
|
|
|
|
2026-01-26 17:47:49 +01:00
|
|
|
if (should_present) {
|
|
|
|
|
// Block if we already have a frame queued (back pressure)
|
|
|
|
|
{
|
|
|
|
|
Threading::MutexLocker const locker { m_mutex };
|
|
|
|
|
while (m_queued_rasterization_tasks > 1 && !m_exit) {
|
|
|
|
|
m_ready_to_paint.wait();
|
|
|
|
|
}
|
|
|
|
|
if (m_exit)
|
|
|
|
|
break;
|
|
|
|
|
}
|
2026-01-26 12:59:43 +01:00
|
|
|
|
2026-01-26 17:47:49 +01:00
|
|
|
if (m_cached_display_list && m_backing_stores.is_valid()) {
|
2026-01-26 12:59:43 +01:00
|
|
|
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();
|
|
|
|
|
|
2026-01-26 17:47:49 +01:00
|
|
|
m_queued_rasterization_tasks++;
|
|
|
|
|
|
|
|
|
|
invoke_on_main_thread([this, viewport_rect, rendered_bitmap_id]() {
|
2026-01-26 15:53:29 +01:00
|
|
|
m_presentation_callback(viewport_rect, rendered_bitmap_id);
|
|
|
|
|
});
|
2026-01-26 17:47:49 +01:00
|
|
|
}
|
|
|
|
|
}
|
2026-01-26 12:59:43 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
2026-01-26 15:53:29 +01:00
|
|
|
RenderingThread::PresentationCallback m_presentation_callback;
|
2026-01-26 12:59:43 +01:00
|
|
|
|
|
|
|
|
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;
|
2026-01-26 17:47:49 +01:00
|
|
|
|
|
|
|
|
Atomic<i32> m_queued_rasterization_tasks { 0 };
|
|
|
|
|
mutable Threading::ConditionVariable m_ready_to_paint { m_mutex };
|
|
|
|
|
|
|
|
|
|
bool m_needs_present { false };
|
|
|
|
|
Gfx::IntRect m_pending_viewport_rect;
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
void decrement_queued_tasks()
|
|
|
|
|
{
|
|
|
|
|
Threading::MutexLocker const locker { m_mutex };
|
|
|
|
|
VERIFY(m_queued_rasterization_tasks >= 1 && m_queued_rasterization_tasks <= 2);
|
|
|
|
|
m_queued_rasterization_tasks--;
|
|
|
|
|
m_ready_to_paint.signal();
|
|
|
|
|
}
|
2026-01-26 12:59:43 +01:00
|
|
|
};
|
|
|
|
|
|
2026-01-26 15:53:29 +01:00
|
|
|
RenderingThread::RenderingThread(PresentationCallback presentation_callback)
|
|
|
|
|
: m_thread_data(adopt_ref(*new ThreadData(Core::EventLoop::current(), move(presentation_callback))))
|
2025-04-28 17:59:55 -06:00
|
|
|
, m_main_thread_exit_promise(Core::Promise<NonnullRefPtr<Core::EventReceiver>>::construct())
|
2025-02-25 04:07:53 +01:00
|
|
|
{
|
2025-04-28 17:59:55 -06:00
|
|
|
// FIXME: Come up with a better "event loop exited" notification mechanism.
|
2026-01-26 12:59:43 +01:00
|
|
|
m_main_thread_exit_promise->on_rejection = [thread_data = m_thread_data](Error const&) -> void {
|
|
|
|
|
thread_data->exit();
|
2025-04-28 17:59:55 -06:00
|
|
|
};
|
2026-01-26 12:59:43 +01:00
|
|
|
Core::EventLoop::current().add_job(m_main_thread_exit_promise);
|
2025-02-25 04:07:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RenderingThread::~RenderingThread()
|
|
|
|
|
{
|
2025-05-29 16:39:43 -06:00
|
|
|
// Note: Promise rejection is expected to signal the thread to exit.
|
|
|
|
|
m_main_thread_exit_promise->reject(Error::from_errno(ECANCELED));
|
2025-06-05 19:58:53 +02:00
|
|
|
if (m_thread) {
|
|
|
|
|
(void)m_thread->join();
|
|
|
|
|
}
|
2025-02-25 04:07:53 +01:00
|
|
|
}
|
|
|
|
|
|
2026-01-26 12:59:43 +01:00
|
|
|
void RenderingThread::start(DisplayListPlayerType)
|
2025-02-25 04:07:53 +01:00
|
|
|
{
|
2026-01-26 12:59:43 +01:00
|
|
|
VERIFY(m_thread_data->has_skia_player());
|
2026-01-23 15:14:34 -06:00
|
|
|
m_thread = Threading::Thread::construct("Renderer"sv, [thread_data = m_thread_data] {
|
2026-01-26 12:59:43 +01:00
|
|
|
thread_data->compositor_loop();
|
2025-02-25 04:07:53 +01:00
|
|
|
return static_cast<intptr_t>(0);
|
2026-01-23 15:14:34 -06:00
|
|
|
});
|
2025-02-25 04:07:53 +01:00
|
|
|
m_thread->start();
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-10 19:24:30 +02:00
|
|
|
void RenderingThread::set_skia_player(OwnPtr<Painting::DisplayListPlayerSkia>&& player)
|
|
|
|
|
{
|
2026-01-26 12:59:43 +01:00
|
|
|
m_thread_data->set_skia_player(move(player));
|
2025-07-10 19:24:30 +02:00
|
|
|
}
|
|
|
|
|
|
2026-01-26 12:59:43 +01:00
|
|
|
void RenderingThread::update_display_list(NonnullRefPtr<Painting::DisplayList> display_list, Painting::ScrollStateSnapshotByDisplayList&& scroll_state_snapshot)
|
2025-02-25 04:07:53 +01:00
|
|
|
{
|
2026-01-26 12:59:43 +01:00
|
|
|
m_thread_data->enqueue_command(UpdateDisplayListCommand { move(display_list), move(scroll_state_snapshot) });
|
|
|
|
|
}
|
2025-02-25 04:07:53 +01:00
|
|
|
|
2026-01-26 12:59:43 +01:00
|
|
|
void RenderingThread::update_backing_stores(RefPtr<Gfx::PaintingSurface> front, RefPtr<Gfx::PaintingSurface> back, i32 front_id, i32 back_id)
|
|
|
|
|
{
|
|
|
|
|
m_thread_data->enqueue_command(UpdateBackingStoresCommand { move(front), move(back), front_id, back_id });
|
|
|
|
|
}
|
2025-02-25 04:07:53 +01:00
|
|
|
|
2026-01-26 15:53:29 +01:00
|
|
|
void RenderingThread::present_frame(Gfx::IntRect viewport_rect)
|
2026-01-26 12:59:43 +01:00
|
|
|
{
|
2026-01-26 17:47:49 +01:00
|
|
|
m_thread_data->set_needs_present(viewport_rect);
|
2025-02-25 04:07:53 +01:00
|
|
|
}
|
|
|
|
|
|
2026-01-26 12:59:43 +01:00
|
|
|
void RenderingThread::request_screenshot(NonnullRefPtr<Gfx::PaintingSurface> target_surface, Function<void()>&& callback)
|
2025-02-25 04:07:53 +01:00
|
|
|
{
|
2026-01-26 12:59:43 +01:00
|
|
|
m_thread_data->enqueue_command(ScreenshotCommand { move(target_surface), move(callback) });
|
2025-02-25 04:07:53 +01:00
|
|
|
}
|
|
|
|
|
|
2026-01-26 17:47:49 +01:00
|
|
|
void RenderingThread::ready_to_paint()
|
|
|
|
|
{
|
|
|
|
|
m_thread_data->decrement_queued_tasks();
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-25 04:07:53 +01:00
|
|
|
}
|