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.
This commit is contained in:
Aliaksandr Kalenik 2026-06-19 16:34:45 +02:00 committed by Jelle Raaijmakers
parent b3f3d68291
commit 5c3e61efb6
4 changed files with 40 additions and 51 deletions

View file

@ -112,7 +112,10 @@ void CompositorState::set_parent_context(Web::Compositor::CompositorContextId co
clear_parent_context(*context);
context->set_parent_context(parent_context_id);
if (!parent_context_id.has_value() || !context->latest_rendered_surface())
if (!parent_context_id.has_value())
return;
if (!context->latest_rendered_surface() && !context->needs_rasterization())
return;
auto* parent_context = context_if_present(*parent_context_id);
@ -268,7 +271,7 @@ void CompositorState::set_display_metadata(Web::Compositor::CompositorContextId
VERIFY(refresh_rate < AK::Infinity<double>);
if (context->set_display_metadata(display_id, refresh_rate))
schedule_pending_present_frame_on_vsync(context_id, *context);
schedule_pending_present_frame(context_id, *context);
}
void CompositorState::present_frame(Web::Compositor::CompositorContextId context_id, Gfx::IntRect viewport_rect)
@ -302,6 +305,16 @@ void CompositorState::present_frame(Web::Compositor::CompositorContextId context
void CompositorState::schedule_present_frame(Web::Compositor::CompositorContextId context_id, ContextState& context, Gfx::IntRect viewport_rect)
{
context.queue_present_frame(viewport_rect);
schedule_pending_present_frame(context_id, context);
}
void CompositorState::schedule_pending_present_frame(Web::Compositor::CompositorContextId context_id, ContextState& context)
{
if (!context.presents_to_client()) {
schedule_containing_context_present(context);
return;
}
schedule_pending_present_frame_on_vsync(context_id, context);
}
@ -311,12 +324,23 @@ void CompositorState::schedule_pending_present_frame_on_vsync(Web::Compositor::C
vsync_scheduler_for_display(context.display_id()).schedule(context.display_refresh_rate());
}
void CompositorState::schedule_containing_context_present(ContextState& context)
{
auto parent_context_id = context.parent_context_id();
if (!parent_context_id.has_value())
return;
auto* parent_context = context_if_present(*parent_context_id);
VERIFY(parent_context);
present_current_frame(*parent_context_id, *parent_context);
}
void CompositorState::schedule_pending_present_frame_if_unblocked(Web::Compositor::CompositorContextId context_id, ContextState& context)
{
if (!context.can_schedule_pending_present_frame_if_unblocked())
return;
schedule_pending_present_frame_on_vsync(context_id, context);
schedule_pending_present_frame(context_id, context);
}
VSyncScheduler& CompositorState::vsync_scheduler_for_display(Optional<u64> display_id)
@ -343,47 +367,6 @@ void CompositorState::present_pending_frames_on_vsync(Optional<u64> display_id)
}
}
void CompositorState::flush_descendant_surfaces_for_screenshot(Web::Compositor::CompositorContextId context_id)
{
// Presents are scheduled on a vsync timer rather than performed synchronously — so a nested navigable's latest
// paint may not yet be published to its parent's compositor surface. A screenshot re-executes the top-level display
// list, whose embedded-content commands read those child surfaces. So, flush any descendant with a deferred
// present synchronously (deepest-first) — to capture a complete frame instead of a stale/blank iframe.
auto* context = context_if_present(context_id);
VERIFY(context);
for (auto& context_entry : m_contexts) {
auto parent_context_id = context_entry.value->parent_context_id();
if (parent_context_id.has_value() && *parent_context_id == context_id)
present_subtree_for_screenshot(context_entry.key);
}
}
bool CompositorState::present_subtree_for_screenshot(Web::Compositor::CompositorContextId context_id)
{
auto* context = context_if_present(context_id);
VERIFY(context);
bool needs_present = context->needs_synchronous_present_for_screenshot();
for (auto& context_entry : m_contexts) {
auto parent_context_id = context_entry.value->parent_context_id();
if (parent_context_id.has_value()
&& *parent_context_id == context_id
&& present_subtree_for_screenshot(context_entry.key))
needs_present = true;
}
if (!needs_present)
return false;
return present_context_synchronously(context_id, *context);
}
bool CompositorState::present_context_synchronously(Web::Compositor::CompositorContextId context_id, ContextState& context)
{
auto composited_context_resolver = resolver_for(context_id);
return context.present_synchronously(*m_display_list_player, &composited_context_resolver);
}
bool CompositorState::request_screenshot(Web::Compositor::CompositorContextId context_id, Gfx::ShareableBitmap& target_bitmap)
{
auto* context = context_if_present(context_id);
@ -392,7 +375,6 @@ bool CompositorState::request_screenshot(Web::Compositor::CompositorContextId co
if (!context->can_paint_screenshot(target_bitmap))
return false;
flush_descendant_surfaces_for_screenshot(context_id);
auto composited_context_resolver = resolver_for(context_id);
context->paint_screenshot(*m_display_list_player, target_bitmap, &composited_context_resolver);
return true;
@ -529,6 +511,13 @@ RefPtr<Gfx::PaintingSurface> CompositorState::resolve_composited_context(Web::Co
auto child_parent_context_id = child_context->parent_context_id();
if (!child_parent_context_id.has_value() || *child_parent_context_id != parent_context_id)
return nullptr;
if (child_context->needs_rasterization()) {
auto composited_context_resolver = resolver_for(child_context_id);
Web::Painting::DisplayListPlayerSkia display_list_player { m_skia_backend_context };
child_context->present_synchronously(display_list_player, &composited_context_resolver);
}
return child_context->latest_rendered_surface();
}

View file

@ -130,13 +130,12 @@ private:
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 flush_descendant_surfaces_for_screenshot(Web::Compositor::CompositorContextId);
bool present_subtree_for_screenshot(Web::Compositor::CompositorContextId);
bool present_context_synchronously(Web::Compositor::CompositorContextId, ContextState&);
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);

View file

@ -547,9 +547,10 @@ Optional<Gfx::IntRect> ContextState::take_pending_present_frame_if_unblocked()
return m_pending_present_frame.release_value();
}
bool ContextState::needs_synchronous_present_for_screenshot() const
bool ContextState::needs_rasterization() const
{
return m_pending_present_frame.has_value() || m_pending_present_frame_scheduled;
return m_pending_present_frame.has_value()
|| (!m_latest_rendered_surface && m_presented_frame.has_value());
}
Optional<Gfx::IntRect> ContextState::current_frame_rect_to_present() const

View file

@ -127,7 +127,7 @@ public:
bool has_pending_present_frame_scheduled_on(Optional<u64> display_id) const;
bool can_schedule_pending_present_frame_if_unblocked() const;
Optional<Gfx::IntRect> take_pending_present_frame_if_unblocked();
bool needs_synchronous_present_for_screenshot() const;
bool needs_rasterization() const;
Optional<Gfx::IntRect> current_frame_rect_to_present() const;
Optional<PreparedFrame> prepare_frame(Web::Painting::DisplayListPlayerSkia&, Gfx::IntRect, CompositedContextResolver const*);
void did_submit_prepared_frame(Gfx::IntRect);