ladybird/Libraries/LibWeb/HTML/RenderingThread.cpp

223 lines
7.6 KiB
C++
Raw Normal View History

/*
* Copyright (c) 2025-2026, 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 {
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 {
Gfx::IntRect viewport_rect;
};
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, RenderingThread::PresentationCallback presentation_callback)
: m_main_thread_event_loop(main_thread_event_loop)
, m_presentation_callback(move(presentation_callback))
{
}
~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();
invoke_on_main_thread([this, viewport_rect = cmd.viewport_rect, rendered_bitmap_id]() {
m_presentation_callback(viewport_rect, 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;
RenderingThread::PresentationCallback m_presentation_callback;
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(PresentationCallback presentation_callback)
: m_thread_data(adopt_ref(*new ThreadData(Core::EventLoop::current(), move(presentation_callback))))
, 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 = [thread_data = m_thread_data](Error const&) -> void {
thread_data->exit();
};
Core::EventLoop::current().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)
{
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();
}
void RenderingThread::set_skia_player(OwnPtr<Painting::DisplayListPlayerSkia>&& player)
{
m_thread_data->set_skia_player(move(player));
}
void RenderingThread::update_display_list(NonnullRefPtr<Painting::DisplayList> display_list, Painting::ScrollStateSnapshotByDisplayList&& scroll_state_snapshot)
{
m_thread_data->enqueue_command(UpdateDisplayListCommand { move(display_list), move(scroll_state_snapshot) });
}
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 });
}
void RenderingThread::present_frame(Gfx::IntRect viewport_rect)
{
m_thread_data->enqueue_command(PresentFrameCommand { viewport_rect });
}
void RenderingThread::request_screenshot(NonnullRefPtr<Gfx::PaintingSurface> target_surface, Function<void()>&& callback)
{
m_thread_data->enqueue_command(ScreenshotCommand { move(target_surface), move(callback) });
}
}