ladybird/Libraries/LibWeb/Compositor/CompositorThread.cpp
Aliaksandr Kalenik d840eea59c LibWeb: Split compositor thread state by context
Move compositor-thread state that belongs to one navigable into a
record keyed by CompositorContextId. Commands now carry that context id
through the queue, and context methods route display lists, scroll
state, backing stores, presentation, and async-scroll state to their own
record.

This keeps the existing one-thread-per-navigable model intact while
preparing the compositor thread to host multiple page contexts.
2026-05-18 20:11:31 +02:00

1821 lines
87 KiB
C++

/*
* Copyright (c) 2025-2026, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibCore/EventLoop.h>
#include <LibCore/Timer.h>
#include <LibGfx/PaintingSurface.h>
#include <LibGfx/SharedImage.h>
#include <LibGfx/SkiaBackendContext.h>
#include <LibGfx/SkiaUtils.h>
#include <LibThreading/Thread.h>
#include <LibWeb/Compositor/AsyncScrollTree.h>
#include <LibWeb/Compositor/AsyncScrollingState.h>
#include <LibWeb/Compositor/BackingStoreManager.h>
#include <LibWeb/Compositor/CompositorThread.h>
#include <LibWeb/HTML/EventLoop/EventLoop.h>
#include <LibWeb/Page/InputEvent.h>
#include <LibWeb/Painting/DisplayListPlayerSkia.h>
#include <AK/Debug.h>
#include <AK/HashMap.h>
#include <AK/NeverDestroyed.h>
#include <AK/Queue.h>
#include <AK/Time.h>
#include <core/SkCanvas.h>
#include <core/SkColor.h>
#include <core/SkPaint.h>
#include <core/SkRRect.h>
#include <gpu/ganesh/GrDirectContext.h>
#include <LibCore/Platform/ScopedAutoreleasePool.h>
namespace Web::Compositor {
static constexpr auto skia_deferred_cleanup_interval = AK::Duration::from_seconds(1);
static constexpr auto skia_aggressive_cleanup_interval = AK::Duration::from_seconds(5);
static constexpr auto skia_deferred_cleanup_resource_age = std::chrono::seconds(5);
static constexpr auto skia_resource_cache_high_watermark = 384 * MiB;
static constexpr auto skia_resource_cache_critical_watermark = 512 * MiB;
struct UpdateDisplayListCommand {
NonnullRefPtr<Painting::DisplayList> display_list;
Painting::DisplayListResourceTransaction resource_transaction;
Painting::ScrollStateSnapshot scroll_state_snapshot;
};
struct AsyncScrollByCommand {
Gfx::FloatPoint position;
Gfx::FloatPoint delta_in_device_pixels;
Gfx::IntRect viewport_rect;
AsyncScrollNodeID scroll_target;
Optional<AsyncScrollOperationID> operation_id;
};
struct ViewportScrollbarDragCommand {
size_t scrollbar_index { 0 };
float primary_position { 0 };
float thumb_grab_position { 0 };
};
struct UpdateScrollStateCommand {
Painting::ScrollStateSnapshot scroll_state_snapshot;
};
struct UpdateCompositorSurfaceCommand {
Painting::CompositorSurfaceId surface_id;
Gfx::SharedImage shared_image;
};
struct ClearCompositorSurfaceCommand {
Painting::CompositorSurfaceId surface_id;
};
struct ViewportSizeUpdatedCommand {
Gfx::IntSize viewport_size;
bool is_top_level_traversable { false };
WindowResizingInProgress window_resize_in_progress { WindowResizingInProgress::No };
};
struct ScreenshotCommand {
NonnullRefPtr<Gfx::PaintingSurface> target_surface;
Function<void()> callback;
};
using CompositorCommand = Variant<UpdateDisplayListCommand, AsyncScrollByCommand, ViewportScrollbarDragCommand,
UpdateScrollStateCommand, UpdateCompositorSurfaceCommand, ClearCompositorSurfaceCommand, ViewportSizeUpdatedCommand,
ScreenshotCommand>;
struct CompositorCommandEnvelope {
CompositorContextId context_id;
CompositorCommand command;
};
static SkRect to_skia_rect(Gfx::IntRect const& rect)
{
return SkRect::MakeXYWH(rect.x(), rect.y(), rect.width(), rect.height());
}
static Gfx::Orientation orientation_for_scrollbar(ViewportScrollbar const& scrollbar)
{
return scrollbar.vertical ? Gfx::Orientation::Vertical : Gfx::Orientation::Horizontal;
}
struct ViewportScrollbarIdentity {
AsyncScrollNodeID scroll_node_id;
bool vertical { false };
};
static ViewportScrollbarIdentity viewport_scrollbar_identity(ViewportScrollbar const& scrollbar)
{
return { scrollbar.scroll_node_id, scrollbar.vertical };
}
static Optional<ViewportScrollbarIdentity> viewport_scrollbar_identity_at(ReadonlySpan<ViewportScrollbar> scrollbars, Optional<size_t> scrollbar_index)
{
if (!scrollbar_index.has_value() || *scrollbar_index >= scrollbars.size())
return {};
return viewport_scrollbar_identity(scrollbars[*scrollbar_index]);
}
static Optional<size_t> find_viewport_scrollbar_index(ReadonlySpan<ViewportScrollbar> scrollbars, ViewportScrollbarIdentity identity)
{
for (size_t i = 0; i < scrollbars.size(); ++i) {
if (scrollbars[i].scroll_node_id == identity.scroll_node_id && scrollbars[i].vertical == identity.vertical)
return i;
}
return {};
}
static void set_or_append_pending_scroll_offset(Vector<AsyncScrollOffset>& pending_scroll_offsets, AsyncScrollOffset const& scroll_offset)
{
for (auto& existing : pending_scroll_offsets) {
if (existing.stable_node_id == scroll_offset.stable_node_id) {
existing.compositor_scroll_offset = scroll_offset.compositor_scroll_offset;
existing.unadopted_scroll_delta.translate_by(scroll_offset.unadopted_scroll_delta);
return;
}
}
pending_scroll_offsets.append(scroll_offset);
}
static Gfx::IntRect scrollbar_gutter_rect(ViewportScrollbar const& scrollbar, bool expanded)
{
return expanded ? scrollbar.expanded_gutter_rect : scrollbar.gutter_rect;
}
static double scrollbar_scroll_size(ViewportScrollbar const& scrollbar, bool expanded)
{
return expanded ? scrollbar.expanded_scroll_size : scrollbar.scroll_size;
}
static Gfx::IntRect translated_thumb_rect(ViewportScrollbar const& scrollbar, Painting::ScrollStateSnapshot const& scroll_state_snapshot, bool expanded)
{
auto thumb_rect = expanded ? scrollbar.expanded_thumb_rect : scrollbar.thumb_rect;
auto scroll_size = scrollbar_scroll_size(scrollbar, expanded);
auto device_offset = scroll_state_snapshot.device_offset_for_index(scrollbar.scroll_frame_index);
if (scrollbar.vertical)
thumb_rect.translate_by(0, static_cast<int>(-device_offset.y() * scroll_size));
else
thumb_rect.translate_by(static_cast<int>(-device_offset.x() * scroll_size), 0);
return thumb_rect;
}
static Gfx::IntRect translated_thumb_rect(ViewportScrollbar const& scrollbar, Gfx::FloatPoint scroll_offset, bool expanded)
{
auto orientation = orientation_for_scrollbar(scrollbar);
auto thumb_rect = expanded ? scrollbar.expanded_thumb_rect : scrollbar.thumb_rect;
thumb_rect.translate_primary_offset_for_orientation(orientation, static_cast<int>(scroll_offset.primary_offset_for_orientation(orientation) * scrollbar_scroll_size(scrollbar, expanded)));
return thumb_rect;
}
static Gfx::IntRect scrollbar_hit_rect(ViewportScrollbar const& scrollbar, Gfx::FloatPoint scroll_offset)
{
static constexpr int scrollbar_hit_slop = 4;
auto rect = translated_thumb_rect(scrollbar, scroll_offset, false).united(translated_thumb_rect(scrollbar, scroll_offset, true));
auto expanded_gutter_rect = scrollbar_gutter_rect(scrollbar, true);
if (!expanded_gutter_rect.is_empty())
rect.unite(expanded_gutter_rect);
rect.inflate(scrollbar_hit_slop, scrollbar_hit_slop);
return rect;
}
static void paint_viewport_scrollbar(Gfx::PaintingSurface& surface, ViewportScrollbar const& scrollbar, Painting::ScrollStateSnapshot const& scroll_state_snapshot, bool expanded)
{
auto thumb_rect = translated_thumb_rect(scrollbar, scroll_state_snapshot, expanded);
auto& canvas = surface.canvas();
SkPaint gutter_fill_paint;
gutter_fill_paint.setColor(to_skia_color(scrollbar.track_color));
canvas.drawRect(to_skia_rect(scrollbar_gutter_rect(scrollbar, expanded)), gutter_fill_paint);
auto skia_thumb_rect = to_skia_rect(thumb_rect);
auto radius = skia_thumb_rect.width() / 2;
auto thumb_rrect = SkRRect::MakeRectXY(skia_thumb_rect, radius, radius);
SkPaint thumb_fill_paint;
thumb_fill_paint.setColor(to_skia_color(scrollbar.thumb_color));
canvas.drawRRect(thumb_rrect, thumb_fill_paint);
SkPaint stroke_paint;
stroke_paint.setStroke(true);
stroke_paint.setStrokeWidth(1);
stroke_paint.setAntiAlias(true);
stroke_paint.setColor(to_skia_color(scrollbar.thumb_color.lightened()));
canvas.drawRRect(thumb_rrect, stroke_paint);
}
static void paint_viewport_scrollbars(Gfx::PaintingSurface& surface, ReadonlySpan<ViewportScrollbar> scrollbars, Painting::ScrollStateSnapshot const& scroll_state_snapshot, Optional<size_t> hovered_scrollbar_index, Optional<size_t> captured_scrollbar_index)
{
for (size_t i = 0; i < scrollbars.size(); ++i)
paint_viewport_scrollbar(surface, scrollbars[i], scroll_state_snapshot, hovered_scrollbar_index == i || captured_scrollbar_index == i);
}
static void flush_surface(Gfx::PaintingSurface& surface)
{
if (auto context = surface.skia_backend_context()) {
context->flush_and_submit(&surface.sk_surface());
auto* skia_context = context->sk_context();
static thread_local Optional<MonotonicTime> s_last_deferred_cleanup;
static thread_local Optional<MonotonicTime> s_last_aggressive_cleanup;
auto const now = MonotonicTime::now();
if (!s_last_deferred_cleanup.has_value() || now - *s_last_deferred_cleanup >= skia_deferred_cleanup_interval) {
s_last_deferred_cleanup = now;
skia_context->performDeferredCleanup(skia_deferred_cleanup_resource_age);
size_t resource_bytes = 0;
skia_context->getResourceCacheUsage(nullptr, &resource_bytes);
if (resource_bytes >= skia_resource_cache_high_watermark && (!s_last_aggressive_cleanup.has_value() || now - *s_last_aggressive_cleanup >= skia_aggressive_cleanup_interval)) {
s_last_aggressive_cleanup = now;
skia_context->performDeferredCleanup(std::chrono::milliseconds(0));
skia_context->getResourceCacheUsage(nullptr, &resource_bytes);
if (resource_bytes >= skia_resource_cache_critical_watermark)
skia_context->purgeUnlockedResources(GrPurgeResourceOptions::kScratchResourcesOnly);
}
}
}
surface.flush();
}
class CompositorThread::ThreadData final : public AtomicRefCounted<ThreadData> {
public:
ThreadData(u64 page_id, NonnullRefPtr<Core::WeakEventLoopReference>&& main_thread_event_loop)
: m_page_id(page_id)
, m_main_thread_event_loop(move(main_thread_event_loop))
{
}
~ThreadData() = default;
struct ContextState final : public AtomicRefCounted<ContextState> {
ContextState(CompositorThread::PagePresentationRegistration page_presentation_registration)
: presents_to_client(page_presentation_registration == CompositorThread::PagePresentationRegistration::Yes)
{
}
bool presents_to_client { false };
Painting::DisplayListResourceStorage display_list_resource_storage;
RefPtr<Painting::DisplayList> cached_display_list;
Painting::ScrollStateSnapshot cached_scroll_state_snapshot;
Vector<ViewportScrollbar> viewport_scrollbars;
Optional<size_t> hovered_viewport_scrollbar_index;
Optional<size_t> captured_viewport_scrollbar_index;
float viewport_scrollbar_thumb_grab_position { 0 };
mutable Sync::Mutex async_scroll_tree_mutex;
AsyncScrollTree async_scroll_tree;
BackingStoreManager backing_store_manager;
CompositorThread::PresentationMode presentation_mode { CompositorThread::PresentToUI {} };
Optional<i32> presented_bitmap_id_awaiting_ack;
bool is_rasterizing { false };
bool needs_present { false };
Gfx::IntRect pending_viewport_rect;
bool has_deferred_async_scroll_present { false };
Gfx::IntRect deferred_async_scroll_present_viewport_rect;
u64 submitted_frame_id { 0 };
u64 completed_frame_id { 0 };
Vector<AsyncScrollOffset> pending_async_scroll_offsets;
Vector<AsyncScrollOperationID> completed_async_scroll_operation_ids;
Atomic<AsyncScrollOperationID> next_async_scroll_operation_id { 0 };
Gfx::IntRect async_scrolling_viewport_rect;
Atomic<bool> has_async_scrolling_state { false };
Atomic<bool> can_accept_async_wheel_events { false };
u64 wheel_event_listener_state_generation { 0 };
WheelRoutingAdmission wheel_routing_admission { WheelRoutingAdmission::NoAsyncScrollingState };
};
void register_context(CompositorContextId context_id, CompositorThread::PagePresentationRegistration page_presentation_registration)
{
Sync::MutexLocker const locker { m_mutex };
VERIFY(!m_contexts.contains(context_id));
if (!m_default_context_id.has_value())
m_default_context_id = context_id;
if (page_presentation_registration == CompositorThread::PagePresentationRegistration::Yes)
m_page_presentation_context_id = context_id;
m_contexts.set(context_id, adopt_ref(*new ContextState(page_presentation_registration)));
}
void unregister_context(CompositorContextId context_id)
{
Sync::MutexLocker const locker { m_mutex };
m_contexts.remove(context_id);
if (m_default_context_id == context_id)
m_default_context_id.clear();
if (m_page_presentation_context_id == context_id)
m_page_presentation_context_id.clear();
}
RefPtr<ContextState> context_state(CompositorContextId context_id)
{
Sync::MutexLocker const locker { m_mutex };
return m_contexts.get(context_id).value_or(nullptr);
}
RefPtr<ContextState> default_context_state()
{
Sync::MutexLocker const locker { m_mutex };
if (!m_default_context_id.has_value())
return nullptr;
auto context = m_contexts.get(*m_default_context_id);
if (!context.has_value())
return nullptr;
return context.value();
}
RefPtr<ContextState> page_presentation_context_state()
{
Sync::MutexLocker const locker { m_mutex };
auto context_id = m_page_presentation_context_id.has_value() ? m_page_presentation_context_id : m_default_context_id;
if (!context_id.has_value())
return nullptr;
auto context = m_contexts.get(*context_id);
if (!context.has_value())
return nullptr;
return context.value();
}
Optional<CompositorContextId> page_presentation_context_id() const
{
Sync::MutexLocker const locker { m_mutex };
return m_page_presentation_context_id.has_value() ? m_page_presentation_context_id : m_default_context_id;
}
u64 page_id() const { return m_page_id; }
bool presents_to_client() const
{
Sync::MutexLocker const locker { m_mutex };
if (!m_page_presentation_context_id.has_value())
return false;
auto context = m_contexts.get(*m_page_presentation_context_id);
return context.has_value() && context.value()->presents_to_client;
}
void stop_presenting_to_client(CompositorContextId context_id)
{
Sync::MutexLocker const locker { m_mutex };
auto context = m_contexts.get(context_id);
if (!context.has_value())
return;
context.value()->presents_to_client = false;
if (m_page_presentation_context_id == context_id)
m_page_presentation_context_id.clear();
}
void set_presentation_mode(CompositorContextId context_id, CompositorThread::PresentationMode mode)
{
Sync::MutexLocker const locker { m_mutex };
if (auto context = m_contexts.get(context_id).value_or(nullptr))
context->presentation_mode = move(mode);
}
void exit()
{
Sync::MutexLocker const locker { m_mutex };
m_exit = true;
m_command_ready.signal();
m_frame_completed.broadcast();
}
void enqueue_command(CompositorContextId context_id, CompositorCommand&& command)
{
Sync::MutexLocker const locker { m_mutex };
m_command_queue.enqueue({ context_id, move(command) });
if constexpr (COMPOSITOR_DEBUG) {
auto context = m_contexts.get(context_id).value_or(nullptr);
dbgln("[Compositor] Compositor command queued (queue_size={}, awaiting_bitmap={}, needs_present={}, deferred_async_present={})",
m_command_queue.size(),
context ? context->presented_bitmap_id_awaiting_ack.value_or(-1) : -1,
context ? context->needs_present : false,
context ? context->has_deferred_async_scroll_present : false);
}
m_command_ready.signal();
}
void enqueue_command(CompositorCommand&& command)
{
VERIFY(m_default_context_id.has_value());
enqueue_command(*m_default_context_id, move(command));
}
u64 set_needs_present(CompositorContextId context_id, Gfx::IntRect viewport_rect)
{
Sync::MutexLocker const locker { m_mutex };
auto context_state = m_contexts.get(context_id);
if (!context_state.has_value())
return 0;
auto& context = *context_state.value();
context.needs_present = true;
context.pending_viewport_rect = viewport_rect;
context.submitted_frame_id++;
m_command_ready.signal();
return context.submitted_frame_id;
}
void mark_frame_complete(ContextState& context, u64 frame_id)
{
Sync::MutexLocker const locker { m_mutex };
context.completed_frame_id = frame_id;
m_frame_completed.broadcast();
}
void wait_for_frame(CompositorContextId context_id, u64 frame_id)
{
Sync::MutexLocker const locker { m_mutex };
auto context_state = m_contexts.get(context_id);
if (!context_state.has_value())
return;
auto& context = *context_state.value();
while (context.completed_frame_id < frame_id && !m_exit)
m_frame_completed.wait();
}
CompositorThread::PendingAsyncScrollUpdates take_pending_async_scroll_updates(CompositorContextId context_id)
{
Sync::MutexLocker const locker { m_mutex };
CompositorThread::PendingAsyncScrollUpdates updates;
if (auto context = m_contexts.get(context_id).value_or(nullptr)) {
AK::swap(updates.scroll_offsets, context->pending_async_scroll_offsets);
AK::swap(updates.completed_operation_ids, context->completed_async_scroll_operation_ids);
}
return updates;
}
bool should_defer_async_scroll_offset_adoption(CompositorContextId context_id) const
{
Sync::MutexLocker const locker { m_mutex };
auto context = m_contexts.get(context_id).value_or(nullptr);
if (!context)
return false;
return !context->pending_async_scroll_offsets.is_empty()
&& context->is_rasterizing;
}
bool should_defer_main_thread_present_for_async_scroll(CompositorContextId context_id) const
{
Sync::MutexLocker const locker { m_mutex };
auto context = m_contexts.get(context_id).value_or(nullptr);
if (!context)
return false;
return !context->pending_async_scroll_offsets.is_empty()
&& (context->is_rasterizing || context->has_deferred_async_scroll_present || has_presented_bitmap_awaiting_ack_while_locked(*context));
}
void invalidate_wheel_event_listener_state(CompositorContextId context_id, u64 generation)
{
Sync::MutexLocker const locker { m_mutex };
auto context = m_contexts.get(context_id).value_or(nullptr);
if (!context)
return;
context->wheel_event_listener_state_generation = max(context->wheel_event_listener_state_generation, generation);
context->wheel_routing_admission = WheelRoutingAdmission::StaleWheelEventListeners;
context->can_accept_async_wheel_events = false;
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Invalidated compositor wheel listener state (generation={})", generation);
}
struct WheelTarget {
Optional<AsyncScrollNodeID> node_id;
bool blocked_by_main_thread_region { false };
bool blocked_by_wheel_event_region { false };
};
WheelTarget hit_test_scroll_node_for_wheel(ContextState& context, Gfx::FloatPoint position, Gfx::FloatPoint delta) const
{
Sync::MutexLocker const locker { context.async_scroll_tree_mutex };
auto scroll_target = context.async_scroll_tree.hit_test_scroll_node_for_wheel(position, delta);
return {
.node_id = scroll_target.node_id,
.blocked_by_main_thread_region = scroll_target.blocked_by_main_thread_region,
.blocked_by_wheel_event_region = scroll_target.blocked_by_wheel_event_region,
};
}
CompositorThread::AsyncScrollEnqueueResult enqueue_async_scroll_by(CompositorContextId context_id, UniqueNodeID expected_document_id, Gfx::FloatPoint position,
Gfx::FloatPoint delta_in_device_pixels, Gfx::IntRect viewport_rect, CompositorThread::AsyncScrollOperationTracking operation_tracking)
{
auto context_state = this->context_state(context_id);
if (!context_state)
return {};
auto& context = *context_state;
if (!context.can_accept_async_wheel_events.load()) {
auto wheel_routing_admission = [&] {
Sync::MutexLocker const locker { m_mutex };
return context.wheel_routing_admission;
}();
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Rejecting async scroll enqueue: compositor cannot accept async wheel events ({})",
wheel_routing_admission_to_string(wheel_routing_admission));
return {};
}
auto scroll_target = hit_test_scroll_node_for_wheel(context, position, delta_in_device_pixels);
if (scroll_target.blocked_by_main_thread_region) {
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Rejecting async scroll enqueue: main-thread wheel region at {},{} device delta {},{}",
position.x(), position.y(), delta_in_device_pixels.x(), delta_in_device_pixels.y());
return {};
}
if (scroll_target.blocked_by_wheel_event_region) {
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Rejecting async scroll enqueue: blocking wheel event region at {},{} device delta {},{}",
position.x(), position.y(), delta_in_device_pixels.x(), delta_in_device_pixels.y());
return {};
}
if (!scroll_target.node_id.has_value()) {
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Rejecting async scroll enqueue: no wheel target at {},{} for device delta {},{}",
position.x(), position.y(), delta_in_device_pixels.x(), delta_in_device_pixels.y());
return {};
}
if (scroll_target.node_id->document_id != expected_document_id) {
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Rejecting async scroll enqueue: stale wheel target at {},{} for current document",
position.x(), position.y());
return {};
}
Optional<AsyncScrollOperationID> operation_id;
if (operation_tracking == CompositorThread::AsyncScrollOperationTracking::Yes)
operation_id = next_async_scroll_operation_id(context);
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Compositor accepted main-thread async scroll enqueue at {},{} device delta {},{} for scroll node {} viewport={}x{} at {},{}",
position.x(), position.y(), delta_in_device_pixels.x(), delta_in_device_pixels.y(), scroll_target.node_id->scroll_frame_index.value(), viewport_rect.width(), viewport_rect.height(), viewport_rect.x(), viewport_rect.y());
enqueue_command(context_id, AsyncScrollByCommand { position, delta_in_device_pixels, viewport_rect, *scroll_target.node_id, operation_id });
return { true, operation_id };
}
bool enqueue_async_scroll_by(Gfx::FloatPoint position, Gfx::FloatPoint delta_in_device_pixels)
{
auto context_state = page_presentation_context_state();
auto context_id = page_presentation_context_id();
if (!context_state || !context_id.has_value())
return false;
auto& context = *context_state;
if (!context.can_accept_async_wheel_events.load()) {
auto wheel_routing_admission = [&] {
Sync::MutexLocker const locker { m_mutex };
return context.wheel_routing_admission;
}();
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Rejecting async scroll enqueue: compositor cannot accept async wheel events ({})",
wheel_routing_admission_to_string(wheel_routing_admission));
return false;
}
auto viewport_rect = [&] {
Sync::MutexLocker const locker { m_mutex };
return context.async_scrolling_viewport_rect;
}();
auto scroll_target = hit_test_scroll_node_for_wheel(context, position, delta_in_device_pixels);
if (scroll_target.blocked_by_main_thread_region) {
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Rejecting async scroll enqueue: main-thread wheel region at {},{} device delta {},{}",
position.x(), position.y(), delta_in_device_pixels.x(), delta_in_device_pixels.y());
return false;
}
if (scroll_target.blocked_by_wheel_event_region) {
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Rejecting async scroll enqueue: blocking wheel event region at {},{} device delta {},{}",
position.x(), position.y(), delta_in_device_pixels.x(), delta_in_device_pixels.y());
return false;
}
if (!scroll_target.node_id.has_value()) {
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Rejecting async scroll enqueue: no wheel target at {},{} for device delta {},{}",
position.x(), position.y(), delta_in_device_pixels.x(), delta_in_device_pixels.y());
return false;
}
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Compositor accepted async scroll enqueue at {},{} device delta {},{} for scroll node {} viewport={}x{} at {},{}",
position.x(), position.y(), delta_in_device_pixels.x(), delta_in_device_pixels.y(), scroll_target.node_id->scroll_frame_index.value(), viewport_rect.width(), viewport_rect.height(), viewport_rect.x(), viewport_rect.y());
enqueue_command(*context_id, AsyncScrollByCommand { position, delta_in_device_pixels, viewport_rect, *scroll_target.node_id, {} });
return true;
}
Optional<size_t> hit_test_viewport_scrollbar(ContextState& context, Gfx::FloatPoint position) const
{
Sync::MutexLocker const locker { context.async_scroll_tree_mutex };
for (size_t i = 0; i < context.viewport_scrollbars.size(); ++i) {
auto const& scrollbar = context.viewport_scrollbars[i];
auto scroll_offset = context.async_scroll_tree.scroll_offset_for_node(scrollbar.scroll_node_id, context.cached_scroll_state_snapshot);
if (!scroll_offset.has_value())
continue;
if (scrollbar_hit_rect(scrollbar, *scroll_offset).to_type<float>().contains(position))
return i;
}
return {};
}
bool handle_viewport_scrollbar_mouse_event(MouseEvent const& event)
{
auto context_state = page_presentation_context_state();
auto context_id = page_presentation_context_id();
if (!context_state || !context_id.has_value())
return false;
auto& context = *context_state;
auto position = Gfx::FloatPoint {
static_cast<float>(event.position.x().value()),
static_cast<float>(event.position.y().value()),
};
auto primary_position_for_scrollbar = [&](ViewportScrollbar const& scrollbar) {
return position.primary_offset_for_orientation(orientation_for_scrollbar(scrollbar));
};
switch (event.type) {
case MouseEvent::Type::MouseDown: {
if (event.button != UIEvents::MouseButton::Primary)
return false;
Optional<size_t> scrollbar_index;
float thumb_grab_position = 0;
float primary_position = 0;
{
Sync::MutexLocker const locker { context.async_scroll_tree_mutex };
for (size_t i = 0; i < context.viewport_scrollbars.size(); ++i) {
auto const& scrollbar = context.viewport_scrollbars[i];
auto scroll_offset = context.async_scroll_tree.scroll_offset_for_node(scrollbar.scroll_node_id, context.cached_scroll_state_snapshot);
if (!scroll_offset.has_value())
continue;
auto expanded = context.hovered_viewport_scrollbar_index == i || context.captured_viewport_scrollbar_index == i;
auto orientation = orientation_for_scrollbar(scrollbar);
auto thumb_rect = translated_thumb_rect(scrollbar, *scroll_offset, expanded);
primary_position = primary_position_for_scrollbar(scrollbar);
if (thumb_rect.to_type<float>().contains(position)) {
thumb_grab_position = primary_position - static_cast<float>(thumb_rect.primary_offset_for_orientation(orientation));
scrollbar_index = i;
break;
}
if (scrollbar_hit_rect(scrollbar, *scroll_offset).to_type<float>().contains(position)) {
auto gutter_rect = scrollbar_gutter_rect(scrollbar, true);
auto thumb_size = static_cast<float>(thumb_rect.primary_size_for_orientation(orientation));
auto gutter_start = static_cast<float>(gutter_rect.primary_offset_for_orientation(orientation));
auto gutter_size = static_cast<float>(gutter_rect.primary_size_for_orientation(orientation));
auto offset_relative_to_gutter = primary_position - gutter_start;
thumb_grab_position = max(min(offset_relative_to_gutter, thumb_size / 2), offset_relative_to_gutter - gutter_size + thumb_size);
scrollbar_index = i;
break;
}
}
if (!scrollbar_index.has_value())
return false;
context.captured_viewport_scrollbar_index = *scrollbar_index;
context.hovered_viewport_scrollbar_index = *scrollbar_index;
context.viewport_scrollbar_thumb_grab_position = thumb_grab_position;
}
schedule_viewport_scrollbar_present(context);
enqueue_command(*context_id, ViewportScrollbarDragCommand { *scrollbar_index, primary_position, thumb_grab_position });
return true;
}
case MouseEvent::Type::MouseMove: {
Optional<size_t> scrollbar_index;
float thumb_grab_position = 0;
float primary_position = 0;
{
Sync::MutexLocker const locker { context.async_scroll_tree_mutex };
if (context.captured_viewport_scrollbar_index.has_value()) {
scrollbar_index = *context.captured_viewport_scrollbar_index;
thumb_grab_position = context.viewport_scrollbar_thumb_grab_position;
if (*scrollbar_index >= context.viewport_scrollbars.size()) {
context.captured_viewport_scrollbar_index.clear();
return false;
}
primary_position = primary_position_for_scrollbar(context.viewport_scrollbars[*scrollbar_index]);
}
}
if (scrollbar_index.has_value()) {
enqueue_command(*context_id, ViewportScrollbarDragCommand { *scrollbar_index, primary_position, thumb_grab_position });
return true;
}
auto hovered_scrollbar_index = hit_test_viewport_scrollbar(context, position);
set_hovered_viewport_scrollbar(context, hovered_scrollbar_index);
return hovered_scrollbar_index.has_value();
}
case MouseEvent::Type::MouseUp: {
Optional<size_t> scrollbar_index;
float thumb_grab_position = 0;
float primary_position = 0;
{
Sync::MutexLocker const locker { context.async_scroll_tree_mutex };
if (!context.captured_viewport_scrollbar_index.has_value())
return false;
scrollbar_index = *context.captured_viewport_scrollbar_index;
thumb_grab_position = context.viewport_scrollbar_thumb_grab_position;
if (*scrollbar_index >= context.viewport_scrollbars.size()) {
context.captured_viewport_scrollbar_index.clear();
return false;
}
primary_position = primary_position_for_scrollbar(context.viewport_scrollbars[*scrollbar_index]);
context.captured_viewport_scrollbar_index.clear();
}
schedule_viewport_scrollbar_present(context);
enqueue_command(*context_id, ViewportScrollbarDragCommand { *scrollbar_index, primary_position, thumb_grab_position });
return true;
}
case MouseEvent::Type::MouseLeave: {
auto has_capture = [&] {
Sync::MutexLocker const locker { context.async_scroll_tree_mutex };
return context.captured_viewport_scrollbar_index.has_value();
}();
set_hovered_viewport_scrollbar(context, {});
return has_capture;
}
case MouseEvent::Type::MouseWheel:
return false;
}
VERIFY_NOT_REACHED();
}
void compositor_loop(DisplayListPlayerType display_list_player_type)
{
initialize_skia_player(display_list_player_type);
while (true) {
{
Sync::MutexLocker const locker { m_mutex };
while (m_command_queue.is_empty() && !has_presentable_work() && !m_exit) {
m_command_ready.wait();
}
if (m_exit)
break;
}
// Drain autoreleased Objective-C objects created by Metal/Skia each iteration,
// since this background thread has no autorelease pool.
Core::ScopedAutoreleasePool autorelease_pool;
while (true) {
auto command = [this]() -> Optional<CompositorCommandEnvelope> {
Sync::MutexLocker const locker { m_mutex };
if (m_command_queue.is_empty())
return {};
auto command = m_command_queue.dequeue();
return command;
}();
if (!command.has_value())
break;
auto context_state = this->context_state(command->context_id);
if (!context_state)
continue;
auto& context = *context_state;
bool should_yield_to_async_scroll_present = false;
command->command.visit(
[this, &context](UpdateDisplayListCommand& cmd) {
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Compositor processing display list update (deferred_async_present={})",
context.has_deferred_async_scroll_present);
context.display_list_resource_storage.apply_transaction(move(cmd.resource_transaction));
context.cached_display_list = move(cmd.display_list);
context.cached_scroll_state_snapshot = move(cmd.scroll_state_snapshot);
auto async_scrolling_state = async_scrolling_state_from_display_list(*context.cached_display_list);
auto async_scrolling_viewport_rect = async_scrolling_state.viewport_rect;
auto const wheel_event_listener_state_generation = async_scrolling_state.wheel_event_listener_state_generation;
auto wheel_routing_admission = wheel_routing_admission_for(async_scrolling_state);
{
Sync::MutexLocker const locker { m_mutex };
if (wheel_event_listener_state_generation < context.wheel_event_listener_state_generation)
wheel_routing_admission = WheelRoutingAdmission::StaleWheelEventListeners;
else
context.wheel_event_listener_state_generation = wheel_event_listener_state_generation;
context.wheel_routing_admission = wheel_routing_admission;
}
context.can_accept_async_wheel_events = wheel_routing_admission == WheelRoutingAdmission::Accepted;
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Compositor wheel routing admission: {} (scroll_nodes={}, sticky_areas={}, blocking_regions={})",
wheel_routing_admission_to_string(wheel_routing_admission),
async_scrolling_state.scroll_nodes.size(),
async_scrolling_state.sticky_areas.size(),
async_scrolling_state.blocking_wheel_event_regions.size());
auto pending_async_scroll_offsets = [this, &context] {
Sync::MutexLocker const locker { m_mutex };
return context.pending_async_scroll_offsets;
}();
{
Sync::MutexLocker const locker { context.async_scroll_tree_mutex };
auto hovered_scrollbar_identity = viewport_scrollbar_identity_at(context.viewport_scrollbars, context.hovered_viewport_scrollbar_index);
auto captured_scrollbar_identity = viewport_scrollbar_identity_at(context.viewport_scrollbars, context.captured_viewport_scrollbar_index);
context.viewport_scrollbars = move(async_scrolling_state.viewport_scrollbars);
context.hovered_viewport_scrollbar_index = hovered_scrollbar_identity.has_value() ? find_viewport_scrollbar_index(context.viewport_scrollbars, *hovered_scrollbar_identity) : Optional<size_t> {};
context.captured_viewport_scrollbar_index = captured_scrollbar_identity.has_value() ? find_viewport_scrollbar_index(context.viewport_scrollbars, *captured_scrollbar_identity) : Optional<size_t> {};
context.async_scroll_tree.set_state(move(async_scrolling_state));
if (!pending_async_scroll_offsets.is_empty()) {
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Reapplying {} pending async scroll offset(s) to display list update",
pending_async_scroll_offsets.size());
if (auto viewport_scroll_offset = reapply_pending_async_scroll_offsets(context, pending_async_scroll_offsets); viewport_scroll_offset.has_value())
async_scrolling_viewport_rect.set_location(viewport_scroll_offset->to_type<int>());
}
context.async_scroll_tree.rebuild_wheel_hit_test_targets(context.cached_display_list, context.cached_scroll_state_snapshot);
}
{
Sync::MutexLocker const locker { m_mutex };
context.async_scrolling_viewport_rect = async_scrolling_viewport_rect;
}
context.has_async_scrolling_state = true;
},
[this, &context, &should_yield_to_async_scroll_present](AsyncScrollByCommand& cmd) {
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Compositor processing async scroll command at {},{} device delta {},{} for scroll node {} (deferred_async_present={})",
cmd.position.x(), cmd.position.y(), cmd.delta_in_device_pixels.x(), cmd.delta_in_device_pixels.y(), cmd.scroll_target.scroll_frame_index.value(), context.has_deferred_async_scroll_present);
auto async_scroll_viewport_rect = cmd.viewport_rect;
Vector<AsyncScrollOffset> scroll_offsets;
{
Sync::MutexLocker const locker { context.async_scroll_tree_mutex };
scroll_offsets = context.async_scroll_tree.apply_scroll_delta(cmd.scroll_target, cmd.delta_in_device_pixels, context.cached_scroll_state_snapshot);
if (scroll_offsets.is_empty()) {
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Dropping async scroll command: scroll tree consumed no delta");
// The operation produced no scroll offset (e.g. already at the scroll boundary), so it
// cannot be reported through the pending-offset path; report its completion separately.
complete_async_scroll_operation(context, cmd.operation_id);
return;
}
context.async_scroll_tree.rebuild_wheel_hit_test_targets(context.cached_display_list, context.cached_scroll_state_snapshot);
}
if (auto viewport_scroll_offset = viewport_scroll_offset_from(scroll_offsets); viewport_scroll_offset.has_value())
async_scroll_viewport_rect.set_location(viewport_scroll_offset->to_type<int>());
store_pending_async_scroll_offsets(context, scroll_offsets, cmd.operation_id);
{
Sync::MutexLocker const locker { m_mutex };
context.async_scrolling_viewport_rect = async_scroll_viewport_rect;
}
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Stored {} pending async scroll offset(s)",
scroll_offsets.size());
invoke_on_main_thread([] {
HTML::main_thread_event_loop().queue_task_to_update_the_rendering();
});
{
Sync::MutexLocker const locker { m_mutex };
context.has_deferred_async_scroll_present = true;
context.deferred_async_scroll_present_viewport_rect = async_scroll_viewport_rect;
}
should_yield_to_async_scroll_present = can_present_deferred_async_scroll(context);
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Compositor async scroll command complete (yield_to_present={})",
should_yield_to_async_scroll_present);
},
[this, &context, &should_yield_to_async_scroll_present](ViewportScrollbarDragCommand& cmd) {
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Compositor processing viewport scrollbar drag (index={}, position={}, grab={})",
cmd.scrollbar_index, cmd.primary_position, cmd.thumb_grab_position);
if (apply_viewport_scrollbar_drag(context, cmd.scrollbar_index, cmd.primary_position, cmd.thumb_grab_position)) {
should_yield_to_async_scroll_present = can_present_deferred_async_scroll(context);
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Compositor viewport scrollbar drag complete (yield_to_present={})",
should_yield_to_async_scroll_present);
}
},
[this, &context](UpdateScrollStateCommand& cmd) {
context.cached_scroll_state_snapshot = move(cmd.scroll_state_snapshot);
if (context.has_async_scrolling_state) {
auto pending_async_scroll_offsets = [this, &context] {
Sync::MutexLocker const locker { m_mutex };
return context.pending_async_scroll_offsets;
}();
Optional<Gfx::FloatPoint> reconciled_viewport_scroll_offset;
{
Sync::MutexLocker const locker { context.async_scroll_tree_mutex };
// A main-thread scroll-state update can be older than compositor-side async
// scrolling. Preserve compositor-visible offsets in the fresh snapshot before
// rebuilding hit-test data from it.
reconciled_viewport_scroll_offset = reapply_pending_async_scroll_offsets(context, pending_async_scroll_offsets);
context.async_scroll_tree.rebuild_wheel_hit_test_targets(context.cached_display_list, context.cached_scroll_state_snapshot);
}
if (reconciled_viewport_scroll_offset.has_value()) {
Sync::MutexLocker const mutex_locker { m_mutex };
auto reconciled_viewport_rect = context.async_scrolling_viewport_rect;
reconciled_viewport_rect.set_location(reconciled_viewport_scroll_offset->to_type<int>());
context.async_scrolling_viewport_rect = reconciled_viewport_rect;
}
}
},
[&context](UpdateCompositorSurfaceCommand& cmd) {
context.display_list_resource_storage.update_compositor_surface(cmd.surface_id, move(cmd.shared_image));
},
[&context](ClearCompositorSurfaceCommand& cmd) {
context.display_list_resource_storage.clear_compositor_surface(cmd.surface_id);
},
[this, &context](ViewportSizeUpdatedCommand& cmd) {
auto allocation = context.backing_store_manager.resize_backing_stores_if_needed(
cmd.viewport_size, cmd.is_top_level_traversable, cmd.window_resize_in_progress);
if (!allocation.has_value())
return;
if (context.has_async_scrolling_state) {
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Compositor resizing backing stores front={} back={} size={}x{}",
allocation->front_bitmap_id, allocation->back_bitmap_id, allocation->size.width(), allocation->size.height());
}
if (auto publication = context.backing_store_manager.allocate_backing_stores(*allocation, m_skia_backend_context, context.presents_to_client); publication.has_value())
publish_backing_store_pair(context, publication.release_value());
},
[this, &context](ScreenshotCommand& cmd) {
if (!context.cached_display_list)
return;
m_skia_player->execute(*context.cached_display_list, context.display_list_resource_storage, context.cached_scroll_state_snapshot, *cmd.target_surface);
paint_viewport_scrollbar_overlay(context, *cmd.target_surface);
flush_surface(*cmd.target_surface);
if (cmd.callback) {
invoke_on_main_thread([callback = move(cmd.callback)]() mutable {
callback();
});
}
});
if (m_exit)
break;
if (should_yield_to_async_scroll_present) {
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Compositor yielding command drain to present async scroll");
break;
}
}
if (m_exit)
break;
bool should_present = false;
RefPtr<ContextState> present_context_state;
Gfx::IntRect viewport_rect;
u64 presenting_frame_id = 0;
bool should_present_deferred_async_scroll = false;
Gfx::IntRect deferred_async_scroll_viewport_rect;
Optional<u64> deferred_async_scroll_presenting_frame_id;
{
Sync::MutexLocker const locker { m_mutex };
for (auto& [_, context] : m_contexts) {
if (context->has_deferred_async_scroll_present && !has_presented_bitmap_awaiting_ack_while_locked(*context)) {
present_context_state = context;
should_present_deferred_async_scroll = true;
deferred_async_scroll_viewport_rect = context->deferred_async_scroll_present_viewport_rect;
context->has_deferred_async_scroll_present = false;
if (context->needs_present) {
deferred_async_scroll_presenting_frame_id = context->submitted_frame_id;
context->needs_present = false;
}
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Compositor selected deferred async present (pending_main_thread_present={})",
deferred_async_scroll_presenting_frame_id.has_value());
break;
}
if (context->needs_present && !has_presented_bitmap_awaiting_ack_while_locked(*context)) {
present_context_state = context;
should_present = true;
viewport_rect = context->pending_viewport_rect;
presenting_frame_id = context->submitted_frame_id;
context->needs_present = false;
break;
}
}
}
if (should_present_deferred_async_scroll)
present_frame(*present_context_state, deferred_async_scroll_viewport_rect, deferred_async_scroll_presenting_frame_id, PresentFrameDelivery::AsyncScroll);
else if (should_present)
present_frame(*present_context_state, viewport_rect, presenting_frame_id, PresentFrameDelivery::MainThread);
}
}
private:
enum class PresentFrameDelivery {
MainThread,
AsyncScroll,
};
bool has_presented_bitmap_awaiting_ack_while_locked(ContextState const& context) const
{
return context.presented_bitmap_id_awaiting_ack.has_value();
}
AsyncScrollOperationID next_async_scroll_operation_id(ContextState& context)
{
return context.next_async_scroll_operation_id.fetch_add(1) + 1;
}
void complete_async_scroll_operation(ContextState& context, Optional<AsyncScrollOperationID> operation_id)
{
if (!operation_id.has_value())
return;
{
Sync::MutexLocker const locker { m_mutex };
context.completed_async_scroll_operation_ids.append(*operation_id);
}
// No scroll offset was produced, so nothing else will wake the main thread; schedule a rendering update so it
// drains the completed operation and resolves the awaiting promise.
invoke_on_main_thread([] {
HTML::main_thread_event_loop().queue_task_to_update_the_rendering();
});
}
void queue_rendering_update_if_async_scroll_updates_pending(ContextState& context)
{
auto has_pending_updates = [this, &context] {
Sync::MutexLocker const locker { m_mutex };
return !context.pending_async_scroll_offsets.is_empty()
|| !context.completed_async_scroll_operation_ids.is_empty();
}();
if (!has_pending_updates)
return;
invoke_on_main_thread([] {
HTML::main_thread_event_loop().queue_task_to_update_the_rendering();
});
}
void store_pending_async_scroll_offsets(ContextState& context, Vector<AsyncScrollOffset> const& scroll_offsets, Optional<AsyncScrollOperationID> operation_id = {})
{
Sync::MutexLocker const locker { m_mutex };
for (auto const& scroll_offset : scroll_offsets)
set_or_append_pending_scroll_offset(context.pending_async_scroll_offsets, scroll_offset);
if (operation_id.has_value())
context.completed_async_scroll_operation_ids.append(*operation_id);
}
Optional<Gfx::FloatPoint> reapply_pending_async_scroll_offsets(ContextState& context, Vector<AsyncScrollOffset> const& pending_scroll_offsets)
{
Optional<Gfx::FloatPoint> viewport_scroll_offset;
for (auto const& pending_scroll_offset : pending_scroll_offsets) {
auto node_id = context.async_scroll_tree.scroll_node_id_for_stable_id(pending_scroll_offset.stable_node_id);
if (!node_id.has_value())
continue;
auto current_scroll_offset = context.async_scroll_tree.scroll_offset_for_node(*node_id, context.cached_scroll_state_snapshot);
if (!current_scroll_offset.has_value())
continue;
// Reapplying pending async offsets is a restoration step for a freshly received
// main-thread snapshot. The pending offset is already the compositor-visible
// position, so applying the unadopted delta here would compound it.
auto reconciled_scroll_offset = context.async_scroll_tree.set_scroll_offset(*node_id, pending_scroll_offset.compositor_scroll_offset, context.cached_scroll_state_snapshot);
if (reconciled_scroll_offset.has_value() && context.async_scroll_tree.scroll_node_is_viewport(*node_id))
viewport_scroll_offset = *reconciled_scroll_offset;
}
return viewport_scroll_offset;
}
static Optional<Gfx::FloatPoint> viewport_scroll_offset_from(Vector<AsyncScrollOffset> const& scroll_offsets)
{
for (auto const& scroll_offset : scroll_offsets) {
if (scroll_offset.stable_node_id.kind == AsyncScrollNodeKind::Viewport)
return scroll_offset.compositor_scroll_offset;
}
return {};
}
void paint_viewport_scrollbar_overlay(ContextState& context, Gfx::PaintingSurface& surface)
{
Vector<ViewportScrollbar> scrollbars;
Optional<size_t> hovered_scrollbar_index;
Optional<size_t> captured_scrollbar_index;
{
Sync::MutexLocker const locker { context.async_scroll_tree_mutex };
scrollbars = context.viewport_scrollbars;
hovered_scrollbar_index = context.hovered_viewport_scrollbar_index;
captured_scrollbar_index = context.captured_viewport_scrollbar_index;
}
paint_viewport_scrollbars(surface, scrollbars, context.cached_scroll_state_snapshot, hovered_scrollbar_index, captured_scrollbar_index);
}
void schedule_viewport_scrollbar_present(ContextState& context)
{
Sync::MutexLocker const locker { m_mutex };
if (context.async_scrolling_viewport_rect.is_empty())
return;
context.has_deferred_async_scroll_present = true;
context.deferred_async_scroll_present_viewport_rect = context.async_scrolling_viewport_rect;
m_command_ready.signal();
}
void set_hovered_viewport_scrollbar(ContextState& context, Optional<size_t> scrollbar_index)
{
bool changed = false;
{
Sync::MutexLocker const locker { context.async_scroll_tree_mutex };
if (context.hovered_viewport_scrollbar_index == scrollbar_index)
return;
changed = true;
context.hovered_viewport_scrollbar_index = scrollbar_index;
}
if (changed)
schedule_viewport_scrollbar_present(context);
}
bool apply_viewport_scrollbar_drag(ContextState& context, size_t scrollbar_index, float primary_position, float thumb_grab_position)
{
Vector<AsyncScrollOffset> scroll_offsets;
{
Sync::MutexLocker const locker { context.async_scroll_tree_mutex };
if (scrollbar_index >= context.viewport_scrollbars.size())
return false;
auto const& scrollbar = context.viewport_scrollbars[scrollbar_index];
auto expanded = context.hovered_viewport_scrollbar_index == scrollbar_index || context.captured_viewport_scrollbar_index == scrollbar_index;
auto scroll_size = scrollbar_scroll_size(scrollbar, expanded);
if (scroll_size == 0)
return false;
auto current_scroll_offset = context.async_scroll_tree.scroll_offset_for_node(scrollbar.scroll_node_id, context.cached_scroll_state_snapshot);
if (!current_scroll_offset.has_value())
return false;
auto orientation = orientation_for_scrollbar(scrollbar);
auto thumb_rect = expanded ? scrollbar.expanded_thumb_rect : scrollbar.thumb_rect;
auto min_thumb_position = static_cast<float>(thumb_rect.primary_offset_for_orientation(orientation));
auto max_thumb_position = min_thumb_position + scrollbar.max_scroll_offset * static_cast<float>(scroll_size);
auto target_thumb_position = AK::clamp(primary_position - thumb_grab_position, min_thumb_position, max_thumb_position);
auto target_scroll_offset = (target_thumb_position - min_thumb_position) / static_cast<float>(scroll_size);
Gfx::FloatPoint delta;
delta.set_primary_offset_for_orientation(orientation, target_scroll_offset - current_scroll_offset->primary_offset_for_orientation(orientation));
if (delta.x() == 0 && delta.y() == 0)
return false;
scroll_offsets = context.async_scroll_tree.apply_scroll_delta(scrollbar.scroll_node_id, delta, context.cached_scroll_state_snapshot);
if (scroll_offsets.is_empty())
return false;
context.async_scroll_tree.rebuild_wheel_hit_test_targets(context.cached_display_list, context.cached_scroll_state_snapshot);
}
auto scroll_offset = viewport_scroll_offset_from(scroll_offsets);
if (!scroll_offset.has_value())
return false;
store_pending_async_scroll_offsets(context, scroll_offsets);
Gfx::IntRect async_scroll_viewport_rect;
{
Sync::MutexLocker const locker { m_mutex };
async_scroll_viewport_rect = context.async_scrolling_viewport_rect;
async_scroll_viewport_rect.set_location(scroll_offset->to_type<int>());
context.async_scrolling_viewport_rect = async_scroll_viewport_rect;
context.has_deferred_async_scroll_present = true;
context.deferred_async_scroll_present_viewport_rect = async_scroll_viewport_rect;
}
invoke_on_main_thread([] {
HTML::main_thread_event_loop().queue_task_to_update_the_rendering();
});
return true;
}
bool has_presentable_work() const
{
for (auto const& [_, context] : m_contexts) {
if ((context->has_deferred_async_scroll_present || context->needs_present)
&& !has_presented_bitmap_awaiting_ack_while_locked(*context)) {
return true;
}
}
return false;
}
bool can_present_deferred_async_scroll(ContextState& context) const
{
Sync::MutexLocker const locker { m_mutex };
return context.has_deferred_async_scroll_present && !has_presented_bitmap_awaiting_ack_while_locked(context);
}
void present_frame(ContextState& context, Gfx::IntRect viewport_rect, Optional<u64> presenting_frame_id = {}, PresentFrameDelivery delivery = PresentFrameDelivery::MainThread)
{
auto delivery_name = delivery == PresentFrameDelivery::AsyncScroll ? "compositor-thread"sv : "main-thread"sv;
{
Sync::MutexLocker const locker { m_mutex };
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Begin {} present (frame={}, awaiting_bitmap={}, viewport={}x{} at {},{})",
delivery_name, presenting_frame_id.value_or(0), context.presented_bitmap_id_awaiting_ack.value_or(-1), viewport_rect.width(), viewport_rect.height(), viewport_rect.x(), viewport_rect.y());
if (delivery == PresentFrameDelivery::AsyncScroll && has_presented_bitmap_awaiting_ack_while_locked(context)) {
context.has_deferred_async_scroll_present = true;
context.deferred_async_scroll_present_viewport_rect = viewport_rect;
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Deferring async scroll present until bitmap {} is ready to paint",
context.presented_bitmap_id_awaiting_ack.value());
return;
}
VERIFY(!has_presented_bitmap_awaiting_ack_while_locked(context));
if (m_exit)
return;
context.is_rasterizing = true;
}
auto presentation_mode = [&] {
Sync::MutexLocker const locker { m_mutex };
return context.presentation_mode;
}();
if (context.cached_display_list && context.backing_store_manager.is_valid()) {
auto should_clear_back_store = presentation_mode.visit(
[](CompositorThread::PresentToUI) { return false; },
[](CompositorThread::PublishToCompositorSurface const&) { return true; });
auto& back_store = context.backing_store_manager.back_store();
if (should_clear_back_store) {
// Embedded navigables leave their PaintConfig canvas unfilled, so double-buffered back stores must be
// cleared before repainting.
back_store.canvas().clear(SK_ColorTRANSPARENT);
}
m_skia_player->execute(*context.cached_display_list, context.display_list_resource_storage, context.cached_scroll_state_snapshot, back_store);
paint_viewport_scrollbar_overlay(context, back_store);
flush_surface(back_store);
i32 rendered_bitmap_id = context.backing_store_manager.back_bitmap_id();
context.backing_store_manager.swap();
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Finished {} display-list replay into bitmap {}",
delivery_name, rendered_bitmap_id);
presentation_mode.visit(
[this, &context, viewport_rect, rendered_bitmap_id, delivery](CompositorThread::PresentToUI) {
if (context.presents_to_client) {
finish_rasterizing(context, rendered_bitmap_id);
if (delivery == PresentFrameDelivery::AsyncScroll) {
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Finished async scroll raster into bitmap {}",
rendered_bitmap_id);
}
VERIFY(CompositorThread::present_frame_to_client(m_page_id, viewport_rect, rendered_bitmap_id));
} else {
Sync::MutexLocker const locker { m_mutex };
context.is_rasterizing = false;
}
},
[this, &context](CompositorThread::PublishToCompositorSurface const& mode) {
{
Sync::MutexLocker const locker { m_mutex };
context.is_rasterizing = false;
}
if (context.has_async_scrolling_state)
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Publishing present to compositor surface");
auto& front_store = context.backing_store_manager.front_store();
VERIFY(CompositorThread::update_compositor_surface_for_context(
mode.target_context_id, mode.surface_id, front_store.snapshot_into_shared_image()));
});
} else {
{
Sync::MutexLocker const locker { m_mutex };
context.is_rasterizing = false;
}
if (context.has_async_scrolling_state) {
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Skipping {} present: cached_display_list={}, backing_stores_valid={}",
delivery_name, !!context.cached_display_list, context.backing_store_manager.is_valid());
}
}
if (presenting_frame_id.has_value())
mark_frame_complete(context, *presenting_frame_id);
queue_rendering_update_if_async_scroll_updates_pending(context);
}
void initialize_skia_player(DisplayListPlayerType display_list_player_type)
{
switch (display_list_player_type) {
case DisplayListPlayerType::SkiaGPUIfAvailable:
m_skia_backend_context = Gfx::SkiaBackendContext::create_independent_gpu_backend();
break;
case DisplayListPlayerType::SkiaCPU:
break;
}
m_skia_player = make<Painting::DisplayListPlayerSkia>(m_skia_backend_context);
}
void publish_backing_store_pair(ContextState& context, BackingStoreManager::Publication&& publication)
{
if (!context.presents_to_client)
return;
VERIFY(CompositorThread::present_backing_stores_to_client(
m_page_id, publication.front_bitmap_id, move(publication.front_shared_image), publication.back_bitmap_id, move(publication.back_shared_image)));
}
template<typename Invokee>
void invoke_on_main_thread(Invokee invokee)
{
if (m_exit)
return;
auto event_loop = m_main_thread_event_loop->take();
if (!event_loop)
return;
event_loop->deferred_invoke([self = NonnullRefPtr(*this), invokee = move(invokee)]() mutable {
invokee();
});
}
u64 m_page_id { 0 };
NonnullRefPtr<Core::WeakEventLoopReference> m_main_thread_event_loop;
mutable Sync::Mutex m_mutex;
mutable Sync::ConditionVariable m_command_ready { m_mutex };
Atomic<bool> m_exit { false };
Queue<CompositorCommandEnvelope> m_command_queue;
HashMap<CompositorContextId, NonnullRefPtr<ContextState>> m_contexts;
Optional<CompositorContextId> m_default_context_id;
Optional<CompositorContextId> m_page_presentation_context_id;
OwnPtr<Painting::DisplayListPlayerSkia> m_skia_player;
RefPtr<Gfx::SkiaBackendContext> m_skia_backend_context;
mutable Sync::ConditionVariable m_frame_completed { m_mutex };
public:
void finish_rasterizing(ContextState& context, i32 bitmap_id)
{
Sync::MutexLocker const locker { m_mutex };
context.is_rasterizing = false;
VERIFY(!context.presented_bitmap_id_awaiting_ack.has_value());
context.presented_bitmap_id_awaiting_ack = bitmap_id;
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Rasterized bitmap {} waiting for ready_to_paint",
bitmap_id);
}
void mark_presented_bitmap_ready_to_paint(i32 bitmap_id)
{
auto context_state = page_presentation_context_state();
if (!context_state)
return;
auto& context = *context_state;
Sync::MutexLocker const locker { m_mutex };
if (context.presented_bitmap_id_awaiting_ack != bitmap_id) {
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Ignoring stale ready_to_paint for bitmap {} while awaiting bitmap {}",
bitmap_id, context.presented_bitmap_id_awaiting_ack.value_or(-1));
return;
}
context.presented_bitmap_id_awaiting_ack.clear();
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Compositor ready_to_paint released bitmap {}",
bitmap_id);
m_command_ready.signal();
}
};
struct FramePresentationState {
RefPtr<Core::WeakEventLoopReference> event_loop;
CompositorThread::BackingStorePresentationCallback backing_store_callback;
CompositorThread::FramePresentationCallback frame_callback;
};
static Sync::Mutex& compositor_presentation_state_mutex()
{
static NeverDestroyed<Sync::Mutex> mutex;
return *mutex;
}
static HashMap<u64, NonnullRefPtr<CompositorThread::ThreadData>>& page_compositors()
{
static NeverDestroyed<HashMap<u64, NonnullRefPtr<CompositorThread::ThreadData>>> compositors;
return *compositors;
}
static HashMap<CompositorContextId, NonnullRefPtr<CompositorThread::ThreadData>>& context_compositors()
{
static NeverDestroyed<HashMap<CompositorContextId, NonnullRefPtr<CompositorThread::ThreadData>>> compositors;
return *compositors;
}
static FramePresentationState& frame_presentation_state()
{
static NeverDestroyed<FramePresentationState> state;
return *state;
}
CompositorThread::Context::Context(NonnullRefPtr<ThreadData> thread_data, CompositorContextId context_id)
: m_thread_data(move(thread_data))
, m_context_id(context_id)
{
m_backing_store_shrink_timer = Core::Timer::create_single_shot(3000, [this] {
enqueue_viewport_size_updated(m_last_viewport_size, m_last_viewport_size_is_top_level_traversable, WindowResizingInProgress::No);
});
}
CompositorThread::Context::~Context()
{
m_backing_store_shrink_timer->on_timeout = {};
m_backing_store_shrink_timer->stop();
m_backing_store_shrink_timer.clear();
unregister_page_compositor(m_thread_data->page_id(), *m_thread_data);
m_thread_data->unregister_context(m_context_id);
{
Sync::MutexLocker const locker { compositor_presentation_state_mutex() };
VERIFY(context_compositors().remove(m_context_id));
}
}
CompositorThread::CompositorThread(u64 page_id, PagePresentationRegistration page_presentation_registration)
: m_thread_data(adopt_ref(*new ThreadData(page_id, Core::EventLoop::current_weak())))
, m_context(create_context(page_presentation_registration))
{
}
CompositorThread::~CompositorThread()
{
m_context.clear();
m_thread_data->exit();
}
OwnPtr<CompositorThread::Context> CompositorThread::create_context(PagePresentationRegistration page_presentation_registration)
{
auto context_id = allocate_compositor_context_id();
m_thread_data->register_context(context_id, page_presentation_registration);
{
Sync::MutexLocker const locker { compositor_presentation_state_mutex() };
VERIFY(!context_compositors().contains(context_id));
context_compositors().set(context_id, m_thread_data);
}
if (page_presentation_registration == PagePresentationRegistration::Yes)
register_page_compositor(m_thread_data->page_id(), m_thread_data);
return adopt_own(*new Context(m_thread_data, context_id));
}
void CompositorThread::register_page_compositor(u64 page_id, NonnullRefPtr<ThreadData> thread_data)
{
Sync::MutexLocker const locker { compositor_presentation_state_mutex() };
auto replaced_existing_compositor = page_compositors().contains(page_id);
page_compositors().set(page_id, move(thread_data));
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Registered page {} for compositor presentation (replaced_existing={})",
page_id, replaced_existing_compositor);
}
void CompositorThread::unregister_page_compositor(u64 page_id, ThreadData& thread_data)
{
Sync::MutexLocker const locker { compositor_presentation_state_mutex() };
auto compositor = page_compositors().find(page_id);
if (compositor == page_compositors().end()) {
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Skipping compositor unregister for page {}: no compositor registered",
page_id);
return;
}
if (compositor->value.ptr() != &thread_data) {
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Skipping compositor unregister for page {}: compositor changed",
page_id);
return;
}
page_compositors().remove(compositor);
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Unregistered page {} from compositor presentation", page_id);
}
bool CompositorThread::update_compositor_surface_for_context(CompositorContextId context_id, Painting::CompositorSurfaceId surface_id, Gfx::SharedImage&& shared_image)
{
RefPtr<ThreadData> thread_data;
{
Sync::MutexLocker const locker { compositor_presentation_state_mutex() };
auto compositor = context_compositors().find(context_id);
if (compositor == context_compositors().end()) {
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Dropping compositor surface {} update for unknown context {}",
surface_id, context_id);
return false;
}
thread_data = compositor->value;
}
thread_data->enqueue_command(context_id, UpdateCompositorSurfaceCommand { surface_id, move(shared_image) });
return true;
}
void CompositorThread::set_frame_presentation_callbacks(NonnullRefPtr<Core::WeakEventLoopReference> event_loop, BackingStorePresentationCallback backing_store_callback, FramePresentationCallback frame_callback)
{
Sync::MutexLocker const locker { compositor_presentation_state_mutex() };
auto& state = frame_presentation_state();
state.event_loop = move(event_loop);
state.backing_store_callback = move(backing_store_callback);
state.frame_callback = move(frame_callback);
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Installed compositor presentation callbacks");
}
void CompositorThread::clear_frame_presentation_callbacks()
{
Sync::MutexLocker const locker { compositor_presentation_state_mutex() };
auto& state = frame_presentation_state();
state.event_loop = nullptr;
state.backing_store_callback = {};
state.frame_callback = {};
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Cleared compositor presentation callbacks");
}
bool CompositorThread::present_backing_stores_to_client(u64 page_id, i32 front_bitmap_id, Gfx::SharedImage&& front_shared_image, i32 back_bitmap_id, Gfx::SharedImage&& back_shared_image)
{
RefPtr<Core::WeakEventLoopReference> event_loop_reference;
{
Sync::MutexLocker const locker { compositor_presentation_state_mutex() };
if (!page_compositors().contains(page_id)) {
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Cannot present backing stores front={} back={} for page {}: no compositor registered",
front_bitmap_id, back_bitmap_id, page_id);
return false;
}
auto& state = frame_presentation_state();
if (!state.backing_store_callback) {
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Cannot present backing stores front={} back={} for page {}: no presentation callback",
front_bitmap_id, back_bitmap_id, page_id);
return false;
}
event_loop_reference = state.event_loop;
}
if (!event_loop_reference) {
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Cannot present backing stores front={} back={} for page {}: no presentation event loop",
front_bitmap_id, back_bitmap_id, page_id);
return false;
}
auto event_loop = event_loop_reference->take();
if (!event_loop) {
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Cannot present backing stores front={} back={} for page {}: presentation event loop is gone",
front_bitmap_id, back_bitmap_id, page_id);
return false;
}
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Queueing UI backing stores for page {} front={} back={}",
page_id, front_bitmap_id, back_bitmap_id);
event_loop->deferred_invoke([page_id, front_bitmap_id, front_shared_image = move(front_shared_image), back_bitmap_id, back_shared_image = move(back_shared_image)]() mutable {
Sync::MutexLocker const locker { compositor_presentation_state_mutex() };
if (!page_compositors().contains(page_id)) {
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Dropping queued UI backing stores for page {} front={} back={}: page unregistered",
page_id, front_bitmap_id, back_bitmap_id);
return;
}
auto& state = frame_presentation_state();
if (state.backing_store_callback) {
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Delivering UI backing stores for page {} front={} back={}",
page_id, front_bitmap_id, back_bitmap_id);
state.backing_store_callback(page_id, front_bitmap_id, move(front_shared_image), back_bitmap_id, move(back_shared_image));
} else {
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Dropping queued UI backing stores for page {} front={} back={}: callback cleared",
page_id, front_bitmap_id, back_bitmap_id);
}
});
return true;
}
bool CompositorThread::present_frame_to_client(u64 page_id, Gfx::IntRect const& viewport_rect, i32 bitmap_id)
{
RefPtr<Core::WeakEventLoopReference> event_loop_reference;
{
Sync::MutexLocker const locker { compositor_presentation_state_mutex() };
if (!page_compositors().contains(page_id)) {
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Cannot present bitmap {} for page {}: no compositor registered",
bitmap_id, page_id);
return false;
}
auto& state = frame_presentation_state();
if (!state.frame_callback) {
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Cannot present bitmap {} for page {}: no presentation callback",
bitmap_id, page_id);
return false;
}
event_loop_reference = state.event_loop;
}
if (!event_loop_reference) {
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Cannot present bitmap {} for page {}: no presentation event loop",
bitmap_id, page_id);
return false;
}
auto event_loop = event_loop_reference->take();
if (!event_loop) {
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Cannot present bitmap {} for page {}: presentation event loop is gone",
bitmap_id, page_id);
return false;
}
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Queueing UI present for page {} bitmap {} viewport={}x{} at {},{}",
page_id, bitmap_id, viewport_rect.width(), viewport_rect.height(), viewport_rect.x(), viewport_rect.y());
event_loop->deferred_invoke([page_id, viewport_rect, bitmap_id] {
Sync::MutexLocker const locker { compositor_presentation_state_mutex() };
if (!page_compositors().contains(page_id)) {
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Dropping queued UI present for page {} bitmap {}: page unregistered",
page_id, bitmap_id);
return;
}
auto& state = frame_presentation_state();
if (state.frame_callback) {
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Delivering UI present for page {} bitmap {} viewport={}x{} at {},{}",
page_id, bitmap_id, viewport_rect.width(), viewport_rect.height(), viewport_rect.x(), viewport_rect.y());
state.frame_callback(page_id, viewport_rect, bitmap_id);
} else {
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Dropping queued UI present for page {} bitmap {}: callback cleared",
page_id, bitmap_id);
}
});
return true;
}
void CompositorThread::presented_bitmap_ready_to_paint(u64 page_id, i32 bitmap_id)
{
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Received compositor ready_to_paint for page {} bitmap {}",
page_id, bitmap_id);
RefPtr<ThreadData> thread_data;
{
Sync::MutexLocker const locker { compositor_presentation_state_mutex() };
auto compositor = page_compositors().find(page_id);
if (compositor == page_compositors().end()) {
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Ignoring compositor ready_to_paint for page {} bitmap {}: no compositor registered",
page_id, bitmap_id);
return;
}
thread_data = compositor->value;
}
thread_data->mark_presented_bitmap_ready_to_paint(bitmap_id);
}
bool CompositorThread::async_scroll_by(u64 page_id, Gfx::FloatPoint position, Gfx::FloatPoint delta_in_device_pixels)
{
RefPtr<ThreadData> thread_data;
{
Sync::MutexLocker const locker { compositor_presentation_state_mutex() };
auto compositor = page_compositors().find(page_id);
if (compositor == page_compositors().end()) {
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Async scroll cannot scroll page {}: no compositor registered", page_id);
return false;
}
thread_data = compositor->value;
}
return thread_data->enqueue_async_scroll_by(position, delta_in_device_pixels);
}
bool CompositorThread::handle_mouse_event(u64 page_id, MouseEvent const& event)
{
RefPtr<ThreadData> thread_data;
{
Sync::MutexLocker const locker { compositor_presentation_state_mutex() };
auto compositor = page_compositors().find(page_id);
if (compositor == page_compositors().end()) {
dbgln_if(COMPOSITOR_DEBUG, "[Compositor] Viewport scrollbar mouse event cannot be handled for page {}: no compositor registered", page_id);
return false;
}
thread_data = compositor->value;
}
return thread_data->handle_viewport_scrollbar_mouse_event(event);
}
void CompositorThread::start(DisplayListPlayerType display_list_player_type)
{
m_thread = Threading::Thread::construct("Compositor"sv, [thread_data = m_thread_data, display_list_player_type] {
thread_data->compositor_loop(display_list_player_type);
return static_cast<intptr_t>(0);
});
m_thread->start();
m_thread->detach();
}
void CompositorThread::Context::set_presentation_mode(PresentationMode mode)
{
m_thread_data->set_presentation_mode(m_context_id, move(mode));
}
void CompositorThread::Context::stop_presenting_to_client()
{
m_thread_data->stop_presenting_to_client(m_context_id);
unregister_page_compositor(m_thread_data->page_id(), *m_thread_data);
}
void CompositorThread::Context::update_display_list(
NonnullRefPtr<Painting::DisplayList> display_list,
Painting::DisplayListResourceTransaction&& resource_transaction,
Painting::ScrollStateSnapshot&& scroll_state_snapshot)
{
m_thread_data->enqueue_command(m_context_id, UpdateDisplayListCommand { move(display_list), move(resource_transaction), move(scroll_state_snapshot) });
}
void CompositorThread::Context::update_compositor_surface(Painting::CompositorSurfaceId surface_id, Gfx::SharedImage&& shared_image)
{
m_thread_data->enqueue_command(m_context_id, UpdateCompositorSurfaceCommand { surface_id, move(shared_image) });
}
void CompositorThread::Context::clear_compositor_surface(Painting::CompositorSurfaceId surface_id)
{
m_thread_data->enqueue_command(m_context_id, ClearCompositorSurfaceCommand { surface_id });
}
void CompositorThread::Context::invalidate_wheel_event_listener_state(u64 generation)
{
m_thread_data->invalidate_wheel_event_listener_state(m_context_id, generation);
}
CompositorThread::AsyncScrollEnqueueResult CompositorThread::Context::async_scroll_by(UniqueNodeID expected_document_id, Gfx::FloatPoint position,
Gfx::FloatPoint delta_in_device_pixels, Gfx::IntRect viewport_rect, AsyncScrollOperationTracking operation_tracking)
{
return m_thread_data->enqueue_async_scroll_by(m_context_id, expected_document_id, position, delta_in_device_pixels, viewport_rect, operation_tracking);
}
bool CompositorThread::Context::should_defer_async_scroll_offset_adoption() const
{
return m_thread_data->should_defer_async_scroll_offset_adoption(m_context_id);
}
bool CompositorThread::Context::should_defer_main_thread_present_for_async_scroll() const
{
return m_thread_data->should_defer_main_thread_present_for_async_scroll(m_context_id);
}
CompositorThread::PendingAsyncScrollUpdates CompositorThread::Context::take_pending_async_scroll_updates()
{
return m_thread_data->take_pending_async_scroll_updates(m_context_id);
}
void CompositorThread::Context::update_scroll_state(Painting::ScrollStateSnapshot&& scroll_state_snapshot)
{
m_thread_data->enqueue_command(m_context_id, UpdateScrollStateCommand { move(scroll_state_snapshot) });
}
void CompositorThread::Context::viewport_size_updated(
Gfx::IntSize viewport_size, bool is_top_level_traversable, WindowResizingInProgress window_resize_in_progress)
{
m_last_viewport_size = viewport_size;
m_last_viewport_size_is_top_level_traversable = is_top_level_traversable;
if (window_resize_in_progress == WindowResizingInProgress::Yes)
m_backing_store_shrink_timer->restart();
enqueue_viewport_size_updated(viewport_size, is_top_level_traversable, window_resize_in_progress);
}
void CompositorThread::Context::enqueue_viewport_size_updated(
Gfx::IntSize viewport_size, bool is_top_level_traversable, WindowResizingInProgress window_resize_in_progress)
{
m_thread_data->enqueue_command(m_context_id,
ViewportSizeUpdatedCommand { viewport_size, is_top_level_traversable, window_resize_in_progress });
}
u64 CompositorThread::Context::present_frame(Gfx::IntRect viewport_rect)
{
return m_thread_data->set_needs_present(m_context_id, viewport_rect);
}
void CompositorThread::Context::wait_for_frame(u64 frame_id)
{
m_thread_data->wait_for_frame(m_context_id, frame_id);
}
void CompositorThread::Context::request_screenshot(NonnullRefPtr<Gfx::PaintingSurface> target_surface, Function<void()>&& callback)
{
m_thread_data->enqueue_command(m_context_id, ScreenshotCommand { move(target_surface), move(callback) });
}
void CompositorThread::set_presentation_mode(PresentationMode mode)
{
m_context->set_presentation_mode(move(mode));
}
void CompositorThread::stop_presenting_to_client()
{
m_context->stop_presenting_to_client();
}
void CompositorThread::update_display_list(
NonnullRefPtr<Painting::DisplayList> display_list,
Painting::DisplayListResourceTransaction&& resource_transaction,
Painting::ScrollStateSnapshot&& scroll_state_snapshot)
{
m_context->update_display_list(move(display_list), move(resource_transaction), move(scroll_state_snapshot));
}
void CompositorThread::update_compositor_surface(Painting::CompositorSurfaceId surface_id, Gfx::SharedImage&& shared_image)
{
m_context->update_compositor_surface(surface_id, move(shared_image));
}
void CompositorThread::clear_compositor_surface(Painting::CompositorSurfaceId surface_id)
{
m_context->clear_compositor_surface(surface_id);
}
void CompositorThread::invalidate_wheel_event_listener_state(u64 generation)
{
m_context->invalidate_wheel_event_listener_state(generation);
}
CompositorThread::AsyncScrollEnqueueResult CompositorThread::async_scroll_by(UniqueNodeID expected_document_id, Gfx::FloatPoint position,
Gfx::FloatPoint delta_in_device_pixels, Gfx::IntRect viewport_rect, AsyncScrollOperationTracking operation_tracking)
{
return m_context->async_scroll_by(expected_document_id, position, delta_in_device_pixels, viewport_rect, operation_tracking);
}
bool CompositorThread::should_defer_async_scroll_offset_adoption() const
{
return m_context->should_defer_async_scroll_offset_adoption();
}
bool CompositorThread::should_defer_main_thread_present_for_async_scroll() const
{
return m_context->should_defer_main_thread_present_for_async_scroll();
}
CompositorThread::PendingAsyncScrollUpdates CompositorThread::take_pending_async_scroll_updates()
{
return m_context->take_pending_async_scroll_updates();
}
void CompositorThread::update_scroll_state(Painting::ScrollStateSnapshot&& scroll_state_snapshot)
{
m_context->update_scroll_state(move(scroll_state_snapshot));
}
void CompositorThread::viewport_size_updated(
Gfx::IntSize viewport_size, bool is_top_level_traversable, WindowResizingInProgress window_resize_in_progress)
{
m_context->viewport_size_updated(viewport_size, is_top_level_traversable, window_resize_in_progress);
}
u64 CompositorThread::present_frame(Gfx::IntRect viewport_rect)
{
return m_context->present_frame(viewport_rect);
}
void CompositorThread::wait_for_frame(u64 frame_id)
{
m_context->wait_for_frame(frame_id);
}
void CompositorThread::request_screenshot(NonnullRefPtr<Gfx::PaintingSurface> target_surface, Function<void()>&& callback)
{
m_context->request_screenshot(move(target_surface), move(callback));
}
}