ladybird/Services/Compositor/CompositorState.h
Aliaksandr Kalenik 5c3e61efb6 Compositor: Rasterize nested contexts during replay
Nested compositor contexts used the same vsync scheduling path as
top-level contexts. That made iframe presents depend on a context's own
timer instead of the display refresh rate, and screenshots needed a
separate descendant flush to avoid stale child surfaces.

Keep top-level contexts on vsync, but make nested contexts dirty their
containing context instead of scheduling themselves. When a composited
child surface is resolved, synchronously rasterize the child if it has
a pending frame, using a separate display-list player so recursive
replay does not reuse the active player state. This lets screenshots and
normal presentation share the same child-surface update path.
2026-06-19 17:09:44 +02:00

156 lines
8.1 KiB
C++

/*
* Copyright (c) 2026, the Ladybird developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/DoublyLinkedList.h>
#include <AK/HashMap.h>
#include <AK/NonnullRefPtr.h>
#include <AK/Optional.h>
#include <AK/OwnPtr.h>
#include <AK/RefCounted.h>
#include <Compositor/ContextState.h>
#include <Compositor/VSyncScheduler.h>
#include <LibCore/Forward.h>
#include <LibGfx/Point.h>
#include <LibGfx/Rect.h>
#include <LibGfx/ShareableBitmap.h>
#include <LibGfx/SharedImage.h>
#include <LibGfx/Size.h>
#include <LibGfx/SkiaBackendContext.h>
#include <LibMedia/Forward.h>
#include <LibWeb/Compositor/Types.h>
#include <LibWeb/Forward.h>
#include <LibWeb/Painting/AccumulatedVisualContext.h>
#include <LibWeb/Painting/CanvasSurfaceRegistry.h>
#include <LibWeb/Painting/DisplayList.h>
#include <LibWeb/Painting/DisplayListPlayerSkia.h>
#include <LibWeb/Painting/ScrollState.h>
namespace Web {
struct MouseEvent;
struct PinchEvent;
}
namespace Compositor {
class CompositorStateClient {
public:
virtual ~CompositorStateClient() = default;
virtual void did_allocate_backing_stores(Web::Compositor::CompositorContextId, i32 front_bitmap_id, Gfx::SharedImage&& front_backing_store, i32 back_bitmap_id, Gfx::SharedImage&& back_backing_store) = 0;
virtual void did_present_frame(Web::Compositor::CompositorContextId, Gfx::IntRect content_rect, i32 bitmap_id) = 0;
};
class CompositorStateWebContentClient {
public:
virtual ~CompositorStateWebContentClient() = default;
virtual void dispatch_mouse_event_to_web_content(u64 page_id, Web::MouseEvent const&) = 0;
virtual void request_rendering_update() = 0;
};
class CompositorState final : public RefCounted<CompositorState> {
public:
static NonnullRefPtr<CompositorState> create(RefPtr<Gfx::SkiaBackendContext>, bool async_scrolling_enabled);
~CompositorState();
enum class ContextOwnerCheckResult {
OwnedByClient,
ContextUnavailable,
ConflictingOwner,
};
void set_client(CompositorStateClient&);
ContextOwnerCheckResult check_context_owner(Web::Compositor::CompositorContextId, CompositorStateWebContentClient&);
void destroy_contexts_for_web_content_client(CompositorStateWebContentClient&);
RefPtr<Gfx::SkiaBackendContext> skia_backend_context() const { return m_skia_backend_context; }
Web::Painting::CanvasSurfaceRegistry& canvas_surface_registry() { return m_canvas_surface_registry; }
Web::Painting::CanvasSurfaceRegistry const& canvas_surface_registry() const { return m_canvas_surface_registry; }
void create_context(Web::Compositor::CompositorContextId, Optional<u64> page_id, CompositorStateWebContentClient&);
void destroy_context(Web::Compositor::CompositorContextId);
void set_parent_context(Web::Compositor::CompositorContextId, Optional<Web::Compositor::CompositorContextId> parent_context_id);
void stop_presenting_to_client(Web::Compositor::CompositorContextId);
void update_display_list(Web::Compositor::CompositorContextId, NonnullRefPtr<Web::Painting::DisplayList>, Web::Painting::AccumulatedVisualContextTree, Web::Painting::DisplayListResourceTransaction&&, Web::Painting::ScrollStateSnapshot&&);
void update_visual_context_tree(Web::Compositor::CompositorContextId, Web::Painting::AccumulatedVisualContextTree);
void update_scroll_state(Web::Compositor::CompositorContextId, Web::Painting::ScrollStateSnapshot&&);
void update_video_frame(Web::Compositor::CompositorContextId, Web::Painting::VideoFrameResourceId, NonnullRefPtr<Media::VideoFrame const>);
void clear_video_frame(Web::Compositor::CompositorContextId, Web::Painting::VideoFrameResourceId);
void invalidate_wheel_event_listener_state(Web::Compositor::CompositorContextId, u64 generation);
bool handle_mouse_event(Web::Compositor::CompositorContextId, Web::MouseEvent const&);
bool dispatch_mouse_event_to_web_content(Web::Compositor::CompositorContextId, Web::MouseEvent const&);
bool handle_pinch_event(Web::Compositor::CompositorContextId, Web::PinchEvent const&);
Web::Compositor::AsyncScrollEnqueueResult async_scroll_by(Web::Compositor::CompositorContextId, Web::UniqueNodeID document_id, Gfx::FloatPoint position, Gfx::FloatPoint delta, Gfx::IntRect viewport_rect, Web::Compositor::AsyncScrollOperationTracking);
bool async_scroll_by(Web::Compositor::CompositorContextId, Gfx::FloatPoint position, Gfx::FloatPoint delta);
bool should_defer_main_thread_present_for_async_scroll(Web::Compositor::CompositorContextId) const;
Web::Compositor::PendingAsyncScrollUpdates take_pending_async_scroll_updates(Web::Compositor::CompositorContextId);
void viewport_size_updated(Web::Compositor::CompositorContextId, Gfx::IntSize, Web::Compositor::WindowResizingInProgress);
void set_display_metadata(Web::Compositor::CompositorContextId, Optional<u64> display_id, double refresh_rate);
void present_frame(Web::Compositor::CompositorContextId, Gfx::IntRect);
bool request_screenshot(Web::Compositor::CompositorContextId, Gfx::ShareableBitmap&);
void presented_bitmap_ready_to_paint(Web::Compositor::CompositorContextId, i32 bitmap_id);
private:
CompositorState(RefPtr<Gfx::SkiaBackendContext>, bool async_scrolling_enabled);
struct PendingAsyncPresent {
PendingAsyncPresent(Web::Compositor::CompositorContextId context_id, Gfx::IntRect viewport_rect, i32 bitmap_id)
: context_id(context_id)
, viewport_rect(viewport_rect)
, bitmap_id(bitmap_id)
{
}
Web::Compositor::CompositorContextId context_id;
Gfx::IntRect viewport_rect;
i32 bitmap_id { 0 };
bool was_cancelled { false };
};
ContextState* context_if_present(Web::Compositor::CompositorContextId);
ContextState const* context_if_present(Web::Compositor::CompositorContextId) const;
void clear_parent_context(ContextState&);
CompositedContextResolver resolver_for(Web::Compositor::CompositorContextId parent_context_id);
RefPtr<Gfx::PaintingSurface> resolve_composited_context(Web::Compositor::CompositorContextId parent_context_id, Web::Compositor::CompositorContextId child_context_id);
void schedule_backing_store_shrink(Web::Compositor::CompositorContextId, ContextState&);
void shrink_backing_stores_after_resize(Web::Compositor::CompositorContextId);
void resize_backing_stores_if_needed(Web::Compositor::CompositorContextId, ContextState&);
void present_current_frame(Web::Compositor::CompositorContextId, ContextState&);
bool apply_context_update_result(
Web::Compositor::CompositorContextId,
ContextState&,
ContextState::ContextUpdateResult const&);
void present_frame(Web::Compositor::CompositorContextId, ContextState&, Gfx::IntRect);
void schedule_present_frame(Web::Compositor::CompositorContextId, ContextState&, Gfx::IntRect);
void schedule_pending_present_frame(Web::Compositor::CompositorContextId, ContextState&);
void schedule_pending_present_frame_on_vsync(Web::Compositor::CompositorContextId, ContextState&);
void schedule_containing_context_present(ContextState&);
void schedule_pending_present_frame_if_unblocked(Web::Compositor::CompositorContextId, ContextState&);
VSyncScheduler& vsync_scheduler_for_display(Optional<u64> display_id);
void present_pending_frames_on_vsync(Optional<u64> display_id);
void publish_backing_stores(Web::Compositor::CompositorContextId, ContextState&, BackingStoreManager::Publication&&);
void did_finish_async_present(PendingAsyncPresent&);
void cancel_pending_async_presents_for_context(Web::Compositor::CompositorContextId);
void schedule_gpu_completion_check();
void check_gpu_completions();
HashMap<Web::Compositor::CompositorContextId, OwnPtr<ContextState>> m_contexts;
DoublyLinkedList<PendingAsyncPresent> m_pending_async_presents;
RefPtr<Gfx::SkiaBackendContext> m_skia_backend_context;
Web::Painting::CanvasSurfaceRegistry m_canvas_surface_registry;
OwnPtr<Web::Painting::DisplayListPlayerSkia> m_display_list_player;
HashMap<Optional<u64>, OwnPtr<VSyncScheduler>> m_vsync_schedulers_by_display;
RefPtr<Core::Timer> m_gpu_completion_timer;
CompositorStateClient* m_client { nullptr };
bool m_async_scrolling_enabled { true };
};
}