diff --git a/Libraries/LibGfx/ImmutableBitmap.h b/Libraries/LibGfx/ImmutableBitmap.h index 4c8c94b9d2a..a444bad00bf 100644 --- a/Libraries/LibGfx/ImmutableBitmap.h +++ b/Libraries/LibGfx/ImmutableBitmap.h @@ -1,14 +1,14 @@ /* - * Copyright (c) 2023-2024, Aliaksandr Kalenik + * Copyright (c) 2023-2025, Aliaksandr Kalenik * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once +#include #include #include -#include #include #include #include @@ -20,7 +20,7 @@ namespace Gfx { struct ImmutableBitmapImpl; -class ImmutableBitmap final : public RefCounted { +class ImmutableBitmap final : public AtomicRefCounted { public: static NonnullRefPtr create(NonnullRefPtr bitmap, ColorSpace color_space = {}); static NonnullRefPtr create(NonnullRefPtr bitmap, AlphaType, ColorSpace color_space = {}); diff --git a/Libraries/LibGfx/PaintingSurface.h b/Libraries/LibGfx/PaintingSurface.h index b840e81b2b9..0760ad076af 100644 --- a/Libraries/LibGfx/PaintingSurface.h +++ b/Libraries/LibGfx/PaintingSurface.h @@ -1,14 +1,14 @@ /* - * Copyright (c) 2024, Aliaksandr Kalenik + * Copyright (c) 2024-2025, Aliaksandr Kalenik * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once +#include #include #include -#include #include #include #include @@ -23,7 +23,7 @@ class SkSurface; namespace Gfx { -class PaintingSurface : public RefCounted { +class PaintingSurface : public AtomicRefCounted { public: enum class Origin { TopLeft, diff --git a/Libraries/LibGfx/TextLayout.h b/Libraries/LibGfx/TextLayout.h index 0a638859839..10a8cdaa660 100644 --- a/Libraries/LibGfx/TextLayout.h +++ b/Libraries/LibGfx/TextLayout.h @@ -7,6 +7,7 @@ #pragma once +#include #include #include #include @@ -33,7 +34,7 @@ typedef struct ShapeFeature { using ShapeFeatures = Vector; -class GlyphRun : public RefCounted { +class GlyphRun : public AtomicRefCounted { public: enum class TextType { Common, diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index 8dd32119fac..c2afad4e37c 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -484,6 +484,7 @@ set(SOURCES HTML/PotentialCORSRequest.cpp HTML/PromiseRejectionEvent.cpp HTML/RadioNodeList.cpp + HTML/RenderingThread.cpp HTML/Scripting/Agent.cpp HTML/Scripting/ClassicScript.cpp HTML/Scripting/Environments.cpp @@ -973,7 +974,7 @@ set(GENERATED_SOURCES serenity_lib(LibWeb web) -target_link_libraries(LibWeb PRIVATE LibCore LibCompress LibCrypto LibJS LibHTTP LibGfx LibIPC LibRegex LibSyntax LibTextCodec LibUnicode LibMedia LibWasm LibXML LibIDL LibURL LibTLS LibRequests LibGC skia) +target_link_libraries(LibWeb PRIVATE LibCore LibCompress LibCrypto LibJS LibHTTP LibGfx LibIPC LibRegex LibSyntax LibTextCodec LibUnicode LibMedia LibWasm LibXML LibIDL LibURL LibTLS LibRequests LibGC LibThreading skia) if (APPLE) target_link_libraries(LibWeb PRIVATE unofficial::angle::libEGL unofficial::angle::libGLESv2) diff --git a/Libraries/LibWeb/HTML/RenderingThread.cpp b/Libraries/LibWeb/HTML/RenderingThread.cpp new file mode 100644 index 00000000000..519880ced48 --- /dev/null +++ b/Libraries/LibWeb/HTML/RenderingThread.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2025, Aliaksandr Kalenik + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +namespace Web::HTML { + +RenderingThread::RenderingThread() + : m_main_thread_event_loop(Core::EventLoop::current()) +{ +} + +RenderingThread::~RenderingThread() +{ + m_exit = true; + m_rendering_task_ready_wake_condition.signal(); + (void)m_thread->join(); +} + +void RenderingThread::start() +{ + VERIFY(m_skia_player); + m_thread = Threading::Thread::construct([this] { + rendering_thread_loop(); + return static_cast(0); + }); + m_thread->start(); +} + +void RenderingThread::rendering_thread_loop() +{ + while (true) { + auto task = [this]() -> Optional { + 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->set_surface(task->painting_surface); + m_skia_player->execute(*task->display_list); + m_main_thread_event_loop.deferred_invoke([callback = move(task->callback)] { + callback(); + }); + } +} + +void RenderingThread::enqueue_rendering_task(RefPtr display_list, NonnullRefPtr painting_surface, Function&& callback) +{ + Threading::MutexLocker const locker { m_rendering_task_mutex }; + m_rendering_tasks.enqueue(Task { move(display_list), move(painting_surface), move(callback) }); + m_rendering_task_ready_wake_condition.signal(); +} + +} diff --git a/Libraries/LibWeb/HTML/RenderingThread.h b/Libraries/LibWeb/HTML/RenderingThread.h new file mode 100644 index 00000000000..e7f385d6da4 --- /dev/null +++ b/Libraries/LibWeb/HTML/RenderingThread.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2025, Aliaksandr Kalenik + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace Web::HTML { + +class RenderingThread { + AK_MAKE_NONCOPYABLE(RenderingThread); + AK_MAKE_NONMOVABLE(RenderingThread); + +public: + RenderingThread(); + ~RenderingThread(); + + void start(); + void set_skia_player(OwnPtr&& player) { m_skia_player = move(player); } + void enqueue_rendering_task(RefPtr, NonnullRefPtr, Function&& callback); + +private: + void rendering_thread_loop(); + + Core::EventLoop& m_main_thread_event_loop; + + OwnPtr m_skia_player; + + RefPtr m_thread; + Atomic m_exit { false }; + + struct Task { + RefPtr display_list; + NonnullRefPtr painting_surface; + Function callback; + }; + // NOTE: Queue will only contain multiple items in case tasks were scheduled by screenshot requests. + // Otherwise, it will contain only one item at a time. + Queue m_rendering_tasks; + Threading::Mutex m_rendering_task_mutex; + Threading::ConditionVariable m_rendering_task_ready_wake_condition { m_rendering_task_mutex }; +}; + +} diff --git a/Libraries/LibWeb/HTML/TraversableNavigable.cpp b/Libraries/LibWeb/HTML/TraversableNavigable.cpp index 0abc2fe374c..4dfac877fc1 100644 --- a/Libraries/LibWeb/HTML/TraversableNavigable.cpp +++ b/Libraries/LibWeb/HTML/TraversableNavigable.cpp @@ -53,12 +53,16 @@ TraversableNavigable::TraversableNavigable(GC::Ref page) , m_session_history_traversal_queue(vm().heap().allocate()) { auto display_list_player_type = page->client().display_list_player_type(); + OwnPtr skia_player; if (display_list_player_type == DisplayListPlayerType::SkiaGPUIfAvailable) { m_skia_backend_context = get_skia_backend_context(); - m_skia_player = make(m_skia_backend_context); + skia_player = make(m_skia_backend_context); } else { - m_skia_player = make(); + skia_player = make(); } + + m_rendering_thread.set_skia_player(move(skia_player)); + m_rendering_thread.start(); } TraversableNavigable::~TraversableNavigable() = default; @@ -1429,13 +1433,13 @@ NonnullRefPtr TraversableNavigable::painting_surface_for_b return *new_surface; } -void TraversableNavigable::paint(DevicePixelRect const& content_rect, Painting::BackingStore& target, PaintOptions paint_options) +RefPtr TraversableNavigable::record_display_list(DevicePixelRect const& content_rect, PaintOptions paint_options) { m_needs_repaint = false; auto document = active_document(); if (!document) - return; + return {}; for (auto& navigable : all_navigables()) { if (auto active_document = navigable->active_document(); active_document && active_document->paintable()) @@ -1447,13 +1451,12 @@ void TraversableNavigable::paint(DevicePixelRect const& content_rect, Painting:: paint_config.should_show_line_box_borders = paint_options.should_show_line_box_borders; paint_config.has_focus = paint_options.has_focus; paint_config.canvas_fill_rect = Gfx::IntRect { {}, content_rect.size() }; - auto display_list = document->record_display_list(paint_config); - if (!display_list) - return; + return document->record_display_list(paint_config); +} - auto painting_surface = painting_surface_for_backing_store(target); - m_skia_player->set_surface(painting_surface); - m_skia_player->execute(*display_list); +void TraversableNavigable::start_display_list_rendering(RefPtr display_list, NonnullRefPtr painting_surface, Function&& callback) +{ + m_rendering_thread.enqueue_rendering_task(move(display_list), move(painting_surface), move(callback)); } } diff --git a/Libraries/LibWeb/HTML/TraversableNavigable.h b/Libraries/LibWeb/HTML/TraversableNavigable.h index 51a980c8b60..88557b26f44 100644 --- a/Libraries/LibWeb/HTML/TraversableNavigable.h +++ b/Libraries/LibWeb/HTML/TraversableNavigable.h @@ -9,12 +9,12 @@ #include #include #include +#include #include #include #include #include #include -#include #ifdef AK_OS_MACOS # include @@ -97,7 +97,8 @@ public: [[nodiscard]] GC::Ptr currently_focused_area(); - void paint(Web::DevicePixelRect const&, Painting::BackingStore&, Web::PaintOptions); + RefPtr record_display_list(DevicePixelRect const&, PaintOptions); + void start_display_list_rendering(RefPtr display_list, NonnullRefPtr painting_surface, Function&& callback); enum class CheckIfUnloadingIsCanceledResult { CanceledByBeforeUnload, @@ -116,6 +117,8 @@ public: bool needs_repaint() const { return m_needs_repaint; } void set_needs_repaint() { m_needs_repaint = true; } + NonnullRefPtr painting_surface_for_backing_store(Painting::BackingStore&); + private: TraversableNavigable(GC::Ref); @@ -137,8 +140,6 @@ private: [[nodiscard]] bool can_go_forward() const; - NonnullRefPtr painting_surface_for_backing_store(Painting::BackingStore&); - // https://html.spec.whatwg.org/multipage/document-sequences.html#tn-current-session-history-step int m_current_session_history_step { 0 }; @@ -162,10 +163,11 @@ private: String m_window_handle; RefPtr m_skia_backend_context; - OwnPtr m_skia_player; HashMap> m_bitmap_to_surface; bool m_needs_repaint { true }; + + RenderingThread m_rendering_thread; }; struct BrowsingContextAndDocument { diff --git a/Libraries/LibWeb/Page/Page.h b/Libraries/LibWeb/Page/Page.h index 7c3944a687e..20ebdb679e0 100644 --- a/Libraries/LibWeb/Page/Page.h +++ b/Libraries/LibWeb/Page/Page.h @@ -324,7 +324,7 @@ public: virtual CSS::PreferredMotion preferred_motion() const = 0; virtual void paint_next_frame() = 0; virtual void process_screenshot_requests() = 0; - virtual void paint(DevicePixelRect const&, Painting::BackingStore&, PaintOptions = {}) = 0; + virtual void start_display_list_rendering(DevicePixelRect const&, Painting::BackingStore&, PaintOptions, Function&& callback) = 0; virtual Queue& input_event_queue() = 0; virtual void report_finished_handling_input_event(u64 page_id, EventResult event_was_handled) = 0; virtual void page_did_change_title(ByteString const&) { } diff --git a/Libraries/LibWeb/Painting/DisplayList.h b/Libraries/LibWeb/Painting/DisplayList.h index 048e5a03264..5d8967cad5f 100644 --- a/Libraries/LibWeb/Painting/DisplayList.h +++ b/Libraries/LibWeb/Painting/DisplayList.h @@ -76,7 +76,7 @@ private: RefPtr m_surface; }; -class DisplayList : public RefCounted { +class DisplayList : public AtomicRefCounted { public: static NonnullRefPtr create() { diff --git a/Libraries/LibWeb/SVG/SVGDecodedImageData.h b/Libraries/LibWeb/SVG/SVGDecodedImageData.h index a56a62f4f3c..fa55ab047b6 100644 --- a/Libraries/LibWeb/SVG/SVGDecodedImageData.h +++ b/Libraries/LibWeb/SVG/SVGDecodedImageData.h @@ -78,7 +78,7 @@ public: virtual void request_file(FileRequest) override { } virtual void paint_next_frame() override { } virtual void process_screenshot_requests() override { } - virtual void paint(DevicePixelRect const&, Painting::BackingStore&, Web::PaintOptions = {}) override { } + virtual void start_display_list_rendering(DevicePixelRect const&, Painting::BackingStore&, PaintOptions, Function&&) override { } virtual bool is_ready_to_paint() const override { return true; } virtual Queue& input_event_queue() override { VERIFY_NOT_REACHED(); } virtual void report_finished_handling_input_event([[maybe_unused]] u64 page_id, [[maybe_unused]] EventResult event_was_handled) override { } diff --git a/Libraries/LibWeb/WebDriver/Screenshot.cpp b/Libraries/LibWeb/WebDriver/Screenshot.cpp index fb451cec201..167db7931f0 100644 --- a/Libraries/LibWeb/WebDriver/Screenshot.cpp +++ b/Libraries/LibWeb/WebDriver/Screenshot.cpp @@ -57,7 +57,14 @@ ErrorOr, WebDriver::Error> draw_bounding_box_fr auto bitmap = MUST(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied, canvas.surface()->size())); auto backing_store = Web::Painting::BitmapBackingStore(bitmap); - browsing_context.page().client().paint(paint_rect.to_type(), backing_store); + IGNORE_USE_IN_ESCAPING_LAMBDA bool did_paint = false; + browsing_context.page().client().start_display_list_rendering(paint_rect.to_type(), backing_store, {}, [&did_paint] { + did_paint = true; + }); + HTML::main_thread_event_loop().spin_until(GC::create_function(HTML::main_thread_event_loop().heap(), [&] { + return did_paint; + })); + canvas.surface()->write_from_bitmap(*bitmap); // 7. Return success with canvas. diff --git a/Services/WebContent/PageClient.cpp b/Services/WebContent/PageClient.cpp index a981a27b1a4..7148350aaaa 100644 --- a/Services/WebContent/PageClient.cpp +++ b/Services/WebContent/PageClient.cpp @@ -206,14 +206,16 @@ void PageClient::process_screenshot_requests() auto rect = page().enclosing_device_rect(dom_node->paintable_box()->absolute_border_box_rect()); auto bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, rect.size().to_type()).release_value_but_fixme_should_propagate_errors(); auto backing_store = Web::Painting::BitmapBackingStore(*bitmap); - paint(rect, backing_store, { .paint_overlay = Web::PaintOptions::PaintOverlay::No }); - client().async_did_take_screenshot(m_id, bitmap->to_shareable_bitmap()); + start_display_list_rendering(rect, backing_store, { .paint_overlay = Web::PaintOptions::PaintOverlay::No }, [this, bitmap] { + client().async_did_take_screenshot(m_id, bitmap->to_shareable_bitmap()); + }); } else { Web::DevicePixelRect rect { { 0, 0 }, content_size() }; auto bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, rect.size().to_type()).release_value_but_fixme_should_propagate_errors(); auto backing_store = Web::Painting::BitmapBackingStore(*bitmap); - paint(rect, backing_store); - client().async_did_take_screenshot(m_id, bitmap->to_shareable_bitmap()); + start_display_list_rendering(rect, backing_store, {}, [this, bitmap] { + client().async_did_take_screenshot(m_id, bitmap->to_shareable_bitmap()); + }); } } } @@ -224,20 +226,23 @@ void PageClient::paint_next_frame() if (!back_store) return; - auto viewport_rect = page().css_to_device_rect(page().top_level_traversable()->viewport_rect()); - paint(viewport_rect, *back_store); - - m_backing_store_manager.swap_back_and_front(); - m_paint_state = PaintState::WaitingForClient; - client().async_did_paint(m_id, viewport_rect.to_type(), m_backing_store_manager.front_id()); + + auto viewport_rect = page().css_to_device_rect(page().top_level_traversable()->viewport_rect()); + start_display_list_rendering(viewport_rect, *back_store, {}, [this, viewport_rect]() { + m_backing_store_manager.swap_back_and_front(); + client().async_did_paint(m_id, viewport_rect.to_type(), m_backing_store_manager.front_id()); + }); } -void PageClient::paint(Web::DevicePixelRect const& content_rect, Web::Painting::BackingStore& target, Web::PaintOptions paint_options) +void PageClient::start_display_list_rendering(Web::DevicePixelRect const& content_rect, Web::Painting::BackingStore& target, Web::PaintOptions paint_options, Function&& callback) { paint_options.should_show_line_box_borders = m_should_show_line_box_borders; paint_options.has_focus = m_has_focus; - page().top_level_traversable()->paint(content_rect, target, paint_options); + auto& traversable = *page().top_level_traversable(); + auto display_list = traversable.record_display_list(content_rect, paint_options); + auto painting_surface = traversable.painting_surface_for_backing_store(target); + traversable.start_display_list_rendering(display_list, painting_surface, move(callback)); } Queue& PageClient::input_event_queue() diff --git a/Services/WebContent/PageClient.h b/Services/WebContent/PageClient.h index f971cfc1fd7..f96da84e314 100644 --- a/Services/WebContent/PageClient.h +++ b/Services/WebContent/PageClient.h @@ -48,7 +48,7 @@ public: virtual void paint_next_frame() override; virtual void process_screenshot_requests() override; - virtual void paint(Web::DevicePixelRect const& content_rect, Web::Painting::BackingStore&, Web::PaintOptions = {}) override; + virtual void start_display_list_rendering(Web::DevicePixelRect const& content_rect, Web::Painting::BackingStore&, Web::PaintOptions, Function&& callback) override; virtual Queue& input_event_queue() override; virtual void report_finished_handling_input_event(u64 page_id, Web::EventResult event_was_handled) override; diff --git a/Services/WebWorker/PageHost.cpp b/Services/WebWorker/PageHost.cpp index c977200c307..eb843174c15 100644 --- a/Services/WebWorker/PageHost.cpp +++ b/Services/WebWorker/PageHost.cpp @@ -77,7 +77,7 @@ Web::CSS::PreferredMotion PageHost::preferred_motion() const return Web::CSS::PreferredMotion::Auto; } -void PageHost::paint(Web::DevicePixelRect const&, Web::Painting::BackingStore&, Web::PaintOptions) +void PageHost::start_display_list_rendering(Web::DevicePixelRect const&, Web::Painting::BackingStore&, Web::PaintOptions, Function&&) { } diff --git a/Services/WebWorker/PageHost.h b/Services/WebWorker/PageHost.h index ea5f043d559..99342b6a192 100644 --- a/Services/WebWorker/PageHost.h +++ b/Services/WebWorker/PageHost.h @@ -33,7 +33,7 @@ public: virtual Web::CSS::PreferredMotion preferred_motion() const override; virtual void paint_next_frame() override { } virtual void process_screenshot_requests() override { } - virtual void paint(Web::DevicePixelRect const&, Web::Painting::BackingStore&, Web::PaintOptions = {}) override; + virtual void start_display_list_rendering(Web::DevicePixelRect const&, Web::Painting::BackingStore&, Web::PaintOptions, Function&& callback) override; virtual void request_file(Web::FileRequest) override; virtual bool is_ready_to_paint() const override { return true; } virtual Web::DisplayListPlayerType display_list_player_type() const override { VERIFY_NOT_REACHED(); }